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

ลดหนี้ / คืนสินค้า

เมนูนี้ใช้สร้างใบลดหนี้จากใบแจ้งหนี้ โดยเลือกได้ทั้งการคืนสินค้าจริงและการลดหนี้ทางการเงินอย่างเดียว กฎข้อมูล สิทธิ และเงื่อนไขของ Mobile กับ POS Terminal เหมือนกัน ต่างกันเพียงรูปแบบหน้าจอ

จุดเริ่มต้น​

แอปจุดเข้าใช้งาน
MobileCreditNoteList → CreditNoteDetail หรือกดทำใบคืนสินค้าจากใบแจ้งหนี้
POS Terminalเมนูแบบ split-pane creditNote หรือกด เปิดใบคืนสินค้า จากรายละเอียดใบแจ้งหนี้

รายการจะถูกจำกัดตาม branchId และ whGrpCode ที่เลือก รองรับค้นหา กรองสถานะ รีเฟรช และโหลดเพิ่ม โดยค่าเริ่มต้นเปิดตัวกรอง มีการคืนสินค้า ซึ่งส่ง isReturn=1; ปิดตัวกรองนี้เพื่อดูใบลดหนี้ทางการเงินอย่างเดียวด้วย

สีพื้นหลังการ์ดในหน้ารายการ​

Mobile และ Terminal ใช้สีพื้นหลังการ์ดใบลดหนี้/คืนสินค้าเหมือนกัน ทั้งคลังหลักและคลังฝาก:

Statusความหมายสีพื้นหลัง
DRAFTร่างฟ้าอ่อน #CBFEFF
NEWใหม่ฟ้าอ่อน #CBFEFF
WAITINGCNรอ B1 อนุมัติเขียวมะนาวอ่อน #CFFD67
INVUPDATEคลังอัปเดตม่วงชมพูอ่อน #E7BCE7
RECEIVEDส่ง B1 สำเร็จเขียวอ่อน #76FFA3
APPROVED / APPROVEอนุมัติม่วง #BB8AE3
CANCELEDยกเลิกส้มอ่อน #FFC385
ERRORRECEIVEDส่ง B1 ไม่สำเร็จส้มอ่อน #FFC385
QC_FAILEDตรวจ QC ไม่ผ่านส้มอ่อน #FFC385
สถานะอื่น / ไม่ระบุสีสำรองส้มอ่อน #FFC385

ปัจจุบัน QC_FAILED ใช้สีส้มสำรองของหน้ารายการ ยังไม่ได้กำหนดสีเฉพาะ ส่วนปุ่มเต็มความกว้าง ตรวจ QC ไม่ผ่าน ในหน้ารายละเอียดของคลังฝากเป็นสีแดง (#C53030) และกดไม่ได้ ทั้งสองส่วนเป็นคนละองค์ประกอบ เอกสารนี้บันทึกสีที่ใช้อยู่ ไม่ได้เปลี่ยนสีในแอป

ขั้นตอนคืนสินค้าแบบครบถ้วน​

  1. เปิดใบแจ้งหนี้และเริ่มแบบร่างใบคืนสินค้า ระบบคัดลอกข้อมูลใบแจ้งหนี้ ลูกค้า บริษัท คลัง พนักงานขาย และรายการสินค้าในใบแจ้งหนี้มาให้ ใบคืนสินค้าเดิมจะโหลดใบแจ้งหนี้ที่ผูกไว้ซ้ำ เพื่อให้ยังเลือกรายการต้นทางได้
  2. เลือก เหตุผล 1 ซึ่งจำเป็นก่อนเพิ่มรายการสินค้าและก่อนบันทึกเอกสาร
  3. เลือกรายการจากใบแจ้งหนี้ ระบบตั้งต้นจำนวน ราคา ยอดเงิน หน่วยนับ คลัง ภาษี และข้อมูลน้ำหนักจากรายการต้นทาง
  4. ระบุประเภทการคืน จำนวนคืน (qtyCN) และยอดลดหนี้ (priceCN) ระบบไม่ยอมให้จำนวนคืนมากกว่าจำนวนในรายการต้นทาง หรือยอดลดหนี้มากกว่ายอดเงินของรายการต้นทาง
  5. บันทึกรายการ หากเอกสารยังไม่มี ระบบจะสร้างเป็น DRAFT ก่อน จากนั้นจะรีเฟรชรายการและยอดรวม
  6. หากเป็นการคืนสินค้าจริง (isReturn = 1) ผู้มีสิทธิ์คลังเลือก Lot/Batch ที่เมนู ตำแหน่งวาง ตัวเลือก Lot มาจาก GET /api/invoices/{invoiceId}?includeLot=true ในฟิลด์ items[].lots ของรายการต้นทางโดยตรง ระบบจะไม่เรียก /api/products/lot และไม่ตรวจสต็อกคงเหลือปัจจุบัน
  7. เมื่อเลือก Lot ระบบบันทึก { lotNo, qty: qtyCN } จำนวนที่บันทึกจึงยึดจำนวนคืนในใบลดหนี้ ไม่ได้ยึดจำนวนที่แสดงของ Lot ในใบแจ้งหนี้ หากมีหลายบรรทัดในใบแจ้งหนี้ที่ใช้รหัสสินค้าเดียวกัน ระบบรวมจำนวนตามเลข Lot เพื่อใช้แสดงตัวเลือกเท่านั้น
  8. เพิ่มรูปหลักฐานหลังเอกสารถูกสร้างแล้ว เมื่อผู้ใช้ยังแก้ไขเอกสารได้ แล้วบันทึกหรือส่งต่อเอกสารตามสิทธิและสถานะด้านล่าง
  9. ก่อนส่ง B1 รายการที่คืนสินค้าจริงทุกรายการต้องมี Lot และต้องติ๊ก Q1/Q2/Q3 อย่างน้อยหนึ่งข้อ อุณหภูมิถูกเก็บเป็นข้อมูลเอกสาร แต่ฝั่งแอปไม่มีการตรวจช่วงอุณหภูมิเพิ่มเติม

กฎ Lot และจำนวนคืน​

กฎพฤติกรรมของแอป
แหล่งข้อมูล Lotเรียกข้อมูลใบแจ้งหนี้ด้วย includeLot=true
ตัวเลือก Lotใช้ invoice.items[].lots; แสดง lotNo เป็น Batch พร้อมจำนวนในใบแจ้งหนี้
การตรวจสต็อกไม่มีการตรวจสต็อกคงเหลือสำหรับ flow คืนสินค้านี้ จึงไม่บล็อกเพราะสต็อกปัจจุบันน้อยกว่า qtyCN
จำนวนที่ส่งเมื่อเลือก Lotส่งเท่ากับ qtyCN ของรายการใบลดหนี้เสมอ
คืนสินค้าจริงisReturn = 1; ต้องเลือก Lot ก่อนส่ง B1
ลดหนี้อย่างเดียวisReturn = 0; ไม่ต้องเลือก Lot ก่อนส่ง B1
ตรวจข้อมูลรายการต้นทางqtyCN ต้องไม่มากกว่า qty ต้นทาง และ priceCN ต้องไม่มากกว่า amount ต้นทาง
หลาย Lotหน้าจอปัจจุบันเลือก Lot ได้หนึ่ง Lot ต่อหนึ่งรายการ และส่ง lots ที่มีหนึ่งรายการ

สถานะและการบันทึก​

สถานะที่แอปแสดง ได้แก่ DRAFT, NEW, WAITINGCN, INVUPDATE, RECEIVED, APPROVED/APPROVE, CANCELED, ERRORRECEIVED และ QC_FAILED

ผู้ดำเนินการ / การทำงานผลลัพธ์หรือเงื่อนไข
บันทึกเอกสารใหม่ครั้งแรกสร้างสถานะ DRAFT
ผู้ที่ไม่ใช่ฝ่ายคลังบันทึกและมีรายการคืนสินค้าเปลี่ยนเป็น WAITINGCN
ผู้ที่ไม่ใช่ฝ่ายคลังบันทึกและไม่มีรายการคืนสินค้าเปลี่ยนเป็น NEW
ฝ่ายคลังบันทึกจาก WAITINGCNเปลี่ยนเป็น INVUPDATE
ฝ่ายคลังบันทึกจาก INVUPDATEคงเป็น INVUPDATE
ฝ่ายคลังบันทึกจากสถานะอื่นระบบบล็อกและแจ้งให้รอขั้นตอนคลัง
ปุ่มส่ง B1 (ทั้งคลังหลักและคลังฝาก)แสดงเฉพาะ NEW หรือ INVUPDATE และต้องผ่านสิทธิ์เดิมกับเงื่อนไข QC ของคลังฝาก สถานะอื่นซ่อนปุ่มทั้งหมด
ส่ง B1เรียก B1 แล้วอัปเดตเป็น RECEIVED เมื่อสำเร็จ
เคยส่ง B1 แล้วส่งซ้ำไม่ได้เมื่อมี datetimeToB1
RECEIVEDการแก้ไขต่าง ๆ เป็นแบบอ่านอย่างเดียวหรือถูกปิดใช้งาน

เงื่อนไขเหล่านี้ใช้เหมือนกันทั้ง development และ release แล้ว ข้อยกเว้นชั่วคราวที่ซ่อน QC และเปิดส่ง B1 เพื่อทดสอบ error ถูกถอดออกหลังทดสอบเสร็จ รวมถึงการข้าม Lot/Q

ตรวจ QC คลังฝากก่อนส่ง B1​

เมื่อ isMain = false, ใบคืนสินค้าเป็น WAITINGCN และ qcStatus = null ผู้มีสิทธิ์ส่ง B1 เดิมจะเห็นปุ่ม ตรวจสอบ QC แทนปุ่มส่ง B1 ระหว่างรอ QC จะซ่อนปุ่มบันทึกด้วย เพื่อป้องกันการบันทึกเปลี่ยนสถานะเป็น INVUPDATE แล้วข้าม QC ส่วนคลังหลักใช้ flow เดิม

ปุ่มและเงื่อนไขล็อกจาก QC ใช้เฉพาะ isMain === false เท่านั้น คลังหลัก (isMain = true) และคลังที่ยังไม่ทราบประเภทจะไม่เข้า flow QC แม้ผล API จะมีฟิลด์ QC มาด้วย

Modal ตรวจสอบ QC มีช่องเหตุผลหลายบรรทัด (ไม่เกิน 500 ตัวอักษร) และปุ่ม ผ่าน / ไม่ผ่าน ถ้าผ่านไม่บังคับเหตุผล ถ้าไม่ผ่านต้องระบุเหตุผลที่ไม่ใช่ข้อความว่าง ระบบส่ง PATCH /api/credit-notes/{id}/qc ด้วย { qcPassed: boolean, qcRemark: string } แล้วโหลดรายละเอียดใบคืนสินค้าใหม่ การตรวจ QC ไม่ได้บันทึกหัวเอกสารหรือส่ง B1 ให้อัตโนมัติ

QC_FAILED เป็นสถานะที่ backend กำหนดใน API ตรวจ QC ไม่ใช่ชื่อสถานะที่แอปตั้งขึ้นเอง เอกสาร API ระบุว่าเมื่อไม่ผ่าน QC ใบคืนสินค้าจะเปลี่ยนเป็น QC_FAILED แอปส่งเฉพาะผลตรวจและเหตุผล แล้วอ่าน qcStatus กับ creditNoteStatus จากรายละเอียดที่โหลดใหม่ ไม่ได้ PATCH creditNoteStatus: "QC_FAILED" เอง

ตัวอย่าง payload เมื่อไม่ผ่าน:

{
"qcPassed": false,
"qcRemark": "สินค้าละลาย บรรจุภัณฑ์ฉีกขาด"
}

ถ้าผ่านโดยไม่ระบุเหตุผล แอปส่ง { "qcPassed": true, "qcRemark": "" }

  • qcStatus = true: กลับมาแสดงบันทึกตามสิทธิ์เดิม ถ้าโหลดแล้วสถานะยังเป็น WAITINGCN จะยังไม่แสดงส่ง B1 ต้องบันทึกตาม flow คลังให้เป็น INVUPDATE ก่อน ปุ่มส่ง B1 แสดงเฉพาะ NEW หรือ INVUPDATE ยังคงตรวจ Lot, Q1/Q2/Q3, การส่งแล้ว และเปลี่ยนสถานะตาม flow เดิม
  • qcStatus = false หรือสถานะ QC_FAILED จาก API: ซ่อนบันทึกและส่ง B1 แสดงปุ่มสีแดงเต็มความกว้าง ตรวจ QC ไม่ผ่าน เพียงปุ่มเดียวในส่วนดำเนินการ กดไม่ได้ และช่องตรวจคลัง เหตุผล รายการสินค้า และรูปหลักฐานแก้ไขไม่ได้
  • ป้องกันการส่งผลซ้ำระหว่างรอ API หากส่งไม่สำเร็จจะคงเหตุผลและแสดงข้อความผิดพลาด หาก PATCH สำเร็จแต่โหลดรายละเอียดล้มเหลว ให้โหลดข้อมูลซ้ำโดยไม่ส่งผล QC ซ้ำ และไม่ปลดล็อก B1 จากการคาดเดาผล

Mobile และ Terminal ใช้กฎ/ตัวสร้าง payload ร่วมใน packages/utils/src/creditNoteQc.ts และ Modal ใน packages/rn-ui/src/CreditNoteQcModal.tsx ฟอร์มนี้เป็นของพนักงาน ไม่มีฟอร์ม QC ให้แก้ไขบนจอรอง

สิทธิและเงื่อนไข​

ตารางด้านล่างเป็นสิทธิ์พื้นฐานตามบทบาท สำหรับคลังฝาก ระหว่างรอ QC จะซ่อนบันทึกเพิ่ม และเมื่อ QC ไม่ผ่านจะล็อกการแก้ไขในหน้ารายละเอียดและซ่อนบันทึก/ส่ง B1 ส่วนเงื่อนไขลบเอกสารจากหน้ารายการแยกต่างหาก

ค่าด้านล่างเป็น deptCode ทั้งหมด ส่วน user-code ที่ได้รับสิทธิ์ B1 เพิ่มเติมคือ 012142, 012570, 012544, 012051, 012566

ความสามารถสิทธิและเงื่อนไข
เปิดเมนูลดหนี้/คืนสินค้า (Mobile/POS Terminal)-2, 1, 2, 3, 4, 7, 8, 9, 12, 13, 16, 20, 21, 22, 23, 27, 28, 29 หรือ user-code ที่ตั้งค่า override ไว้สำหรับเมนู
สร้างจากรายการ Mobile-2, 1, 2, 4, 7, 8, 9, 12, 16, 22, 28, 29
บันทึกเอกสาร7, 8, 9, 12, 22, 28, 29 และสถานะต้องเป็น DRAFT, NEW, WAITINGCN หรือ INVUPDATE
เพิ่มรายการเอกสารไม่ใช่ RECEIVED และผู้ใช้ปัจจุบันเป็นเจ้าของเอกสาร
แก้จำนวนหรือยอดรายการเอกสารไม่ใช่ RECEIVED, ต้องเป็นเจ้าของเอกสาร และอยู่ใน 7, 12, 22, 28, 29
ลบรายการเอกสารไม่ใช่ RECEIVED และผู้ใช้ปัจจุบันเป็นเจ้าของเอกสาร
เลือกตำแหน่งวาง/Lotเอกสารไม่ใช่ RECEIVED และเป็นรายการคืนสินค้าจริง; แผนก 12, 22, 28 เลือกได้ทุกสาขา ส่วนแผนก 20, 23 เลือกได้เมื่อสาขาที่เลือกมี isMain = false เท่านั้น หรือเป็น user-code override ของ B1; ไม่บังคับว่าเป็นเจ้าของเอกสาร
ช่องตรวจรับของคลัง8, 9, 12, 22, 28, 29; Q1/Q2/Q3 และอุณหภูมิจะอ่านอย่างเดียวหลัง RECEIVED
เพิ่ม/ลบรูปหลักฐานเอกสารถูกสร้างแล้วและยังไม่ใช่ RECEIVED
ส่ง B1เอกสารถูกสร้างแล้ว, สถานะเป็น NEW หรือ INVUPDATE, ไม่ติดเงื่อนไขบล็อกจาก QC ของคลังฝาก, ผู้ใช้เป็น 8, 9, 12, 20, 22, 23, 28, 29 หรือ user-code override ของ B1, ไม่มี datetimeToB1, เลือก Lot ครบทุกรายการคืนสินค้า และมี Q1/Q2/Q3 อย่างน้อยหนึ่งข้อเมื่อมีการคืนสินค้าจริง
ลบเอกสารจากรายการห้ามลบเมื่อเป็น RECEIVED, APPROVED หรือ APPROVE; นอกนั้นแผนกยกเลิก -2, 1, 2, 4, 12, 22, 28 ลบได้, สถานะ DRAFT/NEW เจ้าของลบได้, และสถานะ WAITINGCN/INVUPDATE ลบได้ ต้องยืนยันก่อนลบทุกครั้ง

เมื่อ API ล้มเหลวและแสดง Global Alert กด คัดลอกปัญหาส่งทีม IT เพื่อคัดลอกผลตอบกลับจาก backend พร้อม URL/query/payload และ usercode/deptCode ของผู้ส่งคำขอสำหรับตรวจสอบปัญหา ดู การคัดลอก API error

แผนที่ API​

การทำงานAPI / service
รายการใบลดหนี้GET /api/credit-notes ผ่าน getCreditNotes พร้อม scope สาขา/คลัง และตัวเลือก status, ค้นหา, isReturn
รายละเอียดใบลดหนี้GET /api/credit-notes/{creditNoteId}
ใบแจ้งหนี้ต้นทางและ LotGET /api/invoices/{invoiceId}?includeLot=true
สร้าง/แก้ไขหัวเอกสารPOST /api/credit-notes; PATCH /api/credit-notes/{creditNoteId}
สร้าง/แก้ไข/ลบรายการ/api/credit-notes/{creditNoteId}/items
บันทึก Lot ที่เลือกPATCH /api/credit-notes/{creditNoteId}/items/{itemId}/lots พร้อม lots: [{ lotNo, qty }]
รูปหลักฐานendpoint สำหรับอัปโหลด/ลบไฟล์ของใบลดหนี้
ผลตรวจ QCPATCH /api/credit-notes/{id}/qc พร้อม qcPassed และ qcRemark แล้ว GET รายละเอียดใหม่
B1POST /api/credit-notes/{creditNoteId}/to-b1 แล้ว PATCH หัวเอกสารเป็น RECEIVED

ความสอดคล้องของ Mobile และ POS Terminal​

ทั้งสองแอปใช้ Lot จากใบแจ้งหนี้ การตรวจรายการต้นทาง การเปลี่ยนสถานะ เงื่อนไขส่ง B1 และสิทธิแผนก/user-code ชุดเดียวกัน Mobile ใช้หน้าจอแบบ stack และ modal ส่วน POS Terminal ใช้ CreditNoteDetailForm ใน split pane โดย POS Terminal เก็บ sourceInvoiceItems ไว้เมื่อเปิดใบคืนสินค้าเดิม เพื่อให้ตัวเลือก Lot ใช้ข้อมูลใบแจ้งหนี้เดียวกัน

ไฟล์ที่เกี่ยวข้องและเกณฑ์ตรวจสอบ​

ส่วนรับผิดชอบไฟล์
เงื่อนไขสถานะส่ง B1 (NEW / INVUPDATE)packages/utils/src/creditNoteB1.ts
สถานะ QC คลังฝากและตรวจ payloadpackages/utils/src/creditNoteQc.ts
Modal QC ร่วม ป้องกันกดซ้ำ และโหลดข้อมูลซ้ำpackages/rn-ui/src/CreditNoteQcModal.tsx
ฟอร์ม / API ของ Mobileapps/mobile/src/screens/creditNoteStack/creditNoteDetailScreen/index.tsx, apps/mobile/src/services/creditNotes.ts
ฟอร์ม / API ของ Terminalapps/pos-terminal/src/screens/HomeScreen/CreditNoteDetailForm.tsx, apps/pos-terminal/src/services/creditNotes.ts
สีรายการ Mobileapps/mobile/src/screens/creditNoteStack/helpers.ts, apps/mobile/src/ui/colors.ts
สีรายการ Terminalapps/pos-terminal/src/screens/HomeScreen/ListPane.tsx, apps/pos-terminal/src/screens/HomeScreen/config.ts

รันจาก repository root:

yarn typecheck
yarn workspace @bsr/mobile jest --runInBand --watchman=false __tests__/creditNoteQc.test.ts __tests__/creditNoteQcModal.test.tsx __tests__/creditNoteQcTerminal.test.tsx
  • ตรวจปุ่มส่ง B1 ทั้งสองประเภทคลังทุกสถานะ รวมสถานะไม่รู้จัก/ไม่ระบุ ต้องแสดงเฉพาะ NEW และ INVUPDATE ตามสิทธิ์เดิม ทั้งปุ่มและ handler ใช้เงื่อนไขเดียวกัน
  • คลังฝาก WAITINGCN ที่ยังไม่ตรวจ QC ต้องแสดงตรวจ QC และซ่อนบันทึก/ส่ง B1 เมื่อผ่านต้องโหลดรายละเอียดใหม่ ถ้ายังเป็น WAITINGCN ให้บันทึกตาม flow คลังเป็น INVUPDATE ก่อนแสดงส่ง B1 เมื่อไม่ผ่านฟอร์มต้องถูกล็อกและแสดงปุ่มแดงที่กดไม่ได้
  • คลังหลักไม่เข้า QC และไม่ถูกล็อกด้วยผล QC เมื่อกดส่ง B1 ยังคงตรวจ Lot, Q1/Q2/Q3 และ datetimeToB1 เดิม
  • ตรวจเหตุผลบังคับเฉพาะไม่ผ่าน การเก็บเหตุผลและข้อความ error การกดผ่าน/ไม่ผ่านซ้ำอย่างรวดเร็ว การโหลดใหม่โดยไม่ส่ง PATCH ซ้ำ และการไม่ใช้ผลโหลดเก่าหลังออกจากเอกสารนั้น
  • ตรวจปุ่ม QC/บันทึก/B1 ทั้ง __DEV__ = true และ false โดยทั้งสองโหมดต้องไม่ข้ามเงื่อนไขธุรกิจ
  • ตรวจสีการ์ดในหน้ารายการแยกจากปุ่ม QC ไม่ผ่านในหน้ารายละเอียด

ผลตรวจหลังคืนเงื่อนไขและเปลี่ยนชื่อปุ่ม (6442b189): alertResponseCopy.test.tsx, creditNoteQcTerminal.test.tsx, creditNoteQc.test.ts ผ่านรวม 114 กรณี พร้อม TypeScript ของ Mobile/Terminal และ build Wiki สองภาษา การส่ง API ในเทสต์ใช้ mock

เอกสารที่เกี่ยวข้อง​