Customer Management¶
Module Guide chapter — "Maintain Customers" Scope: who a customer is, how they get created / approved / maintained / retired, and how their credit standing is managed. Nav home:
Sales & Receivables → Maintain Customers.
1. Purpose (plain language)¶
Bizwiz sells to thousands of shops ("outlets") across many routes, on behalf of 10+ Kenyan distributor tenants. Customer Management is the master file for those shops plus the debtor ledger that tracks what each owes. It answers four questions:
- Who is the customer? A rich shop record — name, business name, phone(s), GPS location, delivery centre, KRA PIN, credit terms, photo, ID number.
- How do they get into the system? A salesman (or route manager) onboards a shop from the mobile app; the shop goes through verification → approval before it can be sold to. Duplicates, missing GPS, and KRA PINs are triaged in dedicated review queues.
- How are they maintained over their life? Edits to a shop's details can require approval. Shops that go quiet can be made dormant; shops that should be removed are deactivated (soft-deleted); rejected onboardings can be re-onboarded.
- How is their credit standing managed? The system tracks each shop's outstanding balance, enforces credit limits / credit days, and provides controlled write-off and adjustment tools: Bad Debts (write off uncollectable balances), Post-Sales Discounts (retroactive discount + credit note), Withholding clearance (settle the WHT portion an institutional customer withheld), and CRC / Cash from Debtors (record cash a debtor pays directly).
There are two levels of customer record, and understanding the split is essential:
| Record | Table | Level | Think of it as |
|---|---|---|---|
WaCustomer |
wa_customers |
Branch / route parent | The debtor account (one per route/branch), used for branch-level reporting and invoice customers |
WaRouteCustomer |
wa_route_customers |
Individual shop / outlet | The shop on a route — the 50+ field record, the thing salesmen onboard and sell to |
The "Maintain Customers" menu contains: Customer Accounts, Customer Accounts by Branch, Route Customers (accounts), CRC / Cash from Debtors, Tax Withholding (clearance / approved), Bad Debts (pending / approved / restored), and Post Sales Discounts (pending / approved). The onboarding/approval/lifecycle queues (Route Customers listing, Onboarding Requests, Approval Requests, Duplicate, Geotagged, KRA PIN Approval, Rejected, Deactivated, Dormant, Suspendable, Edit Requests, Group Reps) live in the adjacent Route Customers treeview but are all part of this chapter's ownership.
Boundary note. This chapter owns the customer record, lifecycle, and credit standing. Route structure and visit scheduling (geomapping schedules, route assignment) belong to Route Management. Reconciling debtor bank payments belongs to Banking & Receivables Reconciliation. Orders/invoices themselves belong to Order Taking & Sales Invoicing.
2. Users & roles (+ permissions)¶
Access is gated two ways everywhere: either the user has the global super-role (role_id == config('app.allowed_role'), typically 1) or they hold the specific permission string. Permission strings follow module___action.
Permission strings proven in code (grep of database/seeders/, app/, resources/):
| Area | Permission strings |
|---|---|
| Customer accounts / withholding | maintain-customers___view, maintain-customers___initiate-withholding-clearance, maintain-customers___approve-withholding-clearance (+ edit-withholding-clearance checked in controller) |
| Route customer lifecycle | route-customers___view, ___listing, ___overview, ___onboarding-requests, ___verify, ___approval-requests, ___approve, ___duplicate-approval-requests, ___rejected-customers, ___deactivated-customers, ___dormant-customers, ___activate-dormant-customer, ___mark-dormant-from-list, ___suspendable-customers, ___edit, ___location-edit-logs, ___order-location-logs, ___field-visits, ___geomapping-comments, ___geomapping-schedules, ___geomapping-summary |
| Geotagged approvals | checked as can('geotagged-approval-requests', 'route-customers') |
| KRA PIN | kra-pin-approval-requests___view (+ approve, decline checked in controller) |
| Deactivation requests | customer-deactivation-requests___view |
| Edit requests | route-customer-edit-requests___view (+ process, bulk-process checked in controller) |
| Bad debts | bad-debts___view |
| Post-sales discounts | post-sales-discounts___view |
| CRC | crc-management___view, ___add, ___edit, ___print |
| Group reps | route-group-rep___view |
| Bulk utilities | upload-customer-limits___view, bulk-deactivate-customers___view, customer-opening-balance___view |
Typical role mapping (inferred — confirm in §7): field salesmen / route managers onboard shops from the mobile app and initiate edits; branch/office admins run the verification, approval, duplicate, KRA-PIN, and deactivation-request queues; finance / credit control operate bad debts, withholding clearance, post-sales discounts, CRC, opening balances, and credit-limit uploads; group representatives (users.role_id == 187, per GroupRepresentativeController) oversee sets of routes.
Sidebar spine: resources/views/admin/includes/sidebar_includes/sales_and_receivables.blade.php:270-704 (the "Maintain Customers" and "Route Customers" treeviews).
3. Processes (start → finish)¶
3.1 The customer lifecycle state machine¶
wa_route_customers.status moves through these values. The transitions below are proven in RouteCustomerController (Admin), Shared/RouteCustomerController, DuplicateCustomerRequestsController, and GeotaggedCustomerRequestsController.
stateDiagram-v2
[*] --> unverified: Mobile onboarding when REQUIRE_ROUTE_MANAGER_APPROVAL_FOR_SALESMAN_SHOP_ONBOARDING = 1
[*] --> verified: Mobile onboarding by a manager / when manager-approval not required
[*] --> duplicate: Phone or KRA PIN already on a verified/approved/duplicate shop
[*] --> geotagged: Phone NOT required and no phone supplied
[*] --> approved: Web-portal add (non-duplicate mode)
unverified --> verified: verifyShopFromWeb / verifyAll (Onboarding Requests queue)
unverified --> rejected: rejectShopFromWeb
verified --> approved: approve / approveAll (Approval Requests queue)
verified --> rejected: reject
duplicate --> approved: Duplicate Requests — all reasons cleared
duplicate --> duplicate: Duplicate Requests — partial approval (some reasons remain)
duplicate --> rejected: Duplicate Requests — reject
geotagged --> approved: Geotagged Requests — approve (phone now required)
geotagged --> rejected: Geotagged Requests — reject
approved --> dormant: markCustomerDormant (needs can_suspend = true)
dormant --> approved: activateDormantCustomer
approved --> deactivated: soft delete (deleted_at set)
deactivated --> approved: reactivateCustomer
rejected --> unverified: reOnboardRejectedCustomer (if ALLOW_RE_ONBOARDING_REJECTED_ROUTE_CUSTOMERS = 1)
rejected --> [*]
deactivated --> [*]
Notes on the states:
- deactivated is not literally a status value — it is a soft delete (deleted_at set). The "Deactivated Customers" list queries deleted_at IS NOT NULL.
- dormant is an explicit status value plus dormant_at / dormant_by / dormant_reason columns. Reactivating a dormant shop sets it back to approved.
- can_suspend is a boolean flag that gates whether a shop can be made dormant; "Suspendable Customers" lists can_suspend = true AND status != 'dormant'. What sets can_suspend = true is not visible in the reviewed controllers — see §7.
3.2 Onboarding → verification → approval¶
flowchart TD
A[Salesman/manager onboards shop in mobile app] --> B[storeFromApi]
B --> C{Phone required?}
C -- no & no phone --> G[status = geotagged]
C -- else --> D{Duplicate phone / KRA PIN?}
D -- yes --> DUP[status = duplicate + duplicate_reasons JSON]
D -- no --> E{Manager approval required?}
E -- yes --> UV[status = unverified]
E -- no / is manager --> VF[status = verified]
UV --> V[Onboarding Requests: verify → verified]
VF --> AP[Approval Requests: approve → approved]
V --> AP
DUP --> DR[Duplicate Requests review]
G --> GR[Geotagged Requests review]
AP --> LIVE[Shop live: added to open salesman shift, auto-linked to duplicate group]
- Entry point (mobile):
Shared/RouteCustomerController@storeFromApi. It reads settingsshop_onboarding_phone_required,REQUIRE_ROUTE_MANAGER_APPROVAL_FOR_SALESMAN_SHOP_ONBOARDING,ALLOW_KRA_PIN_VALIDATION,REQUIRE_CREDIT_AGREEMENT_FOR_CREDIT_CUSTOMERS. It auto-creates a parentWaCustomerfor the route if none exists, then inserts theWaRouteCustomerwith the computedstatus(Shared/RouteCustomerController.php:463-610). - Duplicate detection at onboarding: a shop is flagged
duplicateif its phone or KRA PIN already exists on another shop whose status isverified/approved/duplicate; the matched reasons are stored induplicate_reasonsJSON. - Verify vs Approve (two-step): Verify (
unverified → verified) is intake/validation done from the Onboarding Requests queue; Approve (verified → approved) is the compliance gate done from the Approval Requests queue. On approval the shop is added to any open salesman shift for the route and auto-linked into its duplicate group. - Web-portal add:
RouteCustomerController@store(Admin) can create a shop directly asapproved(non-duplicate mode) orduplicate(duplicate mode), skipping verification. customer_code: auto-generated on create asRCS-#####(WaRouteCustomer::boot,app/Model/WaRouteCustomer.php:29-34).WaCustomeruses theCUSTOMERSnumber series.
Required onboarding fields (always): route, name, phone, business name, delivery centre, latitude, longitude. Optional (per-type toggleable via onboarding_optional_visibility): town, contact person, customer ID number, KRA PIN, KRA PIN metadata, credit type/limit/days, credit agreement file (app/Support/CustomerOnboardingFields.php:20-42).
3.3 KRA PIN approval¶
When ALLOW_KRA_PIN_VALIDATION = 1, a PIN supplied at onboarding/edit is not written straight to the shop. Instead the PIN is held, and a RouteCustomerKraPinApprovalRequest is created with status = pending (enum). The request records wa_route_customer_id, kra_pin, kra_pin_metadata, initiated_by, and duplicate-tracking (is_duplicate, existing_kra_pin_customer_ids).
flowchart LR
S[PIN supplied at onboarding/edit] --> R[RouteCustomerKraPinApprovalRequest: pending]
R -->|approve| A[status=approved; approved_by/at set; wa_route_customers.kra_pin updated]
R -->|decline| D[status=declined; declined_by/at, decline_reason set; PIN NOT written]
Approve/decline: CustomerController@approveCustomerKraPinApprovalRequest / @declineCustomerKraPinApprovalRequest, permission-gated on kra-pin-approval-requests (approve/decline). Only on approval is wa_route_customers.kra_pin set from the request.
3.4 Deactivation / dormancy / suspendable¶
- Direct deactivation soft-deletes the shop (
deleted_at,deleted_by), moving it to the "Deactivated Customers" list. Reactivation (reactivateCustomer) restores it toapproved. - Deactivation with approval: when
REQUIRE_APPROVAL_FOR_ROUTE_CUSTOMER_DEACTIVATION = 1, deactivations are submitted asRouteCustomerDeactivationRequestrows (status enum) reviewed viaRouteCustomerDeactivationRequestController(approve / decline / bulk). This gates the "Customer Deactivation Requests" menu (sales_and_receivables.blade.php:617). - Dormancy:
markCustomerDormantrequirescan_suspend = trueand a reason; it setsstatus = dormant,dormant_at,dormant_by,dormant_reason,last_dormant_at, and clearscan_suspend.activateDormantCustomerreturns the shop toapproved. There is no scheduled command that auto-marks dormancy — dormancy is triggered manually (no dormant entry inapp/Console, confirmed by grep). - Suspendable list = candidates for dormancy (
can_suspend = true AND status != 'dormant'). - Bulk deactivation (
RouteCustomerBulkDeactivationService,RouteCustomerBulkDeactivationmodel): deactivate by criteria — net sales below a threshold in a date window, with balance-handling rules (deactivate_with_balances,max_balance). Tracksqueued → processing → completed/failedwith per-reason skip counters.
3.5 Bad Debts (pending → approved → [restored])¶
Write off an uncollectable debtor balance. Table customer_account_bad_debts; controller CustomerAccountBadDebtController; GL via BadDebtGlPostingService.
flowchart LR
I[store: create BDT-##### doc, reviewed=false] --> P[Pending Bad Debts]
P -->|approve| A[reviewed=true, approved=true]
A --> DT[WaDebtorTran amount * -1 → reduces debtor balance]
A --> GL[GL: DR Fraud/Bad-Debt acct 55001-007, CR Debtors Control]
- Document series
BDT("Bad Debts"). GL account defaults to55001-007, overridable viasetting('BAD_DEBT_GL_ACCOUNT')(BadDebtGlPostingService.php:38,54). GL posting is idempotent (checkswa_gl_trans). - Setting
ALLOW_CHOOSING_ROUTE_CUSTOMER_WHILE_SENDING_BAD_DEBTlets the initiator pick a specific route customer. - "Restored Bad Debts" is a dead menu link. The
restored/restore_narrationcolumns exist on the table, but the sidebar "Restored Requests" item points tohref="#"(sales_and_receivables.blade.php:359) and thebad-debtsroute group has onlyindex / approved / store / approve(routes/modules/sales_and_receivables.php:673-678). No restore endpoint exists — see §7.
3.6 Post-Sales Discounts (pending → approved → signed)¶
Retroactively discount an already-invoiced sale. Table post_sales_discounts (+ post_sale_discount_items); controller PostSalesDiscountController.
flowchart TD
S[store: pick invoice + per-item discounts, PSD-##### doc, reviewed=false] --> P[Pending]
P -->|approve| A[reviewed=true, approved=true]
A --> DT[WaDebtorTran amount * -1 → reduces debtor balance]
A --> GL[GL reversal: DR Sales 56002-003 + DR VAT Output, CR Debtors Control]
A --> CR[CustomerCredit source='discount' if invoice has route customer; UtilizeCustomerCreditsJob]
A -->|resign| SIGN[Credit note signed to KRA via eTIMS or legacy ESD]
- Per-item validation: discount cannot be negative and cannot exceed the item total. Document series
PSD. - On approval it does three things: writes a negative debtor transaction, posts a GL reversal (legacy: DR sales
56002-003, DR VAT output, CR debtors control; or viaExtensiveGlPostingServicewhen enabled), and — if the invoice is tied to a route customer — creates aCustomerCredit(sourcediscount) and dispatchesUtilizeCustomerCreditsJob. discardrejects a pending request;resignconverts the approved discount into a KRA-signed credit note (eTIMS or legacy ESD path).
3.7 Tax Withholding clearance (initiate → approve → resolve)¶
Institutional customers pay you net of withholding tax and remit a WHT certificate. This flow clears the withheld portion off the debtor balance. Table wa_customer_withholding_clearance_requests; controller WaCustomerWithholdingClearanceRequestController.
flowchart TD
I[settle-from-withholding: initiate against invoice OR RCOP opening balance] --> P[status=1 pending]
P -->|approve| A[status=2 approved]
A --> DT[WaDebtorTran negative → reduces balance]
A --> TE[WaTenderEntry against Withholding VAT GL, marked consumed]
A --> GL[GL: CR Debtors Control, DR VAT Control / payment-method acct]
A --> OVER[Overpayment → CustomerCredit]
A -->|reject approved| REV[Reversal: positive WaDebtorTran + reversal GL, undo allocations, delete credit → back to pending]
A -->|upload certificate| CERT[certificate_filename stored]
A -->|resolve| RES[status=5 resolved; resolved_by/at]
- Status codes:
1pending,2approved,3changes_requested,4unresolved,5resolved. - Two allocation targets: a specific invoice (
wa_internal_requisition_id) or an RCOP opening balance (rcop_opening_balance_id), set byallocation_type. - Certificate reference must be unique. Setting
INVOICE_WITHHOLDING_TAX_CERTIFICATE_GRACE_PERIOD_DAYS(default 20) governs how long an invoice may go before missing-WHT blocks further sales (referenced inCustomerController). - Permissions: initiate =
maintain-customers___initiate-withholding-clearance; approve =maintain-customers___approve-withholding-clearance.
3.8 CRC / Cash from Debtors¶
Record cash a debtor pays directly (outside the normal route collection). Table crc_records; controller CrcManagementController; services CrcPaymentService, CrcGlPostingService, CrcReceiptPresenter.
flowchart LR
C[create/store: customer, amount, payment type] --> PS[CrcPaymentService.receiveFromCustomer]
PS --> DT[WaDebtorTran negative, doc from CHEQUE_REPLACE_BY_CASH series]
PS --> ALLOC[If route customer: allocate to pending invoices; overpayment → CustomerCredit]
PS --> REC[CrcRecord with previous_balance / balance_after]
PS --> GL[GL: DR Cash control 54008-000, CR Debtors Control]
REC --> PR[print → PDF receipt]
- CRC = Cash Received from Customers/Debtors. A CRC row can link to a
WaCustomer, aWaRouteCustomer, and an invoice. - CRC-Unlinked utility (
CustomerController@unlinkedCrcRecords/fetchRouteCustomersForCrc/attachCrcRecord): find CRC receipts not yet tied to a route customer and attach them, re-allocating the payment to invoices.
3.9 Customer edit requests¶
Field-level changes to a shop can be routed through approval. Table route_customer_edit_requests (status enum, changes JSON, reason, requested_by, approved_by); controller RouteCustomerEditRequestController (index / show / process / bulkProcess). An older parallel mechanism exists in phase_two_route_customers_edits (payload JSON). Editable fields include name, phone(s), business name, town, lat/lng, gender, image, KRA PIN + metadata, credit type/limit/days, delivery centre, customer ID number.
3.10 Approval requests for sensitive flags¶
Separately from onboarding approval, toggling sensitive customer flags (e.g. sell_at_standard_cost, turn_off_invoice_transmission_to_tax_authority) can require approval via customer_approval_requests (type, requested_value, status enum) reviewed by CustomerController@approve/declineCustomerApprovalRequest, gated by REQUIRE_APPROVAL_FOR_TURNING_OFF_INVOICE_TRANSMISSION_TO_TAX_AUTHORITY.
4. Tables touched & key data¶
The big shop record — wa_route_customers (the 50+ field customer)¶
Guarded model App\Model\WaRouteCustomer, soft-deletes. Columns assembled from the create migration + all wa_route_customers alter migrations:
Identity & contact: id, customer_code (RCS-#####), external_customer_code, source_key, name, bussiness_name (note misspelling), business_name, trading_as_name, phone, secondary_name, secondary_phone_no, secondary_phone_numbers, email, contact_person, gender, customer_id_number, image / image_url.
Location & routing: route_id, assigned_route_id, customer_id (→ parent WaCustomer), center_id, delivery_centres_id, lat, lng, location_name, town, distance_estimate. Appended attr has_valid_location (lat & lng non-zero).
Tax: kra_pin, kra_pin_metadata (JSON), account_number, turn_off_invoice_transmission_to_tax_authority.
Credit & commercial: is_credit_customer, credit_type, credit_limit, credit_days, return_limit, payment_term_id, price_list_id, credit_customer_id (a WaCustomer used as the credit parent), credit_agreement (file path), customer_target, sell_at_standard_cost, is_key_account, is_jaza_duka, customer_type.
Lifecycle & audit: status, duplicate_reasons (JSON), rejection_reason, rejected_by, rejected_at, approved_by, approval_reason, approved_at, can_suspend, dormant_at, dormant_by, dormant_reason, last_dormant_at, re_onboarded_by, re_onboarded_at, pin_updated_by, pin_updated_at, created_by, deleted_by, deleted_at, created_at, updated_at.
Key model logic: getMyOverallBalance() / getMyOverallBalanceNew() sum the debtor ledger (invoice-linked + standalone + RCOP opening balances − RCOP allocations); getCreditBlockReasons() enforces credit limit and credit days at order time, including limits inherited via credit_customer_id and linked-customer groups (app/Model/WaRouteCustomer.php:194-536).
The account record — wa_customers¶
App\Model\WaCustomer, sluggable, CUSTOMERS number series. Columns include customer_code, customer_name, slug, route_id, delivery_route_id, delivery_centres_id, payment_term_id, credit_limit, is_blocked, is_invoice_customer, bussiness_name, kra_pin, customer_since, contact/address fields.
Supporting tables (proven in migrations)¶
| Table | Purpose | Key columns |
|---|---|---|
wa_debtor_trans |
The debtor sub-ledger; every balance-moving event | wa_customer_id, wa_route_customer_id, wa_sales_invoice_id, amount (sign = direction), document_no, trans_date, channel, reference, branch_id |
rcop_opening_balances / rcop_allocations |
Route-customer opening balances (RCOP) and their settlements | document_no (RCOP-#####), amount, wa_route_customer_id, allocation_source, payment_amount |
route_customer_registrations / route_customer_login_logs |
Onboarding registration + login tracking | — |
route_customer_kra_pin_approval_requests |
KRA PIN approval queue | wa_route_customer_id, kra_pin, kra_pin_metadata, status (enum), initiated_by, approved_by/at, declined_by/at, decline_reason, is_duplicate, existing_kra_pin_customer_ids (JSON) |
customer_approval_requests |
Sensitive-flag approval queue | wa_route_customer_id, type, requested_value, status, requested_by, approved_by/at, declined_by/at, notes, decline_reason |
route_customer_deactivation_requests (+ _submissions, route_customer_bulk_deactivations) |
Deactivation approval + bulk jobs | wa_route_customer_id, status, balance, net_sales, max_balance, deactivate_with_balances, route_ids (JSON), approver/decline audit |
route_customer_edit_requests |
Field-edit approval queue | wa_route_customer_id, changes (JSON), status, reason, requested_by, approved_by |
phase_two_route_customers_edits |
Legacy/alternate edit tracking | payload (JSON), status, initiated_by, approved_by/at |
route_customer_location_edit_logs |
Audit of GPS edits | — |
customer_account_bad_debts |
Bad-debt write-offs | wa_customer_id, wa_route_customer_id, branch_id, amount, narration, document_number (BDT-), reviewed, approved, rejected, restored, restore_narration, reviewed_by/at |
post_sales_discounts / post_sale_discount_items |
Retroactive discounts | wa_customer_id, wa_route_customer_id, invoice_id, amount, document_number (PSD-), reviewed, approved, VAT totals; items carry kra_item_code, tax_manager_id |
wa_customer_withholding_clearance_requests |
WHT clearance | wa_customer_id, certificate_reference, certificate_filename, amount, status (1–5), allocation_type, wa_internal_requisition_id, rcop_opening_balance_id, invoice_allocation_id, customer_credit_id, resolved_by/at |
crc_records |
Cash-from-debtor receipts | user_id, reference, amount, banked_amount, bank_reference, banking_date, wa_customer_id, wa_route_customer_id, wa_sales_invoice_id, receipt fields |
customer_credits / customer_credit_utilizations |
Overpayment / discount credits and their consumption | source_type, source_document_no, source_invoice_id, amounts |
customer_link_groups / customer_link_group_members |
Group shops so credit limits aggregate across "dependent" customers | wa_route_customer_id |
route_representatives |
Group-rep → route assignment | user_id, route_id, created_by |
customer_types / credit_customer_types |
Configurable customer-type catalogs with onboarding_optional_visibility JSON |
title, slug, days, active |
On "type RETAIL / SUPERMARKET": the shop record's customer_type references the configurable CustomerType / CreditCustomerType catalogs (titles set by the tenant), not a hard-coded RETAIL/SUPERMARKET enum. The only SUPERMARKET literal found in code is a POS-operations OTP setting (PosCashSalesController), unrelated to the customer record — see §7.
5. Interactions with other modules¶
- Order Taking & Sales Invoicing — consumes the shop record:
getCreditBlockReasons()blocks/permits orders on credit limit and credit days; invoices writewa_debtor_transrows keyed onwa_route_customer_id/wa_customer_id. Post-sales discounts and withholding clearance both act on already-created invoices. - Route Management — owns route structure and visit scheduling; this chapter owns the shop record referenced by those routes.
wa_route_customers.route_id/assigned_route_id/center_id/delivery_centres_idare the join points. Handoff: geomapping schedules, order-location logs, and route assignment are documented there; the customer's GPS record, location-edit logs, and geotagged-approval queue are documented here. - Banking & Receivables Reconciliation — CRC banking (
banked_amount,bank_reference,banking_date) and debtor-payment reconciliation feed into that module; the debtor ledger (wa_debtor_trans) is the shared surface. - General Ledger — bad debts, post-sales discounts, withholding, CRC, and opening balances all post to
wa_gl_trans(accounts: bad-debt55001-007, sales56002-003, cash control54008-000, plus debtors-control and VAT accounts from company preference / tax manager). - KRA / e-invoicing (eTIMS / ESD) — KRA PIN approval feeds tax compliance; post-sales-discount
resignsigns credit notes;turn_off_invoice_transmission_to_tax_authoritysuppresses transmission for a shop. - Customer credits engine — overpayments (CRC, withholding) and discounts create
customer_credits, consumed byUtilizeCustomerCreditsJob.
6. Alternatives & variants (settings, flavors, legacy)¶
Setting flags proven in code (Laravel setting() / Setting model; slugs from migrations where found):
| Setting | Effect |
|---|---|
shop_onboarding_phone_required / SHOP_ONBOARDING_PHONE_REQUIRED |
If phone not required and none supplied at onboarding, shop is created geotagged. Nuance: the mobile onboarding reads the DB slug shop_onboarding_phone_required; the string SHOP_ONBOARDING_PHONE_REQUIRED appears only in the sidebar blade (it gates whether the Geotagged Requests menu shows). See §7. |
REQUIRE_ROUTE_MANAGER_APPROVAL_FOR_SALESMAN_SHOP_ONBOARDING |
If 1, salesman onboardings land as unverified (need manager verify); else verified. |
ALLOW_KRA_PIN_VALIDATION |
If 1, a supplied KRA PIN is held and routed through the KRA PIN approval queue rather than written immediately. |
ALLOW_CREDIT_DETAILS_ON_SHOP_ONBOARDING |
Whether credit fields appear on the onboarding form. |
REQUIRE_CREDIT_AGREEMENT_FOR_CREDIT_CUSTOMERS |
If 1, a credit-agreement file upload is required for credit customers. |
BLOCK_ROUTE_CUSTOMER_ON_CREDIT_LIMIT |
Whether exceeding the credit limit blocks the shop from ordering. |
BLOCK_DEPENDENT_CUSTOMER_FROM_TAKING_ORDERS |
Blocks "dependent" (link-group) customers from ordering under group credit rules. |
REQUIRE_APPROVAL_FOR_ROUTE_CUSTOMER_DEACTIVATION |
If 1, deactivations go through the Deactivation Requests approval queue (and the menu item appears). |
ALLOW_RE_ONBOARDING_REJECTED_ROUTE_CUSTOMERS |
If 1, rejected shops can be re-onboarded back to unverified. |
ALLOW_FETCHING_DUPLICATE_CUSTOMERS_WITH_UNIQUE_PHONE_NUMBER |
Includes some duplicate-status shops (with unique phones) in the Deactivated list. |
ALLOW_ADD_CUSTOMER_OPENING_BALANCES |
Gates the Customer Opening Balance menu/utility. |
ENABLE_CUSTOMER_CREDITS_AUTO_UTILIZATION |
Auto-applies customer credits. |
ALLOW_CHOOSING_ROUTE_CUSTOMER_WHILE_SENDING_BAD_DEBT |
Lets the bad-debt initiator pick a specific route customer. |
REQUIRE_APPROVAL_FOR_TURNING_OFF_INVOICE_TRANSMISSION_TO_TAX_AUTHORITY |
Routes the "turn off tax transmission" toggle through customer_approval_requests. |
INVOICE_WITHHOLDING_TAX_CERTIFICATE_GRACE_PERIOD_DAYS (default 20) |
Grace before missing-WHT blocks sales. |
BAD_DEBT_GL_ACCOUNT (default 55001-007) |
Overrides the bad-debt GL account. |
SEPARATE_GL_ACCOUNTS_PER_BRANCH |
Uses branch-specific cash-control account in withholding posting. |
ENABLE_KEY_ACCOUNT_SALES_DEDUCTION, JAZA_DUKA_IDENTIFIER |
Render is_key_account / is_jaza_duka toggles on the edit form. |
Flavor / tenant differences: customer types and credit-customer types are per-tenant catalogs (titles + onboarding_optional_visibility), so which optional onboarding fields are mandatory varies by tenant. GL account numbers, permission-role assignments, and the settings above differ per flavor.
Legacy / dual mechanisms: two edit-request tables coexist (route_customer_edit_requests new vs phase_two_route_customers_edits older). getMyOverallBalance() (union-query) and getMyOverallBalanceNew() (separate sums) both exist. old_wa_route_customer_details migrations suggest a historical snapshot of the shop record. A sales_and_receivables.php.bak route file exists alongside the live one.
7. Open questions to confirm¶
SHOP_ONBOARDING_PHONE_REQUIREDvsshop_onboarding_phone_required. Mobile onboarding reads the DB slugshop_onboarding_phone_required; the constant-casedSHOP_ONBOARDING_PHONE_REQUIREDis referenced only in the sidebar blade. Confirm these resolve to the same Setting row (case-insensitive lookup?) or whether the sidebar gate is effectively separate.- What sets
can_suspend = true? No reviewed controller sets it true (it is only cleared tofalseon dormancy/activation). Is there a scheduled job, uploader, or SQL process (e.g. inactivity threshold) that populates the Suspendable list? Not found inapp/Console. - Restored Bad Debts is non-functional. The menu ("Restored Requests") links to
#, norestored-bad-debtsroute/controller method exists, yetrestored/restore_narrationcolumns are present. Is restore intended (planned feature) or dead code to remove? - RETAIL / SUPERMARKET type. The chapter brief lists "type RETAIL/SUPERMARKET" but the shop record's
customer_typepoints to configurableCustomerType/CreditCustomerTypecatalogs; the onlySUPERMARKETliteral is a POS OTP setting. Confirm whether RETAIL/SUPERMARKET are just common tenant-defined type titles rather than a fixed enum. - Group Reps route registration.
GroupRepresentativeControlleris wired under prefixgroup-representative(group-rep.*names) insales_and_receivables.php:519-522, but onlyindex/view/add-routeappear there while the controller hasreassign_route/reassign_all_routes. Confirm where the reassign routes are registered. WaCustomervsWaRouteCustomercredit parenting.credit_customer_idlets a shop inherit aWaCustomer's credit limit; confirm the intended data-entry workflow (which UI sets it) and how it interacts withcustomer_link_groups.- Debtor inquiry / receipt entry (
enterCustomerPayment,debtorTransDetail,allocateReceipts). Thesemaintain-customers.*routes overlap the receivables/reconciliation boundary; confirm the ownership split with the Banking & Receivables Reconciliation chapter. - Role→permission defaults. The role mapping in §2 is inferred from menu gating; confirm against the actual permission seeder / role definitions per flavor.
8. Source references¶
Nav / IA
- resources/views/admin/includes/sidebar_includes/sales_and_receivables.blade.php:270-704 (Maintain Customers + Route Customers treeviews), :359 (dead "Restored Requests" link), :563-565 (SHOP_ONBOARDING_PHONE_REQUIRED gate), :617 (REQUIRE_APPROVAL_FOR_ROUTE_CUSTOMER_DEACTIVATION gate), :2011 (ALLOW_ADD_CUSTOMER_OPENING_BALANCES).
Routes
- routes/web.php:494-504 (withholding), :1047-1073 (customer accounts, debtor inquiry, CRC), :1056-1061 (deactivation requests), :1107 (edit requests), :1136-1163 (route customer accounts / lists), :1185-1197 (KRA PIN + approval requests), :5042-5075 (route customer lifecycle), :5998-6013 (duplicate & geotagged).
- routes/modules/sales_and_receivables.php:519-522 (group reps), :643-651 (CRC management), :673-678 (bad debts), :680-698 (post-sales discounts), :654-655 (invoices-pending-withholding report).
Controllers
- app/Http/Controllers/Admin/RouteCustomerController.php (store, lifecycle queues, bulk views).
- app/Http/Controllers/Shared/RouteCustomerController.php:463-679 (storeFromApi), :1074-1372 (verify/approve), :2269-2914 (deactivated/dormant/suspendable/reactivate/markDormant).
- app/Http/Controllers/Admin/CustomerController.php (index, branchCustomerAccountsIndex, KRA-PIN & approval requests, debtor inquiry, CRC-unlinked).
- app/Http/Controllers/Admin/DuplicateCustomerRequestsController.php, GeotaggedCustomerRequestsController.php, RouteCustomerDeactivationRequestController.php, RouteCustomerEditRequestController.php, GroupRepresentativeController.php, RouteCustomerAccountController.php, CrcManagementController.php, RouteCustomerStatementReportController.php.
- app/Http/Controllers/CustomerAccountBadDebtController.php, app/Http/Controllers/PostSalesDiscountController.php, app/Http/Controllers/Admin/WaCustomerWithholdingClearanceRequestController.php.
Models
- app/Model/WaRouteCustomer.php (esp. :29-45 boot/casts, :194-536 balances & credit blocks), app/Model/WaCustomer.php, app/Model/CustomerLinkGroup.php / CustomerLinkGroupMember.php.
- app/Models/: RouteCustomerKraPinApprovalRequest.php, CustomerApprovalRequest.php, RouteCustomerDeactivationRequest.php, RouteCustomerEditRequest.php, PhaseTwoRouteCustomerEdit.php, RouteCustomerBulkDeactivation.php, CustomerAccountBadDebt.php, WaCustomerWithholdingClearanceRequest.php, WithholdingTaxType.php, CrcRecord.php, CustomerCredit.php, CustomerCreditUtilization.php.
- app/CustomerType.php, app/Models/CreditCustomerType.php, app/Support/CustomerOnboardingFields.php:20-42.
Services / actions
- app/Services/BadDebtGlPostingService.php:38,54, CrcPaymentService.php, CrcGlPostingService.php, CrcReceiptPresenter.php, RouteCustomerBulkDeactivationService.php, CustomerCreditService.php, NewRouteCustomerService.php, CustomerLinkGroupService.php, WithholdingTaxCalculatorService.php, app/Actions/Withholding/ProcessWithholding.php.
Migrations
- database/migrations/2023_09_08_134414_create_wa_route_customers_table.php (+ all *_wa_route_customers* alters through 2025_11_15_205443), 2023_09_08_134414_create_wa_customers_table.php, 2024_07_22_154325_add_cutsomer_type_to_wa_customers_table.php.
- 2025_09_26_155812_create_route_customer_kra_pin_approval_requests_table.php, 2026_05_15_100000_create_customer_approval_requests_table.php, 2026_08_20_100001_create_route_customer_deactivation_requests_table.php, 2026_05_28_093551_create_route_customer_edit_requests_table.php, 2025_08_26_122953_create_customer_account_bad_debts_table.php, 2025_08_27_220808_create_post_sales_discounts_table.php, 2025_01_07_175916_create_wa_customer_withholding_clearance_requests_table.php (+ 2025_08_25 status codes), 2024_10_03_092854_create_crc_records_table.php (+ 2026_03_19, 2026_07_22).
- Setting migrations: 2025_10_02..shop_onboarding_phone_required, 2025_10_14..allow_credit_details_on_shop_onboarding, 2025_10_24..block_route_customer_on_credit_limit, 2025_11_15..require_credit_agreement_for_credit_customers, 2025_12_08..block_dependent_customer_from_taking_orders, 2026_02_17..enable_customer_credits_auto_utilization, 2026_03_05..allow_add_customer_opening_balances.