TradeFlow BD – Multi-Tenant Wholesale ERP
Sell modules on four kernels — one parent stock, one books, wholesale + shop + dropship + pre-order.
Owned architecture and migration for a multi-tenant wholesale ERP — four login surfaces, parent/child tenants, and channel flows on shared stock and money kernels.
My contribution
Product owner & lead engineerOwned architecture and migration for a multi-tenant wholesale ERP — four login surfaces, parent/child tenants, and channel flows on shared stock and money kernels.
What I owned
- Schema split, migration ordering, and local replay
- Money-kernel alignment across wholesale, shop, dropship, and inbound AP
- Platform docs and VPC narrative in the product SPA
Product context
Import / Wholesale OperatorOne platform for import/wholesale operators who run a parent company, sister sales desks, B2B shops, and dropship resellers — without duplicate stock books or booking courier COD as revenue.
Owned architecture and migration for TradeFlow BD — a multi-tenant wholesale ERP where every sell module posts to the same stock and money kernels. Delivered four scoped login surfaces, parent/child tenants with database-enforced isolation, and channel flows (wholesale desk, B2B shop, dropship with COD remittance, pre-order costing) without shadow ledgers or channel overselling.
Core challenge
Evidence: internal-useWholesale import groups run parent holdings, sister desks, shops, and capital partners on disconnected spreadsheets and channel-specific stock — causing overselling, COD mis-reported as revenue, and weak audit trails between sister brands.
User benefit matrix
TradeFlow BD is sell modules on four shared kernels. Each module is a full job; together they keep sales, cash, COD, and stock aligned.
The Value Proposition Canvas
Expand value canvas
Overselling across channels
designedWholesale desk, shop carts, and dropship picks share parent stock without a single ATP pool.
One warehouse quantity enforced by server-side issue/pick RPCs; no per-channel shadow stock.
Landed cost guesswork
designedMargins use invoice-line guesses instead of paid AP and locked shipment costs.
Unit cost stamped from paid AP and locked shipment costs.
COD booked as sales
designedCourier collections get mixed with B2B merchant totals on reports.
Merchant AR at ship, remittance pays the bill, surplus goes to custody cashbook. (Illustrated by flow: ৳2,200 face, ৳1,500 bill, ৳80 courier AP, ৳2,120 remittance, ৳620 leftover).
Cross-tenant data leakage risks
designedFiltering tenant data relies on client-side SPA logic.
Boundary enforced in PostgreSQL via RLS and membership_has_module_action().
What it does
One platform for import/wholesale operators who run a parent company, sister sales desks, B2B shops, and dropship resellers — without duplicate stock books or booking courier COD as revenue.
- Parent owns warehouse lots and consolidated books; child brands issue bills and run storefronts on the same kernels.
- Wholesale (take/condition/walk-in), shop carts, and dropship picks debit the same available-to-promise pool.
- Inbound shipments lock landed cost from paid AP; money kernel handles AR, AP, remittance, and profile cashbook leftover.
- Optional verticals (K-beauty, thrift) use isolated stock tables — never mixed with core `global_stocks`.
Platforms
| Scope | Who | Does |
|---|---|---|
platform | Superadmin | Tenant provision, module catalog, global reference — never company stock or bills |
app | Tenant staff | ERP desks (inbound, wholesale, shop admin, dropship, money, costing) gated by grants |
shop | Buyer / reseller | Catalog, cart, orders, merchant statement — no warehouse bins or cost lock |
investor | Capital partner | Read-only shipment share and statements — no withdraw in v1 |
Modules
| Module | Job | Kernels |
|---|---|---|
| Tenants | Parent/child companies, module packs, staff grants | Access |
| Money | Bills, pays, allocations, cashbook — every channel posts here | Money |
| Inbound shipment | Land cargo, bins, post-to-stock, landed-cost lock | Warehouse, Landed cost |
| Wholesale | Compose, issue, collect — take, condition, walk-in | Warehouse, Money |
| Shop (storefront) | Buyer catalog, cart, orders (`shop` scope) | Access, Warehouse, Money |
| Shop admin | Shops, pricing, groups, orders hub (`app` scope) | Access, Warehouse, Money |
| Dropship | Reseller to recipient; ship/issue; remittance vs COD face | Warehouse, Money |
| Product-based costing | Quote file → demand → inbound → delivery close | Money, Landed cost |
| Thrift | Isolated `thrift_*` vertical + Capacitor warehouse app | Isolated from BW FIFO |
Proof
Impact and product screens
My results
Click any preview to open full size. Use arrow keys to browse.
Build
Stack and engineering choices
What I improved
Monolithic CSS and synchronous libraries loaded on initial shell
Route-level CSS split and lazy heavy libs (Mermaid, ExcelJS, FCM)
Route-level split and dynamic ESM imports
Faster initial dashboard interactive load on cellular connections
Manual SQL execution with unverified migration ordering
Reproducible local replay on Docker Supabase before deploy
Declarative schema domain diffing and ordered migration replay
Zero schema regression downtime during production pushes
Why this stack
- PostgreSQL + RLSTenant and grant boundaries must hold even if the SPA has a bug — membership and module actions evaluated in the database.
- Atomic RPCsStock issue, dropship ship+issue, bill pay, and remittance allocation must commit or roll back as one unit.
- SupabaseAuth, Postgres, realtime, and typed client generation for a small team shipping ERP breadth without a custom API layer for every desk.
- Vue 3 + Quasar 2One SPA serves platform, app, shop, and investor themes with shared components and route-level code splitting.
- Declarative schema splitprocurement, shop_order, and bills_pays evolve independently; `db diff` guards drift across 1,330+ migrations.
- Capacitor (Thrift app)Floor barcode intake and audit against the same database without duplicating thrift inventory in a second stack.
Technology
Frontend
- Vue 3.5
- Quasar 2
- TypeScript
- Pinia
- TanStack Query
- Vue Router 5
Mobile & Hardware
- Capacitor (Android — Thrift warehouse app)
Backend
- Supabase Auth (Google OAuth)
- PostgreSQL RLS + 300+ RPCs
- Supabase Realtime
Database
- PostgreSQL 15+
- Declarative schema domains (procurement, shop_order, bills_pays)
- 1,330+ migrations
DevOps & Infra
- Cloudflare Pages
- Docker local Supabase replay
- Python ETL (catalog sync)
Key decisions
Wholesale import groups run parent holdings, sister desks, shops, and capital partners on disconnected spreadsheets and channel-specific stock — causing overselling, COD mis-reported as revenue, and weak audit trails between sister brands.
Multi-tenant security
Decision: Row-level security and `membership_has_module_action()` on tenant data — not client-side filtering alone.
Outcome: Tenant boundary enforced at the database engine.
Channel overselling
Decision: Stock on parent `global_stocks`; channels consume ATP via pessimistic RPCs (`SELECT FOR UPDATE`).
Outcome: Single source of truth for quantity across wholesale, shop, and dropship.
Shadow wallets
Decision: Unified money kernel — profile, bill, pay, allocation, cashbook — with adjustment notes after issue.
Outcome: One books layer for AR, AP, COD remittance, and leftover.
Monolithic SQL
Decision: Declarative domain folders under `supabase/schemas/<domain>/` with migration diff checks.
Outcome: Safer evolution and reproducible local replay before production push.
Trust
Security and roadmap
Security & reliability
- RLS on tenant tables; financial writes through SECURITY DEFINER RPCs where grants require it.
- Archive-first shipment governance; soft-delete trash with tenant-scoped purge.
- Generated `database.types.ts` after schema changes; local Docker replay of full migration chain.
Future roadmap
- Finish schema extraction for remaining domains (customer, products, reporting, thrift, investor).
- Investor portal v2 — withdrawal workflows beyond read-only.
- Thrift thermal print and iOS companion for floor audit.
Technical appendix (hiring manager depth)
Expand this section only when you need implementation detail. The story above is the recruiter path.
Flagship flow — dropship remittance (numbers)
| Step | What happens |
|---|---|
| Retail COD | Recipient pays courier face value (e.g. ৳2,200) — not booked as merchant sales |
| At ship | B2B merchant bill issued (e.g. ৳1,500) + courier AP (e.g. ৳80) |
| Bank in | Net remittance (e.g. ৳2,120) allocates to merchant bill |
| Leftover | Surplus (e.g. ৳620) credits dropshipper custody cashbook for payout |
Flagship flow — inbound landed cost
- Record shipment cost buckets → draft AP bills per bucket × currency.
- Lock shipment → issue AP.
- Pay vendors → FX from actual pays → restamp unit cost on open lots.
- Corrections via credit/debit notes — not silent edits to issued AP.
Schema & delivery
- Declarative domains:
procurement/,shop_order/,bills_pays/,notifications/(+ unsplitpublic.sql). - Local workflow: edit schemas →
db diff→ migration →backend:localreplay on Docker Supabase. - Frontend: route-level CSS split (~67% first-load CSS reduction on initial shell) and lazy heavy libs (Mermaid, ExcelJS, FCM).
What I owned
- Migration ordering and schema split for safe empty-DB replay.
- Money-kernel alignment across wholesale, shop order, dropship settlement, and inbound AP.
- Platform/docs narrative and VPC structure for operators (in-product guide at
/docson the product SPA).
Case closed
More work.