#history#transaction#atomic#error-handling
ภาพรวม
งานหลายขั้นที่เคยเขียนทีละ Exec และทิ้งค่า error ทำให้ได้ข้อมูลครึ่งๆ กลางๆ แต่ตอบสำเร็จ ช่วง 2026-10-21..24 แก้ให้เขียนใน tx เดียว (ExecBatch / pgx.Tx) คืน error ทุกจุด และให้ค่าที่ derive ได้ (ยอด bin, เครดิต) คำนวณซ้ำได้ผลเดิม
อาการ
- A (a47413ec, 5e3dbd36): WMS ส่งของ (SHIPPED) และบันทึก movement เขียน log ดิบกับยอด bin/order/stop แยกคำสั่ง ถ้าขั้นกลางพังก็ตอบสำเร็จ ยอด bin ลดแบบเพิ่มทีละก้อนและหยุดที่ 0 (
GREATEST(0, ...)) ตามที่เห็นใน diff - B (4de91436): สั่งซื้อ B2B ซ้ำ (retry) ตัดเครดิตผู้แทนจำหน่ายสองครั้ง เพราะบวก
credit_used + x; ตามข้อความ commit - C (4de91436): เลขเอกสาร WMS/TMS/RFQ รูปแบบ HHMMSS ชนกันภายในวินาที/นาที
- D (a47413ec): บันทึกเอกสาร e-Tax ล้มเหลวแต่ตอบ 200; ปัดภาษีผิด (WHT 1% ของ 14.50 ได้ 0.14 แทน 0.15); สั่งซื้อ/ทริปที่ insert หลักพังแล้วยังตัดเครดิตต่อ; ข้อความ
fmt.Sprintfขาด verb เก็บ...ERP%!(EXTRA string=PCS)ในpack_size_desc(B9) - E (239b0997, 2ab00c67, dc975db9): frontend เขียน SQL ตรงเข้าตารางนอก transaction ของ backend (THIN-SQL-xx ตามข้อความ commit 239b0997; ยังไม่ได้ตรวจ diff ละเอียด); serial/shipment เคยไม่ถูกเขียนพร้อมเอกสาร
สาเหตุ (root cause)
- A/D:
_, _ = Exec(...)แล้วตอบ OK ก่อนรู้ผล ผิด AGENTS.md ข้อ 20 - B: ค่า
credit_usedเป็น state สะสมที่อัปเดตแบบ+xจึงไม่ idempotent เมื่อ request ซ้ำ - C: เลขเอกสารใช้เวลาแค่ HHMMSS ไม่มีส่วนสุ่ม
- E: ขัดกฎ thin-client (AGENTS.md ข้อ 10): เขียนข้อมูลผ่าน
query/execจาก browser ทำให้ไม่อยู่ใน tx ของเอกสาร
วิธีแก้
- A:
AdvanceFulfillmentและCreateMovementรวม movement log +WmsBinOpeningSQL+WmsBinRebuildSQL+ อัปเดต order/stop ไว้ในExecBatchเดียว คืน error; ยอด bin = OPENING + SUM(movement ที่มีเครื่องหมาย) ไม่ clamp ที่ 0 และเตือนเมื่อติดลบ - B: ล็อกแถว profile ของผู้แทนแล้วคำนวณ
credit_used/credit_available/rebateใหม่จาก opening + SUM ของmarket_b2b_order(ไม่รวมสถานะ CANCEL) ทุกครั้ง; คอลัมน์credit_used_opening/rebate_openingเก็บยอดตั้งต้นที่ไม่มีแถวออเดอร์อธิบาย - C:
docno.Stamp(layout)= เวลา + มิลลิวินาที + แท็ก hex สุ่ม 4 ตัว - D: etax คำนวณด้วย
big.Ratปัดครึ่งออกจากศูนย์ (etaxAmounts) และตอบ error ถ้าบันทึกพัง; market/tms หยุดเมื่อ insert หลักพัง; แก้%sที่หาย - E: งาน serial/shipment อยู่ใน
saveTransTx/deleteTransTxแล้ว rebuild ใน tx เดียวกัน (ดู บทเรียน Serial / Lot / ข้อมูลขนส่ง (Serial, Lot & Shipment Persistence)); งานเขียนจาก frontend ย้ายเข้า backend (เช่นStockBeginningBalanceScreen.tsxลด 361 บรรทัด)
commit ที่เกี่ยวข้อง
a47413ec— WMS atomic, e-tax big.Rat + คืน error, หยุดเมื่อ insert หลักพัง, แก้ Sprintf4de91436— เช็กเอาต์ตลาดแบบ atomic + เครดิตแบบ derive, TMS/procure atomic,docno.Stamp239b0997— ย้าย SQL เขียนจาก frontend ไป backend + lockdownquery/exec5e3dbd36— rebuild ยอด bin จาก OPENING + movement2ab00c67,dc975db9— serial/shipment บันทึกใน tx เดียวกับเอกสาร
โค้ดปัจจุบันที่ป้องกัน
adapter/inbound/http/wms/handler.go:505-514ExecBatch ใน AdvanceFulfillment,:534CreateMovement,:1995WmsBinOpeningSQL,:2037WmsBinRebuildSQLtms/handler.go:554,569และprocure/handler.go:1382,1398ใช้ opening + movement log ใน batchmarket/handler.go:1874marketDealerOpeningSQL,:1885marketDealerBalanceSQL;:365,387ข้อความ(หน่วย %s)etax/handler.go:461ตอบ error เมื่อบันทึกพัง,:1016etaxAmounts;platform/docno/docno.go:23Stamp- ยังเหลือ
_, _ = ExecBatchที่wms/handler.go:840,1737(buildOverview; ดูเป็นข้อมูลสาธิต ยังไม่ได้ตรวจเต็ม)
บทเรียน / กฎที่ได้
- ห้ามทิ้ง error ของคำสั่งเขียน; งานหลายขั้นที่ต้องไปด้วยกันรวมเป็น batch เดียวแล้วคืน error
- ค่าที่ derive ได้ (ยอดคงเหลือ, เครดิต) ให้คำนวณจากแหล่งเดิม ไม่สะสม
+xเพื่อให้ retry ปลอดภัย (ข้อ 9) - เลขเอกสารต้องมีส่วนสุ่ม/มิลลิวินาที ไม่ใช่เวลาระดับวินาที
- การเขียนที่กระทบเอกสารต้องอยู่ฝั่ง backend ใน tx เดียว
- รัน
go vetทุกครั้ง จับSprintfที่ขาด verb ได้
ดูเพิ่ม
ADR-0009 /query/execBatch เขียนหลาย statement ใน transaction เดียว (atomic) · ADR-0011 ล็อก SQL เขียนจาก browser: ตารางเงิน/สต๊อก/GL ผ่าน /api/process เท่านั้น · นโยบายล็อกการเขียนผ่าน Query Gateway (Query write policy / lockdown) · ความถูกต้องของข้อมูล: เงิน ภาษี Transaction เดียว และ Rebuild (Data Integrity, big.Rat, ExecBatch) · บทเรียนบั๊กการปัดเศษและทศนิยมเงิน (Rounding & Decimal Bugs) · บทเรียน Serial / Lot / ข้อมูลขนส่ง (Serial, Lot & Shipment Persistence)
ที่มาในโค้ด
backend/internal/adapter/inbound/http/wms/handler.go:505,534,1995,2037backend/internal/adapter/inbound/http/market/handler.go:1874,1885backend/internal/platform/docno/docno.go:23- git:a47413ec, git:4de91436, git:5e3dbd36, git:239b0997