160 lines
6.7 KiB
Go
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})
|
|
}
|