Sync stock status on restock and expose live stock on getproductbyvariant
Two gaps found while auditing the order/stock flow: - Receiving stock via an approved stock request only updated products.productstatus (a global per-product flag). A location flagged outofstock by CreateOrder never got reactivated, since nothing touched productlocations.status on the way back in. CreateProductStock now reactivates the specific (tenant, location, product) row to "available" for every "in" entry, the counterpart to how it gets flagged out. - getproductbyvariant returned no stock info at all, so the app could only find out a product was unavailable from the 409 at order time. It now accepts an optional locationid and, when passed, returns live productstock (same SUM(in)-SUM(out) formula the order check uses) and locationstatus per product. Omitting locationid keeps the old response shape. Added MOBILE_ORDER_VERIFICATION.md as a handoff doc for the mobile team covering the expected request/response shapes and how to verify their integration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ type ProductRepository interface {
|
||||
GetProductStocks(tenantID, locationID string) ([]models.Productstocks, error)
|
||||
CreateProductStock(stocks []models.Productstock) error
|
||||
UpdateProductStatus(productIDs []int, status string) error
|
||||
ReactivateProductLocations(refs []models.ProductLocationRef) error
|
||||
CreateProduct(product models.Products) error
|
||||
UpdateProduct(product models.Products) error
|
||||
DeleteProduct(productID int) error
|
||||
@@ -29,7 +30,7 @@ type ProductRepository interface {
|
||||
GetLocationProducts(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Locationproducts, error)
|
||||
GetLocationProductSummary(tenantID, locationID int) ([]models.ProductSummary, error)
|
||||
FetchFilteredProducts(categoryID, subcategoryID, productID, applocationID, tenantID, locationID int, keyword, productStatus, approve string, pageno, pagesize int) ([]models.Tenantproducts, error)
|
||||
GetProductByVariant(tenantid, variantid int) ([]models.Products, error)
|
||||
GetProductByVariant(tenantid, variantid, locationid int) ([]models.Products, error)
|
||||
GetSubcategories(categoryID int) ([]models.Subcategory, error)
|
||||
GetProducts(params models.ProductFilter) ([]models.Products, error)
|
||||
GetTenantInfo(tenantID, applocationID int) (map[string]interface{}, error)
|
||||
@@ -263,6 +264,23 @@ func (r *productRepository) CreateProductStock(stocks []models.Productstock) err
|
||||
return r.db.Table("productstocks").Create(&stocks).Error
|
||||
}
|
||||
|
||||
// ReactivateProductLocations flips productlocations.status back to
|
||||
// "available" for each ref — the counterpart to CreateOrder flagging a
|
||||
// location "outofstock" when its stock hits zero. Without this, a store
|
||||
// that runs out and then restocks via an approved stock request stays
|
||||
// flagged outofstock forever, since receiving stock only ever added to the
|
||||
// productstocks ledger and never touched this per-location flag.
|
||||
func (r *productRepository) ReactivateProductLocations(refs []models.ProductLocationRef) error {
|
||||
for _, ref := range refs {
|
||||
if err := r.db.Table("productlocations").
|
||||
Where("tenantid = ? AND locationid = ? AND productid = ?", ref.Tenantid, ref.Locationid, ref.Productid).
|
||||
Update("status", "available").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *productRepository) UpdateProductStatus(productIDs []int, status string) error {
|
||||
return r.db.Table("products").
|
||||
Where("productid IN ?", productIDs).
|
||||
@@ -567,10 +585,16 @@ func (r *productRepository) FetchFilteredProducts(
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (r *productRepository) GetProductByVariant(tenantid, variantid int) ([]models.Products, error) {
|
||||
func (r *productRepository) GetProductByVariant(tenantid, variantid, locationid int) ([]models.Products, error) {
|
||||
|
||||
var data []models.Products
|
||||
|
||||
// productstock is a correlated subquery (not a JOIN+GROUP BY) so it can
|
||||
// coexist with `p.*` without having to enumerate every products column.
|
||||
// When locationid is 0 (caller didn't scope to a store), both the
|
||||
// subquery and the productlocations join simply match nothing, so
|
||||
// Productstock/Locationstatus come back zero-valued — same response
|
||||
// shape as before this field existed, not an error.
|
||||
err := r.db.
|
||||
Table("products p").
|
||||
Select(`
|
||||
@@ -578,11 +602,19 @@ func (r *productRepository) GetProductByVariant(tenantid, variantid int) ([]mode
|
||||
c.categoryname,
|
||||
d.subcatname AS subcategoryname,
|
||||
COALESCE(pd.discountvalue, 0) AS discountvalue,
|
||||
pd.discountid
|
||||
`).
|
||||
pd.discountid,
|
||||
pl.status AS locationstatus,
|
||||
COALESCE((
|
||||
SELECT SUM(CASE WHEN LOWER(ps.stocktype) = 'in' THEN ps.quantity ELSE 0 END) -
|
||||
SUM(CASE WHEN LOWER(ps.stocktype) = 'out' THEN ps.quantity ELSE 0 END)
|
||||
FROM productstocks ps
|
||||
WHERE ps.productid = p.productid AND ps.tenantid = p.tenantid AND ps.locationid = ?
|
||||
), 0) AS productstock
|
||||
`, locationid).
|
||||
Joins("LEFT JOIN productcategories c ON p.categoryid = c.categoryid").
|
||||
Joins("LEFT JOIN productsubcategories d ON p.subcategoryid = d.subcatid").
|
||||
Joins("LEFT JOIN productdiscounts pd ON pd.productid = p.productid").
|
||||
Joins("LEFT JOIN productlocations pl ON pl.productid = p.productid AND pl.tenantid = p.tenantid AND pl.locationid = ?", locationid).
|
||||
Where("p.tenantid = ? AND p.variants = ?", tenantid, variantid).
|
||||
Order("p.productid DESC").
|
||||
Scan(&data).Error
|
||||
|
||||
Reference in New Issue
Block a user