Bridge global catalogue DB to per-store product catalogue
Adds a separate CatalogueDB (pgvector) connection alongside the main nearledb, plus a new catalogue module (repository/service/controller/ routes) to browse it by brand, category, and keyword, with brand optional so the whole ~237-product catalogue can be browsed unfiltered. Adds the actual bridge: importing a catalogue product snapshots it into the tenant's own products table (keyed on brand+catalogueid, since a catalogue row's bare id is only unique within its own brand table), then links it via the existing productlocations upsert. Re-importing tops up stock and refreshes price instead of duplicating. Also adds an imported-refs endpoint so the frontend can badge already-imported items without diffing full product lists, and wires the new AWS S3 image store used to resolve catalogue product photos. Bumps Go/Docker to 1.24 for the AWS SDK dependency this needs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -515,6 +515,81 @@ func (ctl *ProductController) CreateProductVariant(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (ctl *ProductController) ImportCatalogueProduct(c *fiber.Ctx) error {
|
||||
var data []models.ImportCatalogueProductRequest
|
||||
|
||||
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 len(data) == 0 {
|
||||
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
||||
"code": http.StatusBadRequest,
|
||||
"message": "Request body must contain at least one product",
|
||||
"status": false,
|
||||
})
|
||||
}
|
||||
|
||||
for _, req := range data {
|
||||
if req.Tenantid == 0 || req.Locationid == 0 || req.Brand == "" || req.Catalogueid == 0 || req.Categoryid == 0 || req.Subcategoryid == 0 {
|
||||
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
||||
"code": http.StatusBadRequest,
|
||||
"message": "tenantid, locationid, brand, catalogueid, categoryid, and subcategoryid are required",
|
||||
"status": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if err := ctl.productService.ImportCatalogueProduct(data); err != nil {
|
||||
return c.Status(http.StatusConflict).JSON(fiber.Map{
|
||||
"status": false,
|
||||
"code": http.StatusConflict,
|
||||
"message": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(http.StatusCreated).JSON(fiber.Map{
|
||||
"status": true,
|
||||
"code": http.StatusCreated,
|
||||
"message": "Success",
|
||||
})
|
||||
}
|
||||
|
||||
func (ctl *ProductController) GetImportedCatalogueProducts(c *fiber.Ctx) error {
|
||||
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
|
||||
// brand is optional: omit it to check imported status across every
|
||||
// brand at once, matching the all-brands catalogue browse view.
|
||||
brand := c.Query("brand")
|
||||
|
||||
if tenantID == 0 {
|
||||
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
||||
"code": http.StatusBadRequest,
|
||||
"message": "tenantid is required",
|
||||
"status": false,
|
||||
})
|
||||
}
|
||||
|
||||
refs, err := ctl.productService.GetImportedCatalogueRefs(tenantID, brand)
|
||||
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": refs,
|
||||
})
|
||||
}
|
||||
|
||||
func (ctl *ProductController) DeleteProductLocation(c *fiber.Ctx) error {
|
||||
var input struct {
|
||||
Tenantid int `json:"tenantid"`
|
||||
|
||||
Reference in New Issue
Block a user