POS รับบิลขายจากเครื่องแคชเชียร์ (POS Bill Inbox)
เครื่อง POS ส่งบิลที่ขายเสร็จแล้วมาที่ POST /api/v1/pos/bills ระบบเก็บ JSON ดิบลง pos_bill_inbox ก่อน (คีย์ bill_guid ส่งซ้ำไม่ซ้ำบิล) แล้วค่อยแปลงเป็นเอกสารขาย trans_flag = 44 ผ่าน saveTransTx (ยอดรวม/VAT/GL คำนวณฝั่งเซิร์ฟเวอร์) บิลที่ประมวลผลพลาดยังถูกเก็บไว้และสั่งประมวลผลซ้ำได้ (คอมเมนต์ pos_bill.go:20-24) ส่วนการดึงข้อมูลลงเครื่องอยู่ที่ POS ซิงก์ข้อมูลเครื่องแคชเชียร์ ผูกเครื่อง/ดึงส่วนต่าง/กู้คืน/รูปภาพ (POS Device Sync)
ทำงานอย่างไร
Input บิล (PosBillInput :59, handler pos/sync.go:111)
- Handler อ่าน body ได้ไม่เกิน 1 MiB (
1<<20) แล้วส่ง JSON ดิบให้ReceivePosBill - ฟิลด์:
bill_guid,doc_no,doc_date(YYYY-MM-DD ตอนขายบนเครื่อง),doc_time(HH:MM),cashier_code,cust_code,bill_discount,coupon_code,coupon_amount,points_redeemed,remark,lines[](item_code,barcode,name,unit_code,qty,price,discount),payments[](method,amount) - ราคาในบิลคือราคา ณ ตอนขาย เซิร์ฟเวอร์ไม่ตั้งราคาใหม่ (คอมเมนต์ :75-76)
- ตัวเลขทุกตัวเป็น
Dec= สตริงทศนิยมหรือตัวเลขเปล่าใน JSON ที่เก็บเป็นข้อความตรง ไม่ผ่านfloat64; รูปแบบต้องตรง^-?\d+(\.\d+)?$ไม่งั้นแกะ JSON ไม่ผ่าน (Dec.UnmarshalJSON:37-54) parsePosBill(:105):bill_guidต้องเป็น UUID,doc_no(ผ่านnormalizeDocNo) ยาว 1-25 ตัวอักษร, ต้องมีรายการสินค้าอย่างน้อย 1 รายการ ผิด = error ตรวจสอบ (400)
รับบิล ReceivePosBill (:123)
- INSERT ลง
pos_bill_inbox (bill_guid, machine_code, doc_no, payload)แบบON CONFLICT (bill_guid) DO NOTHING-- ถ้าdoc_noซ้ำกับบิลอื่น (unique indexidx_pos_bill_inbox_doc_no) ตอบ 409 ให้ตรวจรูปแบบเลขที่บิลของเครื่อง - ส่งซ้ำ (ไม่มีแถวใหม่) =
Duplicate = true: ถ้าbill_guidเป็นของเครื่องอื่น = 409; ถ้าบิลประมวลผลแล้ว (สถานะ 1 หรือ 2) ตอบตามที่เก็บไว้ (doneหรือerror+ ข้อความ) ไม่ประมวลผลใหม่; ถ้ายังค้าง (สถานะ 0) ประมวลผลต่อ - อัปเดต
pos_sync_machine.last_bill_at - เรียก
processPosBillแล้วตั้งเวลาตัดสต็อกเป็นชุด (schedulePosStockDrain)
- ผลลัพธ์
PosBillResult:bill_guid,doc_no,status(done|cancelled|error),error,duplicate,total_amount,earned_points--status = "error"ยังหมายถึง "เก็บบิลแล้ว" back office แก้แล้วประมวลผลซ้ำได้ (คอมเมนต์ :93-94) pos_bill_inbox.status: 0 รอ, 1 เสร็จ, 2 ผิดพลาด (คอมเมนต์ schemapos_sync.go:53)
แปลงเป็นเอกสารขาย processPosBill (:202) -- ใน transaction เดียว หมดเวลา 60 วินาที
- ล็อกอ่านแถวใน inbox (
FOR UPDATE) แล้วsavePosBillTx(:314):- อ่านค่าเครื่องจาก
pos_id(คลังpos_ic_wht, ที่เก็บpos_ic_shelf, สาขา, แผนก) +posconfig(ลูกค้าเริ่มต้นcust_code_default,vat_type,vat_rate) +pos_point_condition.baht_per_pointไม่พบเครื่อง = "ไม่พบเครื่อง POS" - ตรวจแต่ละบรรทัด: ต้องมีรหัสสินค้าใน
ic_inventory(loadPosItems:539), จำนวน > 0; ตัวตั้ง/ตัวหารหน่วยนับจากic_unit_use(ไม่พบหน่วย = 1/1); ยอดบรรทัด =qty * price - discountปัด 2 ตำแหน่ง - สร้างเอกสารด้วย
buildTransArgs+saveTransTxเป็นTransType 2,TransFlag 44,InquiryType 1(ขายสด) ส่วนลดท้ายบิล =bill_discountตั้งdeferStock = true(สต็อก/ต้นทุนไม่คำนวณในรอบนี้ แต่ลงคิวpos_stock_queueที่trans.go:573) - ป้องกันชนเอกสารอื่น: ถ้ามี
ic_transflag 44 เลขเดียวกันที่ไม่ใช่บิล POS ของเครื่องนี้ = error "เลขที่บิล ... ซ้ำกับเอกสารขายที่มีอยู่แล้ว" (บิล POS แทนที่ได้เฉพาะสำเนาก่อนหน้าของตัวเอง) - รวมยอดรับชำระ ต้องไม่น้อยกว่ายอดบิลที่เซิร์ฟเวอร์คำนวณ ไม่งั้น error "ยอดรับชำระ ... น้อยกว่ายอดบิล ..."
- แต้ม: ถ้า
baht_per_point > 0และมีสมาชิกที่ไม่ใช่ลูกค้าเริ่มต้น แต้ม = floor(ยอดบิล / บาทต่อแต้ม) (ปัดลงเป็นจำนวนเต็ม) - ตั้ง
ic_trans.is_pos = 1,pos_id,cashier_code,payment_method(posPaymentMethod:527 = วิธีเดียวหรือMIXED; ไม่ระบุ =CASH),sum_point; ตั้งic_trans_detail.is_pos = 1; ลบหัวเอกสารยกเลิก (flag 45) เดิมของบิลนี้ savePosPaymentTx(:424) เขียนcb_trans+cb_trans_detailใหม่ทั้งชุด; แล้วrebuildGLTxสร้างบัญชีแยกประเภทใหม่ (นอกขอบเขตโน้ตนี้ ดู ภาพรวมระบบบัญชี SML AI Account (Accounting Overview))
- อ่านค่าเครื่องจาก
- ถ้ามีรอยยกเลิกบน inbox (
cancelled_at) เรียกcancelPosBillTxต่อ แล้วแต้ม = 0 และstatus = "cancelled" - ตั้ง inbox
status = 1,processed_at,earned_points; คำนวณแต้มคงเหลือสมาชิกใหม่ (rebuildPosCustomerPoints) และยอดคงเหลือคูปอง (rebuildPosCoupon) ถ้ามี - ถ้าสำเร็จ พยายามเขียน
etax_document_logและcrm_customer_activityแบบ best-effort (ผิดพลาดแล้วเงียบ ไม่กระทบบิล,:247-284) ใช้เฉพาะแถวที่ยังไม่มีในตารางนั้น ๆ -- ค่าบางฟิลด์ในคำสั่ง INSERT เป็นค่าคงที่ที่โค้ดเขียนเอง (เช่นสถานะ e-Tax) ไม่ได้มาจากบริการภายนอก (อ่านจาก SQL; ยังไม่ได้ตรวจว่าตารางทั้งสองมีผู้ใช้ปลายทางอื่นหรือไม่) - ถ้าพลาด: ความผิดของข้อมูล (error ตรวจสอบ หรือ PG class 22/23) บันทึก
status = 2+ ข้อความลง inbox แล้วตอบstatus = "error"; ความผิดอื่น (โครงสร้างพื้นฐาน) คืน error ให้เครื่องลองส่งใหม่ภายหลัง (:286-303)
วิธีรับชำระ (savePosPaymentTx :424)
แยกยอดตาม method (ไม่สนตัวพิมพ์): TRANSFER/BANK -> โอน, PROMPTPAY/QR/WALLET -> วอลเล็ต/QR, CARD/CREDIT_CARD/CREDIT -> บัตร, อื่น ๆ ทั้งหมด -> เงินสด จัดสรรยอดบิลตามลำดับ โอน -> วอลเล็ต -> บัตร (ส่วนที่เหลือเป็นเงินสดสุทธิ) เงินทอน = ยอดรับรวม - ยอดบิล (ต่ำสุด 0) เขียน cb_trans (flag 44) และ cb_trans_detail เฉพาะที่ไม่ใช่เงินสด โดย doc_type: 1 = โอน, 3 = บัตร, 21 = QR/วอลเล็ต (:490-497) ใส่ coupon_amount และ point_qty = แต้มที่ใช้แลก
ประมวลผลซ้ำ ReprocessPosBills (:175)
POST /api/v1/pos/bills/reprocess {"all": false} (UserOnly, เฉพาะ back office): เลือกบิลจาก inbox ที่ status <> 1 (หรือทั้งหมดถ้า all = true) เรียงตาม received_at สูงสุด 5000 บิล แล้ว processPosBill ทีละใบ ผลเป็นรายการ PosBillResult และคืน error ทันทีถ้าพลาดแบบโครงสร้างพื้นฐาน ใช้หลังแก้ข้อมูลหลัก (เช่นเพิ่มสินค้าที่ขาด) เพราะ payload ดิบยังอยู่ครบ
ยกเลิกบิล CancelPosBill (:575)
- เรียกจาก
PosShiftActionเมื่อaction = "cancel_bill"(pos_config.go:997): ตั้งcancelled_at,cancel_reason,cancelled_byบนแถว inbox (เครื่องยกเลิกได้เฉพาะบิลของตัวเอง; back office ไม่ระบุเครื่อง) แล้วประมวลผลบิลซ้ำ -- เก็บเป็นเหตุการณ์ดิบเพื่อให้การสร้างใหม่จาก inbox ยังคงยกเลิก cancelPosBillTx(:601) ตามคอมเมนต์อ้างอิง C#_cancelBillForm.cs:126-142: ตั้งlast_status = 1(และis_cancel = 1ที่หัว) ในic_trans,ic_trans_detail,cb_trans_detailของ flag 44; ลบแล้วเพิ่มหัวเอกสารtrans_flag = 45เลขleft('CNM-' || doc_no, 25)ที่doc_refชี้บิลเดิม; แล้วrebuildGLTxให้บิลที่ยกเลิกไม่มีรายการบัญชี ข้อความเหตุผลเริ่มต้น "ยกเลิกบิลขาย POS"
แต้มสมาชิกและคูปอง (สร้างใหม่ ไม่ลดทีละรายการ)
rebuildPosCustomerPoints(:636):point_balance= max(0, ผลรวมsum_pointของic_transที่last_status = 0และไม่ยกเลิก (flag 412 ติดลบ; นับเอกสาร POS หรือ flag 44, 410, 412) ลบด้วยผลรวมcb_trans.point_qtyของเอกสารที่ยังไม่ถูกยกเลิก) ดูเทียบ เครื่องมือกระบวนการระบบ ตรวจ ic_trans / คำนวณแต้มใหม่ / นำเข้าที่อยู่จัดส่ง (Sysproc Tools) (point-recalที่คำนวณsum_pointใหม่ย้อนหลัง)rebuildPosCoupon(:668):balance_amount= max(0,amount- ผลรวมcoupon_amountจากบิล inbox ที่status = 1และไม่ถูกยกเลิก) และถ้าคูปองใช้ครั้งเดียว (single_use = 1) ที่ถูกใช้แล้วจะตั้งlast_status = 1
ตัดสต็อก/ต้นทุนเป็นชุด (pos_stock_queue)
saveTransTxลงรหัสสินค้า (ของเดิมและใหม่ เพื่อให้บิลที่ประมวลผลซ้ำแก้บรรทัดที่หายด้วย) ลงpos_stock_queueเมื่อdeferStock(trans.go:573,queuePosStock:517)schedulePosStockDrain(:686) รวมการรีบิลด์ของบิลหลายใบในช่วงเดียวกันให้เหลือหนึ่งรอบต่อฐานบริษัท หน่วงเวลา 2 วินาที (posStockDrainDelay) -- ขายสินค้าเดียวกัน 100 บิลรีบิลด์รหัสนั้นครั้งเดียวDrainPosStock(:703) ลบจากคิวครั้งละ 200 รหัส (FOR UPDATE SKIP LOCKEDให้หลายอินสแตนซ์ทำงานร่วมกันได้) แล้วrebuildStockTxถ้ารอบใดพลาด รายการยังค้างคิวรอรอบของบิลถัดไป ปริมาณค้างคิวเห็นได้ในstock_queueของListPosDevicesดู POS ซิงก์ข้อมูลเครื่องแคชเชียร์ ผูกเครื่อง/ดึงส่วนต่าง/กู้คืน/รูปภาพ (POS Device Sync)
ที่มาในโค้ด
backend/internal/application/process/pos_bill.go:20-56- คอมเมนต์หลัก,posBillFlag(44),Decbackend/internal/application/process/pos_bill.go:58-120- ชนิดข้อมูลบิล,parsePosBillbackend/internal/application/process/pos_bill.go:122-198-ReceivePosBill,ReprocessPosBillsbackend/internal/application/process/pos_bill.go:200-304-processPosBillbackend/internal/application/process/pos_bill.go:312-419-savePosBillTxbackend/internal/application/process/pos_bill.go:421-513-savePosPaymentTxbackend/internal/application/process/pos_bill.go:515-570-queuePosStock,posPaymentMethod,loadPosItemsbackend/internal/application/process/pos_bill.go:572-680- ยกเลิกบิล, แต้ม, คูปองbackend/internal/application/process/pos_bill.go:682-736- คิวตัดสต็อกเป็นชุดbackend/internal/application/process/trans.go:573-578- ลงคิวสต็อกเมื่อdeferStockbackend/internal/adapter/inbound/http/pos/sync.go:110-125-PostBillbackend/internal/application/process/pos_sync.go:54-70- schemapos_bill_inbox
ดูเพิ่ม
- POS ซิงก์ข้อมูลเครื่องแคชเชียร์ ผูกเครื่อง/ดึงส่วนต่าง/กู้คืน/รูปภาพ (POS Device Sync)
- POS ตั้งค่าร้าน/เครื่อง/โปรโมชัน และกะขาย รับ-ส่งเงิน (POS Config and Shift)
- สถานะต่อเนื่องของเอกสาร กราฟโยงเอกสาร และเลขที่เอกสารถัดไป (Doc flow & numbering)
- ยกเลิกและอนุมัติเอกสาร (Document cancel / approve)
- เครื่องมือกระบวนการระบบ ตรวจ ic_trans / คำนวณแต้มใหม่ / นำเข้าที่อยู่จัดส่ง (Sysproc Tools)
- ภาพรวมระบบบัญชี SML AI Account (Accounting Overview)
- ภาพรวมกลุ่ม process ของ Backend (Process overview)