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

ใบจัดสินค้าสายส่งและขนส่ง

เอกสารนี้สรุป flow, action, status, API, เงื่อนไข และสิทธิ์ของ ใบจัดสินค้าสายส่ง และ ขนส่ง ตาม implementation ปัจจุบันของ Mobile และ POS Terminal

ขอบเขต

อ้างอิงจาก source หลัก:

  • apps/mobile/src/screens/shippingStack/shippingListScreen
  • apps/mobile/src/screens/shippingStack/shippingDetailScreen
  • apps/mobile/src/screens/transportStack/transportListScreen
  • apps/mobile/src/screens/transportStack/transportDetailScreen
  • apps/mobile/src/services/deliveries.ts
  • apps/mobile/src/services/transport.ts
  • apps/pos-terminal/src/screens/HomeScreen/ListPane.tsx
  • apps/pos-terminal/src/screens/HomeScreen/ShippingRouteDetailForm.tsx
  • apps/pos-terminal/src/screens/HomeScreen/TransportDetailForm.tsx
  • apps/pos-terminal/src/screens/HomeScreen/helpers.ts
  • apps/pos-terminal/src/services/splitMenuData.ts
  • apps/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 ใช้
statusDELETEDเอกสารถูกลบ/ยกเลิก ไม่ให้ action หลัก
deliveryStatusnull, undefined, ''กำลังเตรียมใบจัด
deliveryStatusFINISHEDฝ่ายขายจัดใบสั่งขายเสร็จแล้ว หรือขนส่งอนุมัติใบจัดแล้ว
deliveryStatusCHECKLOCATIONคลังเช็คสินค้าแล้ว
deliveryStatusPENDING_FOR_B1รอ B1 ตัดสต๊อก
deliveryStatusSOME_DELIVERY_FAILมีข้อผิดพลาดในการตัดสต๊อก
deliveryStatusDONEตัดสต๊อกเรียบร้อย
loadingStatusemptyยังไม่ยืนยันการโหลดสินค้า
loadingStatusLOADING_DOCKจัดสินค้าไว้ที่ลานแล้ว
loadingStatusLOADEDจัดสินค้าขึ้นรถเรียบร้อย

รายการใบจัดสินค้าสายส่ง

หน้ารายการแสดงข้อมูลหลัก:

  • เลขที่ใบจัด: 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 === 2
  • deptCode === 8
  • deptCode === 22
  • userCode === "999997"
  • acceptedBy ตรงกับ user ปัจจุบัน

ขอ cancel invoice

ทำได้เมื่อ:

  • deptCode อยู่ใน 12, 22, 28
  • detail.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 ใช้:

StatusLabel
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 สำคัญ:

  • branchId
  • shipDate
  • licensePlate
  • driverName
  • whGrpCode, whGrpName
  • userCode
  • routeCode, routeName
  • transportStatus
  • flagBank, flagGas
  • companyCode, 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 หมายถึง:

  • null
  • undefined
  • ''
  • 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 หลัก:

  • customerCode
  • paymentType
  • amount
  • cash
  • transfer
  • notes
  • U_BFP_Longitude, U_BFP_Latitude
  • currentLatitude, currentLongitude
  • signature
  • slipPath
  • shippingSlipPath
  • photo

ประเภท OTHER ต้องมี note, รูปส่งของ, และลายเซ็น ยกเว้น logic เฉพาะ transfer/bill payment ตามหน้าจอ

เติมน้ำมัน

ใน step ลูกค้า footer มีรายการ เติมน้ำมัน

ถ้ายังไม่ completed และ user แก้ไข transport ได้:

  • เปิด modal จัดการเติมน้ำมัน
  • ปุ่ม เติมน้ำมัน เปิดฟอร์มบิลน้ำมัน
  • ปุ่ม ไม่เติมน้ำมัน update flagGas = 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 !== 0
  • flagGas !== 0
  • เลขไมล์สิ้นสุดต้องถูกต้อง
  • เลขไมล์สิ้นสุดต้องมากกว่าเลขไมล์เดิม
  • ถ้ามี mileage balance ห้ามเกินกำหนด

API:

PATCH /transports/{transportId}/finish

หลังสำเร็จ UI refresh แล้วกลับไปหน้า TransportList

ลบ transport จากรายการ

ลบ transport ได้เมื่อ:

  • transport เป็น DRAFT
  • เจ้าของ transport ตรงกับ user ปัจจุบัน

ถ้าไม่มี transportStatus จะถือว่า draft เมื่อยังไม่มี startedAt

รายการ API สำคัญ

การจัดส่ง

ActionMethod และ path
สร้างใบจัดPOST /deliveries
รายละเอียดใบจัดGET /deliveries/{id}
ลบใบจัดDELETE /deliveries/{id}
ยืนยันใบจัดPATCH /deliveries/{id}/confirm
รับงานใบจัดPATCH /deliveries/{id}/accept
update deliveryStatusPATCH /deliveries/{id}/status
update loadingStatusPATCH /deliveries/{id}/loading-dock
จัดเรียง invoicePOST /deliveries/{id}
ลบ invoice จากใบจัดDELETE /deliveries/{id}/invoices/{invoiceId}
ลงชื่อร่วมจัดPOST /deliveries/{id}/assign
บันทึก lotPOST /deliveries/{id}/lots

ขนส่ง

ActionMethod และ path
รายการขนส่งGET /transports
รายละเอียดขนส่งGET /transports/{id}
สร้างขนส่งPOST /transports
แก้ไขขนส่งPATCH /transports/{id}
เลือกใบจัดเข้า transportPOST /transports/{id}/deliveries
ลบใบจัดออกจาก transportDELETE /transports/{id}/deliveries/{deliveryId}
เริ่มขนสินค้าขึ้นรถPATCH /transports/{id}/prepare
ปลดล็อค preparePATCH /transports/{id}/cancel-prepare
เริ่มขนส่งPOST /transports/{id}/start
รับชำระPOST /transports/{id}/pay
จบขนส่งPATCH /transports/{id}/finish
invoice ทั้งหมดของ transportGET /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 นี้ให้ตรวจ:

  1. Mobile shipping helper และ POS helper ให้ rule เท่ากัน
  2. Mobile transport detail และ POS TransportDetailForm ให้ action/guard เท่ากัน
  3. service path ใน deliveries.ts, transport.ts, และ splitMenuData.ts
  4. list card ของ Mobile และ POS Terminal
  5. wiki หน้านี้ และ changelog ถ้าเปลี่ยน behavior ที่ผู้ใช้เห็น