fix on shelf

This commit is contained in:
2026-08-31 15:51:36 +05:30
parent 4bce5ac854
commit d55f101834
11 changed files with 704 additions and 55 deletions

View File

@@ -35,6 +35,7 @@ type ProductService interface {
DeleteProductLocation(tenantid, locationid, productid int) error
ImportCatalogueProduct(reqs []models.ImportCatalogueProductRequest) error
GetImportedCatalogueRefs(tenantid int, brand string) ([]models.ImportedCatalogueRef, error)
RelinkCatalogue(tenantid int, dryRun bool) (*RelinkReport, error)
GetTenantCategories(tenantid int) ([]models.TenantCategory, error)
PublishProduct(tenantID, productID int, price, taxPercent float64) (int, error)
UnpublishProduct(tenantID, productID int) (int, error)
@@ -226,7 +227,6 @@ func (s *productService) GetProductsBySubcategory(params models.ProductFilter) (
products = InStockOnly(products, params.LocationID)
var details []models.SubcategoryProductResponse
var uncategorized []models.Products
@@ -305,10 +305,28 @@ func (s *productService) ImportCatalogueProduct(reqs []models.ImportCataloguePro
return fmt.Errorf("catalogue product not found: brand=%s id=%d", req.Brand, req.Catalogueid)
}
existing, err := s.repo.FindTenantProductByCatalogueRef(req.Tenantid, req.Brand, req.Catalogueid)
// image_id first, catalogueid second.
//
// The order is the whole fix. `catalogueid` is renumbered by every
// re-scrape, so after one the ref lookup misses, this import believes it
// is seeing the product for the first time, and the shop ends up with a
// second copy of something it already stocks. `image_id` is the key the
// catalogue itself deduplicates on and survives both a renumber and a
// rename.
//
// The fallback is not dead code and will not become so: products
// imported before `imageid` existed carry only the id, and a re-import is
// how they acquire the stable key.
existing, err := s.repo.FindTenantProductByImageID(req.Tenantid, catalogueProduct.ImageID)
if err != nil {
return err
}
if existing == nil {
existing, err = s.repo.FindTenantProductByCatalogueRef(req.Tenantid, req.Brand, req.Catalogueid)
if err != nil {
return err
}
}
productID := 0
if existing != nil {
@@ -329,6 +347,15 @@ func (s *productService) ImportCatalogueProduct(reqs []models.ImportCataloguePro
if err := s.repo.UpdateProductCategory(productID, req.Categoryid, req.Subcategoryid); err != nil {
return err
}
// A re-import is also how an older product acquires the stable key,
// and how one whose id has since moved gets pointed back at the row
// it actually came from. Both are written together so they can never
// disagree about which catalogue row this is.
if catalogueProduct.ImageID != "" && existing.Imageid != catalogueProduct.ImageID {
if err := s.repo.SetCatalogueLink(productID, catalogueProduct.ImageID, int(catalogueProduct.ID)); err != nil {
return err
}
}
// Re-importing corrects the CATEGORY too, not just the price.
//
// This branch used to update pricing alone, which made a product
@@ -349,11 +376,14 @@ func (s *productService) ImportCatalogueProduct(reqs []models.ImportCataloguePro
Productsku: catalogueProduct.ProductSKU,
Productbrand: catalogueProduct.Brand,
Catalogueid: int(catalogueProduct.ID),
Productunit: catalogueProduct.Size,
Productcost: req.Productcost,
Retailprice: req.Retailprice,
Taxpercent: req.Taxpercent,
Approve: 1,
// Stored beside the id, and the one that will still mean
// something after the next scrape.
Imageid: catalogueProduct.ImageID,
Productunit: catalogueProduct.Size,
Productcost: req.Productcost,
Retailprice: req.Retailprice,
Taxpercent: req.Taxpercent,
Approve: 1,
}
if len(catalogueProduct.Images) > 0 {
// The first stays where every reader already looks for it.
@@ -415,7 +445,6 @@ func (s *productService) GetTenantCategories(tenantid int) ([]models.TenantCateg
return s.repo.GetTenantCategories(tenantid)
}
// PublishProduct releases a product to every outlet the tenant runs.
//
// The price check lives in the repository rather than here, so it holds for any