WorkTradeFlow BD – Multi-Tenant Wholesale ERP~3 min read
Enterprise ERP & SaaSActive ProductionProprietaryProduct owner & lead engineer

TradeFlow BD – Multi-Tenant Wholesale ERP

Sell modules on four kernels — one parent stock, one books, wholesale + shop + dropship + pre-order.

Role:Lead Framework & Migration Engineer
Timeline:2025 — Present
Vue 3.5Quasar 2SupabaseTypeScriptPostgreSQL 15++1

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 engineer

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.

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 Operator

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.

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-use

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.

Who hurts:
import-operatorshop-buyerdropship-resellercapital-partner

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.

Strategyzer Canvas

The Value Proposition Canvas

Value PropositionTradeFlow BD Platform
Customer SegmentImport / Wholesale Operator
Expand value canvas
Products & Services
Gain Creators
Pain Relievers
Gains
Pains
Customer Jobs

Sector

Title

Body

Import / Wholesale Operator

Overselling across channels

designed
Before

Wholesale desk, shop carts, and dropship picks share parent stock without a single ATP pool.

After

One warehouse quantity enforced by server-side issue/pick RPCs; no per-channel shadow stock.

Metric: Channel ATP pool consistency
Desynced per-channel stock books→Single warehouse ATP pool
WholesaleShopDropship
personal

Landed cost guesswork

designed
Before

Margins use invoice-line guesses instead of paid AP and locked shipment costs.

After

Unit cost stamped from paid AP and locked shipment costs.

Metric: Landed cost valuation basis
Unlocked invoice line guesses→Paid AP and locked freight Restamp
InboundPBC
personal
Dropship reseller / Import operator

COD booked as sales

designed
Before

Courier collections get mixed with B2B merchant totals on reports.

After

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

Metric: Ledger separation (paper vs cash)
Mixed courier cash in sales revenue→Strict AR & custody cashbook split
DropshipMoney desks
personal
Platform/IT owner

Cross-tenant data leakage risks

designed
Before

Filtering tenant data relies on client-side SPA logic.

After

Boundary enforced in PostgreSQL via RLS and membership_has_module_action().

Metric: Database isolation
Application-layer query filtering→PostgreSQL RLS policy enforcement
TenantsAccess kernel
personal

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`.
Platform architecture
flowchart TB ACC["Access: tenants, grants, 4 scopes"] WH["Warehouse: parent lots, ATP"] LC["Landed cost: lock, FX stamp"] MON["Money: bills, pays, cashbook"] ACC --> WH --> LC --> MON subgraph sell["Sell modules"] M1["Tenants"] M2["Inbound"] M3["Wholesale"] M4["Shop + admin"] M5["Dropship"] M6["PBC"] M7["Money desks"] M8["Thrift"] end sell --> ACC M2 --> WH M2 --> LC M3 --> WH M3 --> MON M4 --> WH M5 --> WH M5 --> MON M6 --> MON M7 --> MON

Platforms

ScopeWhoDoes
platformSuperadminTenant provision, module catalog, global reference — never company stock or bills
appTenant staffERP desks (inbound, wholesale, shop admin, dropship, money, costing) gated by grants
shopBuyer / resellerCatalog, cart, orders, merchant statement — no warehouse bins or cost lock
investorCapital partnerRead-only shipment share and statements — no withdraw in v1
How surfaces connect
flowchart LR P["platform"] A["app"] S["shop"] I["investor"] P -->|"provision"| A A -->|"catalog + ops"| S A -->|"capital views"| I

Modules

ModuleJobKernels
TenantsParent/child companies, module packs, staff grantsAccess
MoneyBills, pays, allocations, cashbook — every channel posts hereMoney
Inbound shipmentLand cargo, bins, post-to-stock, landed-cost lockWarehouse, Landed cost
WholesaleCompose, issue, collect — take, condition, walk-inWarehouse, Money
Shop (storefront)Buyer catalog, cart, orders (`shop` scope)Access, Warehouse, Money
Shop adminShops, pricing, groups, orders hub (`app` scope)Access, Warehouse, Money
DropshipReseller to recipient; ship/issue; remittance vs COD faceWarehouse, Money
Product-based costingQuote file → demand → inbound → delivery closeMoney, Landed cost
ThriftIsolated `thrift_*` vertical + Capacitor warehouse appIsolated from BW FIFO
Modules plug into kernels
flowchart LR INB["Inbound"] --> WH["Warehouse kernel"] WH --> BW["Wholesale"] WH --> SH["Shop"] WH --> DS["Dropship"] PBC["PBC / pre-order"] --> DEM["Demand"] DEM --> INB BW --> MON["Money kernel"] SH --> MON DS --> MON PBC --> MON

Proof

Impact and product screens

My results

~67% reduction on initial shellFirst-load CSS reductionFaster initial dashboard interactive load on cellular connections
1,330+ versioned migrations replayed locallyLocal migration replay countZero schema regression downtime during production pushes

Build

Stack and engineering choices

What I improved

Frontend shell bundle sizemeasured
Before

Monolithic CSS and synchronous libraries loaded on initial shell

After

Route-level CSS split and lazy heavy libs (Mermaid, ExcelJS, FCM)

How:

Route-level split and dynamic ESM imports

User benefit:

Faster initial dashboard interactive load on cellular connections

Metric: First-load CSS reduction
100% monolithic shell CSS→~67% reduction on initial shell
Schema deployment reliabilitymeasured
Before

Manual SQL execution with unverified migration ordering

After

Reproducible local replay on Docker Supabase before deploy

How:

Declarative schema domain diffing and ordered migration replay

User benefit:

Zero schema regression downtime during production pushes

Metric: Local migration replay count
0 automated local checks→1,330+ versioned migrations replayed locally

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.
Progressive Disclosure

Architectural Deep-Dive, Schemas & Workflows

Jump:

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)

StepWhat happens
Retail CODRecipient pays courier face value (e.g. ৳2,200) — not booked as merchant sales
At shipB2B merchant bill issued (e.g. ৳1,500) + courier AP (e.g. ৳80)
Bank inNet remittance (e.g. ৳2,120) allocates to merchant bill
LeftoverSurplus (e.g. ৳620) credits dropshipper custody cashbook for payout

Flagship flow — inbound landed cost

  1. Record shipment cost buckets → draft AP bills per bucket × currency.
  2. Lock shipment → issue AP.
  3. Pay vendors → FX from actual pays → restamp unit cost on open lots.
  4. Corrections via credit/debit notes — not silent edits to issued AP.

Schema & delivery

  • Declarative domains: procurement/, shop_order/, bills_pays/, notifications/ (+ unsplit public.sql).
  • Local workflow: edit schemas → db diff → migration → backend:local replay 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 /docs on the product SPA).

Case closed

More work.