Files
backend_fiesta/controllers/tenantController.go
Suriya 6a62dbb9f3 Hold web-created staff to the same rules the till applies
Two paths write `app_users`: the console's `tenants/createstaff`, and the
terminal's `/pos/users`. Only one of them checked anything.

`createstaff` wrote whatever it was handed. A cashier could be created there
with PIN "0451" — which a bigint column stores as 451 — and would then type four
digits at the counter and be refused for ever, with nothing on either screen to
explain it. Or with 1234, which live data already has on eleven accounts. Or
with a PIN somebody at the same outlet already had, which attributes a bill to
whichever row is read first. Or with no way to sign in at all.

None of that surfaced where it was caused. It surfaced at a counter, days later,
as "the new person cannot log in".

So the rules move into `ValidateStaffUser`, and both paths use it: a name, a
role that is actually a role, a PIN the schema can hold and nobody guesses
first, and at least one way to sign in. The duplicate-PIN check runs too, when
the row names an outlet.

The handler also stops answering 500 with a body claiming 409. Every one of
these is something the person filling in the form can fix, so it is a 400
carrying the reason.

`GetStaffs` now returns `rolename` alongside `roleid`, so a console can show
"Supervisor" without mapping ids itself — `app_roles` has six rows for four
back-office roles and most accounts carry an id absent from it, so any mapping
written client-side would be wrong.

This is what makes the two role systems one. A supervisor or cashier created
from the web behaves at the till exactly like one created at the till, because
there is now a single definition of what those are.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 20:37:28 +05:30

583 lines
14 KiB
Go

package controllers
import (
"fmt"
"log"
"nearle/models"
"nearle/services"
"net/http"
"strconv"
"strings"
"github.com/gofiber/fiber/v2"
)
type TenantController struct {
tenantService services.TenantService
}
func NewTenantController(tenantService services.TenantService) *TenantController {
return &TenantController{tenantService: tenantService}
}
func (ctl *TenantController) SearchTenant(c *fiber.Ctx) error {
status := c.Query("status")
searchStr := c.Query("keyword")
data, err := ctl.tenantService.SearchTenant(status, searchStr)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": fmt.Sprintf("Error searching tenants: %v", err),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": data,
})
}
func (ctl *TenantController) GetAllTenants(c *fiber.Ctx) error {
pageno, _ := strconv.Atoi(c.Query("pageno"))
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
status := c.Query("status")
aid, _ := strconv.Atoi(c.Query("applocationid"))
tenanttype := c.Query("tenanttype")
keyword := c.Query("keyword")
details, err := ctl.tenantService.GetAllTenants(pageno, pagesize, aid, status, tenanttype, keyword)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": fmt.Sprintf("Error getting all tenants: %v", err),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": details,
})
}
func (ctl *TenantController) GetTenantLocations(c *fiber.Ctx) error {
tidStr := c.Query("tenantid")
if tidStr == "" {
tidStr = c.Query("tenantId")
}
if tidStr == "" {
tidStr = c.Query("tenantID")
}
if tidStr == "" {
tidStr = c.Query("tenant_id")
}
tid, _ := strconv.Atoi(strings.TrimSpace(tidStr))
data, err := ctl.tenantService.GetTenantLocations(tid)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": fmt.Sprintf("Error getting tenant locations: %v", err),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": data,
})
}
func (ctl *TenantController) GetTenantSlot(c *fiber.Ctx) error {
data, err := ctl.tenantService.GetTenantSlot()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": fmt.Sprintf("Error getting tenant slots: %v", err),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": data,
})
}
func (ctl *TenantController) CreateTenantCustomer(c *fiber.Ctx) error {
var req models.CreateTenantCustomerRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": 400,
"status": false,
"message": "Invalid request body",
"data": fiber.Map{},
})
}
tenantCustomer, err := ctl.tenantService.CreateTenantCustomer(req)
if err != nil {
if strings.Contains(err.Error(), "already exists for this location") {
return c.JSON(fiber.Map{
"code": 409,
"status": false,
"message": "Customer already assigned to this location",
"data": fiber.Map{},
})
}
log.Println("Error inserting tenant customer:", err)
return c.JSON(fiber.Map{
"code": 500,
"status": false,
"message": "Failed to create tenant customer",
"data": fiber.Map{},
})
}
return c.Status(http.StatusOK).JSON(fiber.Map{
"code": 200,
"status": true,
"message": "Tenant customer created successfully",
"data": tenantCustomer,
})
}
func (ctl *TenantController) GetCustomerTenants(c *fiber.Ctx) error {
customerID, err := strconv.Atoi(c.Query("customerid"))
if err != nil || customerID == 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": 400,
"message": "Invalid customerid",
"status": false,
"details": []interface{}{},
})
}
categoryID, _ := strconv.Atoi(c.Query("categoryid"))
tenantFlag, _ := strconv.Atoi(c.Query("tenant")) // 0 = all tenants, 1 = tenants with orders
data, err := ctl.tenantService.GetCustomerTenants(customerID, categoryID, tenantFlag)
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": 500,
"message": err.Error(),
"status": false,
"details": []interface{}{},
})
}
return c.Status(http.StatusOK).JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": data.Details,
})
}
func (ctl *TenantController) GetTenantPricing(c *fiber.Ctx) error {
tid, _ := strconv.Atoi(c.Query("tenantid"))
aid, _ := strconv.Atoi(c.Query("applocationid"))
data, err := ctl.tenantService.GetTenantPricing(tid, aid)
if err != nil {
return c.JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": data,
})
}
func (ctl *TenantController) UpdateLocation(c *fiber.Ctx) error {
var data models.Tenantlocations
if err := c.BodyParser(&data); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"status": false,
"code": http.StatusBadRequest,
"message": "Invalid request body",
})
}
if err := ctl.tenantService.UpdateLocation(data); err != nil {
return c.JSON(fiber.Map{
"status": false,
"code": http.StatusConflict,
"message": err.Error(),
})
}
return c.JSON(fiber.Map{
"status": true,
"code": http.StatusAccepted,
"message": "Location update successful",
})
}
func (ctl *TenantController) CreateLocation(c *fiber.Ctx) error {
var data models.Tenantlocations
if err := c.BodyParser(&data); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Invalid request body",
"status": false,
})
}
err := ctl.tenantService.CreateLocation(data)
if err != nil {
return c.Status(http.StatusConflict).JSON(fiber.Map{
"code": http.StatusConflict,
"message": err.Error(),
"status": false,
})
}
return c.Status(http.StatusCreated).JSON(fiber.Map{
"code": http.StatusCreated,
"message": "Location Successfully Created",
"status": true,
})
}
func (ctl *TenantController) DeleteLocation(c *fiber.Ctx) error {
locationid, err := strconv.Atoi(c.Query("locationid"))
if err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Invalid location ID",
"status": false,
})
}
tenantid, err := strconv.Atoi(c.Query("tenantid"))
if err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Invalid tenant ID",
"status": false,
})
}
if err := ctl.tenantService.DeleteLocation(locationid, tenantid); err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Location Successfully Deleted",
"status": true,
})
}
func (ctl *TenantController) GetStaffs(c *fiber.Ctx) error {
tid, _ := strconv.Atoi(c.Query("tenantid"))
data, err := ctl.tenantService.GetStaffs(tid)
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": data,
})
}
func (ctl *TenantController) CreateStaff(c *fiber.Ctx) error {
var data models.User
if err := c.BodyParser(&data); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Invalid request body",
"status": false,
})
}
if err := ctl.tenantService.CreateStaff(data); err != nil {
// A rejected PIN, a missing name, a role nobody set — these are things
// the person filling in the form can fix, so they come back as 400 with
// the reason. This answered 500 with a body claiming 409, which told a
// console nothing it could act on and told the operator less.
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusCreated,
"message": "Staff created successfully",
"status": true,
})
}
func (ctl *TenantController) UpdateStaff(c *fiber.Ctx) error {
var data models.User
if err := c.BodyParser(&data); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"status": false,
"code": http.StatusBadRequest,
"message": "Invalid request body",
})
}
if err := ctl.tenantService.UpdateStaff(data); err != nil {
return c.Status(http.StatusConflict).JSON(fiber.Map{
"status": false,
"code": http.StatusConflict,
"message": err.Error(),
})
}
return c.JSON(fiber.Map{
"status": true,
"code": http.StatusAccepted,
"message": "Staff updated successfully",
})
}
func (ctl *TenantController) CreateTenantLocation(c *fiber.Ctx) error {
var data models.Tenantlocations
if err := c.BodyParser(&data); err != nil {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"code": 400,
"message": "Invalid request body",
"status": false,
})
}
resp := ctl.tenantService.CreateTenantLocation(data)
return c.Status(fiber.StatusOK).JSON(resp)
}
func (ctl *TenantController) UpdateTenantLocation(c *fiber.Ctx) error {
var data models.Tenantlocations
// Parse JSON body
if err := c.BodyParser(&data); err != nil {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"status": false,
"code": 400,
"message": "Invalid request body",
})
}
// Call service layer
resp := ctl.tenantService.UpdateTenantLocation(data)
// Always return HTTP 200 (as per your API pattern)
return c.Status(fiber.StatusOK).JSON(resp)
}
func (ctl *TenantController) CreateTenantUser(c *fiber.Ctx) error {
var data models.Tenants
if err := c.BodyParser(&data); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": 400,
"status": false,
"message": "Invalid request body",
})
}
result, err := ctl.tenantService.CreateTenantUser(data)
if err != nil {
if err.Error() == "Tenant Already Exists" {
return c.Status(http.StatusConflict).JSON(fiber.Map{
"code": 409,
"status": false,
"message": err.Error(),
})
}
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": 500,
"status": false,
"message": err.Error(),
})
}
return c.Status(http.StatusCreated).JSON(fiber.Map{
"code": 201,
"status": true,
"message": "Successfully Created",
"details": result,
})
}
func (ctl *TenantController) GetTenantInfo(c *fiber.Ctx) error {
log.Printf("[DEBUG] GetTenantInfo OriginalURL: %s, Headers: %v", c.OriginalURL(), c.GetReqHeaders())
// Parse tenant ID
tidStr := c.Query("tenantid")
if tidStr == "" {
tidStr = c.Query("tenantId")
}
if tidStr == "" {
tidStr = c.Query("tenantID")
}
if tidStr == "" {
tidStr = c.Query("tenant_id")
}
if tidStr == "" {
tidStr = c.Get("tenantid")
}
if tidStr == "" {
tidStr = c.Get("tenantId")
}
if tidStr == "" {
tidStr = c.Get("tenantID")
}
if tidStr == "" {
tidStr = c.Get("tenant_id")
}
tid, _ := strconv.Atoi(strings.TrimSpace(tidStr))
// Parse location ID
lidStr := c.Query("locationid")
if lidStr == "" {
lidStr = c.Query("locationId")
}
if lidStr == "" {
lidStr = c.Query("locationID")
}
if lidStr == "" {
lidStr = c.Query("location_id")
}
if lidStr == "" {
lidStr = c.Get("locationid")
}
if lidStr == "" {
lidStr = c.Get("locationId")
}
if lidStr == "" {
lidStr = c.Get("locationID")
}
if lidStr == "" {
lidStr = c.Get("location_id")
}
locationid, _ := strconv.Atoi(strings.TrimSpace(lidStr))
// Parse user ID
uidStr := c.Query("userid")
if uidStr == "" {
uidStr = c.Query("userId")
}
if uidStr == "" {
uidStr = c.Query("userID")
}
if uidStr == "" {
uidStr = c.Query("user_id")
}
if uidStr == "" {
uidStr = c.Query("appuserid")
}
if uidStr == "" {
uidStr = c.Query("appuserId")
}
if uidStr == "" {
uidStr = c.Query("appuserID")
}
if uidStr == "" {
uidStr = c.Query("appuser_id")
}
if uidStr == "" {
uidStr = c.Get("userid")
}
if uidStr == "" {
uidStr = c.Get("userId")
}
if uidStr == "" {
uidStr = c.Get("userID")
}
if uidStr == "" {
uidStr = c.Get("user_id")
}
if uidStr == "" {
uidStr = c.Get("appuserid")
}
if uidStr == "" {
uidStr = c.Get("appuserId")
}
if uidStr == "" {
uidStr = c.Get("appuserID")
}
if uidStr == "" {
uidStr = c.Get("appuser_id")
}
userid, _ := strconv.Atoi(strings.TrimSpace(uidStr))
data, err := ctl.tenantService.GetTenantByID(tid, locationid, userid)
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": "Error fetching tenant info",
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": data,
})
}
func (ctl *TenantController) GetTenantByKeyword(c *fiber.Ctx) error {
keyword := c.Query("keyword")
data, err := ctl.tenantService.GetTenantByKeyword(keyword)
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": 500,
"message": "Error searching tenants by keyword",
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": data,
})
}