Get a quote

Building a Supplier and Purchase Order System for Restaurants and Retailers in MENA

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/

Free PDF Download

Enjoying this article?

Enter your email and get a clean, formatted PDF of this article - free, no spam.

Free. No spam. Unsubscribe any time.

Back to blog
Chat on WhatsApp