package controllers import ( "net/http" "strconv" "github.com/gofiber/fiber/v2" "nearle/models" "nearle/services" ) type DeliverySlotController struct { service services.DeliverySlotService // Asked whether the shop is trading at all. Held here rather than inside // the slot service, which answers one question — is this window still // running — and should not also have to know about shop closures. tenants services.TenantService } func NewDeliverySlotController( service services.DeliverySlotService, tenants services.TenantService, ) *DeliverySlotController { return &DeliverySlotController{service: service, tenants: tenants} } /* GET /v1/web/deliveryslots?tenantid&locationid Everything a branch has configured, active or not, for the console's editor. A branch that has set nothing returns an empty list — see the note on Available about why that is never an error. */ func (ctl *DeliverySlotController) ListDeliverySlots(c *fiber.Ctx) error { tenantID, _ := strconv.Atoi(c.Query("tenantid")) locationID, _ := strconv.Atoi(c.Query("locationid")) if tenantID <= 0 { return c.Status(http.StatusBadRequest).JSON(fiber.Map{ "code": http.StatusBadRequest, "message": "tenantid is required", "status": false, }) } slots, err := ctl.service.ListForBranch(tenantID, locationID) if err != nil { return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ "code": http.StatusInternalServerError, "message": err.Error(), "status": false, }) } return c.Status(http.StatusOK).JSON(fiber.Map{ "code": http.StatusOK, "message": "Success", "status": true, "details": slots, }) } /* PUT /v1/web/deliveryslots The branch's windows, all three together rather than one at a time: they are edited as a set on one screen, and sending them together is what lets the service reject the whole edit when one row is wrong instead of applying part of it. A business objection — an unreadable time, a window ending before it starts, the same key twice — is 409 and not 500. It is an answer about the request, and the message is written to be shown to the person who typed it. */ func (ctl *DeliverySlotController) SaveDeliverySlots(c *fiber.Ctx) error { var req struct { Tenantid int `json:"tenantid"` Locationid int `json:"locationid"` Slots []models.DeliverySlots `json:"slots"` } if err := c.BodyParser(&req); err != nil { return c.Status(http.StatusBadRequest).JSON(fiber.Map{ "code": http.StatusBadRequest, "message": "Invalid request body", "status": false, }) } if req.Tenantid <= 0 { return c.Status(http.StatusBadRequest).JSON(fiber.Map{ "code": http.StatusBadRequest, "message": "tenantid is required", "status": false, }) } if err := ctl.service.Save(req.Tenantid, req.Locationid, req.Slots); err != nil { return c.Status(http.StatusConflict).JSON(fiber.Map{ "code": http.StatusConflict, "message": err.Error(), "status": false, }) } return c.Status(http.StatusOK).JSON(fiber.Map{ "code": http.StatusOK, "message": "Successfully Updated", "status": true, }) } /* GET /v1/mob/deliveryslots/available?tenantid&locationid What the shopper may pick, already filtered and dated. The app renders this list and does no time arithmetic of its own — see the service for why one clock has to be authoritative. An empty list is 200 with `details: []`, NOT an error. It means this branch has set no windows, which is the state every shop is in today, and the app is required to fall back to ordering without one. Returning 404 here would turn an ordinary shop into a broken one. */ func (ctl *DeliverySlotController) AvailableDeliverySlots(c *fiber.Ctx) error { tenantID, _ := strconv.Atoi(c.Query("tenantid")) locationID, _ := strconv.Atoi(c.Query("locationid")) if tenantID <= 0 { return c.Status(http.StatusBadRequest).JSON(fiber.Map{ "code": http.StatusBadRequest, "message": "tenantid is required", "status": false, }) } /* A shut shop offers nothing. Without this, a branch closed for the day still hands the app tomorrow morning — and the shopper picks it, reaches checkout, and is refused by the closed-shop guard in order creation with no warning anything was wrong. The empty list is already the app's "no windows here" path, so this needs nothing new on their side. The reason rides along so the app can say WHY rather than just showing nothing, on an endpoint it is already calling. */ if reason := ctl.tenants.StoreClosedReason(tenantID, locationID); reason != "" { return c.Status(http.StatusOK).JSON(fiber.Map{ "code": http.StatusOK, "message": "Success", "status": true, "details": []models.AvailableDeliverySlot{}, "closedreason": reason, }) } slots, err := ctl.service.Available(tenantID, locationID) if err != nil { return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ "code": http.StatusInternalServerError, "message": err.Error(), "status": false, }) } return c.Status(http.StatusOK).JSON(fiber.Map{ "code": http.StatusOK, "message": "Success", "status": true, "details": slots, }) }