Manual inventory management in retail environments produces stock discrepancies, surprise stockouts, and pricing errors. This is the technical architecture for integrating barcode scanning with POS systems, built for retail and restaurant operations across Lebanon and MENA.
Manual inventory management in retail environments produces a predictable set of problems: stock discrepancies discovered at month end, sales processed against products that ran out days ago, and pricing errors caused by spreadsheet updates that never made it to the POS system. These are not management failures; they are system design failures. The barcode scanner and the point-of-sale terminal operate as separate systems with no live connection between them.
This is the technical architecture for integrating barcode-based inventory tracking with a POS system, built for retail and restaurant operations across Lebanon and the MENA region.
The data model that makes live inventory possible
The schema design determines whether the system can answer useful operational questions later. The most important design decision is to model inventory as a log of movements rather than a single mutable quantity per product.
CREATE TABLE stock_levels (
product_id UUID NOT NULL REFERENCES products(id),
location_id UUID NOT NULL REFERENCES inventory_locations(id),
quantity DECIMAL(12,3) NOT NULL DEFAULT 0,
last_updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY(product_id, location_id)
);
CREATE TABLE stock_movements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
product_id UUID NOT NULL REFERENCES products(id),
from_location_id UUID REFERENCES inventory_locations(id),
to_location_id UUID REFERENCES inventory_locations(id),
quantity DECIMAL(12,3) NOT NULL,
movement_type TEXT NOT NULL,
reference_id UUID,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Every sale writes to stock_movements as a movement from the storage location to the customer. Every goods receipt writes as a movement from the supplier to the warehouse. The current stock level in stock_levels is the materialized sum of all movements, updated atomically with each movement write.
This dual-table design means that the current quantity is always queryable in O(1), while the full movement history is available for reconciliation, audit, and analytics without any destructive updates.
Deducting inventory on sale: the atomic pattern
When a POS sale completes, inventory must be deducted for each sold item in the same database transaction as the sale record write. Two separate writes (first record the sale, then update stock) are vulnerable to partial failure.
func (s *InventoryService) DeductForSale(ctx context.Context, saleID uuid.UUID, items []SaleItem) error {
return s.db.WithTransaction(ctx, func(tx pgx.Tx) error {
for _, item := range items {
result, err := tx.Exec(ctx, `
UPDATE stock_levels
SET quantity = quantity - $1, last_updated_at = NOW()
WHERE product_id = $2 AND location_id = $3
AND quantity >= $1
`, item.Quantity, item.ProductID, item.LocationID)
if err != nil || result.RowsAffected() == 0 {
return fmt.Errorf("insufficient stock for product %s", item.ProductID)
}
_, err = tx.Exec(ctx, `
INSERT INTO stock_movements
(tenant_id, product_id, from_location_id, quantity, movement_type, reference_id)
VALUES ($1, $2, $3, $4, 'sale', $5)
`, item.TenantID, item.ProductID, item.LocationID, item.Quantity, saleID)
if err != nil {
return err
}
}
return nil
})
}
The quantity >= $1 condition in the UPDATE statement prevents negative stock without a separate check-then-update race condition. If the condition is not met, RowsAffected() returns 0, the service returns an error, the transaction rolls back, and the sale is rejected cleanly.
Barcode lookup and stock visibility at the POS
When a cashier scans a product barcode, the POS client sends the barcode to the API and receives back the product details and current available stock at the terminal's location.
Index the barcode column per tenant for fast lookup:
CREATE UNIQUE INDEX products_tenant_barcode_idx ON products(tenant_id, barcode);
A lookup by barcode with a tenant filter hits this index and returns in under 1 millisecond on a warm PostgreSQL instance.
Low-stock alerts: event-driven and scheduled
Low-stock alerts need two delivery paths. An immediate alert when a sale deducts stock below the product's minimum threshold. A scheduled morning report listing all products currently at or below their minimum.
For the scheduled report, run a daily PostgreSQL query:
SELECT p.name, p.barcode, sl.quantity, p.low_stock_threshold, il.name as location
FROM stock_levels sl
JOIN products p ON sl.product_id = p.id
JOIN inventory_locations il ON sl.location_id = il.id
WHERE sl.quantity <= p.low_stock_threshold
AND p.tenant_id = $1
ORDER BY (sl.quantity / NULLIF(p.low_stock_threshold, 0)) ASC;
Ordering by the ratio of current quantity to threshold surfaces the most critically depleted products at the top of the report.
Considerations specific to MENA retail operations
Intermittent connectivity. POS terminals in Lebanon frequently operate during internet outages. Barcode scan events should be queued locally and synced to the central inventory system when connectivity returns. Stock deductions from offline sales should sync with conflict detection.
Multi-unit products. Retail in MENA commonly involves products sold by weight (per kilogram), by count, and by case. The inventory schema must support fractional quantities and unit-of-measure conversions.
Key lessons from production
Model inventory as movements with a materialized current level, not as a mutable number to be decremented. Use the quantity >= $1 UPDATE pattern to prevent negative stock without separate read-then-write races. Write the sale and the stock deduction in a single transaction. Deliver low-stock alerts both immediately at deduction time and via a scheduled daily report.
Need help integrating inventory with your POS?
Voxire builds inventory management and POS integration systems for retail stores, restaurants, and distribution businesses across Lebanon and the MENA region. If your current setup involves manual counts, spreadsheet imports, or delayed inventory reconciliation, we can help redesign it.
https://voxire.com/get-a-quote/
Enjoying this article?
Enter your email and get a clean, formatted PDF of this article - free, no spam.


