Skip to content

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:

  1. Who is the customer? A rich shop record — name, business name, phone(s), GPS location, delivery centre, KRA PIN, credit terms, photo, ID number.
  2. 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.
  3. 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.
  4. 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 settings shop_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 parent WaCustomer for the route if none exists, then inserts the WaRouteCustomer with the computed status (Shared/RouteCustomerController.php:463-610).
  • Duplicate detection at onboarding: a shop is flagged duplicate if its phone or KRA PIN already exists on another shop whose status is verified/approved/duplicate; the matched reasons are stored in duplicate_reasons JSON.
  • 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 as approved (non-duplicate mode) or duplicate (duplicate mode), skipping verification.
  • customer_code: auto-generated on create as RCS-##### (WaRouteCustomer::boot, app/Model/WaRouteCustomer.php:29-34). WaCustomer uses the CUSTOMERS number 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 to approved.
  • Deactivation with approval: when REQUIRE_APPROVAL_FOR_ROUTE_CUSTOMER_DEACTIVATION = 1, deactivations are submitted as RouteCustomerDeactivationRequest rows (status enum) reviewed via RouteCustomerDeactivationRequestController (approve / decline / bulk). This gates the "Customer Deactivation Requests" menu (sales_and_receivables.blade.php:617).
  • Dormancy: markCustomerDormant requires can_suspend = true and a reason; it sets status = dormant, dormant_at, dormant_by, dormant_reason, last_dormant_at, and clears can_suspend. activateDormantCustomer returns the shop to approved. There is no scheduled command that auto-marks dormancy — dormancy is triggered manually (no dormant entry in app/Console, confirmed by grep).
  • Suspendable list = candidates for dormancy (can_suspend = true AND status != 'dormant').
  • Bulk deactivation (RouteCustomerBulkDeactivationService, RouteCustomerBulkDeactivation model): deactivate by criteria — net sales below a threshold in a date window, with balance-handling rules (deactivate_with_balances, max_balance). Tracks queued → processing → completed/failed with 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 to 55001-007, overridable via setting('BAD_DEBT_GL_ACCOUNT') (BadDebtGlPostingService.php:38,54). GL posting is idempotent (checks wa_gl_trans).
  • Setting ALLOW_CHOOSING_ROUTE_CUSTOMER_WHILE_SENDING_BAD_DEBT lets the initiator pick a specific route customer.
  • "Restored Bad Debts" is a dead menu link. The restored / restore_narration columns exist on the table, but the sidebar "Restored Requests" item points to href="#" (sales_and_receivables.blade.php:359) and the bad-debts route group has only index / 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 via ExtensiveGlPostingService when enabled), and — if the invoice is tied to a route customer — creates a CustomerCredit (source discount) and dispatches UtilizeCustomerCreditsJob.
  • discard rejects a pending request; resign converts 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: 1 pending, 2 approved, 3 changes_requested, 4 unresolved, 5 resolved.
  • Two allocation targets: a specific invoice (wa_internal_requisition_id) or an RCOP opening balance (rcop_opening_balance_id), set by allocation_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 in CustomerController).
  • 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, a WaRouteCustomer, 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 write wa_debtor_trans rows keyed on wa_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_id are 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-debt 55001-007, sales 56002-003, cash control 54008-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 resign signs credit notes; turn_off_invoice_transmission_to_tax_authority suppresses transmission for a shop.
  • Customer credits engine — overpayments (CRC, withholding) and discounts create customer_credits, consumed by UtilizeCustomerCreditsJob.

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

  1. SHOP_ONBOARDING_PHONE_REQUIRED vs shop_onboarding_phone_required. Mobile onboarding reads the DB slug shop_onboarding_phone_required; the constant-cased SHOP_ONBOARDING_PHONE_REQUIRED is 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.
  2. What sets can_suspend = true? No reviewed controller sets it true (it is only cleared to false on dormancy/activation). Is there a scheduled job, uploader, or SQL process (e.g. inactivity threshold) that populates the Suspendable list? Not found in app/Console.
  3. Restored Bad Debts is non-functional. The menu ("Restored Requests") links to #, no restored-bad-debts route/controller method exists, yet restored/restore_narration columns are present. Is restore intended (planned feature) or dead code to remove?
  4. RETAIL / SUPERMARKET type. The chapter brief lists "type RETAIL/SUPERMARKET" but the shop record's customer_type points to configurable CustomerType/CreditCustomerType catalogs; the only SUPERMARKET literal is a POS OTP setting. Confirm whether RETAIL/SUPERMARKET are just common tenant-defined type titles rather than a fixed enum.
  5. Group Reps route registration. GroupRepresentativeController is wired under prefix group-representative (group-rep.* names) in sales_and_receivables.php:519-522, but only index/view/add-route appear there while the controller has reassign_route / reassign_all_routes. Confirm where the reassign routes are registered.
  6. WaCustomer vs WaRouteCustomer credit parenting. credit_customer_id lets a shop inherit a WaCustomer's credit limit; confirm the intended data-entry workflow (which UI sets it) and how it interacts with customer_link_groups.
  7. Debtor inquiry / receipt entry (enterCustomerPayment, debtorTransDetail, allocateReceipts). These maintain-customers.* routes overlap the receivables/reconciliation boundary; confirm the ownership split with the Banking & Receivables Reconciliation chapter.
  8. 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.