package services import "nearle/models" // InStockOnly drops the products an outlet cannot actually sell. // // Being on a shelf is not the same as being in stock. Suriya Store's Peelamedu // branch listed three products in the customer app of which exactly one had any // stock, so a shopper could add a chocolate bar to their basket and order // something the shop did not have. The rule the business wants is the simple // one: if we have the stock, it can be ordered — otherwise it is not on offer. // // ── Why here and not in the SQL ────────────────────────────────────────────── // // The obvious implementation is another correlated subquery in GetProducts' // WHERE clause. This is better for a reason that outlives the convenience: // `Productstock` is the number the app DISPLAYS, computed by that query as the // live SUM(in)-SUM(out) balance. Filtering on the same value that is shown makes // it impossible for the two to disagree. A second copy of the balance // expression in a WHERE clause is a copy that can drift from the one in the // SELECT, and the failure it produces — a product listed as "3 in stock" that // the filter considers empty, or worse the reverse — is very hard to see. // // It also filters on the ledger rather than productlocations.status. That flag // is derived and admits drift: SyncProductLocationStatus repairs it on the next // ledger entry, so between an order and that entry it can still read // "available" for an empty shelf. The ledger cannot drift from itself. // // ── The locationID guard ───────────────────────────────────────────────────── // // Not defensive padding. GetProducts computes the stock balance scoped to // params.LocationID, and with no outlet the subquery matches nothing, so EVERY // product comes back with Productstock 0. Filtering that would turn an unscoped // browse into an empty catalogue rather than an unfiltered one. // // The console is deliberately unaffected: its inventory screens read // GetLocationProducts, a different query, and they must keep showing the empty // lines — restocking them is the entire point of that screen. func InStockOnly(products []models.Products, locationID int) []models.Products { if locationID <= 0 { return products } // Rebuilt rather than filtered in place: the caller's slice is the // repository's own result and nothing here should be writing through it. inStock := make([]models.Products, 0, len(products)) for _, p := range products { if p.Productstock > 0 { inStock = append(inStock, p) } } return inStock }