Purchasing Cycle¶
Backend domains:
- app/graphql/purchase_requisitions/
- app/graphql/purchase_orders/
- app/graphql/suppliers/ (supplier quotes live here)
- app/graphql/invoices/ (supplier-side mutations:
supplier_invoice_mutations.py) - app/graphql/expenses/
- app/graphql/credits/ (supplier credit notes)
RBAC Paths: PURCHASE_REQUISITIONS, PURCHASE_ORDERS, SUPPLIER_INVOICES, EXPENSES, SUPPLIER_CREDITS.
Overview¶
flowchart LR
PR[Purchase Requisition] -->|approve| PO[Purchase Order]
SQ[Supplier Quote] -->|convert| PO
PO -->|receive items| INV[Inventory ↑]
PO -->|bill| SI[Supplier Invoice]
SI -->|reversal| SC[Supplier Credit]
SI -->|pay| PAY[Bank ↓ / AP ↓]
EX[Expense] -->|one-off, no PO| PAY
A PYME workflow can be as light as "log the expense from a receipt" or as formal as the full PR → approval → PO → receive → 3-way match → pay flow.
Purchase Requisitions¶
A PR is the only domain today that runs through the approval framework (approval_configs/). It captures intent to buy: requested by an employee, routed to an approver, and converted to a PO once approved.
Key mutations: createPurchaseRequisition, updatePurchaseRequisition, submitPurchaseRequisitionForApproval, approvePurchaseRequisition, rejectPurchaseRequisition, deletePurchaseRequisition, convertPurchaseRequisitionToPo.
Key query: purchaseRequisition (single by id).
Supplier Quotes¶
suppliers/mutations/supplier_quote_mutations.py lets a buyer log multiple supplier offers for the same item set and pick a winner.
Key mutations: createSupplierQuote, updateSupplierQuote, deleteSupplierQuote, createExistingSupplierQuoteForSupplier (replicate to other suppliers).
Purchase Orders¶
A PO is the legal commitment to a supplier. It can be created from scratch, from a supplier quote, or even reverse-engineered from a supplier invoice ("I already received it, log the PO retroactively").
Key mutations: createPurchaseOrder, createPurchaseOrderFromSupplierQuote, createPurchaseOrderFromSupplierInvoice, updatePurchaseOrder, receivePurchaseOrderItems (partial receiving supported), deletePurchaseOrder.
Key queries: getPurchaseOrderById, getPurchaseOrderByNumber, getPurchaseOrderAmountsReport, getPurchaseOrdersBySupplierId.
Receiving a PO posts an inventory entry; the supplier invoice posts the GL leg.
Supplier Invoices¶
Same Invoice model as customer invoices but on the AP side. Mutations live under invoices/mutations/supplier_invoice_mutations.py and Strawberry types under invoices/strawberry/supplier_invoice_*.py.
Use cases: bill from a received PO, bill standalone, scan-and-parse a PDF/image via AI (planned), or import an XLSX batch via invoices/tabular/.
GL leg: Expense or Inventory ↔ AP plus ITBMS receivable for credits the tenant can take.
Expenses (one-off)¶
Expenses are non-PO disbursements: petty cash, fuel, utility bills paid on the spot. They support categorisation (expense_category_*) and post directly to the GL.
Key mutations: createExpenseCategory, updateExpenseCategory, deleteExpenseCategory, createExpense, updateExpense, deleteExpense.
Key queries: getExpenseById, getExpenseCategoryById, getExpenseCategoryByName, searchExpenseCategories.
For repeating expenses (rent, internet) see Recurring Expenses.
Supplier Credits¶
Mirrors customer credit notes for the AP side. Used when a supplier issues a credit (returned product, agreed discount, billing error).
Key mutations: createSupplierCredit, cancelSupplierCredit.
Key queries: getSupplierCreditById, getSupplierCreditsBySupplierInvoiceId.
Cross-cutting¶
- Approval routing is currently wired only on Purchase Requisitions; see Approvals & Sequences.
- Receipt vs. invoice: receiving items on a PO updates stock but does NOT post the GL — that's the supplier invoice's job. This separation lets accounts close cleanly when the bill arrives later.
- 3-way match: PO ↔ Receiving ↔ Supplier Invoice is enforceable at the application layer today; full automated matching is a roadmap item.
- RUC lookup: when entering a supplier, the RUC verification path (
ruc-verification.md) can prefill legal name and DV.