Skip to main content

Mobile And Terminal Architecture

The repository currently supports two React Native apps with overlapping business concerns but different device assumptions.

Roles​

  • apps/mobile is the current POS app for handheld operator workflows.
  • apps/pos-terminal is the dedicated terminal app for larger Android devices, dual-screen scenarios, and tablet-oriented layouts.

App responsibilities​

Mobile POS​

  • Primary operator flow for the existing POS implementation
  • Android release packaging already exposed at workspace root through yarn mobile:apk and yarn mobile:aab
  • Firebase configuration already present in the Android app project

POS terminal​

  • Separate operator pane and customer-facing pane shell already exists
  • Android native bridge BsrCustomerDisplay detects and prepares for external display usage
  • Tablet and iPad-style layouts are part of the design direction
  • Release packaging conventions still need explicit project-level documentation before production rollout

Client integration credential boundary​

  • Android/iOS/Terminal release bundles are inspectable client artifacts. Values compiled from source code, shared packages, build constants, or client .env files must not be treated as secrets.
  • Legacy direct integrations that need password, tax/QR, or Product Hub service credentials must migrate to narrowly scoped server-side proxy endpoints. The proxy owns upstream secrets; signed-in clients use their normal user Bearer token and server-side branch/role authorization.
  • Pre-login password recovery needs a purpose-built rate-limited challenge with generic responses, expiry, and replay protection because it cannot rely on an existing user token.
  • Credential removal must be coordinated across Mobile and POS Terminal, including sibling QR/Product Hub consumers that share an upstream principal, followed by rotation/revocation of credentials contained in released builds and repository history.
  • Until the proxy migration is available, do not add or duplicate client-side service credentials and do not describe moving them to .env as remediation.

Relationship to shared packages​

Notes for future documentation​

  • Add a dedicated page for peripheral integrations when printer, scanner, and payment-device flows are stable.
  • Add a dedicated page for secondary-display rendering once the terminal app moves beyond capability detection into actual presentation rendering.

Shared UI boundary​

@bsr/rn-ui is the shared boundary for React Native UI infrastructure used by both mobile and POS terminal. It intentionally shares baseline behavior/context, not screen-specific layout.

What belongs in the shared boundary:

  • theme provider and hooks: ThemeProvider, useTheme, useThemeMode
  • alert provider and hooks: AlertProvider, useAlert
  • loading provider and hooks: LoadingProvider, useLoading
  • shared theme types and the font contract

What should stay inside each app:

  • screen layout and responsive sizing
  • screen-specific colors that are part of that screen's art direction
  • mock data or API bindings for individual flows
  • native module registration for that app

Impact of this boundary:

  • mobile keeps the existing src/contexts/* import paths, but those files re-export from the shared package to reduce existing-screen churn.
  • POS terminal uses the shared providers directly and has its own AsyncStorage-backed theme storage.
  • Behavior changes in @bsr/rn-ui should be checked against both @bsr/mobile and @bsr/pos-terminal.

Copying API errors from Global Alert​

Mobile and Terminal share the error alert in packages/rn-ui/src/AlertContext.tsx. When an error alert receives an HTTP error with a response body, it shows คัดลอกปัญหาส่งทีม IT. Pressing it copies the complete body, formatted as JSON for structured responses, and shows คัดลอกไปที่คลิปบอร์ดแล้ว inside the same alert. The alert remains open; copying does not confirm, cancel, retry, or resubmit the operation.

Copied JSON contains user, request, and response when Axios config is available. response retains the original backend body, including fields such as statusCode, message, path, and timestamp. request includes the method, URL with query, queryString, Axios params, and payload when present. JSON bodies already transformed into strings by Axios are parsed for readability; absent payloads are omitted. These details come from the app, requiring no backend response change.

user.usercode comes from the saved profile's userCode (or usercode), and user.deptCode from deptCode. Both apps capture only these identifiers on the request config before sending, preserving the initiating user after a user switch or 401 logout. Missing/unreadable profile values become null without blocking the request. This local metadata is not added to HTTP headers or payloads.

Secret fields such as passwords, tokens, and API keys in query parameters and structured request bodies are replaced with [REDACTED]. Headers and complete profiles are excluded. React Native multipart files include names/types instead of file contents or URIs. Free-form text payloads and backend responses remain unchanged; secret redaction is not guaranteed inside free-form text. Without config, copying falls back to the original response-only format. Local validation/network errors without a response and success/info alerts do not show the button. Clipboard failures show a retry message instead of success.

Copying a problem report for IT​

  1. When Global Alert shows an API error, press คัดลอกปัญหาส่งทีม IT.
  2. Wait for คัดลอกไปที่คลิปบอร์ดแล้ว, then paste the copied text into your support channel.
  3. The button only copies diagnostics: it does not automatically message IT, close the alert, or resend the API request. A failed copy displays ไม่สามารถคัดลอกได้ กรุณาลองอีกครั้ง.
JSON fieldMeaning
user.usercode, user.deptCodeUser and department captured when sending the request, rather than the current user at copy time.
request.method, request.urlHTTP method and request URL, including its query.
request.queryString, request.paramsSerialized query string and original Axios params, when available.
request.payloadBody available on the outgoing request config; omitted when no payload exists.
responseOriginal backend body, including statusCode, message, path, and timestamp when supplied.

Source references: shared UI packages/rn-ui/src/AlertContext.tsx, helper packages/utils/src/apiErrorResponse.ts, and request interceptors in apps/mobile/src/services/api.ts / apps/pos-terminal/src/services/api.ts.

Pass the original error to the alert, alongside the existing user-facing message:

catch (error) {
showAlert({
type: "error",
error,
message: getErrorMessage(error, "ไม่สามารถส่งข้อมูลไป B1 ได้"),
showConfirm: true,
});
}

The shared getApiErrorResponseText helper in packages/utils/src/apiErrorResponse.ts takes a snapshot when showAlert runs. Each new alert resets the copy result and uses its own error; there is no global “last API error” cache. Existing caught-error alerts across both apps now forward the original error, including the credit-note B1 flow. API interceptors attach local diagnostic context; error messages, permissions, and retry decisions remain owned by their existing flows.

Clipboard writes use the Clipboard API available in the installed React Native version, only after a user press. Its core export is deprecated upstream; moving to a standalone clipboard package is a separate native-dependency upgrade. This feature does not add native code or a clipboard reader.

Verification covers full JSON/plain-text serialization, missing responses, isolation between alerts, copying without closing/confirming, clipboard failure/retry, and the credit-note B1 catch forwarding its response. Run yarn workspace @bsr/mobile jest --runInBand --watchman=false __tests__/alertResponseCopy.test.tsx __tests__/apiErrorContext.test.ts __tests__/creditNoteQcTerminal.test.tsx from the repository root.

Shared i18n boundary​

@bsr/i18n is the shared boundary for translation resources used by both mobile and POS terminal.

Current structure:

  • shared resources live under packages/i18n/src/locales
  • supported languages are currently th and en
  • the default language is th
  • i18nResources is exported for React i18next initialization in each app

App responsibilities:

  • each app owns its runtime i18next initialization because language detection and provider placement are app bootstrap concerns
  • apps/mobile/src/i18n/index.ts initializes i18n from @bsr/i18n
  • apps/pos-terminal/src/i18n/index.ts initializes i18n from @bsr/i18n
  • old mobile locale files remain as compatibility re-exports, but new translation keys should be added to packages/i18n

Impact of this boundary:

  • translation key changes can affect both apps, so both workspaces should be type-checked before merging
  • screen-specific copy can still be namespaced by app or workflow inside the shared resource object when the text should not be reused
  • runtime language switching can be added later without moving translation resources again

Shared asset boundary​

@bsr/assets is the shared boundary for image assets used by both mobile and POS terminal.

Current structure:

  • image files live under packages/assets/images
  • app-specific font and animation assets remain inside each app for now
  • imports should use group subpaths to avoid pulling every asset group through the package root:
    • @bsr/assets/logos
    • @bsr/assets/icons
    • @bsr/assets/menus
    • @bsr/assets/utils

Compatibility notes:

  • apps/mobile/src/constants/images.ts remains as a compatibility re-export, but new code should import from @bsr/assets/* directly.
  • apps/mobile and apps/pos-terminal both declare @bsr/assets as a workspace dependency and map @bsr/assets/* in TypeScript config.
  • Release bundles should only include assets that Metro sees through static require(...); keeping imports split by group helps avoid unnecessary asset inclusion.

Logo behavior:

  • logos.logoBSR points to packages/assets/images/logos/logo.png.
  • logos.logoBSRWhiteText points to packages/assets/images/logos/logo-white-text.png.
  • Mobile login and branch screens use logoBSRWhiteText in dark mode and logoBSR in light mode.
  • Mobile QR share capture intentionally keeps using logoBSR so shared QR images stay unchanged.
  • POS terminal UserSelectionScreen uses the same logo pair for dark/light mode.

Shared printing boundary​

@bsr/printing is the shared boundary for HTML print templates used by both mobile and POS terminal.

Current structure:

  • pure print helpers and templates live under packages/printing/src/templates
  • shared templates currently cover order pack/storage, borrow pack/storage, invoice pack/storage, and full invoice receipt printing
  • LOGO_STICKER_DATA_URI lives in @bsr/printing because it is a print-template asset used by receipt output in both apps
  • mobile keeps compatibility wrapper files under apps/mobile/src/printing/templates where existing imports need to remain stable
  • POS terminal print helpers call shared templates directly from @bsr/printing
  • Metro config in both apps maps @bsr/printing to packages/printing/src so bundling does not depend only on the workspace symlink in node_modules

Operational behavior:

  • full invoice receipt printing should use the shared buildInvoiceTemplate
  • invoice receipt options include tax data, branch id, print count, claim QR, payment QR, and an optional logoStickerUri override
  • POS terminal invoice receipt printing builds QR claim/payment assets and reads branch, tax, and user from local storage before rendering the shared template
  • changes to templates in @bsr/printing affect both apps, so run both yarn workspace @bsr/mobile tsc --noEmit and yarn workspace @bsr/pos-terminal typecheck

POS Terminal Home split-pane code organization​

The large-screen terminal Home surface is intentionally being split by responsibility as more menu pages are added.

Current structure:

  • apps/pos-terminal/src/screens/HomeScreen/index.tsx owns the Home route state, effects, handlers, menu selection, and top-level split-pane composition
  • panel 1 owns the Home menu and the settings entry; the settings dropdown is intentionally rendered outside the menu ScrollView so it can overlay adjacent panes without being clipped
  • helpers.ts owns pure helpers for booking/customer draft creation, booking-price payload conversion, recalculation, id resolution, list search, and shared action definitions
  • ListPane.tsx owns panel 2 rendering, including list header controls, FlashList rendering, booking-price cards, invoice cards, invoice tabs, and swipe actions
  • DetailPane.tsx owns panel 3 rendering for booking price, invoice detail, and generic detail views
  • InvoicePanels.tsx owns invoice-specific list/detail/card/modal UI
  • DepositPanels.tsx owns deposit-specific panel-2 UI such as the retry filter, list card body, and swipe actions
  • DepositDetailForm.tsx owns the reusable deposit create/detail form used by both the deposit menu and transport embedded deposit flow
  • BookingPriceDetailForm.tsx owns the booking-price detail form
  • BookingProductPicker.tsx owns booking-price product picker and add/edit item overlay behavior
  • ShippingRouteDetailForm.tsx owns the POS terminal shipping-route detail/create flow for the ใบจัดสินค้าสายส่ง menu
  • styles.ts remains the aggregate Home style export, while menu-owned styles can be split into focused files such as depositStyles.ts

Refactor direction:

  • keep API calls and stateful screen orchestration close to index.tsx until each menu flow has a stable boundary
  • keep future extractions focused on stable responsibilities such as per-menu handlers or service orchestration after the menu flow is proven
  • avoid changing behavior during extraction; each refactor pass should type-check both POS Terminal and Mobile when shared package imports or Metro aliases are involved
  • the next likely extraction is ShippingRouteDetailForm.tsx, which should be split into header/list, invoice actions, product/Lot-Batch editor, sort planner, and print/action sections before more shipping behavior is added

POS Terminal shipping-route parity notes​

The POS terminal ใบจัดสินค้าสายส่ง menu is intended to mirror Mobile shippingListScreen and shippingDetailScreen while fitting the three-panel Home workspace.

Implemented behavior:

  • panel 2 renders the shipping-route list with mobile-aligned card colors, search, status selection filter, FlashList pagination, swipe delete, and permission-gated create/delete behavior
  • creating a shipping-route document follows the Mobile flow:
    • select route
    • preload available invoices into local state
    • call POST /api/deliveries/no to create the delivery header
    • use the returned deliveryId to call POST /api/deliveries/{deliveryId}/invoices
    • refresh panel 2 and load panel 3 from the created deliveryId
  • adding invoices in create mode only updates local state until the delivery header exists; adding invoices in detail mode posts directly to the existing deliveryId
  • panel 3 renders shipping detail/create as a FlashList-backed screen; the route/meta header and section header are ListHeaderComponent content for both invoice and product tabs to avoid nested outer scroll views
  • panel 3 includes mobile-style actions for accept, dock confirmation, loaded confirmation, co-worker assignment, add invoice, delete invoice, cancel invoice, sort customer order, send B1, and save product location/Lot data
  • document-level shipping actions (รับงาน, จัดสินค้าที่ลาน, ขึ้นรถแล้ว, ร่วมจัด) live in the panel-3 header three-dot dropdown, while product location saving lives in the product-tab FlashList footer so it scrolls with the content
  • POS terminal opens an automatic accept-work Sweet Alert when Mobile's accept condition is met: finished delivery, assigned to transportation, missing acceptedBy, and an accepting user department
  • the product summary tab groups invoice products, supports Lot/Batch selection, auto-selects a single sufficient lot, validates duplicate/over-quantity lots, and posts location data before updating the delivery status
  • visible shipping print UI matches current Mobile: swipe-print a single invoice row; invoice-batch and product-summary print helpers remain available but their UI entry points stay hidden while Mobile hides print-all and delivery-print actions
  • dark mode readability has been adjusted by using terminal theme colors for shipping detail headers, cards, modals, batch editor, and sort planner content

Known follow-up work:

  • test the full shipping-route flow on real iPad/tablet hardware, especially swipe actions, modal behavior, FlashList headers, keyboard behavior, and dark mode
  • test API behavior with real data for create, add/delete invoice, cancel invoice, accept/dock/loaded/assign, sort order, send B1, and Lot/Batch save
  • test physical printer output for the visible invoice row print action
  • refactor ShippingRouteDetailForm.tsx into smaller components once the UI and behavior are accepted

POS Terminal transport parity notes​

The POS terminal ขนส่ง menu is intended to mirror Mobile transportListScreen and transportDetailScreen while fitting the three-panel Home workspace.

Implemented behavior:

  • panel 2 renders the transport list with mobile-aligned card colors, displayed data, search, FlashList pagination, pull-to-refresh, swipe delete, and permission-gated create/delete behavior
  • panel 2 card colors follow Mobile transport state:
    • finished transports use the confirmed background color
    • started transports use the new/in-progress background color
    • draft transports use the draft background color
  • panel 3 is owned by TransportDetailForm.tsx and helper logic in TransportDetailHelpers.ts
  • panel 3 uses two FlashList-backed steps and avoids nested outer scroll views:
    • step 1 lists delivery sheets attached to the transport, with transport meta in ListHeaderComponent
    • step 2 lists delivery customers, with collection/payment meta in ListHeaderComponent
  • panel 3 bottom actions such as save, start, finish, and finalize-all are rendered as FlashList footer content instead of an absolute bottom bar so they scroll naturally with each step
  • transport create/detail behavior supports:
    • create/update transport header
    • select vehicle/license plate with Mobile-style vehicle cards showing status, vehicle type, and max payload; vehicle search is remote but intentionally non-paginated to match Mobile
    • add delivery sheets to a transport
    • create the transport header first when adding a delivery before transportId exists
    • keep the add-delivery modal open after a successful row add, show row-level loading, prevent duplicate row presses, and remove the added row from the modal list
    • remove delivery sheets from a transport before the transport starts
    • finalize a delivery sheet one-by-one
    • finalize all pending delivery sheets from the bottom action
    • load customer groups for the delivery step
    • manually sort delivery customer order in the step 2 sort planner
    • optimize delivery customer order with Google Routes API using current GPS location and customer coordinates, then save through sortTransportCustomers
    • start and finish transport with mileage validation
    • customer actions for call, navigation, GPS save, payment type selection, cash/transfer/other payment, photo/slip upload, signature capture, QR generation, and submit payment
    • oil/deposit skip actions that update flagGas and flagBank
    • print all invoices and print product summary after all delivery sheets are finalized
  • added POS terminal transport service boundaries:
    • apps/pos-terminal/src/services/transport.ts
    • vehicle and mileage helpers in apps/pos-terminal/src/services/common.ts
    • customer call/profile/GPS helpers in apps/pos-terminal/src/services/customers.ts
    • Google route optimization helper in apps/pos-terminal/src/services/googleRoutes.ts
    • generateTransportPaymentQrCode in apps/pos-terminal/src/services/qr.ts

Known follow-up work:

  • test the full transport flow on real iPad/tablet hardware, especially FlashList headers, modal overlays, camera/signature, GPS permission, swipe actions, and dark mode
  • test API behavior with real data for create/update transport, add/remove delivery, finalize delivery, load customer groups, manual/Google route sorting, payment, GPS save, start/finish, and print actions
  • test physical printer output for transport invoice batch and product summary

POS Terminal deposit parity notes​

The POS terminal ฝากเงิน menu follows Mobile depositListScreen and depositDetailScreen, with the detail form intentionally built as a reusable component for both the normal deposit menu and the transport deposit flow.

Implemented behavior:

  • panel 2 renders the deposit list with Mobile card colors, displayed data, search placeholder, FlashList pagination, pull-to-refresh, and retry-only filter:
    • DELETED deposits use the canceled background
    • deposits with empty RefKey use the error background
    • normal deposits use the confirmed background
    • the generic status filter is hidden for this menu so only the Mobile-style ทั้งหมด / รอส่งใหม่ retry filter remains
  • panel 2 supports swipe actions matching Mobile:
    • retry for deposits with empty RefKey, with owner/override validation in the handler
    • delete by the Mobile deposit delete department-code rule
  • panel 3 uses DepositDetailForm.tsx as a reusable create/detail form:
    • create mode loads branch/user context, companies, cash accounts, and bank accounts
    • detail mode loads persisted deposit data and renders the form read-only
    • company selection reloads cash/bank account options
    • invoice selection is available only in create mode outside transport and hidden for department codes 14 and 30
    • invoice selection uses FlashList with search and load-more pagination; the same pagination behavior was added to Mobile depositDetailScreen
    • selecting invoices makes amount and notes system-derived/read-only
    • send-slip-later is available only in create mode outside transport and shifts the deposit date to yesterday
    • save validates company, cash account, bank account, and either amount/notes or selected invoices, then confirms before createDeposit
    • after a normal deposit create, panel 2 refreshes and panel 3 closes without selecting the created deposit, avoiding an unnecessary GET /deposits/:id
  • transport integration design:
    • transport opens the same DepositDetailForm as an embedded state in transport panel 3 rather than a nested modal on iPad/iOS
    • transport passes DepositTransportContext with transportId, transportPayload, branch/warehouse/company/date/notes defaults
    • after createDeposit, the form updates transport with the passed payload so flagBank is set, then returns control to the transport flow
  • shared account option behavior:
    • Mobile and POS Terminal both dedupe cash-account options by acctCode, keeping the first item when the API returns the same account code for multiple companies

Known follow-up work:

  • test create/detail deposit behavior on iPad/tablet, including account selection modals, invoice selection, date picker, keyboard avoidance, and transport handoff
  • test normal deposit create, invoice-based deposit create, transport deposit handoff, retry, and delete with real API data

POS Terminal expense-bill parity notes​

The POS terminal บิลน้ำมัน menu follows Mobile expenseBillListScreen and expenseBillDetailScreen, with the detail form built as the reusable oil/expense-bill component for both the normal menu and the transport oil flow.

Implemented behavior:

  • panel 2 renders the expense-bill list with Mobile card colors, displayed data, search placeholder, FlashList pagination, pull-to-refresh, and permission-gated create behavior:
    • draft bills with branchId as 0 use the draft background
    • bills with empty RefKey use the error background and expose retry
    • normal bills use the confirmed background
    • the generic status filter is hidden for this menu
  • panel 2 supports swipe actions matching Mobile:
    • retry for bills with empty RefKey when the bill is not a draft branch row
    • delete through DELETE /bills/:billId
  • panel 3 uses ExpenseBillDetailForm.tsx as a reusable create/detail form:
    • create mode loads branch/user context, companies, gas partners, vehicles, and mileage
    • detail mode loads persisted bill data from GET /bills/:billId
    • draft branch rows can be updated through PATCH /bills/:billId by accounting users
    • company selection can reload mileage for the selected vehicle
    • vehicle selection loads old mileage and balance range
    • send-slip-later is available only in create mode outside transport and shifts the bill date to yesterday
    • save validates company, vehicle, dealer, amount, and notes before create/update
  • transport integration design:
    • transport opens ExpenseBillDetailForm as an embedded state in transport panel 3 rather than a nested modal on iPad/iOS
    • transport passes ExpenseBillTransportContext with transportId, transportPayload, branch/warehouse/company/vehicle/date/notes defaults
    • after createExpenseBill, the form updates transport with the passed payload so flagGas is set, then returns control to the transport flow

Known follow-up work:

  • test expense-bill list/detail behavior on iPad/tablet, including selection modals, date picker, keyboard avoidance, mileage loading, retry/delete, and transport handoff with real API data

POS Terminal user selection​

The current POS terminal first screen is UserSelectionScreen at apps/pos-terminal/src/screens/UserSelectionScreen/index.tsx.

Behavior to remember:

  • The screen uses a gradient background, BOONSIRI logo, and horizontal FlashList for user selection.
  • The BOONSIRI brand image comes from @bsr/assets/logos and switches between the normal logo and white-text logo by theme mode.
  • User cards show the full image when available and fall back to firstname plus userCode when no image is available.
  • Fallback user-card typography uses the shared app font contract; the card name is medium-weight white text, and the user code remains at the standard card size.
  • The user list is loaded from GET {POS_HOST}/api/users/sale-persons?whsGrpCode={storedBranch.whsGrpCode}.
  • That sale-person request sends x-api-key: X_API_KEY_TERMINAL_MAIN.
  • The screen reads the selected branch/warehouse from AsyncStorage key branch and uses whsGrpCode to load users. Raw branch and salesperson API payloads must not be written to runtime logs.
  • Pull-to-refresh reloads the app version, rereads the stored branch, and reloads the sale-person list.
  • If the user count is small, the horizontal user list is padded so the visible cards stay centered; when the list is wider than the viewport, it keeps normal horizontal scrolling.
  • Selecting a user opens the login modal with password input, loading state, and shared alert feedback.
  • The alternate employee-code login modal lets an operator enter employee code and password without selecting a user card first.
  • Both login entry points call the POS /login API.
  • Successful login stores auth_token, serialized user, and login_date in DD-MM-YYYY, then enters the terminal Home route.
  • Safe-area and notch handling use SafeAreaProvider, SafeAreaView, and useSafeAreaInsets.
  • The top-left version label comes from apps/pos-terminal/src/native/appVersion.ts.
  • The top-right settings dropdown contains Light mode, Dark mode, and Branch settings.

Native dependency note​

The current shared native baseline is React Native 0.87.1 with the new architecture enabled. Both apps use react-native-screens 4.27.0, Gesture Handler 3.2.1, Reanimated 4.6.0, and Worklets 0.12.1.

Operational notes:

  • Kotlin/deprecated-API warnings from React Native Screens, Gesture Handler, and other native packages during Android builds are dependency warnings, not app-source errors.
  • Do not patch these warnings in node_modules; revisit the package version when a compatible stable release exists or when a verified runtime/navigation problem appears.
  • The root postinstall patch is intentionally limited to the react-native-date-picker AGP 9 ProGuard compatibility change.

POS Terminal branch and startup flow​

POS terminal startup is controlled in apps/pos-terminal/App.tsx.

Startup behavior:

  • On app launch, POS terminal first checks auth_token, user, and login_date.
  • If all login session values exist and login_date is the current day, the app opens Home.
  • If the login session is missing or stale, POS terminal clears only the login session keys: auth_token, user, and login_date.
  • After login-session cleanup, POS terminal checks AsyncStorage keys branch and tax.
  • If either branch setup value is missing, the app opens BranchScreen immediately as a required setup step.
  • In required setup mode, BranchScreen does not show a close button; it shows a settings gear instead so operators can switch dark/light mode before setup is complete.
  • If both branch and tax exist, the app opens UserSelectionScreen.
  • From UserSelectionScreen, operators can reopen branch setup through the top-right settings menu item ตั้งค่าสาขา.
  • When branch setup is opened from settings, BranchScreen shows a close button because the terminal is already configured.

Branch setup behavior:

  • BranchScreen loads branches from GET {MST_HOST}/api/branches through the terminal MST client.
  • The terminal MST client sends x-api-key: X_API_KEY_TERMINAL.
  • The branch API currently returns branch fields such as branchId, whGrpCode, and whGrpName; the screen normalizes both whGrp* and the older whsGrp* field names.
  • After selecting a branch, the app loads warehouses with:
    • GET {MST_HOST}/api/warehouses?pageLimit=40&page=1
    • GET {MST_HOST}/api/warehouses/{branchCode}/sub
  • After selecting a warehouse, the app fetches tax data before completing setup.
  • Setup only completes after both branch and tax are saved.

Stored local data:

// AsyncStorage key: branch
{
BranchId: string | number;
BranchCode: string;
whsGrpName: string;
whsGrpCode: string;
isMain: boolean;
}

// AsyncStorage key: tax
JSON.stringify(taxData ?? []);

// AsyncStorage key: user
JSON.stringify(authenticatedUser);

// AsyncStorage key: auth_token
token;

// AsyncStorage key: login_date
DD - MM - YYYY;

API config note:

  • Shared API constants now live in @bsr/api.
  • apps/mobile/src/constants/config.ts re-exports from @bsr/api so mobile and POS terminal use the same config source.
  • POS terminal uses X_API_KEY_TERMINAL for MST requests and X_API_KEY_TERMINAL_MAIN for the sale-person POS request.
  • Product Hub endpoint settings are also centralized in @bsr/api: use PRODUCT_HUB_SERVER, PRODUCT_HUB_COMPANY_CODE, PRODUCT_HUB_PRODUCT_TREE_PATH, and PRODUCT_HUB_PRODUCTS_PATH rather than adding endpoint literals in a terminal screen or service.
  • Product Hub is configured for UAT. PRD_PRODUCT_HUB_SERVER intentionally remains empty until the confirmed production domain is supplied; production must not fall back to UAT. Product Hub requests require the configured Basic Authentication and bsr-access-token header, whose values must not be copied into documentation or UI code.

Current Product Hub endpoints:

  • GET /pdhub/products/tree accepts company and levels (for example, itemType,itemGroup,grade,sizeRange).
  • GET /pdhub/products accepts company, product filters such as itemType and itemGroup, plus page and pageSize.

POS Terminal Home split-pane workspace​

The POS terminal Home screen is a large-screen workspace rather than the mobile stack sequence.

Panel responsibilities:

  • panel 1 is the permission-gated menu
  • panel 2 is the selected menu list
  • panel 3 is the selected detail or draft-document form

Visibility rules:

  • if no menu is selected, panel 2 and panel 3 are hidden
  • if a menu is selected but no list item or draft document is selected, panel 3 is hidden
  • selecting a menu clears the previous list/detail state unless an unsaved-change guard blocks navigation
  • selecting a list item opens panel 3

Responsive behavior:

  • landscape and wide screens use the three-pane layout
  • narrower landscape screens can collapse the menu pane into an icon-only rail
  • compact layouts keep the same information hierarchy but wrap panes as needed
  • terminal work should prefer maintaining the split-pane model instead of reintroducing mobile stack navigation

Panel 2 header convention:

  • the list header row is the shared header direction for future POS terminal menu pages
  • the small fixed create button sits in the same row as the list title
  • the title is vertically centered with the create button
  • the manually tuned listHeaderRow spacing, including marginBottom, should be preserved when extending other menu lists

Shared behavior in Home:

  • panel 2 list loading uses the shared LoadingProvider overlay instead of an inline loading card
  • destructive or unsaved transitions use the shared alert provider
  • the unsaved-change alert supports cancel, discard, and save-and-exit actions
  • panel 3 adjusts around the keyboard and hides the top settings area while typing
  • panel 3 should not own a generic outer ScrollView; each detail surface should own its own scroll/list container so FlashList-heavy details do not become nested-scroll surfaces

Mobile and terminal parity notes​

Mobile is the behavior source of truth for shared POS workflows. When a shared workflow differs, first check whether POS terminal is only adapting the same behavior to the split-pane workspace. If not, align terminal back to the mobile service boundary and flow.

Service boundary rules:

  • invoice creation belongs in apps/*/src/services/invoices.ts. Do not add or keep duplicate createInvoice helpers in order services.
  • delivery status updates belong in apps/*/src/services/deliveries.ts. Transport screens may call that delivery service, but transport services should not duplicate updateDeliveryStatus.
  • transfer-in list loading should call getTransferIns() by default, matching mobile. If terminal later needs warehouse tabs such as NF-{whsGrpCode}, expose that as visible UI state instead of silently merging warehouses in a terminal-only helper.
  • shared warehouse exclusion rules should use shouldExcludeWarehouseCode, filterWarehousesByCode, and sortWarehousesByCode consistently in both apps.
  • product list helpers are the main intentional service difference today: mobile product screens own subscription/list state directly, while POS terminal needs one-shot list helpers for split-pane panel 2. Terminal-only getProductInventoryList and getBookingProductList are UI adapters over the same Firestore inventory data, not separate product business rules.

Known intentional terminal-only helpers:

  • apps/pos-terminal/src/services/common.ts includes branch and sale-person helpers used by terminal branch/user selection, such as getBranches, getBranchSettingsByUserCode, and getSalePersonsByWhsGrpCode.
  • apps/pos-terminal/src/services/products.ts includes split-pane list helpers for product and booking product selection.
  • apps/pos-terminal/src/storage/session.ts includes clearSessionStoragePreservingBranch for terminal session behavior.

When adding or changing a mobile workflow, check the matching terminal entry points:

  • mobile order detail: apps/mobile/src/screens/orderStack/orderDetailScreen/index.tsx; terminal order detail: apps/pos-terminal/src/screens/HomeScreen/OrderDetailForm.tsx
  • mobile transport detail: apps/mobile/src/screens/transportStack/transportDetailScreen/index.tsx; terminal transport detail: apps/pos-terminal/src/screens/HomeScreen/TransportDetailForm.tsx
  • mobile shipping detail: apps/mobile/src/screens/shippingStack/shippingDetailScreen/index.tsx; terminal shipping detail: apps/pos-terminal/src/screens/HomeScreen/ShippingRouteDetailForm.tsx
  • mobile transfer-in list/detail: apps/mobile/src/screens/transferInStack; terminal transfer-in list/detail: apps/pos-terminal/src/services/splitMenuData.ts and apps/pos-terminal/src/screens/HomeScreen/TransferInDetailForm.tsx
  • mobile product list/detail: apps/mobile/src/screens/productStack; terminal product list/detail: apps/pos-terminal/src/services/splitMenuData.ts and apps/pos-terminal/src/screens/HomeScreen/ProductDetailForm.tsx

POS Terminal booking price parity notes​

The booking price flow in POS terminal should stay close to mobile behavior while using the split-pane surface.

Important parity rules:

  • booking-price list cards use background color to express status, not a visible status badge
  • booking-price cards show booking number, customer name, creator, and amount like mobile
  • booking-price creation starts by selecting a customer
  • customer selection happens in a modal with list, filters, route selection, detail view, actions, swipe support, and pagination-oriented loading behavior
  • panel 3 uses รายละเอียดใบจองราคา as the detail header text with no space between รายละเอียด and ใบจองราคา
  • the booking detail title uses the medium font family
  • booking-price metadata, remark, and product-list title are rendered as the FlashList header for the product item list instead of being wrapped by an outer panel ScrollView
  • booking item rows follow the mobile order-item style:
    • press row to edit
    • swipe row to delete
    • show step-price and weight icons
    • show quantity, amount, and weight-based formula text
  • booking item add/edit runs inside the product picker modal host on iOS instead of opening a nested native modal
  • product picker is a full-screen modal host:
    • left panel contains product search, filters, and the product list
    • add/edit item form is an overlay inside the same modal
    • add mode uses the product name as the overlay title and does not show a price field
    • edit mode keeps the mobile-style edit header and price field
    • selecting an existing item opens edit mode
    • selecting a new product opens add mode
    • quick quantities are 1, 5, 10, 50, and 100
    • step price must be fetched from the real MST API through the terminal MST client
    • step-price row labels must follow mobile wording with unit text, for example ไม่เกิน X {UOM} and เกิน X ไม่เกิน Y {UOM}
    • active step-price rows use accent border and translucent accent background like mobile
  • editing a booking item refreshes inventory from Firestore by productCode before showing stock and flags
  • available stock in the edit overlay comes from refreshed inventory onHand - booking
  • editing a step-price item loads step price from the MST API; currentStepPrice is kept as saved JSON/fallback data, not as the primary price source
  • existing booking prices are editable only on the document created day, matching mobile. POS terminal resolves the current business date from GET /api/healthz and falls back to the device date if the server date is unavailable.
  • when an existing booking price is outside its editable created day, panel 3 must disable adding items, editing items, deleting items, editing remark/header fields, and saving.

API notes:

  • booking price create/update is handled by the terminal booking-price service.
  • branchId must be sent as a number for POST /api/booking-price.
  • adding an item follows the mobile persistence order: ensure/create booking header, post item, update totals, reload detail.
  • editing an existing item immediately patches PATCH /api/booking-price/{bookingPriceId}/items/{itemId}, updates totals, and reloads detail.
  • booking item payloads must send weightBaseFlag and stepPriceFlag as booleans.
  • booking item payloads must preserve product company fallback rules: companyCode falls back to company, and companyName falls back through companyName, ComName, company, and comName.
  • POS terminal uses x-api-key for MST APIs; do not add auth token handling to the terminal MST client.

Native app version and app-update boundaries​

Both apps read native app version/build data through their own AppVersionModule, but JavaScript access is normalized under src/native/appVersion.ts.

Current structure:

  • apps/mobile/src/native/appVersion.ts wraps NativeModules.AppVersionModule for mobile.
  • apps/pos-terminal/src/native/appVersion.ts wraps NativeModules.AppVersionModule for POS terminal.
  • apps/mobile/src/constants/appVersion.ts and apps/pos-terminal/src/constants/appVersion.ts remain as compatibility re-exports only.
  • apps/mobile/src/services/appUpdate.ts and apps/pos-terminal/src/services/appUpdate.ts own required-update checks against Firestore version/vapp.
  • Both implementations compare native versionCode with the remote versionNumber after numeric conversion and support platform-specific Android/iOS fields.

Mobile UI usage:

  • mobile Login shows the version label and required-update modal
  • mobile Home header shows the version label and required-update modal

POS terminal UI usage:

  • POS terminal UserSelectionScreen shows the version label
  • POS terminal UserSelectionScreen and HomeScreen show the same blocking required-update modal when Firestore requires a newer app version
  • POS terminal Home reruns the check when the screen regains focus

Operational notes:

  • Android must register AppVersionPackage in MainApplication.
  • iOS must keep the Swift/Objective-C bridge in the Xcode project.
  • Fresh iOS setup should run pod install, and apps/pos-terminal/ios/Podfile.lock should be kept.
  • react-native-view-shot 5.1.1 includes the new-architecture fix, so the former iOS RCTScrollView postinstall patch is no longer required.