Book 3, Chapter 14: Supplier Portal¶
1. Purpose¶
The Supplier Portal is how Bizwiz opens a door to the outside — to the third-party suppliers who sell goods to the retailer. Everything else in Books 1–3 is internal staff using the ERP. This module is the one place where a non-employee (a supplier's own staff) logs in and interacts with the business.
Two sides fit together:
-
The supplier-facing apps. A supplier can log in (on a phone app or a web portal) and: see the Purchase Orders / LPOs the retailer has raised against them, accept or reject an order, book a delivery slot, upload their invoice and delivery documents, confirm returns, and check payment status (which invoices are paid, which are pending, what bank file the payment went out on). They can also see statements, credit notes, trade discounts / incentives owed to them, sales/stock analytics for their products, request new SKUs, book meetings with the buying team, and top up a wallet.
-
The admin-side management module. Internal staff (buyers, procurement admins) use the "Supplier Portal" menu inside Bizwiz to invite suppliers to the portal, watch pending invites turn into onboarded suppliers, suspend a supplier, impersonate a supplier account (with an approval workflow) to troubleshoot, approve the changes suppliers request on LPOs, approve bank deposits, run billing for the portal subscription, and manage marketing campaigns suppliers can buy into.
The most important architectural fact: the Supplier Portal is not one codebase. It is a distributed system with three tiers, and this chapter's job is to keep them straight.
┌────────────────────────┐ ┌──────────────────────────────┐ ┌───────────────────────────┐
│ Supplier front-ends │ │ Supplier Portal backend │ │ bizwiz tenant (this repo)│
│ • Flutter app │────▶│ (SEPARATE service at │────▶│ • supplier-center / │
│ bwsupplierportalapp │ │ env SUPPLIER_PORTAL_URI) │ │ supplier-portal APIs │
│ • Web portal │ │ identity, onboarding, ERP │◀────│ • admin management UI │
│ (not in either repo)│◀────│ selection, wallet, billing │ │ • the actual PO/AP data │
└────────────────────────┘ └──────────────────────────────┘ └───────────────────────────┘
picks an ERP → issues per-ERP JWT, proxies → reads/writes wa_* tables
The Supplier Portal backend is a distinct application (Bizwiz calls it over HTTP via
env('SUPPLIER_PORTAL_URI'), e.g. /api/onboarding-check, /api/impersonation-url,
/api/top-up-requests/pending). Its source is not in this repo and not in the Flutter repo
— treat it as a black box that owns supplier identity and multi-tenant routing. Each retailer
runs its own bizwiz ("ERP"); the portal backend fans a single supplier login out across all the
ERPs that supplier deals with.
2. Users & roles¶
2a. Supplier users (external)¶
Suppliers authenticate through the portal backend, not through Bizwiz's normal
AdminLoggedIn staff auth.
- Flutter app auth (
bwsupplierportalapp): phone-number + OTP login. The app posts to/api/mobile/login→/api/mobile/verify-otp(endPointLogIn,endPointValidateOTPinlib/utils/constants/api_endpoints.dart:12-14). TheAuthModelreturned carriesrole,user_id,requires_password_set,phone_number,otp_active(lib/models/user.dart). The only app role is"Supplier Admin"(roleDefSupplierAdmin,lib/utils/constants/app_roles.dart:8) — there is a single role enumAppRoles.supplierAdmin. - Multi-ERP selection: after login the supplier picks which retailer/ERP to work in
(
/api/mobile/erps→/api/mobile/erps/select,api_endpoints.dart:43-46). Selecting an ERP returns a fresh JWT scoped to that tenant (change_erp_viewmodel.dart onSelectErp()readsdata.authorization.tokenand stores it), and the app'sappBaseURLthen points at that tenant's bizwiz. So a supplier logs in once but holds N tenant tokens. - Supplier identity to bizwiz: when the portal backend calls a bizwiz data endpoint, the
supplier is resolved by
supplier_code, falling back to KRA PIN (+ optional email) via theResolveSupplierByCodeOrPinmiddleware (app/Http/Middleware/ResolveSupplierByCodeOrPin.php:18-66).
2b. Portal API auth (service-to-service)¶
The supplier-center / supplier-portal data endpoints in bizwiz are protected by the
isr_request middleware (ValidateIncomingSupplierPortalHmacSignature):
- Primary: HMAC-SHA256 signature over the request using
X-Signature,X-Timestamp,X-Nonceheaders (app/Http/Middleware/ValidateIncomingSupplierPortalHmacSignature.php:27-29). - Fallback: legacy Bearer token compared to
ERP_ISR_KEYenv (same file, ~lines 114-176).
This is a trust boundary between the portal backend and each bizwiz tenant — not a
per-supplier auth. The supplier's identity rides in the request body/params and is resolved by
ResolveSupplierByCodeOrPin.
2c. Internal admins (staff)¶
Managed by the normal staff permission system. role_id == 1 is superadmin (sees everything).
Otherwise the sidebar gates each item on a $my_permissions[...] flag. Permission slugs
(from sidebar_includes/supplier_portal.blade.php and controller can() checks):
| Menu item | Permission slug | Controller can() |
|---|---|---|
| Onboarded Suppliers | supplier-maintain-suppliers___view |
can("view","supplier-maintain-suppliers") |
| Pending Invites | pending-suppliers___view |
can("view","pending-suppliers") |
| Invite supplier | — | can("invite","pending-suppliers") |
| Suspend supplier | supplier-portal___* |
can("suspend","supplier-portal") |
| Impersonation Requests | impersonation-requests___view |
can("view","impersonation-requests") / can("manage",…) |
| Do impersonation | — | can("impersonate","supplier-maintain-suppliers") |
| Supplier staff mgmt | — | can("staff","supplier-maintain-suppliers") |
| Logs | supplier-portal___logs |
can("logs","supplier-portal") |
| Approve LPO Changes | lpo-portal-req-approval___view |
(LpoPortalReqApproval) |
| New SKU Requests | request-new-sku___view |
(RequestNewSkuController) |
| Billing Note | billing-description_view |
can("view","billing-description") |
| Top Up Requests | top_up_requests___view / pending_top_up_requests___view |
(SupplierTopUpRequest) |
| Bank Deposits (initial/final) | supplier-bank-deposits-initial-approval___view, …final… |
— |
| Billing (initial/final) | billing-submitted___view, billing-submitted-final___view |
— |
| Schedule Meetings | supplier-schedule-meetings___view, supplier-slot-bookings___view |
(SupplierScheduleMeeting) |
| Marketing Campaigns | marketing-campaigns___view (+ applications/configs/banner/whatsapp) |
(Marketing* controllers) |
| Incentives Dashboard | salesman_incentives_dashboard___view |
(SalesmanIncentivesDashboard) |
| Delivery Slots / Offloading / Vehicle Type | order-delivery-slots___view, supplier-vehicle-type___view |
— |
| API Call Logs | api-call-logs___view |
— |
Note a couple of items are hard-coded to user IDs, not permissions: "Billed Suppliers" is
gated on in_array($logged_user_info->id,[1,1186])
(sidebar_includes/supplier_portal.blade.php).
3. Processes¶
3.0 Route map (where each side lives)¶
| Concern | File |
|---|---|
| Admin management UI (invite/suspend/impersonate/logs/billing-note/meetings) | routes/web.php:5390-5430+ (supplier-portal. prefix) |
| Admin management UI (top-up, incentives, billing, marketing) | routes/modules/supplier_portal.php |
| Supplier data APIs (app/web portal → bizwiz) | routes/api_includes/supplier_portal.php (supplier-center. / supplier-portal. under isr_request) |
| Supplier LPO actions (accept/change/return) | routes/api.php:174-176 + api_includes/supplier_portal.php:37 |
Flutter app endpoints (/api/mobile/*, /api/management-reports/*) |
served by the portal backend, not in this repo |
3.1 Onboarding a supplier (admin invites → supplier joins)¶
Trigger: buyer clicks Invite on the Pending Invites screen.
The source of truth for "in portal vs not" is trade_agreements.linked_to_portal
(SupplierPortalController.php:56). A supplier appears under Pending Invites when they have
an Approved trade agreement whose linked_to_portal is false/null; they move to
Onboarded Suppliers once linked_to_portal = 1 (set by the portal callback at
Api\TradeAgreementController@store, then ordered by linked_at desc).
sequenceDiagram
participant Buyer as Buyer (bizwiz admin)
participant BW as bizwiz SupplierPortalController
participant Portal as Portal backend (SUPPLIER_PORTAL_URI)
participant Supp as Supplier
Buyer->>BW: inviteSupplier() (can invite pending-suppliers)
Note over BW: validate supplier_id in wa_suppliers,<br/>kra_pin required
BW->>Portal: POST /api/onboarding-check<br/>{supplier_code,name,email,kra_pin,source}
Portal-->>BW: result=1 (created) / 0 (validation) / -1 (error)
BW->>Supp: email SupplierNotification / SupplierLinkedNotification
Supp->>Portal: accepts, sets password, logs in
Portal->>BW: TradeAgreementController@store callback
Note over BW: trade_agreements.linked_to_portal = 1, linked_at = now()
Note over Buyer: supplier now shows under "Onboarded Suppliers"
getPendingSuppliers()SupplierPortalController.php:43-95— TradeAgreement wherestatus='Approved'AND (linked_to_portalfalse/null). If the admin lackscan("can-view-all-suppliers","maintain-suppliers"), results are restricted to suppliers linked to them viawa_user_suppliers(lines 66-70).inviteSupplier():477-557— POSTs to portal/api/onboarding-check, emails the supplier.get_all_supplier_from_portal():289-332— TradeAgreement wherelinked_to_portal=true, enriched with portal data viaApiService->get_portal_suppliers().suspend_supplier():414-475— calls portalsuspend_supplierthen toggleswa_suppliers.is_suspendedlocally. (FlutterSupplierModel.suspendedreflects this.)
3.2 Supplier acts on an LPO (the Ch12 seam)¶
Trigger: retailer raises an LPO/PO against the supplier (Ch12 Purchases). It surfaces to the
supplier as wa_purchase_orders with supplier_accepted = false.
The Flutter app fetches LPOs from /api/mobile/lpo-listing (endPointGetLPOListing,
lpo_listing_viewmodel.dart:fetchLpoList) with type = pending|fulfilled|rejected, cursor
pagination (next_cursor), and a search_query. LpoModel carries lpo_number, status,
supplier_code, ship_to, lpo_type, for_approval, supplier_own, reject_reason,
credit_note_amount, plus LpoItemModel lines (quantity, unit_price, vat_amount,
discount_*, return_quantity, free_qualified_stock) (lib/models/lpo_models.dart).
Supplier actions map to bizwiz endpoints (all write wa_purchase_orders):
stateDiagram-v2
[*] --> Sent: HQ raises LPO (supplier_accepted=false)
Sent --> Accepted: acceptLpo() → supplier_accepted=true
Sent --> Rejected: rejectLpo() → supplier_accepted=2
Accepted --> Sent: reverseLpo() → supplier_accepted=false
Accepted --> SlotBooked: slotBooked() (OwnCollection) → slot_booked=true
SlotBooked --> GoodsReleased: goodsReleased() → goods_released=true
Sent --> ChangeRequested: receive_lpo_for_approval() → wa_lpo_portal_req_approval (Pending)
ChangeRequested --> Sent: HQ approves/rejects (Ch12 "Approve LPO Changes")
Accepted --> Discarded: discardLpoFromPortal() → is_hide='Yes'
Accepted --> DetailsUpdated: receive_order_details_update() → invoice/vehicle/docs
Key handlers (app/Http/Controllers/Api/PurchaseOrderController.php unless noted):
- Accept / reject / reverse / slot / goods-released: acceptLpo():578, rejectLpo():596,
reverseLpo():614, slotBooked():632, goodsReleased():650 — set the corresponding
wa_purchase_orders boolean/enum columns.
- Request changes receive_lpo_for_approval():29-114 (route api.php:175) — creates a
wa_lpo_portal_req_approval (status Pending) with wa_lpo_portal_req_approval_items
lines; auto-rejects prior pending requests for the same item. Notifies the PO creator
(LpoChangesApprovalNotification). Admin reviews under Approve LPO Changes
(lpo-portal-req-approval.index).
- Submit delivery details / invoice receive_order_details_update():179-204
(route api.php:174) — writes cu_invoice_number, vehicle_reg_no, driver_name,
driver_phone, receive_note_doc_no, supplier_invoice_no, invoice_control_amount, and a
documents JSON (delivery_note / supplier_invoice / other_documents, from_portal flag).
- Confirm/reject a return lpo_return_accepted():116-177 (route api.php:176) — sets
wa_receive_purchase_orders.return_status = Accepted|Rejected (+ return_comment,
credit_note_doc) and writes a reverse wa_stock_moves row for the returned qty.
- Discard an LPO discardLpoFromPortal() (Admin PurchaseOrderController:214-229,
route supplier_portal.php:37) — wa_purchase_orders.is_hide='Yes' and archives related
inbound_deliveries (status='ARCHIVED').
3.3 Supplier views invoices & payment status (the Ch13 AP seam)¶
Trigger: supplier opens the "Vendor Centre" / payments area in the portal.
Served by VendorCentreController under the supplier-center prefix
(api_includes/supplier_portal.php:44-51):
payables():115-202— the supplier's invoices/statement: readswa_supp_trans(invoice header —trans_date,cu_invoice_number,suppreference,total_amount_inc_vat,vat_amount,withholding_amount,grn_number), nets offpayment_voucher_items/payment_vouchers,financial_notes(credit notes), andadvance_payment_allocationsto showpayable_amount/paid_amountper invoice.grn():507-597— GRNs the supplier delivered (wa_grns+ linkedwa_purchase_orders), split pending (no invoice) vs completed.payments():599-681— payment vouchers with status badge Pending / Approved / Processed / Bounced, plus thewa_bank_files/wa_bank_accountsthe payment went out on.paymentsWithItems():683-746— each voucher's line items resolved polymorphically to the underlyingwa_supp_transinvoice orsupplier_billsbill.
Trade discounts, incentive demands, price-drop/promotion credit notes are exposed via
TradeDiscountController, TradeDiscountDemandController, PriceDropDemandController,
PromotionDemandController, IncentiveDemandController, FinancialNoteController, and
ReturnToSupplierController (see the long supplier-center route list). Payment terms & bank
details come from SupplierPortalMiscellaneousRequestController::getSupplierPaymentDetails()
(wa_suppliers->getPaymentTerm).
3.4 Admin impersonates a supplier (approval workflow)¶
Trigger: support needs to see exactly what a supplier sees.
sequenceDiagram
participant A as Admin (requester)
participant BW as SupplierPortalController
participant TC as TaskCenter
participant Approver
participant Portal as Portal backend
A->>BW: storeImpersonationRequest() {supplier_id,user_id,reason}
Note over BW: no duplicate Pending for same supplier+user
BW->>TC: register task, SMS approvers
Approver->>BW: approveImpersonationRequest() (can manage impersonation-requests)
Note over BW: status=Approved, approved_at=now(), approved_by
BW->>TC: complete, SMS requester
A->>BW: SupplierImpersonationController@show(supplier)
Note over BW: request Approved AND approved_at >= now()-Xh, mark used=1
BW->>Portal: POST /api/impersonation-url {supplier_code,email,kra_pin,is_admin}
Portal-->>BW: {url}
BW-->>A: redirect to portal url (logged in as supplier)
impersonation_requeststable:supplier_id,requested_by,reason,status(Pending/Approved/Rejected),approved_at/by,rejected_at/by,rejection_reason,approval_reason,used(bool). Migration2025_09_09_142750_create_impersonation_requests_table.php.- Validity window =
settingsslugnumber-of-hours-for-impersonation-requests(SupplierPortalController:343,375;SupplierImpersonationController:23). show()(SupplierImpersonationController:17-71): checks Approved + within window, marksused=1, calls portal/api/impersonation-url, redirects.
3.5 Other admin-side processes (brief)¶
- Top-up requests: admin lists/approves supplier wallet top-ups by proxying the portal
backend (
SupplierTopUpRequestController→SUPPLIER_PORTAL_URI/api/top-up-requests/*). - Portal subscription billing:
SupplierPortalSubscriptionBillController(current bill / history to suppliers viabilling/current-bill,billing/bill-history; admin "Billed Suppliers"). Two-stage bank-deposit and billing approvals (initial/final) are separate menu items. - Schedule meetings:
SupplierScheduleMeetingController+SupplierBookingController— suppliers book slots with buyers (supplier-portal/book-meeting,/scheduled-meetings,/cancel-meeting); admin manages slots, exceptions, reschedules, arrival notifications. - Marketing campaigns: suppliers apply to promote products; admin approves per-product,
configures pricing, runs bulk WhatsApp, manages banner slots
(
routes/modules/supplier_portal.php). - Analytics for suppliers (Flutter Home/Reports): summary stats, turnover, total sales,
stock counts, and stock reports (location/dead/overstock/missing/item-sales) via
/api/mobile/*and/api/mobile/procurement-reports/*(api_endpoints.dart:17-35,home_viewmodel.dart).
4. Tables touched & key data¶
Supplier identity / onboarding:
- wa_suppliers — supplier_code, name, slug, email, kra_pin, is_suspended,
bank fields, payment term. Migration 2023_09_08_134414_create_wa_suppliers_table.php.
- trade_agreements — reference, status (Approved/Pending), wa_supplier_id,
linked_to_portal, linked_at. This is the pending↔onboarded gate.
(…create_trade_agreements_table.php, …add_linked_at_column…).
- wa_user_suppliers (WaUserSupplier) — links internal user_id ↔ wa_supplier_id
(scopes which buyer sees which suppliers).
- impersonation_requests — approval workflow (see 3.4).
- settings — SUPPLIER_PORTAL_BILLING_DESCRIPTION (billing note),
number-of-hours-for-impersonation-requests.
Purchase / LPO (Ch12 seam):
- wa_purchase_orders — purchase_no, supplier_accepted (false / true / 2=rejected),
slot_booked, goods_released, is_hide, documents (JSON), cu_invoice_number,
vehicle_reg_no, driver_name, driver_phone.
- wa_lpo_portal_req_approval / wa_lpo_portal_req_approval_items — supplier-requested
LPO changes awaiting HQ (status Pending/Rejected, rejection_message, item qty/price/reason).
- wa_receive_purchase_orders — return_status, return_comment, credit_note_doc.
- wa_stock_moves — reverse movement rows for accepted returns.
- inbound_deliveries — SUPPLIER_COLLECTION docs, archived when an LPO is discarded.
- wa_grns — grn_number, wa_supplier_id, delivery_date, wa_purchase_order_id.
AP / payments (Ch13 seam):
- wa_supp_trans — supplier invoices/statement lines.
- payment_vouchers / payment_voucher_items — payments + allocations (polymorphic to
wa_supp_trans or supplier_bills).
- financial_notes — credit/debit notes. advance_payment_allocations.
- wa_bank_files / wa_bank_file_items / wa_bank_accounts — payment execution.
Supplier items / incentives:
- wa_inventory_item_suppliers, wa_inventory_items, pack_sizes.
- wa_inventory_location_transfers / _items / _item_returns, restaurants
(branches), routes, wa_inventory_assigned_items (parent/child).
Owned by the portal backend, not bizwiz: the supplier login identity, per-ERP JWTs, wallet balances, top-up requests, subscription billing ledgers, and the portal activity log feed all live in the
SUPPLIER_PORTAL_URIservice. Bizwiz only mirrors/reads them viaApiService.
5. Interactions with other modules¶
- Ch12 Purchases (POs/LPOs) — the core seam. LPOs originate in Purchases; the portal is the
supplier's window to accept/reject/change/deliver against them. Supplier writes land back on
wa_purchase_orders,wa_lpo_portal_req_approval*,wa_receive_purchase_orders,wa_stock_moves,wa_grns. The admin Approve LPO Changes screen (lpo-portal-req-approval) closes the loop. GRN generation and stock impact are Ch12/inventory. - Ch13 Accounts Payable — the "payment status / statement / credit notes" the supplier sees
are AP artifacts:
wa_supp_trans(invoices),payment_vouchers(+bank files),financial_notes(CNs),advance_payment_allocations. The portal is a read view onto AP; it does not post GL. Supplier-submitted invoices (receive_order_details_update) feed the AP invoicing pipeline. - Inventory / Reports — supplier analytics (turnover, dead/overstock/missing stock, item-sales) reuse the same report controllers as internal reporting, filtered to the supplier.
- Trade Agreements —
trade_agreements.linked_to_portalis the onboarding gate; new-SKU and trade-discount demands flow throughTradeAgreement*/TradeDiscount*controllers. - Notifications / SMS / TaskCenter — invites, impersonation, and LPO-change approvals use
the email +
AlertService/SmsScenarios+TaskCenterServiceinfrastructure.
6. Alternatives & variants¶
- Two supplier front-ends. (1) The Flutter app
bwsupplierportalapp— phone+OTP, multi-ERP, focused on LPO listing + analytics + profile (bottom-nav Home / LPOs / Reports / Profile, single "Supplier Admin" role). (2) A web portal (source not in either repo) that drives the richersupplier-centersurface (invoices, GRNs, payments, discounts, returns, meetings, marketing). Both talk to the same bizwiz APIs via the portal backend. - Distributed vs monolith. Unlike every other chapter, the "portal" logic is split across the
external
SUPPLIER_PORTAL_URIservice and this bizwiz repo. Bizwiz is the data/tenant tier; the portal backend is the identity/tenant-router tier. - Auth variants. Service auth to bizwiz has a modern HMAC path and a legacy Bearer
(
ERP_ISR_KEY) fallback. Supplier resolution triessupplier_codefirst, then KRA PIN. supplier_acceptedtri-state (false/true/2) rather than a clean enum — legacy boolean overloaded to carry "rejected". Confirm downstream readers treat2as rejected.- Permission vs hard-coded gates. Most menu items use permission slugs; a few
(Billed Suppliers) are gated on literal user IDs
[1,1186]. - Marketing / meetings / top-ups / bank-deposits are optional sub-features layered on the core portal; a tenant may not use all of them.
7. Open questions to confirm¶
CODE-PROVEN¶
- Onboarding gate is
trade_agreements.linked_to_portalset by portal callback (SupplierPortalController.php:56, 62-65, 303). - Invite calls portal
/api/onboarding-check; suspend calls portal then toggleswa_suppliers.is_suspended(:508-514, 433-450). - Impersonation is a Pending→Approved→used workflow with an expiry window from
settings; the actual login URL is minted by the portal/api/impersonation-url(SupplierPortalController:853-953,SupplierImpersonationController:17-71). - Supplier LPO actions write specific
wa_purchase_orderscolumns; change-requests createwa_lpo_portal_req_approval*; returns writewa_receive_purchase_orders+wa_stock_moves(Api\PurchaseOrderController.php:29-204, 578-666). - Invoices/payments/GRNs come from
wa_supp_trans,payment_vouchers(+items),financial_notes,wa_grns,wa_bank_files(VendorCentreController.php:115-746). - Data APIs are guarded by HMAC (
isr_request) +ResolveSupplierByCodeOrPin. - Flutter app: phone+OTP, single "Supplier Admin" role, multi-ERP token swap
(
bwsupplierportalapp/lib/…).
INFERENCE (verify)¶
- The
/api/mobile/*and/api/management-reports/*endpoints the Flutter app calls are served by the portal backend (or a per-ERP bizwiz not in this repo), not by any route file in this bizwiz repo. No matching routes exist here — inferred from the dynamicappBaseURL+ ERP-selection JWT swap. Confirm which service actually hosts/api/mobile/login,/api/mobile/lpo-listing,/api/mobile/erps. - The web portal front-end (invoices/discounts/meetings UI) is a separate app; its repo
location is unknown. Inferred from the
supplier-centerAPI surface having no bizwiz Blade views. - Exact ownership boundary of wallet / top-up / subscription-billing ledgers (portal backend vs bizwiz mirror) — bizwiz only proxies; the authoritative store is assumed portal-side.
- Whether
supplier_accepted = 2is universally interpreted as "rejected" across all readers. - Full permission-seeder definitions for the slugs in §2c (values inferred from sidebar +
controller
can()calls, not from a seeder read).
8. Source references¶
bizwiz — nav & routes
- resources/views/admin/includes/sidebar_includes/supplier_portal.blade.php — module boundary,
all menu items + permission gates.
- routes/web.php:5390-5430+ — admin supplier-portal. management routes (pending, onboarded,
impersonation, suspend, invite, staff, billing-description, meetings).
- routes/modules/supplier_portal.php — top-up, salesman-incentives, billing, marketing campaigns/
applications/configs/banner/whatsapp.
- routes/api_includes/supplier_portal.php — supplier data APIs (supplier-portal. +
supplier-center. under isr_request): items, incentives, meetings, discounts, invoices,
GRNs, payments, credit notes, returns, billing.
- routes/api.php:174-176 — supplier LPO portal actions (details-update, lpo-for-approval,
return-status).
bizwiz — controllers
- app/Http/Controllers/Admin/SupplierPortalController.php — onboarding, invite (:477),
suspend (:414), impersonation workflow (:853-953), logs (:97-257), billing-description
(:629-676), staff (:559-627).
- app/Http/Controllers/Admin/SupplierImpersonationController.php:17-71 — mint impersonation URL.
- app/Http/Controllers/Api/PurchaseOrderController.php:29-204, 578-666 — supplier LPO
accept/reject/change/return/slot/goods-released.
- app/Http/Controllers/Admin/PurchaseOrderController.php:214-229 — discardLpoFromPortal.
- app/Http/Controllers/Admin/VendorCentreController.php:115-746 — payables/grn/payments.
- app/Http/Controllers/Api/SupplierPortalMiscellaneousRequestController.php — items, branches,
payment details, incentives.
- app/Http/Controllers/SupplierTopUpRequestController.php,
SupplierPortalSubscriptionBillController.php, SupplierScheduleMeetingController.php,
SalesmanIncentivesDashboardController.php, Marketing*Controller.php.
bizwiz — middleware / services / models / migrations
- app/Http/Middleware/ValidateIncomingSupplierPortalHmacSignature.php (isr_request).
- app/Http/Middleware/ResolveSupplierByCodeOrPin.php:18-66.
- app/Services/ApiService.php + app/helpers.php:3114 (SUPPLIER_PORTAL_URI).
- Model/WaUserSupplier.php; migrations
2023_09_08_134414_create_wa_suppliers_table.php,
2024_04_06_134419_create_trade_agreements_table.php,
2024_08_31_110218_add_linked_at_column_in_trade_agreements_table.php,
2025_09_09_142750_create_impersonation_requests_table.php.
bwsupplierportalapp (Flutter)
- lib/utils/constants/api_endpoints.dart — all /api/mobile/* endpoints + dynamic baseURL.
- lib/services/auth_service.dart — supplier profile, ERP, token; BaseAuthService (OTP).
- lib/ui/views/change_erp/change_erp_viewmodel.dart — ERP selection → per-ERP JWT swap.
- lib/ui/views/lpo_listing/lpo_listing_viewmodel.dart — LPO listing (pending/fulfilled/rejected,
cursor paging).
- lib/ui/views/home/home_viewmodel.dart — dashboard analytics.
- lib/models/lpo_models.dart, lib/models/user.dart (SupplierModel, AuthModel, ErpModel).
- lib/utils/constants/app_roles.dart — single "Supplier Admin" role.
- lib/utils/constants/app_variables.dart — app IDs, prefs keys.