ข้ามไปยังเนื้อหาหลัก

สถาปัตยกรรม Mobile และ Terminal

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

บทบาท

  • 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.

หน้าที่ของแต่ละแอป

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

ความสัมพันธ์กับ shared packages

หมายเหตุ 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

@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.

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

@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.

ขอบเขตการพิมพ์ร่วม

@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

QR Payment feature flag

packages/api/src/config.ts กำหนด ENABLE_QR_PAYMENT เป็น 0 หรือ 1 เพื่อควบคุมการสร้าง QR Payment โดยไม่กระทบ QR Claim:

Environment / flagพฤติกรรม
BASE_API_ENV = 'prd'สร้าง QR Payment ได้เสมอ โดยไม่สนค่า flag
BASE_API_ENV = 'uat', ENABLE_QR_PAYMENT = 1สร้าง QR Payment ตามปกติ
BASE_API_ENV = 'uat', ENABLE_QR_PAYMENT = 0ไม่เรียก QR API; action สร้าง QR เองจะแจ้งว่า QR Payment ใน UAT ยังไม่เปิดใช้

เมื่อปิด flag การพิมพ์ใบแจ้งหนี้และใบค่าขนส่งของทั้ง Mobile/POS Terminal ยังพิมพ์ต่อได้ แต่เว้น QR Payment เป็นภาพว่าง ส่วน QR Claim ในใบแจ้งหนี้ยังสร้างตามปกติ

โครงสร้างโค้ด split-pane ของ POS Terminal Home

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 และ settings entry; settings dropdown ตั้งใจ render นอก ScrollView ของเมนู เพื่อให้ overlay ข้าม pane ข้างๆ ได้โดยไม่ถูกตัด
  • 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 เช่น retry filter, list card body, และ swipe actions
  • DepositDetailForm.tsx owns reusable deposit create/detail form ที่ใช้ทั้งเมนูฝากเงินและ flow ฝากเงินแบบ embedded ในขนส่ง
  • 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 และ menu-owned styles สามารถแยกเป็นไฟล์เฉพาะ เช่น 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

หมายเหตุ parity ของใบจัดส่งสินค้าสายส่งบน POS Terminal

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 (รับงาน, จัดสินค้าที่ลาน, ขึ้นรถแล้ว, ร่วมจัด) อยู่ใน dropdown ปุ่มสามจุดที่ header ของ panel 3 ส่วนปุ่มบันทึกตำแหน่งสินค้าอยู่ใน FlashList footer ของ product tab เพื่อให้ scroll ไปกับเนื้อหา
  • POS Terminal เปิด Sweet Alert รับงานอัตโนมัติเมื่อเข้าเงื่อนไขรับงานเดียวกับ Mobile: delivery finished, ถูกจัดขึ้นขนส่งแล้ว, ยังไม่มี acceptedBy, และ user มีสิทธิรับงาน
  • 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

หมายเหตุ parity ของขนส่งบน POS Terminal

เมนู ขนส่ง ใน POS Terminal ตั้งใจให้ mirror Mobile transportListScreen และ transportDetailScreen โดยปรับให้เข้ากับ Home workspace แบบสาม panel

พฤติกรรมที่ทำแล้ว:

  • panel 2 แสดง transport list ด้วยสีการ์ด, ข้อมูล, search, FlashList pagination, pull-to-refresh, swipe delete, และ create/delete permission ตาม Mobile
  • สีการ์ด panel 2 อิงสถานะ transport ตาม Mobile:
    • transport ที่ finished ใช้สี confirmed
    • transport ที่ started ใช้สี new/in-progress
    • transport draft ใช้สี draft
  • panel 3 อยู่ใน TransportDetailForm.tsx และ helper logic ใน TransportDetailHelpers.ts
  • panel 3 ใช้ FlashList-backed 2 step และไม่ใช้ outer ScrollView ซ้อน:
    • step 1 แสดงรายการใบจัดที่อยู่ใน transport โดยเอา transport meta เข้า ListHeaderComponent
    • step 2 แสดงรายการส่งสินค้าแยกตามลูกค้า โดยเอา collection/payment meta เข้า ListHeaderComponent
  • Home split-pane ใช้แนวทาง patch panel 2 จาก panel 3 สำหรับเมนูที่มี detail reload:
    • เมนู ขนส่ง patch transport list item จาก getTransportDetail ล่าสุด หลัง update/start/finish/deposit/expense-bill handoff
    • เมนู ใบจัดสินค้าสายส่ง patch shipping-route list item จาก detail ล่าสุดหลัง onRefresh
    • create เอกสารใหม่ยัง refresh list ใหม่ เพราะยังไม่มี item เดิมใน panel 2 ให้ patch อย่างแน่นอน
  • ListPane reset scroll ไปบนสุดเมื่อโหลดข้อมูลชุดใหม่โดย remount FlashList ด้วย reset key จากเมนู/tab/filter/item แรก แต่ไม่ reset ตอน load-more
  • action ด้านล่างของ panel 3 เช่น save, start, finish, และ finalize-all ถูก render เป็น FlashList footer content แทน absolute bottom bar เพื่อให้ scroll ไปกับ step ปัจจุบัน
  • transport create/detail รองรับ:
    • create/update transport header
    • เลือกทะเบียนรถด้วยการ์ดแบบ Mobile ที่แสดงสถานะ, ประเภทรถ, และน้ำหนักบรรทุกสูงสุด; search เป็น remote search แต่ยังไม่ทำ pagination เพื่อให้ตรงกับ Mobile
    • เพิ่มใบจัดเข้า transport
    • ถ้ายังไม่มี transportId จะสร้าง transport header ก่อน แล้วค่อยเพิ่มใบจัด
    • modal เพิ่มใบจัดไม่ปิดหลัง add สำเร็จ, แสดง row-level loading, กันกดซ้ำ row เดิม, และลบ row ที่ add สำเร็จออกจาก modal list
    • ลบใบจัดออกจาก transport ก่อนเริ่ม transport
    • อนุมัติใบจัดทีละใบ
    • อนุมัติใบจัดทั้งหมดจาก bottom action
    • โหลด customer groups สำหรับ step รายการส่งสินค้า
    • จัดเรียงลำดับลูกค้าใน step 2 แบบ manual ผ่าน sort planner
    • จัดเรียงลำดับลูกค้าด้วย Google Routes API โดยใช้ GPS ปัจจุบันและพิกัดลูกค้า แล้วบันทึกผ่าน sortTransportCustomers
    • เริ่มและจบ transport พร้อม mileage validation
    • customer actions สำหรับโทร, นำทาง, บันทึก GPS, เลือกประเภทรับชำระ, cash/transfer/other payment, photo/slip upload, signature capture, QR generation, และ submit payment
    • skip เติมน้ำมัน/ฝากเงิน โดยอัปเดต flagGas และ flagBank
    • print ใบแจ้งหนี้ทั้งหมด และ print product summary หลังใบจัดทุกใบถูกอนุมัติแล้ว
  • summary meta step 2 ใช้สูตรเดียวกับ Mobile:
    • ยอดรวม คือยอดเงินที่เก็บจริงจาก cash + transfer
    • ยอด เงินสด และ โอน/QR รวมช่อง cash/transfer จากลูกค้า OTHER ด้วย แต่จำนวนเจ้านับเฉพาะประเภทหลักนั้น
    • อื่นๆ แสดงจำนวนลูกค้า OTHER และยอด cash + transfer ของกลุ่ม OTHER
    • เคส OTHER ที่ system note หลัง // system : มีคำว่า ไม่รับสินค้า จะไม่รวมยอดเงินเข้า อื่นๆ
    • meta ไม่จ่าย คำนวณจากยอดต้องจ่ายของลูกค้า OTHER หัก cash + transfer ที่จ่ายมา
  • เพิ่ม service boundary ฝั่ง POS Terminal สำหรับ transport:
    • apps/pos-terminal/src/services/transport.ts
    • vehicle และ mileage helpers ใน apps/pos-terminal/src/services/common.ts
    • customer call/profile/GPS helpers ใน apps/pos-terminal/src/services/customers.ts
    • Google route optimization helper ใน apps/pos-terminal/src/services/googleRoutes.ts
    • generateTransportPaymentQrCode ใน apps/pos-terminal/src/services/qr.ts

งานที่ต้องตามต่อ:

  • ทดสอบ transport flow เต็มบน iPad/tablet จริง โดยเฉพาะ FlashList headers, modal overlays, camera/signature, GPS permission, swipe actions, และ dark mode
  • ทดสอบ API กับข้อมูลจริงสำหรับ create/update transport, add/remove delivery, finalize delivery, load customer groups, manual/Google route sorting, payment, GPS save, start/finish, และ print actions
  • ทดสอบ printer จริงสำหรับ transport invoice batch และ product summary

หมายเหตุ parity ของฝากเงินบน POS Terminal

เมนู ฝากเงิน ใน POS Terminal อิง Mobile depositListScreen และ depositDetailScreen โดยตั้งใจทำ detail form เป็น reusable component สำหรับทั้งเมนูฝากเงินปกติ และ flow ฝากเงินที่เปิดจากขนส่ง

พฤติกรรมที่ทำแล้ว:

  • panel 2 แสดง deposit list ด้วยสีการ์ด, ข้อมูล, search placeholder, FlashList pagination, pull-to-refresh, และ filter รอส่งใหม่ ตาม Mobile:
    • deposit ที่ DELETED ใช้สี canceled
    • deposit ที่ RefKey ว่างใช้สี error
    • deposit ปกติใช้สี confirmed
    • ซ่อน status filter กลางของ list pane สำหรับเมนูนี้ เหลือเฉพาะ filter ทั้งหมด / รอส่งใหม่ แบบ Mobile
  • panel 2 รองรับ swipe action ตาม Mobile:
    • ส่งอีกครั้งสำหรับ deposit ที่ RefKey ว่าง โดยเช็ค owner/override ใน handler
    • ลบตาม department-code rule ของ Mobile deposit
  • panel 3 ใช้ DepositDetailForm.tsx เป็น reusable create/detail form:
    • create mode โหลด branch/user context, companies, cash accounts, และ bank accounts
    • detail mode โหลดข้อมูล deposit ที่บันทึกแล้ว และแสดง form แบบ read-only
    • การเลือกบริษัทจะ reload cash/bank account options
    • เลือก invoice ได้เฉพาะ create mode ที่ไม่ใช่ transport และซ่อนสำหรับ deptCode 14 และ 30
    • modal เลือก invoice ใช้ FlashList พร้อม search และ load-more pagination โดยเพิ่ม behavior เดียวกันให้ Mobile depositDetailScreen แล้ว
    • เมื่อเลือก invoice แล้ว amount และ notes จะถูกคำนวณ/สร้างจากระบบและแก้เองไม่ได้
    • ส่งสลิปย้อนหลังใช้ได้เฉพาะ create mode ที่ไม่ใช่ transport และปรับ deposit date เป็นเมื่อวาน
    • save ตรวจ company, cash account, bank account, และ amount/notes หรือ selected invoices แล้ว confirm ก่อน createDeposit
    • หลังสร้าง deposit จากเมนูฝากเงินปกติ จะ refresh panel 2 และปิด panel 3 โดยไม่เลือก deposit ที่เพิ่งสร้าง เพื่อไม่ให้ยิง GET /deposits/:id โดยไม่จำเป็น
  • แนวทางกรณีกดฝากเงินจากขนส่ง:
    • transport เปิด DepositDetailForm ตัวเดียวกันเป็น state ย่อยใน panel 3 ของขนส่ง ไม่ใช่ nested modal บน iPad/iOS
    • transport ส่ง DepositTransportContext เช่น transportId, transportPayload, branch/warehouse/company/date/notes defaults และ amount จากยอดเงินสดที่ต้องนำฝาก
    • default company ของ flow จากขนส่งเป็น BFPDB
    • amount จาก transport ถูกแสดงใน form และ lock ไม่ให้แก้ไขเหมือน Mobile
    • หลัง createDeposit สำเร็จ form จะ update transport ด้วย payload ที่ส่งมาเพื่อ set flagBank แล้วคืน control กลับ flow ขนส่ง
  • พฤติกรรมบัญชีเงินสดร่วมกัน:
    • Mobile และ POS Terminal dedupe cash-account options ด้วย acctCode โดยเก็บรายการแรกไว้ เมื่อ API ส่งบัญชีเดียวกันกลับมาซ้ำข้าม company

งานที่ต้องตามต่อ:

  • ทดสอบ create/detail deposit บน iPad/tablet รวมถึง account selection modals, invoice selection, date picker, keyboard avoidance, และ transport handoff
  • ทดสอบ create deposit ปกติ, create จาก invoice หลายใบ, create จาก transport, retry, และ delete กับ API จริง

หมายเหตุ parity ของบิลน้ำมันบน POS Terminal

เมนู บิลน้ำมัน ใน POS Terminal อิง Mobile expenseBillListScreen และ expenseBillDetailScreen โดยทำ detail form เป็น reusable component สำหรับทั้งเมนูบิลน้ำมันปกติ และ flow เติมน้ำมันที่เปิดจากขนส่ง

พฤติกรรมที่ทำแล้ว:

  • panel 2 แสดง expense-bill list ด้วยสีการ์ด, ข้อมูล, search placeholder, FlashList pagination, pull-to-refresh, และ create permission ตาม Mobile:
    • bill draft ที่ branchId เป็น 0 ใช้สี draft
    • bill ที่ RefKey ว่างใช้สี error และแสดง retry
    • bill ปกติใช้สี confirmed
    • ซ่อน status filter กลางของ list pane สำหรับเมนูนี้
  • panel 2 รองรับ swipe action ตาม Mobile:
    • ส่งอีกครั้งสำหรับ bill ที่ RefKey ว่างและไม่ใช่ draft branch row
    • ลบผ่าน DELETE /bills/:billId
  • panel 3 ใช้ ExpenseBillDetailForm.tsx เป็น reusable create/detail form:
    • create mode โหลด branch/user context, companies, gas partners, vehicles, และ mileage
    • detail mode โหลดข้อมูล bill ที่บันทึกแล้วจาก GET /bills/:billId
    • draft branch row update ผ่าน PATCH /bills/:billId โดย user บัญชี
    • เลือกบริษัทแล้วสามารถ reload mileage ของรถที่เลือกไว้
    • เลือกรถแล้วโหลด old mileage และ balance range
    • ส่งสลิปย้อนหลังใช้ได้เฉพาะ create mode ที่ไม่ใช่ transport และปรับ bill date เป็นเมื่อวาน
    • save ตรวจ company, vehicle, dealer, amount, และ notes ก่อน create/update
  • แนวทางกรณีกดเติมน้ำมันจากขนส่ง:
    • transport เปิด ExpenseBillDetailForm เป็น state ย่อยใน panel 3 ของขนส่ง ไม่ใช่ nested modal บน iPad/iOS
    • transport ส่ง ExpenseBillTransportContext เช่น transportId, transportPayload, branch/warehouse/company/vehicle/date/notes defaults
    • default company ของ flow จากขนส่งเป็น BFPDB
    • payload บิลน้ำมัน normalize transportId เป็น number/null ก่อนส่ง API
    • หลัง createExpenseBill สำเร็จ form จะ update transport ด้วย payload ที่ส่งมาเพื่อ set flagGas แล้วคืน control กลับ flow ขนส่ง

งานที่ต้องตามต่อ:

  • ทดสอบ expense-bill list/detail บน iPad/tablet รวมถึง selection modals, date picker, keyboard avoidance, mileage loading, retry/delete, และ transport handoff กับ API จริง

การเลือกผู้ใช้บน POS Terminal

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, logs the stored value, and uses whsGrpCode to load users.
  • 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

The current stable react-native-screens version in the workspace is 4.24.0.

Operational notes:

  • As of 2026-05-03, npm marks 4.24.0 as the latest stable release.
  • 4.25.0-beta.1 exists under the next dist-tag, and nightlies exist, but they are not stable releases.
  • react-native-screens@4.24.0 release notes say it is the last release tested with legacy architecture; both mobile and POS terminal currently have newArchEnabled=true.
  • Kotlin warnings from node_modules/react-native-screens/android/... during Android builds are dependency warnings, not app-source errors. Do not patch node_modules; wait for a stable upgrade unless a runtime/navigation bug appears.

Flow เลือกสาขาและเริ่มต้นบน POS Terminal

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.
  • การตั้งค่าจะถือว่าเสร็จเมื่อบันทึกทั้ง branch และ tax แล้ว

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.

Workspace แบบ split-pane ของ POS Terminal Home

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

หมายเหตุ parity ระหว่าง Mobile และ Terminal

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

หมายเหตุ parity ของใบจองราคาบน POS Terminal

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 และการบังคับอัปเดต

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 และ apps/pos-terminal/src/services/appUpdate.ts เป็นเจ้าของ required-update check กับ Firestore version/vapp.
  • ทั้งสองแอปเปรียบเทียบ native versionCode กับ remote versionNumber หลังแปลงเป็นตัวเลข และรองรับ field แยก Android/iOS.

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 และ HomeScreen แสดง blocking required-update modal แบบเดียวกับ mobile เมื่อ Firestore บังคับเวอร์ชั่นใหม่
  • POS terminal Home เช็คซ้ำเมื่อ screen กลับมา 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.
  • The existing iOS react-native-view-shot build error involving RCTScrollView is handled by the existing postinstall patch and is not caused by AppVersionModule.