changes
This commit is contained in:
@@ -23,6 +23,7 @@ type ProductRepository interface {
|
||||
CreateProductStock(stocks []models.Productstock) error
|
||||
UpdateProductStatus(productIDs []int, status string) error
|
||||
SyncProductLocationStatus(refs []models.ProductLocationRef) error
|
||||
EnsureProductLocation(refs []models.ProductLocationRef) error
|
||||
CreateProduct(product models.Products) error
|
||||
UpdateProduct(product models.Products) error
|
||||
DeleteProduct(productID int) error
|
||||
@@ -49,6 +50,7 @@ type ProductRepository interface {
|
||||
GetImportedCatalogueRefs(tenantid int, brand string) ([]models.ImportedCatalogueRef, error)
|
||||
GetTenantCategories(tenantid int) ([]models.TenantCategory, error)
|
||||
UpdateProductPricing(productid int, retailprice, productcost, taxpercent float64) error
|
||||
UpdateProductCategory(productid, categoryid, subcategoryid int) error
|
||||
}
|
||||
|
||||
type productRepository struct {
|
||||
@@ -299,6 +301,49 @@ func (r *productRepository) CreateProductStock(stocks []models.Productstock) err
|
||||
return r.db.Table("productstocks").Create(&stocks).Error
|
||||
}
|
||||
|
||||
// EnsureProductLocation puts a product on an outlet's shelf if it is not there
|
||||
// already, and leaves it completely alone if it is.
|
||||
//
|
||||
// Receiving stock used to write the ledger and nothing else. The ledger is not
|
||||
// what the customer app reads: GetProducts joins productlocations and then
|
||||
// filters `pl.locationid = ?`, so a product with no row for that outlet is
|
||||
// dropped by the join no matter how much stock arrived. A shop could request a
|
||||
// product, have it approved, receive it, watch the stock rise in the console —
|
||||
// and the product was still not for sale, with nothing anywhere saying why.
|
||||
//
|
||||
// SyncProductLocationStatus below cannot cover this: it is an UPDATE, so with
|
||||
// no row to update it succeeds having changed nothing.
|
||||
//
|
||||
// INSERT ... SELECT ... WHERE NOT EXISTS rather than an upsert, deliberately.
|
||||
// An upsert here would carry a price, and the only price this code could supply
|
||||
// is the master retailprice — which would overwrite an outlet's own, carefully
|
||||
// different, price every time a delivery arrived. Existing rows must not be
|
||||
// touched; this only ever creates the missing one.
|
||||
//
|
||||
// The seeded status is "outofstock" because it is true at the instant of the
|
||||
// insert. The caller runs SyncProductLocationStatus immediately after, which
|
||||
// derives the real value from the ledger, so a genuine receipt corrects it in
|
||||
// the same call.
|
||||
func (r *productRepository) EnsureProductLocation(refs []models.ProductLocationRef) error {
|
||||
for _, ref := range refs {
|
||||
if err := r.db.Exec(`
|
||||
INSERT INTO productlocations (tenantid, locationid, productid, price, status)
|
||||
SELECT ?, ?, ?, COALESCE(p.retailprice, 0), 'outofstock'
|
||||
FROM products p
|
||||
WHERE p.productid = ? AND p.tenantid = ?
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM productlocations pl
|
||||
WHERE pl.tenantid = ? AND pl.locationid = ? AND pl.productid = ?
|
||||
)`,
|
||||
ref.Tenantid, ref.Locationid, ref.Productid,
|
||||
ref.Productid, ref.Tenantid,
|
||||
ref.Tenantid, ref.Locationid, ref.Productid).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncProductLocationStatus recomputes productlocations.status for each ref
|
||||
// from the productstocks ledger: "available" when the live SUM(in)-SUM(out)
|
||||
// balance is positive, "outofstock" when it is not.
|
||||
@@ -1124,6 +1169,31 @@ func (r *productRepository) GetTenantCategories(tenantid int) ([]models.TenantCa
|
||||
|
||||
// UpdateProductPricing updates only the pricing fields on a product
|
||||
// snapshot, used when a catalogue product is re-imported with new pricing.
|
||||
// UpdateProductCategory files a product under a category after the fact.
|
||||
//
|
||||
// It exists because nothing else could. `UpdateProduct` writes only
|
||||
// productlocations.status despite its name, and re-importing a product the
|
||||
// tenant already has took the `existing != nil` branch, which corrected the
|
||||
// pricing and left the category as it was. So a product imported with
|
||||
// categoryid 0 was permanently invisible to the customer app — the endpoint it
|
||||
// browses rejects categoryid 0 outright — with no API able to repair it. Seven
|
||||
// products across three tenants were in that state, six outlets showing an
|
||||
// empty shop to shoppers while the console listed their stock.
|
||||
//
|
||||
// A zero is never written. Callers pass whatever the import request carried,
|
||||
// and a request that omits the category must not erase one that is already
|
||||
// correct — the guard belongs here rather than in each caller.
|
||||
func (r *productRepository) UpdateProductCategory(productid, categoryid, subcategoryid int) error {
|
||||
if categoryid <= 0 {
|
||||
return nil
|
||||
}
|
||||
updates := map[string]interface{}{"categoryid": categoryid}
|
||||
if subcategoryid > 0 {
|
||||
updates["subcategoryid"] = subcategoryid
|
||||
}
|
||||
return r.db.Table("products").Where("productid = ?", productid).Updates(updates).Error
|
||||
}
|
||||
|
||||
func (r *productRepository) UpdateProductPricing(productid int, retailprice, productcost, taxpercent float64) error {
|
||||
return r.db.Table("products").
|
||||
Where("productid = ?", productid).
|
||||
|
||||
Reference in New Issue
Block a user