product on shelf
This commit is contained in:
@@ -130,3 +130,30 @@ func (ctl *CatalogueUploadController) MarkShelved(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
return c.JSON(fiber.Map{"code": 200, "message": "Shelving recorded", "status": true})
|
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})
|
||||||
|
}
|
||||||
|
|||||||
13
main.go
13
main.go
@@ -156,6 +156,19 @@ func main() {
|
|||||||
ON catalogueuploads (tenantid, locationid, created DESC)`).Error; err != nil {
|
ON catalogueuploads (tenantid, locationid, created DESC)`).Error; err != nil {
|
||||||
log.Println("⚠️ could not add catalogueuploads scope index:", err)
|
log.Println("⚠️ could not add catalogueuploads scope index:", err)
|
||||||
}
|
}
|
||||||
|
// The sheet's own prices and opening stock, kept so shelving can happen
|
||||||
|
// after the browser that uploaded it is gone.
|
||||||
|
//
|
||||||
|
// Added separately from the CREATE above because the table already exists in
|
||||||
|
// production without it. The ingest service holds none of this — its
|
||||||
|
// catalogue is shared by every merchant and carries no price and no stock —
|
||||||
|
// so before this column the join could only be made in the tab that did the
|
||||||
|
// upload, and that tab is normally long closed by the time their admin
|
||||||
|
// releases the drop and the run finishes.
|
||||||
|
if err := db.DB.Exec(
|
||||||
|
`ALTER TABLE catalogueuploads ADD COLUMN IF NOT EXISTS sheetrows jsonb`).Error; err != nil {
|
||||||
|
log.Fatal("could not add catalogueuploads.sheetrows:", err)
|
||||||
|
}
|
||||||
|
|
||||||
f := facade.NewFacade(db.DB, db.CatalogueDB)
|
f := facade.NewFacade(db.DB, db.CatalogueDB)
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,24 @@ type CatalogueUpload struct {
|
|||||||
// the service reports, so a drop that never runs still says how big it was.
|
// the service reports, so a drop that never runs still says how big it was.
|
||||||
Rowcount int `json:"rowcount" gorm:"column:rowcount"`
|
Rowcount int `json:"rowcount" gorm:"column:rowcount"`
|
||||||
|
|
||||||
|
// The sheet itself, as the console parsed it: SKU, price and opening stock
|
||||||
|
// per row, as JSON.
|
||||||
|
//
|
||||||
|
// Stored because the ingest service cannot hold it and nothing else can.
|
||||||
|
// Their pipeline writes the GLOBAL catalogue, which every merchant shares
|
||||||
|
// and which therefore carries no price and no stock; both live only in the
|
||||||
|
// sheet. Shelving is the step that joins the two, and it used to be possible
|
||||||
|
// only in the browser tab that did the upload, because that tab was the only
|
||||||
|
// place the parsed rows existed.
|
||||||
|
//
|
||||||
|
// That failed in the ordinary case rather than a rare one. A drop waits for
|
||||||
|
// their admin to release it and the run then takes minutes, so by the time
|
||||||
|
// there is anything to shelve the tab is usually gone — and the products sit
|
||||||
|
// in the global catalogue, unpriced and unstocked, with no way left to
|
||||||
|
// finish. Keeping the rows here is what lets the Uploads page complete it
|
||||||
|
// days later.
|
||||||
|
Sheetrows string `json:"sheetrows" gorm:"column:sheetrows;type:jsonb"`
|
||||||
|
|
||||||
// ── Cached from the ingest service, by whoever last polled ──────────────
|
// ── Cached from the ingest service, by whoever last polled ──────────────
|
||||||
|
|
||||||
Laststatus string `json:"laststatus" gorm:"column:laststatus;default:pending"`
|
Laststatus string `json:"laststatus" gorm:"column:laststatus;default:pending"`
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type CatalogueUploadRepository interface {
|
|||||||
List(tenantID, locationID, pageNo, pageSize int) ([]models.CatalogueUpload, error)
|
List(tenantID, locationID, pageNo, pageSize int) ([]models.CatalogueUpload, error)
|
||||||
UpdateStatus(upload *models.CatalogueUpload) error
|
UpdateStatus(upload *models.CatalogueUpload) error
|
||||||
MarkShelved(batchID string, shelved, skipped int) error
|
MarkShelved(batchID string, shelved, skipped int) error
|
||||||
|
AttachSheet(batchID, sheetRows string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type catalogueUploadRepository struct {
|
type catalogueUploadRepository struct {
|
||||||
@@ -148,3 +149,29 @@ func (r *catalogueUploadRepository) MarkShelved(batchID string, shelved, skipped
|
|||||||
"shelvedat": gorm.Expr("CURRENT_TIMESTAMP"),
|
"shelvedat": gorm.Expr("CURRENT_TIMESTAMP"),
|
||||||
}).Error
|
}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AttachSheet stores the sheet's rows against a receipt that has none.
|
||||||
|
//
|
||||||
|
// The rescue path for a receipt filed before the rows were kept, and for one
|
||||||
|
// whose upload predates this column. The prices and opening stock exist only in
|
||||||
|
// the spreadsheet; if the receipt was written without them, the only way back is
|
||||||
|
// for somebody to hand the file over again.
|
||||||
|
//
|
||||||
|
// Deliberately not part of UpdateStatus. That call is made by any browser
|
||||||
|
// polling the ingest service and must never touch what the console owns; this
|
||||||
|
// one is a person supplying missing data, and the two should not be able to
|
||||||
|
// happen by accident in each other's place.
|
||||||
|
func (r *catalogueUploadRepository) AttachSheet(batchID, sheetRows string) error {
|
||||||
|
if strings.TrimSpace(batchID) == "" {
|
||||||
|
return errors.New("batchid is required")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(sheetRows) == "" {
|
||||||
|
// Empty would be stored as `[]` and read back as "this sheet had no
|
||||||
|
// rows", which is a different claim from "nobody has supplied them
|
||||||
|
// yet" — and the second is what makes the shelve button appear.
|
||||||
|
return errors.New("sheetrows is required")
|
||||||
|
}
|
||||||
|
return r.db.Model(&models.CatalogueUpload{}).
|
||||||
|
Where("batchid = ?", batchID).
|
||||||
|
Update("sheetrows", sheetRows).Error
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,5 +29,9 @@ func RegisterUploadRoutes(api fiber.Router, f *facade.Facade) {
|
|||||||
// The other half: priced, shelved and stocked at a branch. Kept apart from
|
// The other half: priced, shelved and stocked at a branch. Kept apart from
|
||||||
// /update because the ingest service confirms the shared global catalogue
|
// /update because the ingest service confirms the shared global catalogue
|
||||||
// and knows nothing about a shop's shelf.
|
// and knows nothing about a shop's shelf.
|
||||||
|
// The rescue path: hand the spreadsheet over again for a receipt whose
|
||||||
|
// prices and opening stock were never stored, so it can still be shelved.
|
||||||
|
uploads.Put("/sheet", f.CatalogueUploadController.AttachSheet)
|
||||||
|
|
||||||
uploads.Put("/shelved", f.CatalogueUploadController.MarkShelved)
|
uploads.Put("/shelved", f.CatalogueUploadController.MarkShelved)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"nearle/models"
|
"nearle/models"
|
||||||
"nearle/repositories"
|
"nearle/repositories"
|
||||||
)
|
)
|
||||||
@@ -23,6 +25,7 @@ type CatalogueUploadService interface {
|
|||||||
List(tenantID, locationID, pageNo, pageSize int) ([]models.CatalogueUpload, error)
|
List(tenantID, locationID, pageNo, pageSize int) ([]models.CatalogueUpload, error)
|
||||||
UpdateStatus(upload *models.CatalogueUpload) error
|
UpdateStatus(upload *models.CatalogueUpload) error
|
||||||
MarkShelved(batchID string, shelved, skipped int) error
|
MarkShelved(batchID string, shelved, skipped int) error
|
||||||
|
AttachSheet(batchID, sheetRows string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type catalogueUploadService struct {
|
type catalogueUploadService struct {
|
||||||
@@ -40,6 +43,13 @@ func (s *catalogueUploadService) Record(upload *models.CatalogueUpload) error {
|
|||||||
// the pipeline had it when it is in fact waiting for a person.
|
// the pipeline had it when it is in fact waiting for a person.
|
||||||
upload.Laststatus = "pending"
|
upload.Laststatus = "pending"
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(upload.Sheetrows) == "" {
|
||||||
|
// `sheetrows` is jsonb, and Postgres rejects an empty string for it —
|
||||||
|
// "invalid input syntax for type json" — which would fail the whole
|
||||||
|
// receipt over a field nobody supplied. An empty ARRAY is the honest
|
||||||
|
// value: no rows were sent, so there is nothing to shelve from later.
|
||||||
|
upload.Sheetrows = "[]"
|
||||||
|
}
|
||||||
return s.repo.Record(upload)
|
return s.repo.Record(upload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,3 +64,7 @@ func (s *catalogueUploadService) UpdateStatus(upload *models.CatalogueUpload) er
|
|||||||
func (s *catalogueUploadService) MarkShelved(batchID string, shelved, skipped int) error {
|
func (s *catalogueUploadService) MarkShelved(batchID string, shelved, skipped int) error {
|
||||||
return s.repo.MarkShelved(batchID, shelved, skipped)
|
return s.repo.MarkShelved(batchID, shelved, skipped)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *catalogueUploadService) AttachSheet(batchID, sheetRows string) error {
|
||||||
|
return s.repo.AttachSheet(batchID, sheetRows)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user