สถาปัตยกรรม Mobile และ Terminal
The repository currently supports two React Native apps with overlapping business concerns but different device assumptions.
บทบาท
apps/mobileis the current POS app for handheld operator workflows.apps/pos-terminalis 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:apkandyarn 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
BsrCustomerDisplaydetects 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-uishould be checked against both@bsr/mobileand@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
thanden - the default language is
th i18nResourcesis 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.tsinitializes i18n from@bsr/i18napps/pos-terminal/src/i18n/index.tsinitializes 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.tsremains as a compatibility re-export, but new code should import from@bsr/assets/*directly.apps/mobileandapps/pos-terminalboth declare@bsr/assetsas 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.logoBSRpoints topackages/assets/images/logos/logo.png.logos.logoBSRWhiteTextpoints topackages/assets/images/logos/logo-white-text.png.- Mobile login and branch screens use
logoBSRWhiteTextin dark mode andlogoBSRin light mode. - Mobile QR share capture intentionally keeps using
logoBSRso shared QR images stay unchanged. - POS terminal
UserSelectionScreenuses 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_URIlives in@bsr/printingbecause it is a print-template asset used by receipt output in both apps- mobile keeps compatibility wrapper files under
apps/mobile/src/printing/templateswhere existing imports need to remain stable - POS terminal print helpers call shared templates directly from
@bsr/printing - Metro config in both apps maps
@bsr/printingtopackages/printing/srcso bundling does not depend only on the workspace symlink innode_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
logoStickerUrioverride - POS terminal invoice receipt printing builds QR claim/payment assets and reads
branch,tax, anduserfrom local storage before rendering the shared template - changes to templates in
@bsr/printingaffect both apps, so run bothyarn workspace @bsr/mobile tsc --noEmitandyarn 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.tsxowns 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.tsowns pure helpers for booking/customer draft creation, booking-price payload conversion, recalculation, id resolution, list search, and shared action definitionsListPane.tsxowns panel 2 rendering, including list header controls, FlashList rendering, booking-price cards, invoice cards, invoice tabs, and swipe actionsDetailPane.tsxowns panel 3 rendering for booking price, invoice detail, and generic detail viewsInvoicePanels.tsxowns invoice-specific list/detail/card/modal UIDepositPanels.tsxowns deposit-specific panel-2 UI เช่น retry filter, list card body, และ swipe actionsDepositDetailForm.tsxowns reusable deposit create/detail form ที่ใช้ทั้งเมนูฝากเงินและ flow ฝากเงินแบบ embedded ในขนส่งBookingPriceDetailForm.tsxowns the booking-price detail formBookingProductPicker.tsxowns booking-price product picker and add/edit item overlay behaviorShippingRouteDetailForm.tsxowns the POS terminal shipping-route detail/create flow for theใบจัดสินค้าสายส่งmenustyles.tsremains the aggregate Home style export และ menu-owned styles สามารถแยกเป็นไฟล์เฉพาะ เช่นdepositStyles.ts
Refactor direction:
- keep API calls and stateful screen orchestration close to
index.tsxuntil 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/noto create the delivery header - use the returned
deliveryIdto callPOST /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
ListHeaderComponentcontent 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.tsxinto 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
- step 1 แสดงรายการใบจัดที่อยู่ใน transport โดยเอา transport meta เข้า
- 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 อย่างแน่นอน
- เมนู
ListPanereset 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
- deposit ที่
- panel 2 รองรับ swipe action ตาม Mobile:
- ส่งอีกครั้งสำหรับ deposit ที่
RefKeyว่าง โดยเช็ค owner/override ใน handler - ลบตาม department-code rule ของ Mobile deposit
- ส่งอีกครั้งสำหรับ 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 ที่ส่งมาเพื่อ setflagBankแล้วคืน control กลับ flow ขนส่ง
- transport เปิด
- พฤติกรรมบัญชีเงินสดร่วมกัน:
- Mobile และ POS Terminal dedupe cash-account options ด้วย
acctCodeโดยเก็บรายการแรกไว้ เมื่อ API ส่งบัญชีเดียวกันกลับมาซ้ำข้าม company
- Mobile และ POS Terminal dedupe cash-account options ด้วย
งานที่ต้องตามต่อ:
- ทดสอบ 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 สำหรับเมนูนี้
- bill draft ที่
- panel 2 รองรับ swipe action ตาม Mobile:
- ส่งอีกครั้งสำหรับ bill ที่
RefKeyว่างและไม่ใช่ draft branch row - ลบผ่าน
DELETE /bills/:billId
- ส่งอีกครั้งสำหรับ bill ที่
- 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 ที่ส่งมาเพื่อ setflagGasแล้วคืน control กลับ flow ขนส่ง
- transport เปิด
งานที่ต้องตามต่อ:
- ทดสอบ 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/logosand switches between the normal logo and white-text logo by theme mode. - User cards show the full
imagewhen available and fall back tofirstnameplususerCodewhen 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 useswhsGrpCodeto 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
/loginAPI. - Successful login stores
auth_token, serializeduser, andlogin_dateinDD-MM-YYYY, then enters the terminal Home route. - Safe-area and notch handling use
SafeAreaProvider,SafeAreaView, anduseSafeAreaInsets. - The top-left version label comes from
apps/pos-terminal/src/native/appVersion.ts. - The top-right settings dropdown contains
Light mode,Dark mode, andBranch 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.0as the latest stable release. 4.25.0-beta.1exists under thenextdist-tag, and nightlies exist, but they are not stable releases.react-native-screens@4.24.0release notes say it is the last release tested with legacy architecture; both mobile and POS terminal currently havenewArchEnabled=true.- Kotlin warnings from
node_modules/react-native-screens/android/...during Android builds are dependency warnings, not app-source errors. Do not patchnode_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, andlogin_date. - If all login session values exist and
login_dateis 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, andlogin_date. - After login-session cleanup, POS terminal checks AsyncStorage keys
branchandtax. - If either branch setup value is missing, the app opens
BranchScreenimmediately as a required setup step. - In required setup mode,
BranchScreendoes not show a close button; it shows a settings gear instead so operators can switch dark/light mode before setup is complete. - If both
branchandtaxexist, the app opensUserSelectionScreen. - From
UserSelectionScreen, operators can reopen branch setup through the top-right settings menu itemตั้งค่าสาขา. - When branch setup is opened from settings,
BranchScreenshows a close button because the terminal is already configured.
Branch setup behavior:
BranchScreenloads branches fromGET {MST_HOST}/api/branchesthrough 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, andwhGrpName; the screen normalizes bothwhGrp*and the olderwhsGrp*field names. - After selecting a branch, the app loads warehouses with:
GET {MST_HOST}/api/warehouses?pageLimit=40&page=1GET {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.tsre-exports from@bsr/apiso mobile and POS terminal use the same config source.- POS terminal uses
X_API_KEY_TERMINALfor MST requests andX_API_KEY_TERMINAL_MAINfor 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
listHeaderRowspacing, includingmarginBottom, should be preserved when extending other menu lists
Shared behavior in Home:
- panel 2 list loading uses the shared
LoadingProvideroverlay 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 duplicatecreateInvoicehelpers 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 duplicateupdateDeliveryStatus. - transfer-in list loading should call
getTransferIns()by default, matching mobile. If terminal later needs warehouse tabs such asNF-{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, andsortWarehousesByCodeconsistently 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
getProductInventoryListandgetBookingProductListare UI adapters over the same Firestore inventory data, not separate product business rules.
Known intentional terminal-only helpers:
apps/pos-terminal/src/services/common.tsincludes branch and sale-person helpers used by terminal branch/user selection, such asgetBranches,getBranchSettingsByUserCode, andgetSalePersonsByWhsGrpCode.apps/pos-terminal/src/services/products.tsincludes split-pane list helpers for product and booking product selection.apps/pos-terminal/src/storage/session.tsincludesclearSessionStoragePreservingBranchfor 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.tsandapps/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.tsandapps/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, and100 - 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
productCodebefore 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;
currentStepPriceis 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/healthzand 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.
branchIdmust be sent as a number forPOST /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
weightBaseFlagandstepPriceFlagas booleans. - booking item payloads must preserve product company fallback rules:
companyCodefalls back tocompany, andcompanyNamefalls back throughcompanyName,ComName,company, andcomName. - POS terminal uses
x-api-keyfor 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.tswrapsNativeModules.AppVersionModulefor mobile.apps/pos-terminal/src/native/appVersion.tswrapsNativeModules.AppVersionModulefor POS terminal.apps/mobile/src/constants/appVersion.tsandapps/pos-terminal/src/constants/appVersion.tsremain as compatibility re-exports only.apps/mobile/src/services/appUpdate.tsและapps/pos-terminal/src/services/appUpdate.tsเป็นเจ้าของ required-update check กับ Firestoreversion/vapp.- ทั้งสองแอปเปรียบเทียบ native
versionCodeกับ remoteversionNumberหลังแปลงเป็นตัวเลข และรองรับ 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
UserSelectionScreenshows the version label - POS terminal
UserSelectionScreenและHomeScreenแสดง blocking required-update modal แบบเดียวกับ mobile เมื่อ Firestore บังคับเวอร์ชั่นใหม่ - POS terminal Home เช็คซ้ำเมื่อ screen กลับมา focus
Operational notes:
- Android must register
AppVersionPackageinMainApplication. - iOS must keep the Swift/Objective-C bridge in the Xcode project.
- Fresh iOS setup should run
pod install, andapps/pos-terminal/ios/Podfile.lockshould be kept. - The existing iOS
react-native-view-shotbuild error involvingRCTScrollViewis handled by the existing postinstall patch and is not caused byAppVersionModule.