From Manual Procurement to Intelligent Order Orchestration
A Technical Whitepaper on the Architecture, Design, and
Implementation of a Modern Procurement Platform
Michaelsoft Procurement is a production-grade, multi-tenant procurement orchestration platform purpose-built for small to medium businesses in Kenya and emerging markets. It transforms the fragmented, paper-based procurement lifecycle — supplier sourcing, price negotiation, purchase ordering, goods receiving, invoicing, and reconciliation — into a unified, intelligent, real-time digital workspace.
The system serves as a central nervous system for procurement operations, connecting business owners, purchasing managers, suppliers, and accounting teams through a shared data model with granular role-based access control.
| Capability | Description | |
|---|---|---|
| ● | Inventory Management | Product catalog with Excel bulk import, margin analytics, price history |
| ● | Supplier Directory | Contact management, logo uploads, preferred supplier marking, status flags |
| ● | Supplier Price Matrix | Global price book with bulk entry, cheapest-price lookup, margin badges |
| ● | Purchase Orders | Full lifecycle: draft → quoted → allocated → delivered → closed |
| ● | Supplier Quotations | Per-order item pricing via secure shareable links, approval workflow |
| ● | Cost Allocation Engine | Automatic cheapest-supplier allocation across multi-item orders |
| ● | Invoice Management | Multi-order linking, payment status, auto expenditure creation |
| ● | Sales Reconciliation | Daily cash + paybill + POS tracking with variance detection |
| ● | M-Pesa Reconciliation | Daily cash + float + expected amount tracking |
| ● | Executive Dashboard | KPIs, margin pie charts, order trends, quotation comparisons |
| ● | Reports Module | Cost distribution, item allocation, price trends, delivery status |
| ● | Export Hub | PDF and Excel exports for orders, invoices, expenditures, reconciliations |
| ● | Real-Time Chat | 1-on-1 and group messaging via Ably with reactions, replies, presence |
| ● | Delegation System | Amber notification dots when teammates create or modify data |
| ● | Granular Permissions | Module:subModule access control via invitation-based permission matrices |
| ● | M-Pesa Payments | Daraja STK Push and IntaSend integration for license purchase/renewal |
| ● | License System | Duration tiers (1 month to lifetime), auto-activation, expiration countdown |
| ● | Demo Mode | Multi-industry fixture data (pharmacy, food, hardware, retail, fashion) |
Small and medium businesses in emerging markets face a distinct set of procurement challenges:
Fragmented Data. Supplier price lists sit in WhatsApp messages, handwritten notebooks, and scattered spreadsheets. There is no single source of truth for what items cost across different vendors.
Manual Allocation. Choosing which supplier to buy each item from requires manually comparing prices across multiple quotations — a laborious, error-prone process that discourages proper price comparison.
Delayed Visibility. Business owners learn about stock deliveries, invoice statuses, and cash positions days or weeks after the fact. There is no real-time operational picture.
Trust Deficit. In delegated procurement environments (owner → purchasing manager), the owner has limited visibility into pricing decisions and supplier selections, leading to micromanagement or unchecked spending.
Payment Fragmentation. Licensing and subscription payments must work through M-Pesa — Kenya’s dominant mobile money platform — which adds integration complexity not addressed by global SaaS payment solutions.
No Graceful On-Ramp. Procurement software typically requires a full subscription commitment before users can evaluate whether the system fits their workflow.
Michaelsoft Procurement was designed around five core principles:
Offline-Resilient UX. Kenya’s internet connectivity is variable. The system uses optimistic UI updates extensively — every mutation is reflected instantly in the interface, with background reconciliation against the server.
Supplier-First Architecture. Unlike traditional procurement systems that treat suppliers as secondary metadata, this platform elevates suppliers to first-class entities with their own portal (via secure quotation links), delivery tracking, and price submission workflows.
Real-Time by Default. Every state change — a new quotation, a delivery confirmation, a chat message — propagates instantly to all authorized participants via Ably WebSocket channels. There is no "refresh to see updates."
Graceful Licensing. Unlicensed users can explore a full-featured multi-industry demo environment with realistic fixture data. The read-only mode for expired licenses ensures data is never locked away.
Mobile-First for Emerging Markets. The interface is responsive and optimized for the smaller screens and slower connections typical of African SMB environments.
/api/chat/* /api/payments/* /api/settings/* /api/user/* /api/supplier-quotations/*Core procurement entities and their relationships
idemailroleinvited_bypermissionsfull_nameidrefdatestatusnotesuser_ididorder_idproduct_idquantityselling_priceorder_idproduct_idsupplier_idunit_priceout_of_stockdeliveredis_selectedidnamecontactphone / emaillogo_urlstatusis_preferredidnameselling_pricebuying_priceunitproduct_idsupplier_idunit_priceout_of_stockFinancial & accounting entities
idinvoice_numberdatecompanyamountpaidorder_idsdiscountiddatedescriptionamountcategoryinvoice_idpaididdatecashpaybillpos_totalcommentsLicensing, chat & access entities
idkey (unique)statusemaildurationexpires_atpaididuser_idamount_kshphone_numberpayment_statusdaraja_checkout_ididsender_idreceiver_idhost_idtextis_group_chatreactionsdelivery_statereply_to_ididnamehost_iddescriptionicon_urlidemailinvited_bypermissionsstatusroleThe central catalog entity. Products have a name (stored in UPPERCASE for consistent lookups), selling_price (the retail price), buying_price (the cost price from suppliers), and unit (the unit multiplier, e.g., 100 for a pack of 100 tablets).
The marginStatus is a computed field derived from the buying and selling prices:
Contacts with status tracking (active, inactive, on_hold), optional logo uploads (stored in Supabase Storage), and a is_preferred flag for quick reference.
A many-to-many relationship between products and suppliers storing unit_price and out_of_stock status. This is the "price book" — the global reference for what each supplier charges for each product.
The core workflow entity. Orders have a unique human-readable ref (generated as ORD-XXXX), a date, notes, and a status that progresses through a state machine (see Section 5).
Orders belong to a user_id (the effective business owner) and track created_by (the specific user who created it) for audit purposes.
Line items within an order, linking to a product with a quantity and selling_price (the price at which the business intends to sell the item).
This is the system’s most sophisticated data structure. It captures:
‘manual’ or ‘submission’ (from supplier portal)A secure, token-gated mechanism for suppliers to submit prices:
pending → under_review → approved | rejectedInvoice records link to one or more orders via order_ids (JSON array). When an invoice is created, the system can optionally auto-create a linked expenditure record, connecting the procurement and accounting sides of the business.
The monetization layer. License keys have a duration (1 month, 3 months, 6 months, 1 year, lifetime), a status (active, used, expired, paused, suspended, revoked), and an expires_at timestamp. Subscription orders capture the payment intent via M-Pesa STK Push.
The multi-tenant access control system. Users have a role (admin, user, invited) and invited_by to establish the delegation hierarchy. Invitations carry a permission matrix — a JSON array of Category:Permission strings that gate access to every UI section.
Real-time messages with delivery tracking:
sending → sent → delivered → read (for DMs)delivered_count / read_count / member_countreply_to_id with a cached reply_to snapshot for offline accessThe system uses a dual-status model. The database stores the canonical status (set by the user/process), but the UI computes an effective status via getEffectiveOrderStatus() that reflects the actual delivery state based on item-level data:
function getEffectiveStatus(order, orderPrices) {
// Closed and Draft pass through
if (order.status === 'closed') return 'closed';
if (order.status === 'draft') return 'draft';
// Analyze each item's fulfillment state
const fulfillmentItems = order.items.map(item => {
const itemPrices = orderPrices.filter(p => p.productId === item.productId);
const deliveredPrice = itemPrices.find(p => p.delivered);
const selectedPrice = deliveredPrice || itemPrices.find(p => p.isSelected);
const winningPrice = selectedPrice || cheapestPrice(itemPrices);
return { item, winningPrice };
});
const anyDelivered = fulfillmentItems.some(x => x.winningPrice?.delivered);
const anyPending = fulfillmentItems.some(x => !x.winningPrice?.delivered);
if (anyDelivered && anyPending) return 'partial';
if (anyDelivered) return 'delivered';
return order.status; // Fall through to quoted/allocated
}
This computation ensures that even if a user forgets to update the order status, the dashboard and reports always reflect the true operational state.
Every significant order mutation is logged to order_audit_logs:
interface OrderAuditLog {
id: string;
orderId: string;
orderRef: string;
actionType: 'EDIT' | 'DELETE' | 'CLOSE';
reason: string; // User-provided reason for the change
userId: string;
userName: string;
createdAt: string;
}
The audit trail is visible from the Reports page and serves as a compliance record for procurement decisions.
The supplier quotation system enables procurement teams to securely share order details with suppliers and receive priced quotations — without the supplier needing an account on the platform.
When a procurement user clicks "Generate Link" for a supplier:
supplier_price_submissions record with a unique UUID tokensubmission_token is generated server-sidehttps://procurement.michaelsoft.co.ke/supplier-quote/{token}// Token generation (server-side)
const submissionToken = crypto.randomUUID();
const tokenExpiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
The supplier-facing page is a lightweight, no-auth form that displays:
Submitted data flows through Zod validation and rate limiting before being written to the database.
When a submission is approved:
order_supplier_prices via upsertquoted)unit_price is preserved in previous_unit_priceThe dashboard features a "Quotation Comparison" matrix that displays, for each product in an order, the prices from all suppliers who have quoted:
| Product | Supplier A | Supplier B | Supplier C |
|---|---|---|---|
| Item 1 | KSH 1,200 | KSH 1,350 | KSH 1,180 |
| Item 2 | KSH 800 | KSH 750 | KSH 850 |
| Item 3 | KSH 2,000 | KSH 2,200 | KSH 1,950 |
| Cheapest | 1 item | 1 item | 1 item |
Green = Cheapest price for this product
The cheapest-supplier allocation logic:
function allocateOrder(order, orderPrices) {
for (const item of order.items) {
const cheapest = findCheapestSupplier(item.productId, orderPrices);
if (cheapest) {
allocate(item, cheapest);
}
}
}
For each product, the engine:
is_selected) pricesThe sales reconciliation module tracks daily sales across three channels:
| Date | Cash | Paybill | POS Total | Variance |
|---|---|---|---|---|
| 2026-06-15 | 25,000 | 12,000 | 35,000 | +2,000 |
| 2026-06-14 | 22,000 | 15,000 | 38,000 | -1,000 |
| 2026-06-13 | 28,000 | 10,000 | 36,000 | +2,000 |
Variance = (Cash + Paybill) − POS Total. Positive = more cash collected than expected. Negative = cash shortage (requires investigation).
Tracks mobile money agent float against expected balances:
| Date | Cash | Float | Expected | Total | Variance |
|---|---|---|---|---|---|
| 2026-06-15 | 28,000 | 50,000 | 80,000 | 78,000 | -2,000 |
| 2026-06-14 | 32,000 | 50,000 | 80,000 | 82,000 | +2,000 |
Invoices and expenditures are bidirectionally linked:
// When an invoice is created:
const invoice = await createInvoice(data);
if (createExpenditure) {
await createExpenditure({
date: invoice.date,
description: `Invoice ${invoice.invoiceNumber} - ${invoice.company}`,
amount: invoice.amount - invoice.discount,
category: 'Purchase',
invoiceId: invoice.id,
paid: invoice.paid,
});
}
// When an invoice's paid status changes:
// → Linked expenditure is auto-updated in the background
// → Dashboard profit calculation reflects the change instantly
This ensures the profit calculation on the executive dashboard (Revenue − Expenditure = Potential Profit) is always accurate without manual double-entry.
Real-time features are powered by Ably Realtime (WebSocket), replacing an earlier Supabase Realtime implementation that proved unreliable for chat workloads.
| Channel | Events | Purpose |
|---|---|---|
chat:{hostId} | message | New message persistence & broadcast |
chat:{hostId} | reaction | Reaction add/remove |
chat:{hostId} | delivery | DM delivery state changes |
chat:{hostId} | delivery_count | Group message aggregate counts |
typing:{hostId} | typing | Ephemeral typing indicators |
The system implements a dual delivery tracking model:
Direct Messages (1-on-1):
Group Messages:
Idempotent Message Sending: Each message send includes a clientNonce — a unique 32-character hex string generated client-side. The server uses (sender_id, client_nonce) as a unique constraint, ensuring that network retries never create duplicate messages.
The presence system tracks online/offline status with three layers:
setInterval writes last_seen_at to the profiles tablepresence.leave() AND writes last_seen_at to DB as a fallbackWhen a user comes back online, a background task identifies unacknowledged messages and publishes delivery events for them.
When an invited user creates or modifies data, an amber notification dot appears in the inviter’s sidebar:
recordActivity(‘orders’, ‘create’, orderId, meta)delegation_activities tableThe system intelligently routes notifications:
| Duration | Use Case | Price Point |
|---|---|---|
| 1 month | Monthly subscription | Low |
| 3 months | Quarterly commitment | Mid-High |
| 6 months | Semi-annual | High |
| 1 year | Annual commitment | Higher |
| Lifetime | Perpetual license | Premium |
When a license expires, is paused, or is revoked, the system enters read-only mode:
// Event listener on the dashboard layout
document.addEventListener('click', handleBlockedClick, true);
function handleBlockedClick(event) {
// Allow sidebar, chat, pagination, and sign-out
// Block all other interactive elements
// Show "License Required" toast with shake animation
}
This ensures businesses never lose access to their data, even when payment lapses.
Two payment providers are supported:
Safaricom Daraja API (Primary):
The demo mode provides a full-featured environment for prospective users to evaluate the system without a license:
// Demo mode determination (synchronous, safe anywhere)
function isDemoMode(): boolean {
return (
!authState.isInitializing &&
authState.hasLicenseKey === false &&
!authState.isAdmin &&
licenseStatus === 'none' &&
demoState.isDemoOptedIn === true
);
}
| Industry | Example Products | Suppliers |
|---|---|---|
| Pharmacy | Paracetamol, Amoxicillin, Metformin | Pharmacy Solutions Ltd, Global Pharma Distributors |
| Food & Grocery | Rice 5kg, Cooking Oil 2L, Wheat Flour | Food Solutions Ltd, Metro Wholesalers |
| Hardware | Cement 50kg, Steel Rebar, Gloss Paint | Hardware Solutions Ltd, Global Hardware Distributors |
| Retail | LED TV 43", Bluetooth Headphones, Kettle | Retail Solutions Ltd, Metro Retail Wholesalers |
| Fashion | Denim Jeans, Cotton T-Shirts, Sneakers | Fashion Solutions Ltd, Global Apparel Distributors |
Each industry dataset includes:
When a user switches industries, the system:
The system uses Supabase Auth with Google OAuth (PKCE flow):
/auth/callbacksb-supabase-auth-token)Role Hierarchy:
Admin: Bypasses all license checks. Can manage users, invitations, license keys, and view all data.
Owner: Has full access by default. Can invite teammates with granular permissions.
Invited User: Access gated by permission matrix from their invitation.
Permissions are structured as Category:Permission strings:
// Example permission matrix for an invited user
[
"General", // Category-level: grants access to ALL General sub-modules
"General:Dashboard", // Module-level: grants access to Dashboard specifically
"Inventory:Products",
"Inventory:Suppliers",
"Sales:Orders",
"Accounts:Invoices",
"Analytics:Reports"
]
Permission checking:
hasPermission(‘Sales:Orders’)profile.permissions array:All database tables have Supabase RLS policies that restrict access to:
user_id = auth.uid())user_id = getEffectiveUserId())API routes use requireLicense() middleware:
requireLicense() Middlewareinvited_by chainApplied to: quotation link generation (10/minute), email sending (5/minute), chat messages (30/minute).
All API boundaries use Zod schemas:
// Example: Chat message validation
const chatMessageSchema = z.object({
content: z.string()
.min(1, 'Message cannot be empty')
.max(5000, 'Message too long'),
groupId: z.string().uuid().optional(),
replyToId: z.string().uuid().optional(),
});
// Example: Payment initiation
const initiatePaymentSchema = z.object({
pricingTierId: z.string().uuid('Invalid pricing tier ID'),
phoneNumber: z.string()
.transform(s => s.replace(/\D/g, ''))
.refine(s => s.length === 10 || s.length === 12, 'Invalid phone'),
});
The system employs a stale-while-revalidate pattern for data fetching:
All mutation operations follow this pattern:
async function updateOrder(id, updates) {
// 1. Snapshot current state
const snapshot = get().orders;
// 2. Apply optimistic UI update
set(state => ({
orders: state.orders.map(o =>
o.id === id ? { ...o, ...updates } : o
),
}));
try {
// 3. Persist to DB
await supabase.from('orders').update(dbUpdates).eq('id', id);
} catch (error) {
// 4. Rollback on failure
set({ orders: snapshot });
throw error;
}
}
Products use pagination-through for large catalogs:
// Fetch all products in 1000-record chunks
let from = 0;
let to = 999;
let hasMore = true;
while (hasMore) {
const { data: chunk } = await supabase
.from('products')
.select()
.range(from, to);
if (chunk.length === 1000) {
from += 1000; to += 1000;
} else {
hasMore = false;
}
}
Key performance indexes (from migration scripts):
orders(user_id, date DESC) — Dashboard recent ordersorder_items(order_id) — Item lookupsorder_supplier_prices(order_id, product_id, supplier_id) — Price lookups (unique constraint)chat_messages(host_id, created_at DESC) — Chat history queriesprofiles(invited_by) — Delegate lookupsThe scripts/db-backup.sh script provides automated backups:
const securityHeaders = [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
];
The executive dashboard computes the following KPIs:
| KPI | Formula | Source | |
|---|---|---|---|
| ● | Revenue | Σ(Cash + Paybill) for period | Sales Reconciliation |
| ● | Total Expenditure | Σ(Amount) for period | Expenditures |
| ● | Potential Profit | Revenue − Expenditure | Computed |
| ● | Active Orders | Count of orders in period | Orders |
| ● | Good Margin Products | Count where marginStatus = ‘good’ | Products |
| ● | Supplier Coverage | (Items with prices / Total items) × 100 | Order Prices |
| ● | Delivery Rate | (Delivered orders / Total allocated) × 100 | Orders |
The system generates professional PDFs using client-side HTML + window.print():
| Template | Content | Purpose | |
|---|---|---|---|
| ● | Quotation Request | Items with blank price columns | Share with suppliers |
| ● | Purchase Order | Allocated items by supplier with prices | Issue to suppliers |
| ● | Invoice | Multi-order billing with items | Send to clients |
| ● | Invoice Report | All invoices with summaries | Accounting |
| ● | Expenditure Report | All expenses categorized | Financial review |
| ● | Product Price List | Catalog with margin analysis | Internal review |
| ● | Product Analysis | Single product with price trend | Deep dive |
| ● | Allocation Summary | Cheapest supplier by item | Procurement decision |
| ● | Orders List | All orders with item details | Operations |
| ● | Sales Reconciliation | Daily sales with variances | Daily close |
| ● | M-Pesa Reconciliation | Mobile money daily tracking | Financial control |
Real-Time Data Flow (Chat):