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>
583 lines
14 KiB
Go
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,
|
|
})
|
|
}
|