Files
backend_fiesta/controllers/tenantController.go
2026-09-09 15:44:27 +05:30

760 lines
20 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,
})
}
// AssignStaff moves one of a merchant's people to a branch, or takes them off.
//
// `locationid` 0 unassigns, and is a real instruction rather than a missing
// value — somebody can leave a shop before the next one opens, and the console
// needs a way to say that which is not "delete the account".
//
// The tenant comes from the request and every check is scoped to it in the
// query, so a userid belonging to another business matches nothing and the call
// fails rather than moving a stranger's staff.
func (ctl *TenantController) AssignStaff(c *fiber.Ctx) error {
var input struct {
Tenantid int `json:"tenantid"`
Userid int `json:"userid"`
Locationid int `json:"locationid"`
Unassign bool `json:"unassign"`
}
if err := c.BodyParser(&input); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false, "message": "Invalid input",
})
}
if input.Tenantid <= 0 || input.Userid <= 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false,
"message": "tenantid and userid are required",
})
}
// The rule lives in services.ResolveAssignment so it can be tested without
// a request: a zero locationid must never be read as "unassign", because a
// dropped field looks exactly like one.
location, err := services.ResolveAssignment(input.Locationid, input.Unassign)
if err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false, "message": err.Error(),
})
}
if err := ctl.tenantService.AssignStaffToBranch(input.Tenantid, input.Userid, location); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false, "message": err.Error(),
})
}
return c.JSON(fiber.Map{"code": 200, "status": true, "message": "Success"})
}
// UpdateTenantProfile lets a merchant change their own business record.
//
// The body is read as a free-form map rather than into `models.Tenants`,
// deliberately. Binding to the struct would make every column on the table a
// candidate for writing and leave "which of these may a merchant set?" answered
// by whichever fields a form happened to send. The allowlist in
// services.TenantProfileUpdate answers it in one place instead, and everything
// absent from a request is left alone rather than blanked.
func (ctl *TenantController) UpdateTenantProfile(c *fiber.Ctx) error {
fields := map[string]any{}
if err := c.BodyParser(&fields); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false, "message": "Invalid input",
})
}
// The row to write is named by `tenantid`, and it is the one value in the
// body that is never a value to write.
tenantID := 0
switch id := fields["tenantid"].(type) {
case float64:
tenantID = int(id)
case int:
tenantID = id
}
if tenantID <= 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false,
"message": "tenantid is required",
})
}
if err := ctl.tenantService.UpdateTenantProfile(tenantID, fields); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false, "message": err.Error(),
})
}
return c.JSON(fiber.Map{"code": 200, "status": true, "message": "Success"})
}
// UpdateOwnProfile lets somebody change their own name, mobile or email.
//
// Read as a map rather than into `models.User` for the same reason as the shop
// profile: `app_users` keeps identity next to authorisation, so binding to the
// struct would make `roleid`, `locationid`, `status`, `password` and `pin`
// candidates for writing. The allowlist in services.OwnProfileUpdate answers
// "what may a person change about themselves?" in one place.
//
// Scoped by userid AND tenantid — the existing `users/update` checks only the
// userid, which is why the store user's account page has been read-only rather
// than editable.
func (ctl *TenantController) UpdateOwnProfile(c *fiber.Ctx) error {
fields := map[string]any{}
if err := c.BodyParser(&fields); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false, "message": "Invalid input",
})
}
readID := func(key string) int {
switch id := fields[key].(type) {
case float64:
return int(id)
case int:
return id
}
return 0
}
userID, tenantID := readID("userid"), readID("tenantid")
if userID <= 0 || tenantID <= 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false,
"message": "userid and tenantid are both required",
})
}
if err := ctl.tenantService.UpdateOwnProfile(userID, tenantID, fields); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false, "message": err.Error(),
})
}
return c.JSON(fiber.Map{"code": 200, "status": true, "message": "Success"})
}
// AssignPartner puts a merchant under a delivery partner, or takes them out of
// one.
//
// `partnerid` 0 is a real instruction here — it means "this merchant uses their
// own riders" — so it is read as sent rather than treated as absent. Everywhere
// else in the tenant API a zero means "not supplied"; this is the exception and
// it is the reason the route exists separately.
func (ctl *TenantController) AssignPartner(c *fiber.Ctx) error {
var body struct {
Tenantid int `json:"tenantid"`
Partnerid *int `json:"partnerid"`
}
if err := c.BodyParser(&body); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false, "message": "Invalid request body",
})
}
if tid, _ := strconv.Atoi(c.Query("tenantid")); tid != 0 {
body.Tenantid = tid
}
if body.Tenantid <= 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false, "message": "tenantid is required",
})
}
// A pointer, so "no partner" and "field omitted" are different requests.
// Sent as a plain int, an omitted field would read as 0 and quietly unassign
// a merchant's partner.
if body.Partnerid == nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false,
"message": "partnerid is required — send 0 to take the partner away",
})
}
if err := ctl.tenantService.AssignPartner(body.Tenantid, *body.Partnerid); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "status": false, "message": err.Error(),
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK, "status": true, "message": "Successfully Updated",
})
}