Most restaurants and retailers in Lebanon manage supplier relationships through WhatsApp and paper invoices. When a business operates multiple branches and handles dozens of suppliers, the absence of a supplier management system shows up in food cost variance, invoice disputes, and inventory discrepancies.
Most restaurants and retail stores in Lebanon manage supplier relationships through WhatsApp messages, paper invoices, and prices kept in someone's head. This approach works at low volume. When a business operates multiple branches, handles dozens of suppliers, and processes hundreds of purchase orders per month, the absence of a supplier management system becomes an operational liability that shows up in food cost variance, invoice disputes, and inventory discrepancies.
The core problem with informal supplier management
Without a supplier management system, the same information exists in too many places. A manager negotiates a price with a supplier. The procurement staff receives an invoice at a different price. There is no agreed record to resolve the discrepancy. A supplier delivers short and the receiving staff does not document the shortage. The invoice gets paid in full.
These are not catastrophic failures. They are small leaks that compound. Over a year of running a medium-sized restaurant in Lebanon, untracked shortages, price drift, and undocumented discrepancies add up to meaningful margin loss.
What a supplier management system needs to do
A supplier management system for a Lebanese restaurant or retail operation needs to handle:
Supplier master data: name, contact, delivery schedule, payment terms, product categories. This alone eliminates the problem of placing orders with undocumented suppliers.
Contracted price records: the agreed price per unit for each product from each supplier, with effective dates. When an invoice arrives at a price that differs from the contracted price, the system flags it for review before payment is approved.
Purchase order lifecycle: draft, sent, partially received, fully received, invoiced, paid. Every stage creates a record.
Goods receipt: the actual quantities received versus ordered, at the item level. Shortages are documented automatically. The inventory system updates from receipt records, not from purchase order quantities.
Invoice reconciliation: the system compares the received invoice against the purchase order and goods receipt. Discrepancies block automatic payment approval.
Data model for purchase order management
CREATE TABLE suppliers (
id bigserial PRIMARY KEY,
tenant_id bigint NOT NULL,
name text NOT NULL,
contact_name text,
phone text,
payment_terms text, -- 'cod', 'net_30', 'net_15'
delivery_days text[], -- ['monday', 'thursday']
active boolean NOT NULL DEFAULT true
);
CREATE TABLE supplier_price_list (
id bigserial PRIMARY KEY,
supplier_id bigint NOT NULL REFERENCES suppliers(id),
tenant_id bigint NOT NULL,
product_id bigint NOT NULL,
unit_price decimal(12, 4) NOT NULL,
unit text NOT NULL,
effective_from date NOT NULL,
effective_to date
);
CREATE TABLE purchase_orders (
id bigserial PRIMARY KEY,
tenant_id bigint NOT NULL,
supplier_id bigint NOT NULL REFERENCES suppliers(id),
status text NOT NULL DEFAULT 'draft',
ordered_at timestamptz,
expected_at date,
received_at timestamptz,
total_ordered decimal(12, 2),
total_received decimal(12, 2),
notes text
);
CREATE TABLE purchase_order_items (
id bigserial PRIMARY KEY,
purchase_order_id bigint NOT NULL REFERENCES purchase_orders(id),
product_id bigint NOT NULL,
ordered_qty decimal(10, 3) NOT NULL,
received_qty decimal(10, 3),
unit text NOT NULL,
unit_price decimal(12, 4) NOT NULL,
total decimal(12, 2) GENERATED ALWAYS AS (ordered_qty * unit_price) STORED
);
Integrating with inventory
The receiving step is where the supplier management system connects to inventory. When goods receipt is recorded:
func (s *PurchaseService) RecordReceipt(ctx context.Context, req ReceiptRequest) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
for _, item := range req.Items {
// Update purchase order item with actual received quantity
_, err = tx.ExecContext(ctx, `
UPDATE purchase_order_items
SET received_qty = $1
WHERE id = $2 AND purchase_order_id = $3
`, item.ReceivedQty, item.ItemID, req.PurchaseOrderID)
if err != nil {
return err
}
// Add to inventory
_, err = tx.ExecContext(ctx, `
INSERT INTO inventory_movements
(tenant_id, product_id, location_id, type, quantity, reference_type, reference_id, unit_cost)
VALUES ($1, $2, $3, 'receipt', $4, 'purchase_order', $5, $6)
`, req.TenantID, item.ProductID, req.LocationID,
item.ReceivedQty, req.PurchaseOrderID, item.UnitCost)
if err != nil {
return err
}
}
_, err = tx.ExecContext(ctx, `
UPDATE purchase_orders SET status = 'received', received_at = now()
WHERE id = $1
`, req.PurchaseOrderID)
if err != nil {
return err
}
return tx.Commit()
}
Price variance detection
Before recording a goods receipt, the system compares the invoice price against the contracted price for each line item:
func (s *PurchaseService) CheckPriceVariance(ctx context.Context, tenantID, supplierID int64, items []InvoiceItem) ([]PriceVariance, error) {
var variances []PriceVariance
for _, item := range items {
var contractedPrice decimal.Decimal
err := s.db.QueryRowContext(ctx, `
SELECT unit_price
FROM supplier_price_list
WHERE tenant_id = $1
AND supplier_id = $2
AND product_id = $3
AND effective_from <= CURRENT_DATE
AND (effective_to IS NULL OR effective_to >= CURRENT_DATE)
ORDER BY effective_from DESC
LIMIT 1
`, tenantID, supplierID, item.ProductID).Scan(&contractedPrice)
if err == sql.ErrNoRows {
continue // No contracted price on record
}
variancePct := item.InvoicePrice.Sub(contractedPrice).
Div(contractedPrice).Mul(decimal.NewFromInt(100))
if variancePct.Abs().GreaterThan(decimal.NewFromFloat(2.0)) {
variances = append(variances, PriceVariance{
ProductID: item.ProductID,
ContractedPrice: contractedPrice,
InvoicePrice: item.InvoicePrice,
VariancePct: variancePct,
})
}
}
return variances, nil
}
Operational lessons from Lebanese and MENA deployments
Keep the receiving interface minimal. The employee at the receiving dock is not a system administrator. If the receipt screen requires more than three taps to record received quantities, it will not be used consistently.
Support Arabic product names natively. Most product names in Lebanese restaurant operations are Arabic or transliterated Arabic. A system that forces Latin-character entry will be worked around.
Do not build the full feature set on day one. Start with the supplier directory and purchase order creation. Add price comparison and invoice reconciliation after the team has adopted the basic workflow. Adoption is more important than completeness in the first six months.
Build price variance alerts before full invoice reconciliation. Knowing that a supplier charged 8% more than the contracted price is operationally useful even before the complete reconciliation workflow is implemented.
Key lessons from production
The highest-value features are supplier price records and goods receipt documentation. Price records give you a reference for invoice disputes. Goods receipts update inventory from actual quantities, not theoretical purchase quantities. Everything else is built on top of these two capabilities.
Need a supplier management system built for your operation?
Voxire builds operational software for restaurants, retailers, and businesses across Lebanon and the MENA region. Reach out at https://voxire.com/get-a-quote/
Enjoying this article?
Enter your email and get a clean, formatted PDF of this article - free, no spam.



