Files
backend_fiesta/controllers/catalogueUploadController.go
2026-08-31 14:59:48 +05:30

160 lines
6.7 KiB
Go

package controllers
import (
"net/http"
"strconv"
"strings"
"nearle/models"
"nearle/services"
"github.com/gofiber/fiber/v2"
)
type CatalogueUploadController struct {
service services.CatalogueUploadService
}
func NewCatalogueUploadController(service services.CatalogueUploadService) *CatalogueUploadController {
return &CatalogueUploadController{service: service}
}
// RecordUpload stores the receipt for a spreadsheet just accepted by the ingest
// service.
//
// Called the moment the drop is accepted, before any polling — that is the one
// instant the batch id is guaranteed to exist and guaranteed not to have been
// lost. Everything after it (the review wait, the run, the shelving) can be
// recovered from the id; the id itself cannot be recovered from anything.
func (ctl *CatalogueUploadController) RecordUpload(c *fiber.Ctx) error {
var input models.CatalogueUpload
if err := c.BodyParser(&input); err != nil {
return c.JSON(fiber.Map{"code": http.StatusBadRequest, "message": "Invalid input", "status": false})
}
if strings.TrimSpace(input.Batchid) == "" {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "batchid is required — it is the only credential for reading the result back",
"status": false,
})
}
if input.Tenantid <= 0 {
// A receipt with no tenant belongs to nobody and would be visible to
// every store login that asks for "all". Refused rather than stored
// unscoped.
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "tenantid is required — a receipt has to belong to a merchant",
"status": false,
})
}
if err := ctl.service.Record(&input); err != nil {
return c.JSON(fiber.Map{"code": http.StatusInternalServerError, "message": err.Error(), "status": false})
}
return c.JSON(fiber.Map{"code": 200, "message": "Upload recorded", "status": true, "details": input})
}
// GetUploads lists receipts for whoever is asking.
//
// `tenantid=0` means every tenant and is how a Nearle Admin sees the platform.
// The console decides which to send from the session — the same rule the import
// itself follows — because a store login reading another merchant's receipts
// would see what that merchant stocks.
func (ctl *CatalogueUploadController) GetUploads(c *fiber.Ctx) error {
tenantID, _ := strconv.Atoi(c.Query("tenantid", "0"))
locationID, _ := strconv.Atoi(c.Query("locationid", "0"))
pageNo, _ := strconv.Atoi(c.Query("pageno", "1"))
pageSize, _ := strconv.Atoi(c.Query("pagesize", "50"))
data, err := ctl.service.List(tenantID, locationID, pageNo, pageSize)
if err != nil {
return c.JSON(fiber.Map{"code": http.StatusInternalServerError, "message": err.Error(), "status": false})
}
// Never null. An empty list is a legitimate answer — a shop that has never
// uploaded — and a JSON null here makes every caller guard a case that is
// really just "none yet".
if data == nil {
data = []models.CatalogueUpload{}
}
return c.JSON(fiber.Map{"code": 200, "message": "Success", "status": true, "details": data})
}
// UpdateUpload caches what the ingest service last reported.
//
// Written by the browser that is polling, because that is where the reading
// happens: the batch id is the credential and the read is anonymous, so the
// person waiting is already holding the answer. This endpoint only writes it
// down so the next person does not have to wait for it again.
func (ctl *CatalogueUploadController) UpdateUpload(c *fiber.Ctx) error {
var input models.CatalogueUpload
if err := c.BodyParser(&input); err != nil {
return c.JSON(fiber.Map{"code": http.StatusBadRequest, "message": "Invalid input", "status": false})
}
if strings.TrimSpace(input.Batchid) == "" {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "message": "batchid is required", "status": false,
})
}
if err := ctl.service.UpdateStatus(&input); err != nil {
return c.JSON(fiber.Map{"code": http.StatusInternalServerError, "message": err.Error(), "status": false})
}
return c.JSON(fiber.Map{"code": 200, "message": "Upload updated", "status": true})
}
// MarkShelved records that the products reached a branch's shelf with a price
// and their opening stock.
//
// Separate from UpdateUpload on purpose. The ingest service confirms the global
// catalogue, which every merchant shares and which therefore carries no price
// and no stock — so "added" and "on sale here" are two different claims, made by
// two different systems, and a receipt that ran them together would report
// products as sellable that no customer can buy.
func (ctl *CatalogueUploadController) MarkShelved(c *fiber.Ctx) error {
var input struct {
Batchid string `json:"batchid"`
Shelved int `json:"shelved"`
Skipped int `json:"skipped"`
}
if err := c.BodyParser(&input); err != nil {
return c.JSON(fiber.Map{"code": http.StatusBadRequest, "message": "Invalid input", "status": false})
}
if strings.TrimSpace(input.Batchid) == "" {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "message": "batchid is required", "status": false,
})
}
if err := ctl.service.MarkShelved(input.Batchid, input.Shelved, input.Skipped); err != nil {
return c.JSON(fiber.Map{"code": http.StatusInternalServerError, "message": err.Error(), "status": false})
}
return c.JSON(fiber.Map{"code": 200, "message": "Shelving recorded", "status": true})
}
// AttachSheet supplies the prices and opening stock for a receipt that has
// none, so its products can still be put on a shelf.
//
// Needed because the receipt and the sheet used to part company. The ingest
// service holds neither price nor stock — its catalogue is shared by every
// merchant — so both live only in the spreadsheet, and a receipt written before
// those rows were stored has no way to finish. Handing the file over again is
// the only way back, and this is where it lands.
func (ctl *CatalogueUploadController) AttachSheet(c *fiber.Ctx) error {
var input struct {
Batchid string `json:"batchid"`
Sheetrows string `json:"sheetrows"`
}
if err := c.BodyParser(&input); err != nil {
return c.JSON(fiber.Map{"code": http.StatusBadRequest, "message": "Invalid input", "status": false})
}
if strings.TrimSpace(input.Batchid) == "" || strings.TrimSpace(input.Sheetrows) == "" {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest, "message": "batchid and sheetrows are both required", "status": false,
})
}
if err := ctl.service.AttachSheet(input.Batchid, input.Sheetrows); err != nil {
return c.JSON(fiber.Map{"code": http.StatusInternalServerError, "message": err.Error(), "status": false})
}
return c.JSON(fiber.Map{"code": 200, "message": "Sheet attached", "status": true})
}