Mobile Transport และ Vehicles
หน้านี้บันทึก flow ของหน้า transport detail และการจัดการสถานะยานพาหนะบน mobile
ขอบเขต
อิงจาก:
apps/mobile/src/screens/transportStack/transportDetailScreen/index.tsxapps/mobile/src/screens/vehicleStack/vehicleListScreen/index.tsxapps/mobile/src/services/common.tsapps/mobile/src/services/googleRoutes.tsapps/mobile/src/navigation/RootNavigator.tsxapps/mobile/src/screens/homeScreen/index.tsx
เมนูยานพาหนะ
หน้า Home มีเมนู ยานพาหนะ ในกลุ่ม ข้อมูล
- ใช้ icon
menus.menu11 - วางถัดจาก
ผู้ใช้ทั้งหมด - ใช้ permission เดียวกับ
ผู้ใช้ทั้งหมดคือMENU_EMPLOYEE.has(deptCode) - navigate ไป
VehicleList - ไม่มี create shortcut
หน้า Vehicle list
VehicleList ถูก register ใน RootNavigator และ render หน้า vehicleStack/vehicleListScreen
หน้าจอนี้:
- โหลดข้อมูลรถด้วย
getVehicles({ search })จากservices/common - ค้นหาได้ด้วย header/search pattern แบบเดียวกับ list screen เช่น
invoiceListScreen - refresh list หลังบันทึกสถานะรถ
- ใช้
licensePlateเป็น identity ของรายการเมื่อมีค่า
ข้อมูลในการ์ดรถแสดง field หลักชุดเดียวกับ vehicle picker ใน transportDetailScreen:
- ทะเบียนรถพร้อม label สถานะรถ
- ประเภทรถ
- น้ำหนักบรรทุกสูงสุด หน่วยกิโลกรัม
สีพื้นหลังการ์ดแยกตามสถานะ:
vehicleStatus | สีการ์ด |
|---|---|
UNCHECKED | แดงอ่อน |
NOT_READY | เหลืองอ่อน |
READY | เขียวอ่อน |
Modal อัปเดตสถานะรถ
เมื่อกดการ์ดรถ จะเปิด modal สำหรับแก้สถานะ
ใน modal มี:
- radio 3 ตัวเลือก:
UNCHECKED:ยังไม่ตรวจสอบREADY:พร้อมใช้งานNOT_READY:ยังไม่พร้อมใช้งาน
vehicleStatusNoteเป็น textarea หลายบรรทัด- action
ยกเลิกและบันทึก
สี active ของ radio รองรับ dark mode เพื่อให้ข้อความที่เลือกอ่านได้ชัดเจน
เมื่อบันทึก จะเรียก:
PATCH api/vehicles/{licensePlate}/vehicle-status
payload:
{
"vehicleStatus": "READY",
"vehicleStatusNote": "ตรวจสอบแล้ว พร้อมใช้งาน"
}
API update ใช้ domain/base URL เดียวกับ getVehicles ผ่าน mstApi
การอนุมัติใบจัดใน Transport detail
transportDetailScreen สามารถอนุมัติใบจัดโดยอัปเดตสถานะใบจัดเป็น:
{
"deliveryStatus": "FINISHED"
}
สิทธิ์การอนุมัติถูกควบคุมด้วย canFinalizeTransportDeliveries
เงื่อนไขจะเป็น true เมื่อครบทุกข้อ:
- หน้าจอไม่ใช่ create mode
- user ปัจจุบันแก้ไข transport นี้ได้
- transport ยังไม่เริ่มงาน (
detail.startedAtว่าง) canFinalizeShippingSheet(user)อนุญาต department ของ user
canFinalizeShippingSheet(user) อนุญาต dept code:
-2, 1, 2, 4, 7, 8, 9, 13, 16, 29, 30
POS Terminal ใช้ rule pending delivery เดียวกับ Mobile สำหรับการแสดงปุ่มอนุมัติใน transport detail
ปุ่มอนุมัติใบจัดทั้งหมดด้านล่าง
bottom bar ของหน้า transport detail รองรับ action อนุมัติใบจัดทั้งหมดควบคู่กับปุ่มบันทึกเดิม
เมื่อแสดงผล:
- ปุ่มซ้าย:
อนุมัติใบจัดทั้งหมด - ปุ่มขวา: action เดิม เช่น
บันทึก
ปุ่ม อนุมัติใบจัดทั้งหมด จะแสดงเฉพาะเมื่อ:
- อยู่ที่ step รายการใบจัด
canFinalizeTransportDeliveriesเป็น true- มีรายการใบจัดอย่างน้อย 1 ใบที่
deliveryStatusเป็นnull,undefined, ค่าว่าง, whitespace, หรือ stringnull
เมื่อกด อนุมัติใบจัดทั้งหมด ระบบจะแสดง confirmation alert ก่อนยิง API ทุกครั้ง หลังผู้ใช้กดยืนยัน ระบบจะ loop รายการใบจัดที่ยัง pending ทั้งหมด, finalize ทีละใบ, แล้ว refresh transport detail ปัจจุบันด้วย transportId
เงื่อนไขก่อนเริ่มการขนส่ง
ปุ่ม เริ่มการขนส่ง ใน transportDetailScreen จะเริ่มงานได้เฉพาะเมื่อใบจัดทุกใบในรายการมีสถานะ deliveryStatus แล้ว
ระบบถือว่าใบจัดยังไม่พร้อมเริ่มขนส่งเมื่อ:
deliveryStatusเป็นnulldeliveryStatusเป็นundefineddeliveryStatusเป็นค่าว่างหลังtrim()deliveryStatusเป็น stringnull
ถ้ายังมีใบจัดที่ไม่มีสถานะ ระบบจะหยุด flow ก่อนเปิด modal เริ่มขนส่ง และมี guard ซ้ำก่อน submit เริ่มขนส่งจริง เพื่อกันกรณีข้อมูลเปลี่ยนระหว่าง modal เปิดอยู่
การจัดเรียงลูกค้าตามพิกัดด้วย Google
sort planner ในหน้า transport detail รองรับ action จัดเรียงเส้นทางด้วย Google
footer ของ modal จัดเรียงแบ่งเป็น 2 แถว:
- แถวแรก:
เคลียร์รายชื่อจัดเรียง, toggle ชื่อ (แสดงชื่อเดิม/แสดงชื่อย่อ), ปุ่มปิด, และปุ่มบันทึก - แถวที่สอง: ปุ่มเต็มบรรทัด
จัดเรียงลูกค้าตามพิกัดด้วย Google
เมื่อกด จัดเรียงลูกค้าตามพิกัดด้วย Google ระบบจะแสดง confirmation alert ก่อน หลังผู้ใช้กดยืนยัน:
- หน้าจออ่าน GPS ปัจจุบันของเครื่องด้วย
Geolocation.getCurrentPosition - ใช้ตำแหน่งปัจจุบันของเครื่องเป็น route
origin - ลูกค้าที่มี
U_BFP_LatitudeและU_BFP_Longitudeที่ถูกต้องจะถูกใช้เป็นจุดในเส้นทาง - ลูกค้าที่ไกลจากตำแหน่งปัจจุบันที่สุดจะถูกใช้เป็น Google Routes
destination - ลูกค้าที่เหลือและมี GPS จะถูกส่งเป็น
intermediates services/googleRoutes.optimizeGoogleRoute(...)เรียก Google RoutescomputeRoutesพร้อมoptimizeWaypointOrder: true- หน้าจออ่าน
routes.optimizedIntermediateWaypointIndexจาก response แล้วนำมาเรียงรายการใน sort planner ใหม่ - ลูกค้าที่ไม่มี GPS ที่ถูกต้องจะยังอยู่ใน payload และถูกต่อท้ายหลังลูกค้าที่ Google จัดเรียงแล้ว โดยคงลำดับเดิม
หลังเรียงเสร็จ ระบบจะแสดง alert เรียงเสร็จสิ้นแล้ว
action นี้อัปเดตเฉพาะ state ของ sort planner เท่านั้น ยังไม่บันทึก backend ทันที ผู้ใช้ต้องกดปุ่มบันทึกของ modal เอง ซึ่งจะ persist ลำดับปัจจุบันผ่าน flow เดิม sortTransportCustomers({ transportId, customerCodes })
หมายเหตุ operation:
- Google API key อ่านจาก
constants/config.tsชื่อGOOGLE_ROUTE_API_KEY - เพราะ origin ใช้ GPS ปัจจุบันของเครื่อง เส้นทางจะเริ่มจากตำแหน่งเครื่อง ณ ตอนผู้ใช้กดยืนยัน ถ้าต้องการเริ่มจากคลัง/สาขา เครื่องควรอยู่ที่คลัง/สาขานั้น หรือปรับ flow ให้ใช้พิกัดคลังที่เก็บไว้แทน
- implementation ปัจจุบันจำกัดการจัดเส้นทางด้วย Google ไว้ที่ลูกค้าที่มี GPS ถูกต้องไม่เกิน 26 รายการต่อครั้ง
ปุ่มพิมพ์ใน Header
header menu ของหน้า transport detail ไม่แสดง ยืนยันใบจัดสายส่งทั้งหมด แล้ว การอนุมัติใบจัดทั้งหมดถูกย้ายไปอยู่ที่ปุ่มด้านล่างแทน
header menu จะแสดงปุ่มพิมพ์เฉพาะเมื่อ:
- หน้าจอไม่ใช่ create mode
- มี
transportId - transport มีใบจัดอย่างน้อย 1 ใบ
- ทุกใบจัดมี
deliveryStatusที่ไม่ใช่nullและไม่ใช่ค่าว่าง
เมื่อครบเงื่อนไข จะมีเมนู:
พิมพ์ใบแจ้งหนี้ทั้งหมดพิมพ์ใบจัดสินค้า
ทั้งสอง action โหลดข้อมูล invoice ผ่าน:
GET api/transports/{transportId}/invoices
implementation อยู่ใน services/transport ชื่อ getTransportCustomerInvoices(transportId)
พิมพ์ใบแจ้งหนี้ทั้งหมด
พิมพ์ใบแจ้งหนี้ทั้งหมด ใช้ข้อมูลจาก getTransportCustomerInvoices(transportId) โดยตรง
- response ถูก group ตามลูกค้า
- ลูกค้า 1 คนมีได้หลาย invoice
- หน้าจอ flatten invoice ของทุกลูกค้า ตามลำดับ
sortของลูกค้า - เรียก
printInvoice(invoice, { printIndex })โดยตรง จึงไม่เรียกgetInvoiceDetailซ้ำ - ใช้ invoice print template เดียวกับ flow อื่นที่ผ่าน
printInvoice
เพื่อลดความเสี่ยง native print crash เมื่อพิมพ์หลายใบต่อเนื่อง loop จะรอให้ React Native interactions จบก่อน และหน่วง 800ms หลังพิมพ์แต่ละใบ ก่อนเริ่มใบถัดไป
พิมพ์ใบจัดสินค้า
พิมพ์ใบจัดสินค้า ใช้ข้อมูลจาก getTransportCustomerInvoices(transportId) เช่นกัน
- flatten invoice จากลูกค้าทุกคน
- map
itemsของแต่ละ invoice ให้เป็น shapeproductsแบบเดียวกับ shipping screen - รวมสินค้าทั้งหมดด้วย
buildGroupedProducts - พิมพ์ผ่าน
printShippingProductSummary(...)โดยใช้ template ใบจัดสินค้าเดียวกับshippingDetailScreen
การรับชำระลูกค้าแบบอื่นๆ
ใน step รายการส่งสินค้าแยกตามลูกค้า เมื่อเลือก action ชำระเงินและเลือกประเภท อื่นๆ (OTHER) ระบบใช้ flow สำหรับกรณีชำระไม่ปกติ เช่น รับสินค้าบางรายการ, ไม่รับสินค้า, หรือแบ่งชำระ
เงื่อนไข validation หลัก:
- ต้องกรอก
รายละเอียดเพิ่มเติมทุกครั้ง - ต้องอัปโหลด
รูปส่งของ - ต้องมีลายเซ็นลูกค้า เพราะ
OTHERไม่ใช่TRANSFERหรือBILL_PAYMENT - ถ้าเลือก
ไม่รับสินค้าระบบไม่เช็กยอดเงิน และตอน submit จะส่งcash = 0และtransfer = 0 - ถ้าไม่ได้เลือก
ไม่รับสินค้าระบบเช็กcash + transferเทียบกับgrandAmount cash + transferต้องไม่เกินgrandAmountทุกกรณี รวมถึงรับสินค้าบางรายการและแบ่งชำระ- ถ้าไม่ได้เลือก
รับสินค้าบางรายการระบบยังบังคับให้cash + transferไม่น้อยกว่าgrandAmount - ถ้าเลือก
รับสินค้าบางรายการระบบยอมให้cash + transferน้อยกว่าgrandAmountได้ แต่ยังห้ามเกินgrandAmount
ตอน submit OTHER จะ append system note หลังหมายเหตุผู้ใช้ เช่นยอดเงินสด, ยอดโอน, และ flag รับสินค้าบางรายการ, ไม่รับสินค้า, หรือ แบ่งชำระ เพื่อให้คลัง/การเงินตรวจต่อได้
หมายเหตุการดูแล
- ต้อง keep เอกสาร Home menu ใน
workflows/home-menu-and-permissionsให้ตรงกับ route และ permission ของVehicleList - ให้คง API สถานะรถไว้บน
mstApiเว้นแต่ backend route จะย้ายออกจาก MST domain - ถ้าเปลี่ยน permission ฝั่ง shipping ให้ตรวจ
transportDetailScreenด้วย เพราะหน้านี้ reusecanFinalizeShippingSheet(user) - ต้อง keep print actions ของ transport ให้ตรงกับ print helper และ template ที่ใช้ร่วมกับ
shippingDetailScreen