Michaelsoft
Procurement System

From Manual Procurement to Intelligent Order Orchestration

A Technical Whitepaper on the Architecture, Design, and
Implementation of a Modern Procurement Platform

Version1.0PublishedJune 2026
StackNext.js 16 · React 19 · Supabase · AblyClassificationInternal / Technical Reference
Domainprocurement.michaelsoft.co.ke
Confidential — For Authorized Recipients Only
QR Code to download whitepaper as PDF Scan to download PDF

Table of Contents

  1. Executive Summary3
  2. The Procurement Challenge4
  3. System Architecture Overview5
  4. Core Domain Model7
  5. Order Lifecycle & State Machine10
  6. Supplier Quotation Engine12
  7. Financial Reconciliation Framework15
  8. Real-Time Collaboration Infrastructure16
  9. Licensing & Monetization19
  10. Multi-Industry Demo Environment21
  11. Security & Governance22
  12. Performance & Scalability25
  13. Deployment & Operations27
  14. Roadmap28

01 Executive Summary

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.

Key Capabilities

CapabilityDescription
●Inventory ManagementProduct catalog with Excel bulk import, margin analytics, price history
●Supplier DirectoryContact management, logo uploads, preferred supplier marking, status flags
●Supplier Price MatrixGlobal price book with bulk entry, cheapest-price lookup, margin badges
●Purchase OrdersFull lifecycle: draft → quoted → allocated → delivered → closed
●Supplier QuotationsPer-order item pricing via secure shareable links, approval workflow
●Cost Allocation EngineAutomatic cheapest-supplier allocation across multi-item orders
●Invoice ManagementMulti-order linking, payment status, auto expenditure creation
●Sales ReconciliationDaily cash + paybill + POS tracking with variance detection
●M-Pesa ReconciliationDaily cash + float + expected amount tracking
●Executive DashboardKPIs, margin pie charts, order trends, quotation comparisons
●Reports ModuleCost distribution, item allocation, price trends, delivery status
●Export HubPDF and Excel exports for orders, invoices, expenditures, reconciliations
●Real-Time Chat1-on-1 and group messaging via Ably with reactions, replies, presence
●Delegation SystemAmber notification dots when teammates create or modify data
●Granular PermissionsModule:subModule access control via invitation-based permission matrices
●M-Pesa PaymentsDaraja STK Push and IntaSend integration for license purchase/renewal
●License SystemDuration tiers (1 month to lifetime), auto-activation, expiration countdown
●Demo ModeMulti-industry fixture data (pharmacy, food, hardware, retail, fashion)

Technology Stack

Presentation Layer
Next.js 16 App Router React 19 Tailwind CSS 4 Framer Motion Recharts Lucide Icons
State Management
Zustand 5 (client stores) Zod 4 (validation)
Real-Time
Ably Realtime WebSocket Presence API
Database & Auth
Supabase PostgreSQL Row-Level Security Storage (logos)
Integrations
Safaricom Daraja API IntaSend Payments Resend Email Kapso WhatsApp

02 The Procurement Challenge

2.1 The Problem Space

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.

2.2 Design Principles

Michaelsoft Procurement was designed around five core principles:

  1. 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.

  2. 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.

  3. 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."

  4. 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.

  5. Mobile-First for Emerging Markets. The interface is responsive and optimized for the smaller screens and slower connections typical of African SMB environments.


03 System Architecture Overview

3.1 High-Level Architecture

DNS / Vercel Edgeprocurement.michaelsoft.co.ke
Next.js 16 Application
PublicPages
AuthRoutes
DashboardRoutes
AdminRoutes
API Routes
/api/chat/*   /api/payments/*   /api/settings/*   /api/user/*   /api/supplier-quotations/*
Zustand State Layer
useAuthStore · useOrderStore · useChatStore · useProductStore · useInvoiceStore · useSettingsStore
Supabase
PostgreSQL
Auth (SSO)
Row-Level Security
Storage
Realtime
Ably Real-Time
Chat:{hostId}
Typing:{hostId}
Presence
External Services
Safaricom Daraja API
IntaSend Payments
Resend (Email)
Kapso (WhatsApp)
Google Analytics

04 Core Domain Model

4.1 Entity Relationship Diagram

Core procurement entities and their relationships

1 : Many (ownership)
Many : Many (junction)
Many : 1 (reference)
1 : 1 (linked)
1 : Many (chat)
Profiles (Users)
id
email
role
invited_by
permissions
full_name
Orders
id
ref
date
status
notes
user_id
Order Items
id
order_id
product_id
quantity
selling_price
┌─ ── 1 : N ── 1 : N ─┐
Order Supplier Prices
order_id
product_id
supplier_id
unit_price
out_of_stock
delivered
is_selected
Suppliers
id
name
contact
phone / email
logo_url
status
is_preferred
┌─ ── N : 1 ── via junction ─┐
Products
id
name
selling_price
buying_price
unit
Supplier Prices
product_id
supplier_id
unit_price
out_of_stock
┌─ ── 1 : N (price book) ─┐

Financial & accounting entities

Invoices
id
invoice_number
date
company
amount
paid
order_ids
discount
Expenditures
id
date
description
amount
category
invoice_id
paid
Sales Reconciliation
id
date
cash
paybill
pos_total
comments
┌─ ── 1 : 1 (invoice_id) ─┐

Licensing, chat & access entities

License Keys
id
key (unique)
status
email
duration
expires_at
paid
Subscription Orders
id
user_id
amount_ksh
phone_number
payment_status
daraja_checkout_id
┌─ ── 1 : 1 (payment intent) ─┐
Chat Messages
id
sender_id
receiver_id
host_id
text
is_group_chat
reactions
delivery_state
reply_to_id
Chat Groups
id
name
host_id
description
icon_url
Invitations
id
email
invited_by
permissions
status
role

4.2 Entity Definitions

Products

The 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:

marginPercent = ((sellingPrice - buyingPrice) / buyingPrice) × 100
Good
marginPercent ≥ 33%
Low
marginPercent ≥ 15%
Loss
0 < marginPercent < 15%
Pending
sellingPrice = 0

Suppliers

Contacts with status tracking (active, inactive, on_hold), optional logo uploads (stored in Supabase Storage), and a is_preferred flag for quick reference.

Supplier Prices (Global Price Matrix)

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.

Orders

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.

Order Items

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).

Order Supplier Prices (Quotation-Scoped Prices)

This is the system’s most sophisticated data structure. It captures:

Supplier Price Submissions

A secure, token-gated mechanism for suppliers to submit prices:

Invoices & Expenditures

Invoice 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.

License Keys & Subscription Orders

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.

Profiles & Invitations

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.

Chat Messages

Real-time messages with delivery tracking:


05 Order Lifecycle & State Machine

5.1 State Transitions

DRAFTInitial state for new orders
Add items, request supplier quotes
QUOTEDAt least one item has supplier price data
Some items quoted, some not
PARTIALLY_QUOTEDSome items have prices, others don’t
All items have prices, suppliers selected
ALLOCATEDItems assigned to cheapest/selected suppliers
Generate purchase orders, export POs
EXPORTEDPurchase orders issued to suppliers
DELIVEREDGoods received
PARTIALPartial delivery
CLOSEDOrder fully fulfilled, accounting complete

5.2 Effective Status Computation

The 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.

5.3 Order Audit Trail

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.


06 Supplier Quotation Engine

6.1 Architecture

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.

Procurement Team
Supplier
1 Create Order
2 Generate Link
 
Link sent to supplier
 
3 Open Secure Link
4 Enter Prices / Notes
5 Submit Quotation
Notification sent
6 Review Quotation
7 Approve / Reject
8 Prices Saved to Order
 

6.2 Secure Link Generation

When a procurement user clicks "Generate Link" for a supplier:

  1. The system creates a supplier_price_submissions record with a unique UUID token
  2. A cryptographically random 32-char hex submission_token is generated server-side
  3. The link format is: https://procurement.michaelsoft.co.ke/supplier-quote/{token}
  4. The link auto-copies to the clipboard
  5. The token expires after 24 hours (configurable)
// Token generation (server-side)
const submissionToken = crypto.randomUUID();
const tokenExpiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours

6.3 Supplier Price Submission Portal

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.

6.4 Approval Workflow

Status Flow PENDING → UNDER_REVIEW → APPROVED (prices saved to order)
→ REJECTED (supplier notified with reason)

When a submission is approved:

  1. All submitted prices are written to order_supplier_prices via upsert
  2. The order’s status is updated (at least one item now has a price → quoted)
  3. The supplier’s previous unit_price is preserved in previous_unit_price
  4. A delegation notification is created for the approval action

6.5 Price Comparison Matrix

The dashboard features a "Quotation Comparison" matrix that displays, for each product in an order, the prices from all suppliers who have quoted:

ProductSupplier ASupplier BSupplier C
Item 1KSH 1,200KSH 1,350KSH 1,180
Item 2KSH 800KSH 750KSH 850
Item 3KSH 2,000KSH 2,200KSH 1,950
Cheapest1 item1 item1 item

Green = Cheapest price for this product

6.6 Allocation Engine

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:

  1. Prioritizes delivered prices (already fulfilled by this supplier)
  2. Falls back to manually selected (is_selected) prices
  3. Falls back to cheapest available price
  4. Excludes out-of-stock items
  5. Returns a structured allocation report showing which supplier gets which items, total values, and coverage percentages

07 Financial Reconciliation Framework

7.1 Sales Reconciliation

The sales reconciliation module tracks daily sales across three channels:

DateCashPaybillPOS TotalVariance
2026-06-1525,00012,00035,000+2,000
2026-06-1422,00015,00038,000-1,000
2026-06-1328,00010,00036,000+2,000

Variance = (Cash + Paybill) − POS Total. Positive = more cash collected than expected. Negative = cash shortage (requires investigation).

7.2 M-Pesa Reconciliation

Tracks mobile money agent float against expected balances:

DateCashFloatExpectedTotalVariance
2026-06-1528,00050,00080,00078,000-2,000
2026-06-1432,00050,00080,00082,000+2,000

7.3 Invoice-Expenditure Linkage

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.


08 Real-Time Collaboration Infrastructure

8.1 Architecture

Real-time features are powered by Ably Realtime (WebSocket), replacing an earlier Supabase Realtime implementation that proved unreliable for chat workloads.

Client A
(Buyer)
WebSocket
chat:{hostId}
Ably Service
chat:{hostId}
typing:{hostId}
Presence
chat:{hostId}
Client B
(Supplier Rep)
WebSocket
Supabase (Persist) chat_messages · chat_groups · chat_group_members

8.2 Channel Layout

ChannelEventsPurpose
chat:{hostId}messageNew message persistence & broadcast
chat:{hostId}reactionReaction add/remove
chat:{hostId}deliveryDM delivery state changes
chat:{hostId}delivery_countGroup message aggregate counts
typing:{hostId}typingEphemeral typing indicators

8.3 Message Delivery Tracking

The system implements a dual delivery tracking model:

Direct Messages (1-on-1):

Sending client → Sent → Delivered → Read
Visual Indicators
✓Sent
✓✓Delivered
✓✓Read (blue)

Group Messages:

Aggregate Tracking
delivered_countHow many group members received the message
read_countHow many have read it
member_countTotal recipients (excluding sender)
Display"2/5 delivered, 1/5 read"

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.

8.4 Presence System

The presence system tracks online/offline status with three layers:

  1. Ably Presence: Real-time enter/leave/update events on the chat channel
  2. DB Heartbeat: A 30-second setInterval writes last_seen_at to the profiles table
  3. Leave Handling: On page unload, the system calls presence.leave() AND writes last_seen_at to DB as a fallback

When a user comes back online, a background task identifies unacknowledged messages and publishes delivery events for them.

8.5 Delegation Notifications

When an invited user creates or modifies data, an amber notification dot appears in the inviter’s sidebar:

Delegation Notification Workflow
1DelegatedUser creates order
2Calls recordActivity(‘orders’, ‘create’, orderId, meta)
3Activity written to delegation_activities table
4Host sees amber dot on sidebar "Orders" section
5On click → dot clears → user taken to relevant section

The system intelligently routes notifications:


09 Licensing & Monetization

9.1 License Model

ACTIVE
PAUSED
SUSPENDED
USED
Activated but still valid
EXPIRED
REVOKED

9.2 Duration Tiers

DurationUse CasePrice Point
1 monthMonthly subscriptionLow
3 monthsQuarterly commitmentMid-High
6 monthsSemi-annualHigh
1 yearAnnual commitmentHigher
LifetimePerpetual licensePremium

9.3 Read-Only Mode

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.

9.4 Payment Integration

Two payment providers are supported:

Safaricom Daraja API (Primary):

Safaricom Daraja API (Primary)
1User enters phone number
2System initiates STK Push via Daraja API
3User confirms on phone
4Daraja calls webhook with payment confirmation
5License is activated and email is sent
IntaSend (Fallback)
1System creates invoice via IntaSend API
2IntaSend sends STK Push to user’s phone
3Webhook validates challenge string
4License activated on success

10 Multi-Industry Demo Environment

10.1 Architecture

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
  );
}

10.2 Industry Datasets

IndustryExample ProductsSuppliers
PharmacyParacetamol, Amoxicillin, MetforminPharmacy Solutions Ltd, Global Pharma Distributors
Food & GroceryRice 5kg, Cooking Oil 2L, Wheat FlourFood Solutions Ltd, Metro Wholesalers
HardwareCement 50kg, Steel Rebar, Gloss PaintHardware Solutions Ltd, Global Hardware Distributors
RetailLED TV 43", Bluetooth Headphones, KettleRetail Solutions Ltd, Metro Retail Wholesalers
FashionDenim Jeans, Cotton T-Shirts, SneakersFashion Solutions Ltd, Global Apparel Distributors

10.3 Dataset Characteristics

Each industry dataset includes:

10.4 Switching Experience

When a user switches industries, the system:

  1. Clears all Zustand stores
  2. Loads the new industry’s fixture data
  3. Shows a full-screen overlay with spinner during the transition
  4. All charts and reports reflect the new data instantly
Demo Data Flow User clicks "Pharmacy" → stores.clear() → loadDemoPharmacyData() → instant switch

11 Security & Governance

11.1 Authentication

The system uses Supabase Auth with Google OAuth (PKCE flow):

Google OAuth (PKCE Flow)
1User clicks "Sign in with Google"
2Redirected to Google OAuth consent screen
3Google redirects to /auth/callback
4Supabase handles PKCE code exchange
5Session cookie set (sb-supabase-auth-token)
6User redirected to dashboard

11.2 Authorization Model

Role Hierarchy:

Admin System super-admin
Owner Subscribed user, no invited_by
Invited User Has invited_by, role-based permissions

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.

11.3 Permission System

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’)
1Is admin? → true
2Is banned/revoked/paused? → false
3Has own active license? → true (treated as owner)
4Check profile.permissions array:
If empty → false
If includes ‘Sales:Orders’ → true
If includes ‘Sales’ (category) → true
Otherwise → false

11.4 Row-Level Security (Supabase RLS)

All database tables have Supabase RLS policies that restrict access to:

11.5 Server-Side License Enforcement

API routes use requireLicense() middleware:

requireLicense() Middleware
1Extract JWT from Authorization header
2Check admin email → bypass
3Resolve invited_by chain
4For invited users: check own license, fall back to host’s
5For owners: check own license
6Validate: not expired, not paused, not suspended, not revoked

11.6 Rate Limiting

In-Memory Rate Limiter
WindowPer-key sliding (default 60s)
LookupO(1), zero DB overhead
PruningPeriodic expired entry cleanup
Distributed Mode (Vercel)
Fast PathSame in-memory lookup
SyncBackground sync to Supabase
CoordinationCross-instance for multi-server

Applied to: quotation link generation (10/minute), email sending (5/minute), chat messages (30/minute).

11.7 Input Validation

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'),
});

12 Performance & Scalability

12.1 Data Fetching Strategy

The system employs a stale-while-revalidate pattern for data fetching:

1 First Load
  1. Check if store data already loaded (length > 0 guard)
  2. If empty, fetch from Supabase
  3. Loader/spinner shown during first fetch
2 Subsequent Navigations
  1. Data is already in Zustand store, instant render
  2. Background refresh if data is stale (>5 min old)
  3. No loading state for cached data
3 Cache Invalidation
  1. Local mutations update store optimistically
  2. Demo mode switch clears all stores
  3. Manual refresh via heartbeat (10s interval for auth)

12.2 Optimistic Updates

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;
  }
}

12.3 Large Dataset Handling

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;
  }
}

12.4 Real-Time Performance

12.5 Database Indexes

Key performance indexes (from migration scripts):


13 Deployment & Operations

13.1 Deployment Architecture

Vercel (Serverless)
Next.js SSR/SSG
API Routes (Node.js)
Edge Middleware
Supabase Cloud
PostgreSQL Database
Auth (GoTrue) + Google OAuth
Storage (S3) Logos/Assets
Third-Party
Ably → WebSocket real-time
Resend → Transactional emails
Daraja → M-Pesa STK Push
Kapso → WhatsApp notifications
Google → Analytics & OAuth

13.2 Environment Configuration

Required — application won’t start without these
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
SUPABASE_SERVICE_ROLE_KEY
Authentication
NEXT_PUBLIC_ADMIN_EMAILSuper-admin email
Communications
RESEND_API_KEY
ABLY_API_KEY
KAPSO_API_KEY
Payments — Safaricom Daraja API
MPESA_CONSUMER_KEY
MPESA_CONSUMER_SECRET
MPESA_BUSINESS_SHORTCODE
MPESA_PASSKEY
MPESA_CALLBACK_URL
MPESA_ENVIRONMENTsandbox | production
Payments — IntaSend
INTASEND_SECRET_KEY
INTASEND_PUBLISHABLE_KEY
INTASEND_ENVIRONMENT

13.3 Backup Strategy

The scripts/db-backup.sh script provides automated backups:

13.4 Security Headers

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=()' },
];

14 Roadmap

Achieved (v1.0)

Completed

In Progress

Future


Appendix A: Key Performance Indicators

The executive dashboard computes the following KPIs:

KPIFormulaSource
●RevenueΣ(Cash + Paybill) for periodSales Reconciliation
●Total ExpenditureΣ(Amount) for periodExpenditures
●Potential ProfitRevenue − ExpenditureComputed
●Active OrdersCount of orders in periodOrders
●Good Margin ProductsCount where marginStatus = ‘good’Products
●Supplier Coverage(Items with prices / Total items) × 100Order Prices
●Delivery Rate(Delivered orders / Total allocated) × 100Orders

Appendix B: PDF Export Templates

The system generates professional PDFs using client-side HTML + window.print():

TemplateContentPurpose
●Quotation RequestItems with blank price columnsShare with suppliers
●Purchase OrderAllocated items by supplier with pricesIssue to suppliers
●InvoiceMulti-order billing with itemsSend to clients
●Invoice ReportAll invoices with summariesAccounting
●Expenditure ReportAll expenses categorizedFinancial review
●Product Price ListCatalog with margin analysisInternal review
●Product AnalysisSingle product with price trendDeep dive
●Allocation SummaryCheapest supplier by itemProcurement decision
●Orders ListAll orders with item detailsOperations
●Sales ReconciliationDaily sales with variancesDaily close
●M-Pesa ReconciliationMobile money daily trackingFinancial control

Appendix C: Data Flow Summary

User Action
Zustand Store → Optimistic UI Update
Supabase API Call
Confirm Update
Rollback + Show Error
Activity Recorder
Delegation Notif.
(Amber dot)
WhatsApp Notif.
(Optional)

Real-Time Data Flow (Chat):

User Sends Message
Optimistic Message (state: ‘sending’)
POST /api/chat/messages
Supabase INSERT + Ably Publish
Sender: Update state
state: ‘sent’
Recipient: Ably event
Append message → Ack delivery → DB update