ใบจัดสินค้าสายส่งและขนส่ง
เอกสารนี้สรุป flow, action, status, API, เงื่อนไข และสิทธิ์ของ ใบจัดสินค้าสายส่ง และ ขนส่ง ตาม implementation ปัจจุบันของ Mobile และ POS Terminal
ขอบเขต
อ้างอิงจาก source หลัก:
apps/mobile/src/screens/shippingStack/shippingListScreenapps/mobile/src/screens/shippingStack/shippingDetailScreenapps/mobile/src/screens/transportStack/transportListScreenapps/mobile/src/screens/transportStack/transportDetailScreenapps/mobile/src/services/deliveries.tsapps/mobile/src/services/transport.tsapps/pos-terminal/src/screens/HomeScreen/ListPane.tsxapps/pos-terminal/src/screens/HomeScreen/ShippingRouteDetailForm.tsxapps/pos-terminal/src/screens/HomeScreen/TransportDetailForm.tsxapps/pos-terminal/src/screens/HomeScreen/helpers.tsapps/pos-terminal/src/services/splitMenuData.tsapps/pos-terminal/src/services/transport.ts
Mobile และ POS Terminal ใช้ rule เดียวกันสำหรับ flow หลัก ถ้าระบุว่า “ทั้งสองฝั่ง” หมายถึง Mobile และ POS Terminal
คำศัพท์หลัก
| คำ | ความหมาย |
|---|---|
| ใบจัดสินค้าสายส่ง | เอกสาร delivery route ที่รวม invoice/order สำหรับสายส่ง |
| ขนส่ง | transport trip ที่นำใบจัดหลายใบไปผูกกับทะเบียนรถและลูกค้า |
| เจ้าของใบจัด | userCode === detail.userCode |
| เจ้าของขนส่ง | userCode === transport.userCode |
| ยืนยันใบจัดสายส่ง | การตั้ง confirmedAt ผ่าน PATCH /deliveries/{id}/confirm |
| รับงานใบจัด | คลังรับงานผ่าน PATCH /deliveries/{id}/accept |
| อนุมัติใบจัดในขนส่ง | ขนส่งตั้ง deliveryStatus = FINISHED ให้ใบจัดรายใบ |
สถานะใบจัดสินค้าสายส่ง
| Field | ค่า | ความหมายที่ UI ใช้ |
|---|---|---|
status | DELETED | เอกสารถูกลบ/ยกเลิก ไม่ให้ action หลัก |
deliveryStatus | null, undefined, '' | กำลังเตรียมใบจัด |
deliveryStatus | FINISHED | ฝ่ายขายจัดใบสั่งขายเสร็จแล้ว หรือขนส่งอนุมัติใบจัดแล้ว |
deliveryStatus | CHECKLOCATION | คลังเช็คสินค้าแล้ว |
deliveryStatus | PENDING_FOR_B1 | รอ B1 ตัดสต๊อก |
deliveryStatus | SOME_DELIVERY_FAIL | มีข้อผิดพลาดในการตัดสต๊อก |
deliveryStatus | DONE | ตัดสต๊อกเรียบร้อย |
loadingStatus | empty | ยังไม่ยืนยันการโหลดสินค้า |
loadingStatus | LOADING_DOCK | จัดสินค้าไว้ที่ลานแล้ว |
loadingStatus | LOADED | จัดสินค้าขึ้นรถเรียบร้อย |
รายการใบจัดสินค้าสายส่ง
หน้ารายการแสดงข้อมูลหลัก:
- เลขที่ใบจัด:
deliveryNo - วันที่สร้าง:
createdAt - เส้นทาง:
routeName - พนักงานขาย:
createdBy - เวลา/สถานะยืนยันใบจัด:
- ถ้า
confirmedBy === "SYSTEM"แสดง(ข้อมูลเก่า {finishedAt}) - ถ้า
confirmedAtมีค่า แสดงเวลาconfirmedAt - ถ้า
confirmedAtเป็น'',null, หรือundefinedแสดง(ยังไม่ confirm)
- ถ้า
- พนักงานคลัง:
acceptedByName - เวลารับงานคลัง:
acceptedAt - พนักงานร่วมจัดสาย:
grpDriverName
Icon เพิ่มเติม:
loadingStatus === "LOADING_DOCK"แสดง icon ลานโหลดloadingStatus === "LOADED"แสดง icon รถ/โหลดขึ้นรถdeliveryStatus === "DONE"แสดง icon check
Key ของรายการ:
- Mobile ใช้
deliveryIdเป็น key หลัก - Mobile fallback เป็น
${deliveryNo ?? 'shipping'}-${index} - POS Terminal ใช้
SplitListItem.idที่ normalize จากdeliveryId - POS Terminal dedupe ด้วย
deliveryId; ถ้าไม่มี ใช้deliveryNo:{deliveryNo}
สิทธิ์เมนูใบจัดสายส่ง
เมนู ใบจัดส่งสินค้าสายส่ง แสดงเฉพาะ main branch และ MENU_SHIPPING.has(deptCode)
MENU_SHIPPING:
-2, 1, 2, 3, 4, 7, 29, 21, 8, 9, 12, 22, 27, 28, 13, 16, 14, 30
Shortcut สร้างใบจัดบน Mobile ใช้ CAN_CREATE_SHIPPING:
-2, 1, 2, 4, 7, 29, 21, 8, 9, 13, 16
สิทธิ์สร้าง/อนุมัติใบจัดใน helper ใช้ canFinalizeShippingSheet:
-2, 1, 2, 4, 7, 8, 9, 13, 16, 29, 30
Action ใบจัดสายส่ง
สร้างใบจัด
สร้างใบจัดด้วย invoice ที่เลือก:
POST /deliveries
payload เป็น array ของ invoice:
[
{ "invoiceId": 123 }
]
เพิ่ม invoice เข้าใบจัด
เพิ่ม invoice ได้เมื่อ:
- ผู้ใช้เป็นเจ้าของใบจัด:
userCode === detail.userCode detail.status !== "DELETED"confirmedAtยังว่าง
เมื่อมี confirmedAt แล้ว ห้ามเพิ่ม/แก้ไข/จัดเรียง invoice
ลบ invoice ออกจากใบจัด
ลบ invoice ได้เมื่อ:
- ผู้ใช้เป็นเจ้าของใบจัด:
userCode === detail.userCode detail.status !== "DELETED"confirmedAtยังว่าง- invoice นั้น
toB1 !== 1
API:
DELETE /deliveries/{deliveryId}/invoices/{invoiceId}
จัดเรียง invoice
จัดเรียงได้เมื่อ:
- ไม่ใช่ใบจัดที่ส่งเข้า transport แล้ว (
toTransportation !== 1) - ยังแก้ไข invoice ได้ตาม rule เจ้าของเอกสารและ
confirmedAtยังว่าง
API:
POST /deliveries/{deliveryId}
payload:
[123, 456, 789]
ยืนยันใบจัดสายส่ง
ปุ่ม ยืนยันใบจัดสายส่ง แสดงใน header เมื่อ:
- ผู้ใช้เป็นเจ้าของใบจัด
detail.status !== "DELETED"confirmedAtยังว่าง
ก่อนยืนยัน UI แสดง confirm alert:
หากยืนยันแล้ว จะไม่สามารถเพิ่ม แก้ไข หรือลบใบแจ้งหนี้ได้
API:
PATCH /deliveries/{deliveryId}/confirm
หลังสำเร็จ UI แจ้ง:
รอให้ขนส่งนำข้อมูลใบจัดเข้าขนส่ง และยืนยันใบ จึงจะดำเนินการต่อได้ หากต้องการแก้ไขใบจัดสายส่ง กรุณาให้คลัง เข้าไปปลดล็อคที่ขนส่ง
ผลของการ confirm:
- backend ตั้ง
confirmedAt,confirmedBy,confirmedByName - UI lock การเพิ่ม/แก้ไข/ลบ invoice
- ขนส่งสามารถดึงใบจัดนี้ไปเลือกได้ เพราะ transport picker filter
isConfirmed=1 - งานคลังสามารถรับงานได้ถ้าเงื่อนไขรับงานครบ
รับงานใบจัด
ปุ่มรับงานแสดง/auto prompt เมื่อ:
confirmedAtมีค่าtoTransportation === 1หรือ"1"- ยังไม่มี
acceptedBy - user อยู่ใน dept รับงาน
Dept รับงาน:
12, 22, 28
API:
PATCH /deliveries/{deliveryId}/accept
ยืนยันสินค้าที่ลานและขึ้นรถ
Dept ที่ทำ dock/loading ได้:
8, 12, 22, 28, 29
Action:
- ถ้ามี
deliveryStatusแล้วและยังไม่มีloadingStatusแสดงยืนยันการจัดสินค้าที่ลาน - เมื่อกด ส่ง
loadingStatus = LOADING_DOCK - ถ้า
loadingStatus === LOADING_DOCKแสดงยืนยันการจัดสินค้าขึ้นรถสำเร็จ - เมื่อกด ส่ง
loadingStatus = LOADED
API:
PATCH /deliveries/{deliveryId}/loading-dock
payload:
{ "loadingStatus": "LOADING_DOCK" }
หรือ:
{ "loadingStatus": "LOADED" }
ลงชื่อร่วมจัดสินค้า
ปุ่ม ลงชื่อร่วมจัดสินค้า แสดงเมื่อ:
loadingStatus === "LOADING_DOCK"- user อยู่ใน dept ลงชื่อร่วมจัด
Dept ลงชื่อร่วมจัด:
8, 12, 13, 14, 22, 28, 29, 30
API:
POST /deliveries/{deliveryId}/assign
payload:
{
"driverCode": "userCode",
"driverName": "ชื่อผู้ใช้"
}
ส่งข้อมูลไป B1 และแก้ lot
สิทธิ์ส่งไป B1:
2, 8, 12, 22, 28
แสดงเมื่อ:
detail.status !== "DELETED"deliveryStatusเป็นCHECKLOCATION,PENDING_FOR_B1, หรือSOME_DELIVERY_FAIL
ถ้า deliveryStatus เป็น PENDING_FOR_B1 หรือ SOME_DELIVERY_FAIL และมี invoice ที่ lot มีปัญหา UI แสดง action เลือก lot สินค้าที่มีปัญหาใหม่
ตรวจสินค้า
ตรวจสินค้าได้เมื่อ user เข้าเงื่อนไขใดเงื่อนไขหนึ่ง:
deptCode === 2deptCode === 8deptCode === 22userCode === "999997"acceptedByตรงกับ user ปัจจุบัน
ขอ cancel invoice
ทำได้เมื่อ:
deptCodeอยู่ใน12, 22, 28detail.deliveryStatus !== "DONE"- invoice ไม่มีส่วนลด:
discount <= 0 - invoice ไม่ใช้ credit note:
useCreditNote <= 0 - invoice ยังไม่ส่ง B1:
toB1 !== 1 - มี
invoiceId
ลบใบจัดจากหน้ารายการ
Mobile list และ POS list ใช้ rule ใกล้เคียงกัน:
- ถ้า
deliveryStatusเป็นnullหรือundefined: เจ้าของเอกสาร หรือ user ที่มีdelDelivery === 1ลบได้ - ถ้า
deliveryStatus === "FINISHED":deptCode === 16หรือ admin หรือเจ้าของเอกสารลบได้ - สถานะอื่นลบไม่ได้
สถานะขนส่ง
getTransportWorkflowStatus ใช้ค่า transportStatus หรือ status ก่อน ถ้าไม่มีจึง infer จาก timestamp:
| สถานะขั้นตอนงาน | เงื่อนไข |
|---|---|
| empty | ยังไม่มี transportNo |
DRAFT | มี transportNo, ยังไม่ prepare/start |
PREPARING | เริ่มขนสินค้าขึ้นรถแล้ว |
IN_PROGRESS | มี startedAt |
FINISHED | มี finishedAt |
CANCELED / CANCELLED / CANCEL | ถือว่ายกเลิก |
Label ที่ UI ใช้:
| Status | Label |
|---|---|
DRAFT | เตรียมขนส่ง |
PREPARING | กำลังขนสินค้าขึ้นรถ |
IN_PROGRESS | กำลังขนส่ง |
FINISHED | ขนส่งเสร็จสิ้น |
| canceled | ยกเลิก |
สิทธิ์เมนูขนส่ง
เมนู ขนส่ง แสดงเฉพาะ main branch และ MENU_TRANSPORT.has(deptCode)
MENU_TRANSPORT:
-2, 1, 2, 3, 4, 7, 29, 21, 8, 9, 12, 13, 16, 14, 22, 27, 28, 30
Shortcut สร้างขนส่งบน Mobile ใช้ CAN_CREATE_TRANSPORT:
-2, 1, 2, 3, 4, 7, 29, 21, 8, 9, 13, 16, 14, 30
Action ขนส่ง
สร้าง/แก้ไขขนส่ง
สร้าง:
POST /transports
แก้ไข:
PATCH /transports/{transportId}
field สำคัญ:
branchIdshipDatelicensePlatedriverNamewhGrpCode,whGrpNameuserCoderouteCode,routeNametransportStatusflagBank,flagGascompanyCode,companyName
Validation ขั้นต่ำ:
- ต้องมีทะเบียนรถ
- ต้องมีพนักงานขับรถ
- ต้องมีวันที่ขนส่ง
ผู้แก้ไขขนส่งได้:
- create mode: แก้ได้
- detail mode: ต้องเป็นเจ้าของขนส่ง (
userCode === transport.userCode)
เมื่อเริ่มงานแล้วหรือไม่ใช่ DRAFT step ข้อมูลหลักจะ lock
เลือกใบจัดเข้า transport
modal เลือกใบจัดเรียก:
GET /deliveries?page=1&pageLimit=32&sort=DESC&sortBy=CreatedAt&search={search}&branchId={branchId}&toTransportation=0&deliveryStatus=&status=ACTIVE&isConfirmed=1
เงื่อนไข:
- transport ยังเป็น create mode หรือ
DRAFT - ผู้ใช้แก้ไข transport ได้
- ต้องเลือกทะเบียนรถแล้ว
- ใบจัดต้องยังไม่ถูกผูกขนส่ง:
toTransportation=0 - ใบจัดต้อง active:
status=ACTIVE - ใบจัดต้องยืนยันแล้ว:
isConfirmed=1 - modal ค้นหาได้ทั้ง Mobile และ POS Terminal
หลังเลือกใบจัด:
POST /transports/{transportId}/deliveries
payload:
{ "deliveryId": 123 }
ลบใบจัดออกจาก transport
ลบได้เมื่อ:
- ผู้ใช้แก้ไข transport ได้
- transport ยังเป็น
DRAFT - ยังไม่
startedAt
API:
DELETE /transports/{transportId}/deliveries/{deliveryId}
อนุมัติใบจัดรายใบใน transport
ปุ่ม check รายใบแสดงเมื่อ:
- ผู้ใช้แก้ไข transport ได้
- transport เป็น
DRAFT - transport ไม่ถูกยกเลิก
- ใบจัดนั้น
deliveryStatusเป็น pending
pending delivery status หมายถึง:
nullundefined''- whitespace
- string
"null"หรือ"NULL"
API:
PATCH /deliveries/{deliveryId}/status
payload:
{ "deliveryStatus": "FINISHED" }
อนุมัติใบจัดทั้งหมด
ปุ่มล่าง อนุมัติใบจัดทั้งหมด แสดงเมื่อ:
- อยู่ step รายการใบจัด
- user แก้ไข transport ได้
- transport เป็น
DRAFT - transport ไม่ถูกยกเลิก
- มีใบจัดอย่างน้อย 1 ใบที่ยัง pending
เมื่อยืนยัน ระบบ loop ใบจัด pending ทุกใบ และเรียก API status เป็น FINISHED ทีละใบ
เริ่มขนสินค้าขึ้นรถ
ปุ่ม เริ่มขนสินค้าขึ้นรถ แสดงเมื่อ:
- transport ไม่ใช่ create mode
- user แก้ไข transport ได้
- transport เป็น
DRAFT - ใบจัดทุกใบมี
deliveryStatusแล้ว - transport ไม่ถูกยกเลิก
API:
PATCH /transports/{transportId}/prepare
ผลคือ transport เข้าสถานะ PREPARING
ปลดล็อคขนสินค้าขึ้นรถ
เมื่อ transport เป็น PREPARING และไม่ถูกยกเลิก UI แสดง action unlock
ผู้กดต้องมี deptCode:
8, 12, 22, 28, 29, 4, 2
ถ้าไม่มีสิทธิ์ UI แจ้ง:
คุณไม่มีสิทธิ์ปลดล็อค
ก่อน submit ต้องกรอกหมายเหตุ ถ้าไม่กรอก UI แจ้ง:
กรุณาระบุหมายเหตุ
API:
PATCH /transports/{transportId}/cancel-prepare
payload:
{ "note": "เหตุผลการปลดล็อค" }
ผลคือปลดจาก PREPARING กลับมาให้แก้ไข/จัดการก่อนเริ่มขนส่งต่อ
เริ่มการขนส่ง
ปุ่ม เริ่มการขนส่ง ใช้ได้เมื่อ:
- transport มี
transportId - มีทะเบียนรถ
- ใบจัดทุกใบมี
deliveryStatusแล้ว - transport เป็น
PREPARING - user แก้ไข transport ได้
ถ้าใบจัดยังไม่ครบสถานะ UI แจ้ง:
ใบจัดสายส่งทุกใบต้องถูกยืนยันก่อน จึงจะเริ่มขนส่งได้
ถ้ายังไม่ prepare UI แจ้ง:
กรุณาเริ่มขนสินค้าขึ้นรถก่อนเริ่มขนส่ง
API:
POST /transports/{transportId}/start
หรือ wrapper startTransport(...) ตาม payload ลูกค้า/พิกัด/ค่าขนส่งที่หน้าจอสร้างจากข้อมูล step ลูกค้า
รับชำระลูกค้า
ใน step ลูกค้า เมื่อเลือก action ชำระเงิน UI ใช้ payTransport
API:
POST /transports/{transportId}/pay
payload หลัก:
customerCodepaymentTypeamountcashtransfernotesU_BFP_Longitude,U_BFP_LatitudecurrentLatitude,currentLongitudesignatureslipPathshippingSlipPathphoto
ประเภท OTHER ต้องมี note, รูปส่งของ, และลายเซ็น ยกเว้น logic เฉพาะ transfer/bill payment ตามหน้าจอ
เติมน้ำมัน
ใน step ลูกค้า footer มีรายการ เติมน้ำมัน
ถ้ายังไม่ completed และ user แก้ไข transport ได้:
- เปิด modal
จัดการเติมน้ำมัน - ปุ่ม
เติมน้ำมันเปิดฟอร์มบิลน้ำมัน - ปุ่ม
ไม่เติมน้ำมันupdateflagGas = 1
ก่อนจบ transport ถ้า flagGas === 0 UI แจ้ง:
คุณยังไม่จัดการเติมน้ำมัน
ฝากเงิน
ใน step ลูกค้า footer มีรายการ ฝากเงิน
ปัจจุบันซ่อนปุ่ม ฝากเงิน ไว้ แต่ comment โค้ดเดิมไว้เพื่อเปิดกลับได้ภายหลัง
เหลือ action ที่ใช้งาน:
- ปุ่ม
ไม่ฝากเงิน - เมื่อกด update
flagBank = 1
ก่อนจบ transport ถ้า flagBank === 0 UI แจ้ง:
คุณยังไม่จัดการฝากเงิน
สิ้นสุดการขนส่ง
ปุ่ม สิ้นสุดการขนส่ง แสดงเมื่อ:
- transport มี
startedAt - ยังไม่มี
finishedAt - user แก้ไข transport ได้
ก่อนจบต้องผ่าน guard:
- จำนวนงานที่ส่งเสร็จต้องครบ:
totalFinished === total flagBank !== 0flagGas !== 0- เลขไมล์สิ้นสุดต้องถูกต้อง
- เลขไมล์สิ้นสุดต้องมากกว่าเลขไมล์เดิม
- ถ้ามี mileage balance ห้ามเกินกำหนด
API:
PATCH /transports/{transportId}/finish
หลังสำเร็จ UI refresh แล้วกลับไปหน้า TransportList
ลบ transport จากรายการ
ลบ transport ได้เมื่อ:
- transport เป็น
DRAFT - เจ้าของ transport ตรงกับ user ปัจจุบัน
ถ้าไม่มี transportStatus จะถือว่า draft เมื่อยังไม่มี startedAt
รายการ API สำคัญ
การจัดส่ง
| Action | Method และ path |
|---|---|
| สร้างใบจัด | POST /deliveries |
| รายละเอียดใบจัด | GET /deliveries/{id} |
| ลบใบจัด | DELETE /deliveries/{id} |
| ยืนยันใบจัด | PATCH /deliveries/{id}/confirm |
| รับงานใบจัด | PATCH /deliveries/{id}/accept |
| update deliveryStatus | PATCH /deliveries/{id}/status |
| update loadingStatus | PATCH /deliveries/{id}/loading-dock |
| จัดเรียง invoice | POST /deliveries/{id} |
| ลบ invoice จากใบจัด | DELETE /deliveries/{id}/invoices/{invoiceId} |
| ลงชื่อร่วมจัด | POST /deliveries/{id}/assign |
| บันทึก lot | POST /deliveries/{id}/lots |
ขนส่ง
| Action | Method และ path |
|---|---|
| รายการขนส่ง | GET /transports |
| รายละเอียดขนส่ง | GET /transports/{id} |
| สร้างขนส่ง | POST /transports |
| แก้ไขขนส่ง | PATCH /transports/{id} |
| เลือกใบจัดเข้า transport | POST /transports/{id}/deliveries |
| ลบใบจัดออกจาก transport | DELETE /transports/{id}/deliveries/{deliveryId} |
| เริ่มขนสินค้าขึ้นรถ | PATCH /transports/{id}/prepare |
| ปลดล็อค prepare | PATCH /transports/{id}/cancel-prepare |
| เริ่มขนส่ง | POST /transports/{id}/start |
| รับชำระ | POST /transports/{id}/pay |
| จบขนส่ง | PATCH /transports/{id}/finish |
| invoice ทั้งหมดของ transport | GET /transports/{id}/invoices |
Parity ระหว่าง Mobile และ POS Terminal
สิ่งที่ต้องคงให้ตรงกัน:
- list card ใบจัดสายส่งใช้
confirmedAtสำหรับเวลายืนยัน - ถ้า
confirmedBy === "SYSTEM"แสดง(ข้อมูลเก่า {finishedAt}) - ถ้าไม่มี
confirmedAtแสดง(ยังไม่ confirm) - รับงานใบจัดต้องใช้
confirmedAtมีค่า ไม่ใช่confirmedAtว่าง - modal เลือกใบจัดในขนส่งต้องส่ง
isConfirmed=1 - modal เลือกใบจัดในขนส่งต้องค้นหาได้
- ปุ่มฝากเงินใน transport ถูกซ่อน เหลือ
ไม่ฝากเงิน - ปุ่ม unlock prepare ใช้ deptCode ชุดเดียวกัน
Checklist การดูแล
เมื่อแก้ flow นี้ให้ตรวจ:
- Mobile shipping helper และ POS helper ให้ rule เท่ากัน
- Mobile transport detail และ POS
TransportDetailFormให้ action/guard เท่ากัน - service path ใน
deliveries.ts,transport.ts, และsplitMenuData.ts - list card ของ Mobile และ POS Terminal
- wiki หน้านี้ และ changelog ถ้าเปลี่ยน behavior ที่ผู้ใช้เห็น