เมื่อส่วนลดติดลบในใบลดหนี้
โปรแกรมคำนวณถูก แต่ทำไมคนถึงบอกว่าผิด?

มีเรื่องเล็ก ๆ จากการทำ ERP ที่ผมเพิ่งนึกขึ้นมาได้ว่า
บางทีคนเขียนโปรแกรมกับคนทำธุรกิจ อาจกำลัง “อ่านตัวเลข” คนละแบบกัน
เป็นเรื่องของใบลดหนี้ครับ
"ลดหนี้ หมายถึง ลดมูลค่าเงินที่เป็นหนี้ต่อกันทางบัญชี
แต่สาเหตุของการลดหนี้ โดยทั่วไปเป็นไปได้สองกรณี
“ราคาสินค้าลดลง” เป็นการคืนเงินจากส่วนต่างมูลค่าจากราคาที่เปลี่ยน
“จำนวนสินค้าลดลง” หรือ “คืนสินค้า” เป็นการคืนเงินจากส่วนต่างมูลค่าจากจำนวนที่เปลี่ยน แล้วกระทบไปสู่สินค้าคงเหลือที่เปลี่ยนด้วย
ลองสมมติว่ามีการขายสินค้า
ราคาชิ้นละ 1,000 บาท จำนวน 2 ชิ้น ส่วนลด 45 บาท
ดังนั้น
1,000 × 2 − 45 = 1,955 บาท
ทุกคนเข้าใจตรงกัน
โปรแกรมเมอร์ก็เข้าใจ นักบัญชีก็เข้าใจ คนขายก็เข้าใจ
ไม่มีอะไรซับซ้อน
ทีนี้ลูกค้าคืนสินค้าทั้งบิล
ERP ก็ต้องออกใบลดหนี้
ในระบบ เราใช้วิธีบันทึกจำนวนสินค้าเป็นค่าลบ
จากเดิม
จำนวน = 2
กลายเป็น
จำนวน = -2
วิธีนี้มีเหตุผลอยู่แล้ว เพราะมันทำให้ transaction ของสต็อกเดินตามธรรมชาติ
ขายไป 2
+2
คืนกลับ 2
-2
สุดท้ายเหลือศูนย์
เราไม่จำเป็นต้องสร้าง flag พิเศษขึ้นมาอีกว่า “รายการนี้เป็นการคืนสินค้า” เพราะตัวเลข -2 เองก็บอกเรื่องราวของมันได้
ผมชอบแนวคิดนี้นะครับ
เพราะ ERP เป็นระบบที่ตัวเลขสามารถเชื่อมโยงกันไปทั้งระบบได้
แต่ปัญหาเกิดขึ้นตรงส่วนลด 45 บาท
เมื่อจำนวนกลายเป็น -2 โปรแกรมก็ยังใช้สูตรเดิม
1,000 × -2 − 45
คำตอบคือ
-2,045 บาท
ตอนเห็นครั้งแรกก็ต้องหยุดนิดหนึ่ง
เพราะถ้าถามคนทั่วไปว่า
“บิลเดิม 1,955 บาท แล้วคืนสินค้าทั้งบิล ใบลดหนี้ควรเป็นเท่าไหร่?”
คำตอบน่าจะเป็น
-1,955 บาท
ไม่มีใครน่าจะตอบว่า
-2,045 บาท
ทั้งที่โปรแกรมก็ไม่ได้คำนวณผิด
ความแปลกอยู่ตรงไหน?
ถ้ามองเฉพาะคณิตศาสตร์ สูตรนี้ถูกต้อง
1,000 × -2 − 45 = -2,045แต่ถ้ามองว่าใบลดหนี้กำลัง “ย้อนกลับ” บิลเดิม เราอาจกำลังคิดอีกแบบ
บิลเดิมคือ
(1,000 × 2) − 45= 1,955ถ้าย้อนกลับทั้งรายการ ก็เป็น
-(1,955)= -1,955หรือถ้าแยกเป็นรายการ
ราคาสินค้า +2,000
ส่วนลด -45
-----------------------
สุทธิ 1,955เมื่อ reverse
ราคาสินค้า -2,000
ส่วนลด +45
-----------------------
สุทธิ -1,955จุดที่น่าสนใจจึงไม่ได้อยู่ที่เครื่องหมาย -
แต่เป็นคำถามว่า
เราเอาเครื่องหมายลบไปใส่ตรงไหน และกำลังบอกอะไรด้วยเครื่องหมายนั้น?
เพราะ “ส่วนลด” อาจไม่ได้หมายถึงสิ่งเดียวกับที่โปรแกรมคิด
เวลาผมเขียนโปรแกรม ผมอาจมองว่า
ส่วนลด 45 บาท = ตัวเลข 45 ที่ต้องนำไปลบ
มันจึงเป็นเรื่องธรรมดามากที่จะเขียน
net = price * qty - discountแต่คนทำธุรกิจอาจไม่ได้มองส่วนลดแบบนั้น
เขาอาจมองว่า
บิลนี้เคยให้ส่วนลดไป 45 บาท
เมื่อมีการยกเลิกหรือคืนรายการเดิม สิ่งที่กำลังทำจึงไม่ใช่ “ลดอีก 45 บาท”
แต่เป็น
“ย้อนกลับสิ่งที่เคยทำไป”
ดังนั้นส่วนลดเดิมที่เคยทำให้ยอดลดลง 45 บาท เมื่อ reverse กลับต้องกลายเป็นการเพิ่ม 45 บาท
ตรงนี้เป็นเรื่องของความหมาย มากกว่าคณิตศาสตร์
ที่น่าสนใจคือ มันไม่ได้เกิดขึ้นบ่อย
ถ้าเป็นส่วนลด 10%
อาจไม่เกิดปัญหาแบบนี้
ถ้าเป็นการลดราคาต่อหน่วย ก็อาจไม่เกิดปัญหาแบบนี้
แต่กรณีนี้เป็นส่วนลดแบบจำนวนเงินที่ใส่ หลังจากคำนวณราคาคูณจำนวนแล้ว
เช่น
2,000 − 45
แล้วบังเอิญรายการนี้ถูกนำไปทำใบลดหนี้โดยเปลี่ยนจำนวนเป็น -2
จึงเกิดกรณีประหลาดขึ้นมา
มันเป็น edge case ที่ค่อนข้างเฉพาะตัว
ใบลดหนี้ก็ไม่ได้เกิดทุกวันเหมือนใบขาย
คืนสินค้าทั้งบิลก็ไม่ได้เกิดทุกวัน
และต้องบังเอิญมีส่วนลดแบบจำนวนเงินหลังคูณจำนวนอีก
ระบบจึงอาจทำงานมาเป็นเวลานานโดยไม่มีใครพบเรื่องนี้
ผมก็ไม่เคยเจอ
จนกระทั่งวันหนึ่ง มีข้อมูลชุดนี้เข้ามา
แล้วตัวเลขก็ฟ้องขึ้นมาว่า
“เดี๋ยวนะ… ทำไมคืนของ 1,955 บาท กลายเป็นลดหนี้ 2,045 บาท?”
ผมว่าโปรแกรมเมอร์ไม่ได้ผิด
จริง ๆ แล้วผมไม่ค่อยอยากเรียกเรื่องนี้ว่า bug ของโปรแกรมเมอร์ด้วยซ้ำ
เพราะถ้าเอาโจทย์มาให้ตั้งแต่แรกว่า
ราคา 1,000 จำนวน -2 ส่วนลด 45 สูตรคือ ราคา × จำนวน − ส่วนลด
โปรแกรมเมอร์ก็คงตอบ
-2,045
ไม่มีเหตุผลที่จะตอบอย่างอื่น
สิ่งที่หายไปในโจทย์ ไม่ใช่ตัวเลข
แต่คือ ความหมายของรายการ
-2 อาจไม่ได้หมายถึงเพียง “จำนวนติดลบ 2”
แต่มันอาจหมายถึง
“คืนสินค้าที่เคยขายไป 2 ชิ้น”
และเมื่อมองอย่างนั้น
ใบลดหนี้ก็อาจไม่ได้เป็น transaction ใหม่ที่เอาค่าลบไปใส่ในสูตรเดิมทั้งหมด
แต่มันคือการ
ย้อนกลับผลของ transaction เดิม
ซึ่งเป็นคนละวิธีคิดกัน
แล้วผมก็ลองแก้ปัญหาแบบไม่เพิ่ม flag
หลังจากเจอเคสนี้ ผมปรับวิธีคำนวณส่วนลดใหม่
ไม่ได้เพิ่ม flag ว่า
ถ้าเป็นใบลดหนี้ ให้คำนวณแบบนี้
เพราะนั่นจะทำให้เรากลับไปสู่โลกที่เอกสารแต่ละประเภทต้องมีตรรกะพิเศษของตัวเอง
ผมลองแยก “เครื่องหมาย” ออกจาก “มูลค่าที่จะคำนวณ”
ขั้นตอนจึงประมาณนี้
ดูก่อนว่าค่าตั้งต้นเป็นบวกหรือลบ
เอาค่าสัมบูรณ์มาใช้คำนวณส่วนลด
คำนวณด้วยตรรกะเดิม
ถ้าค่าตั้งต้นเป็นลบ ก็กลับเครื่องหมายของผลลัพธ์กลับไป
กรณีเดิมจึงกลายเป็น
ค่าตั้งต้น = 1,000 × -2
= -2,000เอาเฉพาะมูลค่ามาคำนวณ
abs(-2,000) − 45
= 2,000 − 45
= 1,955แล้วคืนเครื่องหมายเดิม
-1,955ผลลัพธ์จึงตรงกับสิ่งที่เราต้องการ
และที่ผมชอบคือ เราไม่ได้บอกโปรแกรมว่า
“นี่คือใบลดหนี้นะ”
เราเพียงบอกมันว่า
“ค่าตั้งต้นนี้มีทิศทางเป็นบวกหรือลบ”
จากนั้นคำนวณ “มูลค่า” แยกออกจาก “ทิศทาง” แล้วค่อยนำกลับมาประกอบกัน
เรื่องเล็ก ๆ แต่ทำให้คิดเรื่อง ERP ใหม่อีกครั้ง
ผมชอบปัญหาแบบนี้ตรงที่มันเล็กมาก
เวลาพูดถึงการออกแบบ ERP เรามักคุยเรื่องใหญ่ ๆ
Accounting
Inventory
Document Flow
Tax
Costing
Integration
แต่บางครั้งปัญหาที่ทำให้เราต้องกลับไปคิดเรื่อง design ใหม่ กลับเป็นตัวเลขเล็ก ๆ แค่ 45 บาท
และที่น่าสนใจกว่านั้นคือ ไม่มีใครผิดเลย
โปรแกรมเมอร์ไม่ได้คำนวณผิด
คนทำธุรกิจก็ไม่ได้เข้าใจเลขผิด
แต่ทั้งสองคนกำลังมองคำว่า “ส่วนลด” และ “คืนสินค้า” จากคนละมุม
โปรแกรมเมอร์เห็น
-2
คนทำธุรกิจเห็น
“คืนของ 2 ชิ้น”
โปรแกรมเมอร์เห็น
discount = 45
คนทำธุรกิจเห็น
“ส่วนลดที่เคยให้ไป 45 บาท”
พอเอาสองความหมายนี้มาเจอกันในสูตรเดียวกัน จึงเกิดเรื่องที่ดูเหมือนขัดแย้งขึ้นมา
บางทีนี่อาจเป็นอีกเหตุผลหนึ่งที่ทำให้การทำ ERP ไม่เหมือนการเขียนโปรแกรมคำนวณทั่วไป
เพราะสุดท้ายแล้ว
ตัวเลขใน ERP ไม่ได้มีแค่ค่า แต่มีความหมายของเหตุการณ์ที่เกิดขึ้นอยู่ข้างหลังตัวเลขเหล่านั้นด้วย
และบางครั้ง เราอาจต้องเข้าใจเหตุการณ์นั้นให้ได้เสียก่อน
แล้วค่อยเขียนสูตรครับ
ที่จริงแล้ว ผมคิดว่าบทเรียนจากส่วนลด 45 บาทครั้งนี้ ไม่ได้อยู่ที่การใช้ abs() ด้วยซ้ำ เพราะถ้าเราแก้ปัญหาว่า “ถ้าติดลบก็ให้คำนวณอีกแบบ” วันหนึ่งก็คงมีกรณีใหม่ให้ต้องเพิ่มเงื่อนไขอีก
สิ่งที่น่าสนใจกว่าคือ
เราเพิ่งเห็นว่า เครื่องหมายของตัวเลขใน ERP อาจกำลังบอกเหตุการณ์บางอย่างอยู่
-2 ไม่ได้บอกเพียงว่า “จำนวนติดลบ”
แต่มันกำลังบอกว่า
มีการคืนสินค้าจำนวน 2 ชิ้น
-1,955 ก็ไม่ได้เป็นเพียง “ยอดติดลบ”
แต่มันกำลังบอกว่า
กำลังย้อนกลับมูลค่า 1,955 บาทของรายการเดิม
เมื่อคิดแบบนี้ ปัญหาที่เกิดขึ้นจึงอาจไม่ใช่เรื่องสูตรผิด
แต่เป็นเพราะเราเอา เครื่องหมายของรายการ ไปปนกับ วิธีคำนวณมูลค่า
ตอนแรกโปรแกรมเห็น
-2,000 - 45
แต่คนใช้ไม่ได้กำลังบอกว่า
“เอา -2,000 ไปหักด้วย 45”
เขากำลังบอกว่า
“ช่วยย้อนกลับรายการเดิมที่มีมูลค่า 1,955 บาท”
สองประโยคนี้ให้ความรู้สึกเหมือนกันมาก เมื่อมองจากตัวเลข แต่กลับเป็นคนละเรื่อง เมื่อมองจากเหตุการณ์
แล้วผมก็เริ่มสงสัยว่า ใน ERP ที่เราเขียนกันอยู่ทุกวัน มีตัวเลขอีกกี่ตัวที่เราเห็นเป็นเพียง “ค่า” แต่คนใช้เห็นเป็น “เหตุการณ์”
ยอดติดลบ อาจหมายถึงการคืนเงิน
จำนวนติดลบ อาจหมายถึงการคืนสินค้า
ยอดคงค้าง อาจหมายถึงสิ่งที่ยังไม่ได้เกิดขึ้นครบ
ตัวเลขเหล่านี้อาจดูเหมือนเป็นข้อมูลชนิดเดียวกันในโปรแกรม
แต่ในโลกจริง มันอาจกำลังเล่าเรื่องคนละเรื่องกัน
นี่ทำให้ผมกลับมาคิดว่า เวลาที่เราออกแบบข้อมูลใน ERP เราควรถามเพียงว่า
“ตัวเลขนี้มีค่าเท่าไร?”
หรือเราควรถามอีกคำถามหนึ่งด้วยว่า
“ตัวเลขนี้กำลังบอกว่าเกิดอะไรขึ้น?”
และบางทีคำถามที่สำคัญกว่านั้นอาจเป็น
ถ้าเราเอาเครื่องหมายของตัวเลขออกไป เหลืออะไรที่บอกเราได้ว่าเหตุการณ์นี้คืออะไร?
Transaction สต็อคออกที่ติดลบ อาจหมายถึงการรับคืนสินค้าฝั่งขาย และถ้าเป็นสต็อคเข้าติดลบเป็นการส่งคืนสินค้าฝั่งซื้อ
เพราะถ้าคำตอบคือ “ไม่เหลืออะไรเลย”
นั่นอาจหมายความว่า เราไม่ได้เก็บความหมายของเหตุการณ์ไว้ในข้อมูล
เราเพียงเก็บผลลัพธ์ของการคำนวณไว้เท่านั้น
และสำหรับ ERP ผมไม่แน่ใจว่าสองอย่างนี้ควรเป็นเรื่องเดียวกันหรือเปล่าครับ



ความคิดเห็น