Skip to main content

Customers

Customers is the SAP customer lookup and operational launcher. Users search branch-scoped customers, inspect profile/address/credit/point data, call the customer, start sales documents, request status changes, add delivery/billing addresses, mark default shipping addresses, and request GPS-coordinate updates. Editing customer master data itself is handed off to Customer Management.

App Entry Points

AppTarget
MobileCustomerList mode default, booking, borrow, or request -> CustomerDetail
POS Terminalcustomer split menu -> CustomerDetailForm

End-To-End Workflow

List Screen Controls

ControlMobile BehaviorPOS Terminal Behavior
Backnavigation.goBack() from CustomerManagementListHeader.Back is handled by the HomeScreen menu/pane shell, not by the customer list component itself.
Search inputUpdates search; submit calls getCustomerSapList({ page: 1, search, shippingType, routeCode, isRequest }).Updates shared listSearch; submit calls loadCustomerList({ page: 1, search, shippingType, routeCode }).
Clear searchClears the local search value. A new API fetch happens on the next submit/refresh.Clears shared list search through the split-pane controls.
Route filterOpens a route selection modal. Selecting a route reloads page 1 with routeCode.Opens route selector in ListPane; selecting a route clears selected detail and reloads page 1 with routeCode.
Shipping type tabsสายส่ง sends shippingType: "1"; หน้าร้าน sends shippingType: "2"; tapping the active tab clears the filter.Same behavior through customerShippingType; selecting a tab reloads page 1 and clears the selected detail.
Pull refreshReloads page 1 with current filters.Reloads page 1 with current filters.
Infinite scrollonEndReached loads the next page while hasMore is true.Split list onLoadMore calls loadCustomerList() with customerPageRef.current.
Row tapOpens CustomerDetail with customerId, mode, and origin. Missing customerId shows an error.Selects split-list row and loads getCustomerSplitDetail(item) into the right pane.
Row swipe primary actionHidden in request mode. Otherwise launches the mode action: sales order, booking price, or borrow. Sales-order mode checks blocked reason and existing order for the same branch/day.Not implemented as row swipe for customer. Create-document actions are inside the detail pane.
Header menu: add customerVisible only when canShowCreateCustomerMenu; opens CustomerManagementEdit with empty draft.Customer creation is handled through Customer Management menu, not the customer split list header.

Detail Screen Buttons And Actions

Button / ActionMobilePOS Terminal
BackCloses address form if it is open; otherwise navigation.goBack().Detail pane close comes from the shared pane header.
Pull refreshReloads /customers/:customerId detail, addresses, and branches.Refresh detail is called from HomeScreen via onRefreshCustomerDetail.
Phone chips / phone buttonsCalls callCustomer(customerId) to record /customers/:id/re-call, then opens tel:{phone}.Same service call and tel: open; also validates customer ID first.
Create sales orderChecks create permission and getSalesOrderBlockedReason(). If an existing same-day branch order exists, asks whether to open the existing order or create a new one.Checks blocked reason, then HomeScreen either prompts/opens sales-order draft or switches to sales-order detail.
Booking priceChecks create permission and blocked reason, then opens BookingPriceDetail with bookingPriceId: null and customerId.Builds booking-price draft detail and switches active menu to bookingPrice.
Borrow fishChecks create permission and blocked reason, then opens BorrowDetail with borrowId: null and customerId.Builds borrow-fish draft detail and switches active menu to borrowFish.
Edit customerOpens CustomerManagementEdit with the current customerId; guarded by canEditCustomerFromDetail.Builds a customer-management edit draft in HomeScreen and switches to the customer-management form.
Request close statusVisible when status is ACTIVE and user can toggle customer status. Confirms, then calls updateCustomerStatus(customerCode, { status: "INACTIVE" }).Same request with confirmation inside CustomerDetailForm.
Request open statusVisible when status is INACTIVE and user can toggle customer status. Confirms, then calls updateCustomerStatus(customerCode, { status: "ACTIVE" }).Same request with confirmation inside CustomerDetailForm.
Add addressOpens address form if canManageCustomerAddresses.Opens address modal if canManageAddresses(user).
Approve requestMobile request mode only. Validates customer data with validateCustomerBeforeApproval({ customer, addresses, branches }), then calls approveCustomerRequest(customerCode).Approval for status requests is handled by the separate Customer Activation menu, not normal Customer detail.
Reject requestMobile request mode only. Calls rejectCustomerRequest(customerCode).Handled by Customer Activation menu.
Back button at bottomReturns to previous screen.Not applicable; pane close/menu selection handles navigation.

Address Actions

Button / ActionBehavior
Open mapRequires address-management permission and existing latitude/longitude. Opens Google Maps search URL with U_BFP_Latitude,U_BFP_Longitude.
Set default addressOnly for ShipTo addresses. Skips if already default. Requires customerCode and addressId, confirms, then calls setCustomerDefaultAddress(customerCode, addressId) and reloads detail.
Pin / request GPS updateRequires address-management permission and addressId. Opens GPS request form with current coordinates and optional note. Submit calls requestCustomerAddressGpsUpdate({ addressId, U_BFP_Latitude, U_BFP_Longitude, note }); approval happens in Customer Address Management.
Add address: address typeBillTo means billing address; ShipTo means delivery address. New address defaults to ShipTo.
Legal-entity billing branchMobile supports head-office/branch handling for BillTo: head office defaults to branch code 00000 and branch name สำนักงานใหญ่; branch entries require both code and name when provided. Terminal normalizes the same payload behavior.
Search addressOpens Thai address lookup (dbAddress) and fills tambon/amphur/province/zip/block fields.
Use current locationRequests location permission and fills latitude/longitude from the device GPS.
Choose from mapMobile uses MapPickerModal; Terminal uses an embedded Google Maps WebView. The selected coordinate fills latitude/longitude.
Route pickerRequired when customer shippingType === "1" and the new address is ShipTo; route is saved as U_ISS_RouteCode and U_ISS_RouteName.
Save addressValidates permission, customerCode, address type, required address fields, legal-entity branch rules, and route requirement for delivery customers. Then calls createCustomerAddressByPass(customerCode, payload), reloads detail, and closes the form.
Cancel / closeCloses the modal/form and resets unsaved address state.

Add Address Form

Field / ButtonMobilePOS Terminal
Add address entryButton label เพิ่มที่อยู่ใหม่; calls handleOpenAddAddress().Action button label เพิ่มที่อยู่; calls handleOpenAddAddress().
Permission guardUses access.canManageCustomerAddresses; otherwise shows คุณไม่มีสิทธิ์เพิ่มที่อยู่.Uses canManageAddresses(user); otherwise shows the same permission error.
Initial stateOpens full-screen address form with addressType: "ShipTo" and clears address search.Opens modal with addressType: "ShipTo" and clears address search.
Address typeRadio buttons: ที่อยู่ในการเปิดบิล -> BillTo, ที่อยู่สำหรับจัดส่ง -> ShipTo.Buttons: เปิดบิล -> BillTo, จัดส่ง -> ShipTo.
Legal entity branchFor legal-entity BillTo, Mobile shows head-office/branch choices. Head office sets branchCode: "00000", branchName: "สำนักงานใหญ่". Branch mode requires both branch code and branch name if either is entered.Terminal normalizes the payload the same way: non-legal entities and ShipTo clear branch fields; legal-entity BillTo defaults empty branch to head office.
Address nameRequired. Saved as addressName.
StreetRequired. Saved as street.
Extra note / landmarkOptional. Saved as checkAddressName.
Search Thai addressOpens lookup from dbAddress; selecting an item fills block, city, zipCode, U_BFP_Amphur, and U_BFP_Tambon.
Tambon / Amphur / Province / ZipRequired after address lookup or manual input. Mobile renders these as disabled fields after lookup; Terminal fields are editable text fields.
GPS from deviceButton ดึงพิกัดจากอุปกรณ์ / ดึงพิกัด; requests location permission and fills U_BFP_Latitude, U_BFP_Longitude.
Map pickerButton เลือกจากแผนที่; Mobile opens MapPickerModal, Terminal opens Google Maps WebView. Confirming writes selected latitude/longitude into the form.
Delivery routeRequired only when customer shippingType === "1" and address type is ShipTo. Saves selected route as U_ISS_RouteCode and U_ISS_RouteName.
SaveMobile button label บันทึก; Terminal button label บันทึกที่อยู่; both call createCustomerAddressByPass(customerCode, payload).
CancelMobile button label กลับ; Terminal button label ยกเลิก; both close the form/modal and reset unsaved address state.

Save Address Validation

CheckFailure Behavior
Address-management permissionShows คุณไม่มีสิทธิ์เพิ่มที่อยู่ and does not submit.
Missing customerCodeShows ไม่พบรหัสลูกค้า.
Missing address typeShows กรุณาเลือกประเภทที่อยู่.
Missing required fieldsShows กรุณากรอกข้อมูลที่อยู่ให้ครบ: ...; required fields are address name, street, tambon, amphur, province, and zip code.
Legal-entity BillTo branch is partially filledMobile requires either head office or both branch code and branch name.
Delivery customer ShipTo without routeShows กรุณาเลือกสายส่งสำหรับที่อยู่นี้.
API successCloses form/modal, reloads detail, and shows เพิ่มที่อยู่เรียบร้อยแล้ว.
API failureKeeps user in context and shows ไม่สามารถเพิ่มที่อยู่ได้ or the API error message.

GPS Coordinate Request

StepBehavior
Open request formMobile button ปักหมุดตำแหน่ง; Terminal button ขออัปเดตพิกัด. Both preload existing U_BFP_Latitude and U_BFP_Longitude if present.
Use current locationRequests device location permission and fills the request latitude/longitude fields.
Map pickerTerminal toggles map picker inside CustomerLocationForm; Mobile uses its map picker flow.
NoteOptional note is sent as note for the approver.
SubmitRequires numeric addressId and non-empty latitude/longitude.
APICalls requestCustomerAddressGpsUpdate({ addressId, U_BFP_Latitude, U_BFP_Longitude, note }).
ResultShows ส่งคำขอสำเร็จ กรุณารอการอนุมัติ; the actual approval/rejection is documented in Customer Address Management.

Default Shipping Address

RuleBehavior
Button visibilityMobile shows ตั้งเป็นค่าเริ่มต้น only for ShipTo rows in the address action area. Terminal shows ตั้งค่าเริ่มต้น for non-default addresses and omits it for default rows.
PermissionRequires address-management permission. Otherwise shows คุณไม่มีสิทธิ์ตั้งค่าที่อยู่เริ่มต้น.
Address typeOnly ShipTo can be default. BillTo returns ตั้งค่าเริ่มต้นได้เฉพาะที่อยู่จัดส่ง.
Already defaultNo API call; the current default row is treated as no-op.
Required IDsRequires customerCode and addressId; otherwise shows ไม่พบข้อมูลที่อยู่ที่ต้องการแก้ไข.
APICalls setCustomerDefaultAddress(customerCode, addressId).
SuccessReloads customer detail and shows ตั้งค่าที่อยู่จัดส่งเริ่มต้นเรียบร้อยแล้ว.

Data And API Flow

ConcernBehavior
List APIgetCustomerSapList() calls /customers?page={page}&pageLimit={PAGESIZE}&search={search}&shippingType={shippingType}&branchId={branchId}&routeCode={routeCode}.
Request-mode listMobile request mode appends isRequest=1; normal Customer menu does not.
Detail APIgetCustomerSapById(customerId) calls /customers/:customerId and expects profile fields, addresses, and branches.
Profile by codegetCustomerSapByCode(customerCode) calls /customers/:customerCode/profile; used by other flows that start from code instead of ID.
Point balancegetCustomerPointBalance(customerCode) calls /customers/:customerCode/point-balance; used by order/benefit flows that need the latest point balance.
Re-call loggingcallCustomer(customerId) posts /customers/:customerId/re-call before opening the phone dialer.
Status requestupdateCustomerStatus(customerCode, { status }) patches /customers/:customerCode/status. It is a request workflow, not a direct silent status mutation in the UI.
Address bypasscreateCustomerAddressByPass(customerCode, payload) patches /customers/:customerCode/addresses.
GPS update requestrequestCustomerAddressGpsUpdate(payload) posts /customer-draft/address-lat-lng.
Default addresssetCustomerDefaultAddress(customerCode, addressId) patches /customers/:customerCode/addresses/:addressId/default.
Customer request approvalMobile request mode calls approveCustomerRequest(customerCode) -> PATCH /customers/:customerCode/approve; POS Terminal handles the same service in Customer Activation.
Customer request rejectionMobile request mode calls rejectCustomerRequest(customerCode) -> PATCH /customers/:customerCode/reject; POS Terminal handles the same service in Customer Activation.
Direct address GPS updateupdateCustomerAddressGps(customerCode, addressId, payload) patches /customers/:customerCode/addresses/:addressId; this is used by related transport flows, while the Customer detail screen uses the approval request flow above.
Customer pointsCustomer detail displays tier, point, pointToNextTier, nextTierName, credit total, credit limit, and available credit-note amount when present in the detail payload.

Request Payload Examples

The examples below omit the API host and shared Content-Type header. PATH is also omitted from the URL, as in the endpoint table above. All IDs and customer data are examples only. GET endpoints have no JSON request body; their query string and path parameters are the request input.

Customer list

GET /customers?page=1&pageLimit=32&sort=DESC&sortBy=CreatedAt&search=%E0%B8%9A%E0%B8%A3%E0%B8%B4%E0%B8%A9%E0%B8%B1%E0%B8%97%20%E0%B9%80%E0%B8%AD&shippingType=1&branchId=101&routeCode=R001

Request mode adds &isRequest=1:

GET /customers?page=1&pageLimit=32&sort=DESC&sortBy=CreatedAt&search=&shippingType=&branchId=101&routeCode=&isRequest=1

Customer detail

GET /customers/12345

Customer profile by code

GET /customers/C000123/profile

Customer point balance

GET /customers/C000123/point-balance

Re-call logging

POST /customers/12345/re-call
Content-Type: application/json

{}

Request customer status change

PATCH /customers/C000123/status
Content-Type: application/json

{
"status": "INACTIVE"
}

Use "ACTIVE" when requesting that an inactive customer be opened again.

Add a customer address

PATCH /customers/C000123/addresses
Content-Type: application/json

{
"addressType": "ShipTo",
"branchCode": "",
"branchName": "",
"checkAddressName": "ใกล้ตลาดตัวอย่าง",
"addressName": "บ้านลูกค้าตัวอย่าง",
"street": "99/9 หมู่ 4",
"block": "ต.สุรศักดิ์ อ.ศรีราชา",
"city": "จ.ชลบุรี",
"country": "TH",
"zipCode": "20110",
"visOrder": "",
"U_BFP_Amphur": "อ.ศรีราชา",
"U_BFP_Tambon": "ต.สุรศักดิ์",
"U_ISS_RouteCode": "R001",
"U_ISS_RouteName": "สายส่งศรีราชา",
"U_BFP_Latitude": "13.173200",
"U_BFP_Longitude": "100.931500"
}

For a legal-entity BillTo, send the selected branch in branchCode and branchName; head office is "00000" and "สำนักงานใหญ่". BillTo addresses clear the route fields. For a delivery customer, ShipTo requires U_ISS_RouteCode and U_ISS_RouteName.

Request an address GPS update

POST /customer-draft/address-lat-lng
Content-Type: application/json

{
"addressId": 9876,
"U_BFP_Latitude": "13.173200",
"U_BFP_Longitude": "100.931500",
"note": "ขอแก้พิกัดหน้าร้านจากตำแหน่งล่าสุด"
}

addressId is sent as a number. The latitude and longitude are sent as strings, and note may be empty.

Set the default shipping address

PATCH /customers/C000123/addresses/9876/default
Content-Type: application/json

{}

Approve a customer request

PATCH /customers/C000123/approve
Content-Type: application/json

{}

Reject a customer request

PATCH /customers/C000123/reject
Content-Type: application/json

{}
PATCH /customers/C000123/addresses/9876
Content-Type: application/json

{
"U_BFP_Latitude": "13.173200",
"U_BFP_Longitude": "100.931500"
}

This endpoint is not the normal Customer detail submit path; the Customer detail screen submits a GPS request for approval instead.

Permissions And Guards

TopicMobilePOS Terminal
Menu permissionNormal customer menu uses CUSTOMER_MENU_DEPT_CODES: -2, 1, 2, 3, 4, 7, 8, 9, 13, 14, 16, 21, 27, 29, 30, plus admin/full non-main warehouse access.Terminal MENU_CUSTOMER: -2, 1, 2, 3, 4, 7, 29, 21, 8, 9, 13, 14, 27, 30, 16.
Request mode permissionCUSTOMER_REQUEST_DEPT_CODES: 2, 4, 7, 16, 29, plus admin/full non-main warehouse access.Separate customerActivation menu uses MENU_CAN_ACTIVE_CUSTOMER: 2, 4, 7, 16, 29.
Create customer documentsMobile uses sales-order access for detail/list primary actions. Customer permission context also has action dept codes -2, 1, 2, 4, 7, 8, 9, 13, 29.canCreateCustomerDocument() allows admin/full non-main warehouse access or dept codes -2, 1, 2, 4, 7, 8, 9, 13, 16, 21, 29.
Toggle statusMobile uses customer action permission; status action is shown only for active/inactive states.Terminal uses CUSTOMER_ACTION_DEPT_CODES: -2, 1, 2, 4, 7, 8, 9, 13, 29, plus admin.
Address actionsMobile address permission allows admin/full non-main warehouse access or dept codes 13, 14, 30.Terminal address actions allow admin or dept codes 13, 14, 30.
Edit from detailMobile blocks some list edit cases but detail edit is available through canEditCustomerFromDetail for customer action users.Terminal shows edit action when status-action permission passes.
Blocked customer/orderDocument creation calls getSalesOrderBlockedReason(customer, user) before starting order/booking/borrow.
Existing orderSales-order creation checks getExistingCustomerOrderForBranch(customer, branch) and prompts open existing vs create new.

Mobile vs POS Terminal

TopicMobilePOS Terminal
Navigation modelDedicated stack screens and route params.HomeScreen split-pane list/detail; actions switch active menu and load draft detail.
Customer modesdefault, booking, borrow, request.Normal customer menu; activation/request work is separate customerActivation.
List actionsRow tap plus swipe primary action.Row tap only; actions are in detail pane.
Create documentsNavigates to target stack detail screen.Builds draft detail and changes active menu to target document.
Address formFull-screen form inside CustomerDetail.Modal form inside right pane.
Map pickerNative/shared MapPickerModal.Embedded Google Maps WebView picker.

Developer Handoff Map

AreaCode
Mobile list/detailapps/mobile/src/screens/customerStack/customerListScreen, customerDetailScreen
Mobile permissionsapps/mobile/src/utils/customerPermissions.ts
Mobile copy/modesapps/mobile/src/screens/customerStack/config.ts
Terminal detailapps/pos-terminal/src/screens/HomeScreen/CustomerDetailForm.tsx
Terminal list/detail wiringapps/pos-terminal/src/screens/HomeScreen/index.tsx, DetailPane.tsx, ListPane.tsx
Terminal split dataapps/pos-terminal/src/services/splitMenuData.ts -> getCustomerSplitList, getCustomerSplitDetail
Customer serviceapps/*/src/services/customers.ts
Address helpersapps/mobile/src/utils/customerAddress.ts, terminal local helpers in CustomerDetailForm.tsx