สคีมบัญชีเรียกเก็บที่กระทบยอดได้: ใบแจ้งหนี้และการชำระเงิน
เรียนรู้วิธีออกแบบสคีมบัญชีเรียกเก็บโดยแยกใบแจ้งหนี้ การชำระเงิน เครดิต และการปรับ เพื่อให้ฝ่ายการเงินกระทบยอดและตรวจสอบยอดรวมได้ง่าย

ทำไมข้อมูลบิลถึงกระทบยอดไม่ได้
สำหรับฝ่ายการเงิน “กระทบยอด” คือคำสัญญาง่าย ๆ: ยอดรวมในรายงานตรงกับระเบียนต้นทาง และตัวเลขทุกตัวสามารถย้อนแหล่งที่มาได้ ถ้าเดือนนั้นบอกว่าเก็บเงินได้ $12,430 คุณควรชี้ไปยังการชำระเงินจริง (และการคืนเงินใด ๆ) บอกได้ว่าเงินเหล่านั้นใช้กับใบแจ้งหนี้ไหน และอธิบายความแตกต่างทุกอย่างด้วยระเบียนที่มีวันที่
ข้อมูลการเรียกเก็บมักหยุดกระทบยอดเมื่อฐานข้อมูลเก็บผลลัพธ์แทนความจริง คอลัมน์อย่าง paid_amount, balance, หรือ amount_due ถูกอัปเดตเมื่อเวลาผ่านไปด้วยตรรกะของแอปฯ บั๊กหนึ่งครั้ง การลองใหม่หนึ่งครั้ง หรือการ "แก้ไข" ด้วยมือเดียว อาจเปลี่ยนประวัติได้โดยเงียบ ๆ หลายสัปดาห์ต่อมา ตารางใบแจ้งหนี้อาจบอกว่าใบแจ้งหนี้ "จ่ายแล้ว" แต่แถวการชำระเงินไม่ตรงกัน หรือมีการคืนเงินโดยไม่มีเครดิตที่ตรงกัน
สาเหตุทั่วไปอีกประการคือการผสมประเภทเอกสารต่างกันเข้าด้วยกัน ใบแจ้งหนี้ไม่ใช่การชำระเงิน รายการเครดิตไม่ใช่การคืนเงิน การปรับปรุงไม่เหมือนกับส่วนลด เมื่อสิ่งเหล่านี้ถูกยัดรวมในแถว "transactions" เดียวที่มีฟิลด์แบบเลือกมากมาย การรายงานจะกลายเป็นการเดา และการตรวจสอบจะกลายเป็นการโต้แย้ง
ความไม่ตรงกันพื้นฐานชัดเจน: แอปฯ มักสนใจสถานะปัจจุบัน ("การเข้าถึงยังใช้งานอยู่ไหม?"), ขณะที่ฝ่ายการเงินสนใจเส้นทางเหตุการณ์ ("เกิดอะไรขึ้น เมื่อไหร่ และทำไม?") สคีมบัญชีเรียกเก็บต้องรองรับทั้งสองอย่าง แต่การย้อนรอยต้องเป็นฝ่ายชนะ
ออกแบบเพื่อผลลัพธ์นี้:
- ยอดรวมชัดเจนต่อผู้ใช้แต่ละราย ต่อใบแจ้งหนี้ และต่องวดทางบัญชี
- ทุกการเปลี่ยนแปลงถูกบันทึกเป็นแถวใหม่ (ไม่ถูกเขียนทับ)
- โซ่เชื่อมจากใบแจ้งหนี้ไปยังการชำระเงิน เครดิต การคืนเงิน และการปรับปรุงครบถ้วน
- สามารถคำนวณยอดรวมใหม่จากรายการดิบและได้คำตอบเดียวกัน
ตัวอย่าง: หากลูกค้าชำระ $100 แล้วได้เครดิต $20 รายงานของคุณควรแสดงว่าเก็บเงินได้ $100, ได้เครดิต $20, และสุทธิ $80 โดยไม่แก้ไขยอดใบแจ้งหนี้ต้นฉบับ
แยกใบแจ้งหนี้ การชำระเงิน เครดิต และการปรับปรุง
ถ้าคุณต้องการสคีมบัญชีเรียกเก็บที่กระทบยอดได้ ให้ถือแต่ละประเภทเอกสารเป็นเหตุการณ์ชนิดต่างกัน การผสมเป็นตาราง "transactions" เดียวอาจดูเป็นระเบียบ แต่จะทำให้ความหมายคลุมเครือ
-
ใบแจ้งหนี้เป็นการเรียกร้อง: "ลูกค้าค้างชำระ" เก็บเป็นเอกสารที่มีหัวกระดาษ (ลูกค้า เลขที่ใบแจ้งหนี้ วันออก วันถึงกำหนด สกุลเงิน ยอดรวม) และบรรทัดรายการแยกต่างหาก (สิ่งที่ขาย จำนวน ราคาต่อหน่วย หมวดภาษี) เก็บยอดหัวกระดาษไว้เพื่อความรวดเร็วได้ แต่คุณต้องสามารถอธิบายยอดรวมจากบรรทัดได้เสมอ
-
การชำระเงินคือการเคลื่อนย้ายเงิน: "เงินจากลูกค้ามาที่เรา" ในการชำระด้วยบัตรมักมีการอนุมัติ (authorization) และการจับเงินจริง (capture) หลายระบบเก็บการอนุมัติไว้เป็นระเบียนเชิงปฏิบัติการและใส่เฉพาะการจับเงินที่ถูกโพสต์เข้า ledger เพื่อไม่ให้การรายงานเงินสดบวม
-
บันทึกเครดิต (credit memo) ลดสิ่งที่ลูกค้าต้องจ่ายโดยไม่จำเป็นต้องคืนเงิน
-
การคืนเงิน (refund) คือเงินสดออก พวกมันมักเกิดพร้อมกันแต่ไม่เหมือนกัน
สรุปการเคลื่อนไหวบัญชี:
- Invoice: เพิ่มลูกหนี้และรายได้ (หรือรายได้รอการตัด)
- Payment: เพิ่มเงินสดและลดลูกหนี้
- Credit memo: ลดลูกหนี้
- Refund: ลดเงินสด
การปรับปรุง (adjustment) คือการแก้ไขเมื่อความเป็นจริงไม่ตรงกับระเบียน การปรับต้องมีบริบทเพื่อให้ฝ่ายการเงินเชื่อถือได้ บันทึกว่าใครสร้าง เมื่อไหร่ รหัสเหตุผล และหมายเหตุสั้น ๆ เช่น “ตัดหนี้ 0.03 เพราะปัดเศษ” หรือ “ย้ายยอดจากระบบเก่า”
กฎปฏิบัติ: ถามตัวเองว่า "ถ้าไม่มีใครผิดพลาด เอกสารนี้จะยังมีอยู่ไหม?" ใบแจ้งหนี้ การชำระเงิน บันทึกเครดิต และการคืนเงิน ยังคงมีอยู่ การปรับควรพบเจอน้อย ชัดเจน และตรวจทานได้ง่าย
เลือกรูปแบบ ledger ที่ฝ่ายการเงินตรวจสอบได้
สคีมบัญชีเรียกเก็บที่กระทบยอดได้เริ่มจากแนวคิดเดียว: เอกสารอธิบายสิ่งที่เกิดขึ้น และการโพสต์ลง ledger พิสูจน์ยอดรวม ใบแจ้งหนี้ การชำระเงิน หรือบันทึกเครดิตเป็นเอกสาร ส่วน ledger คือชุดรายการที่บวกกันได้ เรียบร้อย
เอกสาร vs การโพสต์ (เก็บทั้งสองอย่าง)
เก็บเอกสารไว้ (หัวและบรรทัดของใบแจ้งหนี้ ใบเสร็จการชำระเงิน บันทึกเครดิต) เพราะคนต้องอ่านมัน แต่อย่าใช้ยอดรวมเอกสารเพียงอย่างเดียวเป็นแหล่งความจริงสำหรับการกระทบยอด
ให้โพสต์เอกสารแต่ละฉบับลงในตาราง ledger เป็นรายการคงตัวหนึ่งรายการหรือหลายรายการ จากนั้นฝ่ายการเงินสามารถรวมรายการตามบัญชี ลูกค้า สกุลเงิน และวันที่โพสต์แล้วได้คำตอบเดิมทุกครั้ง
โมเดลง่าย ๆ ที่เป็นมิตรต่อการตรวจสอบมีไม่กี่ข้อ:
- รายการคงตัว: ห้ามแก้ไขจำนวนที่โพสต์แล้ว การเปลี่ยนแปลงเป็นรายการใหม่
- เหตุการณ์การโพสต์ชัดเจน: เอกสารแต่ละฉบับสร้างชุดการโพสต์ด้วยอ้างอิงที่ไม่ซ้ำ
- ตรรกะสมดุล: รายการรวมกันอย่างถูกต้อง (มักเดบิตเท่ากับเครดิตในระดับบริษัท)
- แยกวันที่: แยกวันที่ของเอกสาร (ลูกค้าเห็น) กับวันที่โพสต์ (เข้ารายงาน)
- อ้างอิงเสถียร: เก็บอ้างอิงภายนอก (เลขที่ใบแจ้งหนี้ ID ของผู้ให้บริการชำระเงิน) ข้าง IDs ภายใน
กุญแจตามธรรมชาติ vs ID สำรอง
ใช้ ID สำรองเพื่อการเชื่อมและประสิทธิภาพ แต่เก็บกุญแจตามธรรมชาติที่คงทนด้วย เช่น เลขที่ใบแจ้งหนี้ และ ID ผู้ให้บริการ (เช่นรหัสชาร์จของเครื่องรับชำระ) ฝ่ายการเงินจะแจกแจง "Invoice INV-10483" แม้ ID ภายในจะเปลี่ยนไปแล้ว
ย้อนกลับโดยไม่ลบประวัติ
เมื่อต้องยกเลิก อย่าลบหรือเขียนทับ โพสต์การย้อนกลับ: รายการใหม่ที่สะท้อนยอดเดิมโดยใช้เครื่องหมายตรงข้าม ลิงก์กลับไปยังการโพสต์ต้นฉบับ
ตัวอย่าง: การชำระเงิน $100 ถูกใช้ไปกับใบแจ้งหนี้ผิด ทำเป็นสองขั้นตอน: ย้อนกลับการโพสต์ที่ใช้ผิดนั้น จากนั้นโพสต์การประยุกต์ใช้ใหม่กับใบแจ้งหนี้ที่ถูกต้อง
แบบร่างสคีมทีละขั้น (ตารางและคีย์)
สคีมบัญชีเรียกเก็บกระทบยอดได้ดีกว่าเมื่อแต่ละประเภทเอกสารมีตารางของตัวเองและเชื่อมด้วยตารางการจัดสรรที่ชัดเจน (แทนการเดาความสัมพันธ์ทีหลังกว่า)
เริ่มจากชุดตารางแกนหลักขนาดเล็ก แต่ละตารางมีคีย์หลักชัดเจน (UUID หรือ bigserial) และ foreign key ที่จำเป็น:
- customers:
customer_id(PK), และตัวระบุเสถียรเช่นexternal_ref(unique) - invoices:
invoice_id(PK),customer_id(FK),invoice_number(unique),issue_date,due_date,currency - invoice_lines:
invoice_line_id(PK),invoice_id(FK),line_type,description,qty,unit_price,tax_code,amount - payments:
payment_id(PK),customer_id(FK),payment_date,method,currency,gross_amount - credits:
credit_id(PK),customer_id(FK),credit_number(unique),credit_date,currency,amount
แล้วเพิ่มตารางที่ทำให้ยอดรวมตรวจสอบได้: allocations การชำระเงินหรือเครดิตหนึ่งรายการอาจครอบคลุมหลายใบแจ้งหนี้ และใบแจ้งหนี้หนึ่งใบอาจถูกชำระด้วยหลายการชำระเงิน
ใช้ตารางเชื่อมด้วยคีย์ของตัวเอง (อย่าใช้แค่คีย์ผสม):
- payment_allocations:
payment_allocation_id(PK),payment_id(FK),invoice_id(FK),allocated_amount,posted_at - credit_allocations:
credit_allocation_id(PK),credit_id(FK),invoice_id(FK),allocated_amount,posted_at
สุดท้าย เก็บการปรับแยกต่างหากเพื่อให้ฝ่ายการเงินเห็นว่ามีอะไรเปลี่ยนและทำไม ตาราง adjustments สามารถอ้างอิงเรคคอร์ดเป้าหมายด้วย invoice_id (nullable) และเก็บจำนวนเดลตา โดยไม่เขียนทับประวัติ
เพิ่มฟิลด์ตรวจสอบทุกที่ที่คุณโพสต์เงิน:
created_at,created_byreason_code(write-off, rounding, goodwill, chargeback)source_system(manual, import, Stripe, support tool)
เครดิต การคืนเงิน และการตัดหนี้โดยไม่ทำให้ยอดรวมพัง
ปัญหาการกระทบยอดส่วนใหญ่เริ่มเมื่อเครดิตและการคืนเงินถูกบันทึกเป็น "negative payments" หรือเมื่อการตัดหนี้ถูกผสมเข้ากับบรรทัดใบแจ้งหนี้ สคีมสะอาดเก็บแต่ละประเภทเอกสารเป็นระเบียนของตัวเอง และจุดเดียวที่พวกมันติดต่อกันคือผ่านการจัดสรรที่ชัดเจน
เครดิตควรแสดงเหตุผลที่ลดสิ่งที่ลูกค้าต้องจ่าย ถ้ามันใช้กับใบแจ้งหนี้หนึ่งใบ ให้บันทึกบันทึกเครดิตเดียวและจัดสรรไปยังใบแจ้งหนี้นั้น ถ้ามันใช้ครอบคลุมหลายใบ ให้จัดสรรบันทึกเครดิตเดียวไปยังหลายใบ เครดิตยังคงเป็นเอกสารเดียวที่มีการจัดสรรหลายรายการ
การคืนเงินเป็นเหตุการณ์แบบการชำระเงิน ไม่ใช่ "negative payment" การคืนเงินคือเงินสดออก ดังนั้นเก็บเป็นเรคคอร์ดแยก (มักเชื่อมโยงกับการชำระเงินต้นทางเพื่ออ้างอิง) แล้วจัดสรรเหมือนการชำระเงิน นี่ช่วยให้แทรซชัดเมื่อ statement ธนาคารแสดงทั้งเงินเข้าและเงินออก
การชำระเงินบางส่วนและเครดิตบางส่วนทำงานแบบเดียวกัน: เก็บยอดรวมของการชำระเงินหรือเครดิตบนแถวของตัวเอง แล้วใช้แถวจัดสรรเพื่อระบุว่าจัดสรรเท่าใดกับแต่ละใบแจ้งหนี้
กฎการโพสต์ที่ป้องกันการนับซ้ำ
กฎเหล่านี้กำจัดความแตกต่างลึกลับได้มาก:
- ห้ามเก็บการชำระเงินเป็นลบ ใช้รายการ refund แทน
- ห้ามลดยอดใบแจ้งหนี้หลังโพสต์ ใช้ credit memo หรือ adjustment
- โพสต์เอกสารเพียงครั้งเดียว (มี
posted_at) และห้ามแก้ไขจำนวนหลังโพสต์ - สิ่งเดียวที่เปลี่ยนยอดคงเหลือของใบแจ้งหนี้คือผลรวมของการจัดสรรที่โพสต์แล้ว
- การตัดหนี้เป็น adjustment ที่มีรหัสเหตุผล และจัดสรรไปยังใบแจ้งหนี้เหมือนเครดิต
ภาษี ค่าธรรมเนียม สกุลเงิน และการปัดเศษ
ปัญหาการกระทบยอดมักเริ่มจากยอดรวมที่คุณสร้างใหม่ไม่ได้ กฎที่ปลอดภัยที่สุดคือ: เก็บบรรทัดดิบที่สร้างบิล และเก็บยอดรวมที่แสดงให้ลูกค้าดูด้วย
ภาษีและค่าธรรมเนียม: เก็บในระดับบรรทัด
เก็บจำนวนภาษีและค่าธรรมเนียมต่อบรรทัด ไม่ใช่แค่ฟิลด์สรุปใบแจ้งหนี้ ผลิตภัณฑ์ต่างกันอาจมีอัตราภาษีต่างกัน ค่าธรรมเนียมอาจต้องเสียภาษีหรือไม่ และการยกเว้นมักใช้กับบางบรรทัดเท่านั้น ถ้าคุณเก็บแค่ tax_total เดียว สักวันคุณจะเจอกรณีที่อธิบายไม่ได้
เก็บ:
- บรรทัดดิบ (สิ่งที่ขาย qty unit price discount)
- ยอดคำนวณของบรรทัด (
line_subtotal,line_tax,line_total) - ยอดสรุปของใบแจ้งหนี้ (
subtotal,tax_total,total) - อัตราภาษีและประเภทภาษีที่ใช้
- ค่าธรรมเนียมเป็นบรรทัดแยก (เช่น "ค่าธรรมเนียมการประมวลผลการชำระเงิน")
นี่ทำให้ฝ่ายการเงินสร้างยอดรวมใหม่และยืนยันได้ว่าการคำนวณภาษีทำอย่างสม่ำเสมอ
บัญชีหลายสกุล: เก็บทั้งสิ่งที่เกิดขึ้นและวิธีรายงาน
ถ้าคุณรองรับหลายสกุล ให้บันทึกทั้งสกุลเงินรายการและยอดสำหรับการรายงาน ขั้นต่ำที่ใช้งานได้คือ: currency_code บนเอกสารการเงินทุกฉบับ, fx_rate ที่ใช้เมื่อโพสต์, และยอดสำหรับการรายงานแยกต่างหาก (เช่น amount_reporting) ถ้าบัญชีปิดในสกุลเงินเดียว
ตัวอย่าง: ลูกค้าถูกเรียกเก็บ 100.00 EUR บวก VAT 20.00 EUR เก็บบรรทัด EUR เหล่านั้นและยอดรวม พร้อม fx_rate ที่ใช้เมื่อโพสต์ และยอดแปลงสำหรับการรายงาน
การปัดเศษต้องมีการจัดการเฉพาะตัว เลือกกฎการปัดเศษหนึ่งแบบ (ต่อบรรทัดหรือทั้งใบ) และเมื่อการปัดทำให้เกิดความแตกต่าง ให้บันทึกเป็นรายการปรับปัดเศษแทนการเปลี่ยนยอดแบบเงียบ ๆ
สถานะ วันที่โพสต์ และสิ่งที่ไม่ควรเก็บเป็นข้อเท็จจริง
การกระทบยอดจะยุ่งเมื่อใช้ "สถานะ" เป็นทางลัดให้ความจริงทางบัญชี ให้ถือสถานะเป็นฉลากเวิร์กโฟลว์ และรายการ ledger ที่โพสต์แล้วเป็นข้อเท็จจริง
ทำให้สถานะเข้มงวดและน่าเบื่อ แต่ละสถานะควรตอบได้: เอกสารนี้มีผลต่อยอดรวมหรือไม่?
- Draft: ภายในเท่านั้น ยังไม่โพสต์ ห้ามขึ้นในรายงาน
- Issued: สรุปแล้วและส่งได้ พร้อมที่จะโพสต์ (หรือโพสต์แล้ว)
- Void: ยกเลิก; ถ้าโพสต์แล้ว ต้องมีการย้อนกลับ
- Paid: ชำระครบโดยการชำระเงินและเครดิตที่โพสต์แล้ว
- Refunded: เงินถูกส่งคืนผ่าน refund ที่โพสต์แล้ว
วันที่สำคัญกว่าที่หลายทีมคาด ฝ่ายการเงินจะถามว่า "อันนี้อยู่ในเดือนไหน?" และคำตอบของคุณไม่ควรขึ้นกับบันทึกกิจกรรม UI
issued_at: เมื่อใบแจ้งหนี้เป็นฉบับสมบูรณ์posted_at: เมื่อมันนับในรายงานการบัญชีsettled_at: เมื่อเงินชำระหรือยืนยันแล้วvoided_at/refunded_at: เมื่อการย้อนกลับมีผล
สิ่งที่ไม่ควรเก็บเป็นความจริง: ตัวเลขที่ได้จากการคำนวณแล้วที่สร้างใหม่ไม่ได้ เช่น balance_due, is_overdue, customer_lifetime_value เหล่านี้เป็นมุมมองแคชได้ถ้าคุณสามารถคำนวณใหม่จาก invoices, payments, credits, allocations และ adjustments เสมอ
ตัวอย่างเล็ก: การลองชำระเงินหลายครั้งโดน gateway ถ้าไม่มี idempotency_key คุณอาจเก็บการชำระเงินสองรายการ และทำเครื่องหมายใบแจ้งหนี้ว่า "paid" ฝ่ายการเงินจะเห็นเงินสดเกินมา เก็บ idempotency_key ต่อการพยายามชาร์จภายนอกและปฏิเสธสำเนาที่ซ้ำบนระดับฐานข้อมูล
รายงานที่ฝ่ายการเงินคาดหวังตั้งแต่วันแรก
สคีมบัญชีเรียกเก็บพิสูจน์ตัวเองเมื่อฝ่ายการเงินตอบคำถามพื้นฐานได้เร็วและได้ยอดรวมเดิมทุกครั้ง
ทีมส่วนใหญ่เริ่มจาก:
- Aging ของลูกหนี้: ยอดที่ยังค้างตามลูกค้าและช่วงอายุ (0-30, 31-60 ฯลฯ)
- เงินสดรับ: เงินที่เก็บได้รายวัน สัปดาห์ เดือน โดยอิงวันที่โพสต์การชำระ
- รายได้เทียบกับเงินสด: ใบแจ้งหนี้ที่โพสต์เทียบกับการชำระที่โพสต์
- เส้นทางตรวจสอบสำหรับการส่งออก: สามารถไล่จากบรรทัด GL ในการส่งออกกลับไปยังเอกสารและแถวการจัดสรรที่สร้างมัน
Aging คือจุดที่การจัดสรรสำคัญที่สุด Aging ไม่ใช่ "ยอดใบแจ้งหนี้ลบยอดการชำระ" แต่มันคือ "ยอดที่ยังเปิดค้างบนแต่ละใบแจ้งหนี้ ณ วันที่" นั่นต้องเก็บว่าการชำระ เครดิต หรือการปรับต่าง ๆ ถูกจัดสรรไปยังใบแจ้งหนี้ใดและเมื่อใดที่โพสต์
เงินสดรับควรถูกขับเคลื่อนโดยตาราง payments ไม่ใช่สถานะใบแจ้งหนี้ ลูกค้าจ่ายก่อน ล่าช้า หรือบางส่วนได้
รายได้เทียบกับเงินสดคือเหตุผลที่ใบแจ้งหนี้และการชำระเงินต้องแยกกัน ตัวอย่าง: คุณออกใบแจ้งหนี้ $1,000 วันที่ 30 มี.ค. ได้ $600 วันที่ 5 เม.ย. และออกเครดิต $100 วันที่ 20 เม.ย. รายได้เป็นของมี.ค. (การโพสต์ใบแจ้งหนี้) เงินสดเป็นของเม.ย. (การโพสต์การชำระ) และเครดิตลดลูกหนี้เมื่อโพสต์ การจัดสรรเชื่อมทุกอย่างเข้าด้วยกัน
ตัวอย่างสถานการณ์: ลูกค้าหนึ่ง รายการเอกสารสี่ประเภท
ลูกค้าหนึ่ง เดือนหนึ่ง สี่ประเภทเอกสาร แต่ละเอกสารถูกเก็บครั้งเดียว และเงินไหลผ่านตารางการจัดสรร (เรียกอีกอย่างว่า "applications") นั่นทำให้ยอดสุดท้ายคำนวณใหม่ได้ง่ายและตรวจสอบได้ง่าย
สมมติลูกค้า C-1001 (Acme Co.)
เรคคอร์ดที่คุณสร้าง
invoices
| invoice_id | customer_id | invoice_date | posted_at | currency | total |
|---|---|---|---|---|---|
| INV-10 | C-1001 | 2026-01-05 | 2026-01-05 | USD | 120.00 |
payments
| payment_id | customer_id | received_at | posted_at | method | amount |
|---|---|---|---|---|---|
| PAY-77 | C-1001 | 2026-01-10 | 2026-01-10 | card | 70.00 |
credits (credit memo, goodwill credit, etc.)
| credit_id | customer_id | credit_date | posted_at | reason | amount |
|---|---|---|---|---|---|
| CR-5 | C-1001 | 2026-01-12 | 2026-01-12 | service issue | 20.00 |
adjustments (การแก้ไขย้อนหลัง ไม่ใช่การขายใหม่)
| adjustment_id | customer_id | adjustment_date | posted_at | note | amount |
|---|---|---|---|---|---|
| ADJ-3 | C-1001 | 2026-01-15 | 2026-01-15 | underbilled fee | 5.00 |
allocations (สิ่งนี้แหละที่ทำให้ยอดกระทบยอดได้)
| allocation_id | doc_type_from | doc_id_from | doc_type_to | doc_id_to | posted_at | amount |
|---|---|---|---|---|---|---|
| AL-900 | payment | PAY-77 | invoice | INV-10 | 2026-01-10 | 70.00 |
| AL-901 | credit | CR-5 | invoice | INV-10 | 2026-01-12 | 20.00 |
วิธีคำนวณยอดคงเหลือของใบแจ้งหนี้
สำหรับ INV-10 ผู้ตรวจสอบสามารถคำนวณยอดคงเหลือจากแถวต้นทางได้:
open_balance = invoice.total + sum(adjustments) - sum(allocations)
ดังนั้น: 120.00 + 5.00 - (70.00 + 20.00) = 35.00 ที่ต้องชำระ
เพื่อไล่แหล่งที่มาของ "35.00":
- เริ่มจากยอดใบแจ้งหนี้ (INV-10)
- บวกการปรับที่โพสต์ที่ผูกกับใบแจ้งหนี้เดียวกัน (ADJ-3)
- หักแต่ละการจัดสรรที่โพสต์ไปยังใบแจ้งหนี้ (AL-900, AL-901)
- ยืนยันว่าการจัดสรรแต่ละรายการชี้ไปยังเอกสารต้นทางจริง (PAY-77, CR-5)
- ตรวจสอบวันที่และ
posted_atเพื่ออธิบายไทมไลน์
ข้อผิดพลาดที่พบบ่อยซึ่งทำให้การกระทบยอดพัง
ปัญหาการกระทบยอดส่วนใหญ่ไม่ใช่ "บั๊กคณิตศาสตร์" แต่เป็นกฎที่หายไป ทำให้เหตุการณ์จริง ๆ เดียวกันถูกบันทึกต่างกันขึ้นอยู่กับคนที่ทำ
กับดักที่พบบ่อยคือการใช้แถวลบเป็นทางลัด แถวบรรทัดลบ การชำระเงินลบ และบรรทัดภาษีลบ อาจหมายถึงสิ่งต่างกันหลายอย่าง ถ้าอนุญาตค่าลบ ให้กำหนดนโยบายการย้อนกลับที่ชัดเจน (เช่น: ใช้แถวย้อนกลับที่อ้างอิงแถวเดิมเท่านั้น และอย่าผสมความหมายการย้อนกลับกับส่วนลด)
สาเหตุบ่อยอีกประการคือการเปลี่ยนประวัติ ถ้าใบแจ้งหนี้ออกแล้ว อย่าแก้มันทีหลังเพื่อให้ตรงกับราคาหรือที่อยู่ที่ถูกต้อง ให้เก็บเอกสารต้นฉบับและโพสต์การปรับหรือบันทึกเครดิตที่อธิบายการเปลี่ยนแปลง
รูปแบบที่มักทำให้ยอดรวมพัง:
- ใช้แถวลบโดยไม่มีนโยบายย้อนกลับที่เข้มงวดและการอ้างอิงกลับไปยังแถวเดิม
- แก้ไขใบแจ้งหนี้เก่าหลังออกแทนการโพสต์การปรับหรือบันทึกเครดิต
- ผสมรหัสรายการ gateway กับ ID ภายในโดยไม่มีตารางแมปและแหล่งข้อมูลความจริงที่ชัดเจน
- ให้โค้ดแอปฯ คำนวณยอดรวมในขณะที่แถวสนับสนุน (tax, fee, rounding, allocations) หายไป
- ไม่แยกระหว่าง "การย้ายเงิน" (cash movement) กับ "การจัดสรรเงิน" (เงินถูกใช้กับใบแจ้งหนี้ไหน)
ข้อสุดท้ายนี้ทำให้เกิดความสับสนมากที่สุด ตัวอย่าง: ลูกค้าจ่าย $100 แล้วคุณใช้ $60 กับ Invoice A และ $40 กับ Invoice B การชำระเงินเป็นการเคลื่อนเงินครั้งเดียว แต่สร้างการจัดสรรสองรายการ ถ้าคุณเก็บแค่ "payment = invoice" คุณจะไม่รองรับการชำระบางส่วน การชำระเกิน หรือการย้ายการชำระ
เช็คลิสต์และขั้นตอนถัดไป
ก่อนเพิ่มฟีเจอร์อื่น ให้แน่ใจว่าพื้นฐานทำงาน สคีมบัญชีเรียกเก็บกระทบยอดได้เมื่อยอดแต่ละตัวสามารถย้อนกลับไปยังแถวเฉพาะ และการเปลี่ยนแปลงทุกอย่างมีร่องรอยการตรวจสอบ
การตรวจสอบการกระทบยอดด่วน
รันเช็กเหล่านี้บนตัวอย่างเล็ก (ลูกค้าหนึ่ง เดือนหนึ่ง) แล้วกับชุดข้อมูลทั้งหมด:
- ตัวเลขที่โพสต์ในรายงานสามารถย้อนกลับไปยังแถวต้นทาง (invoice line, payment, credit memo, adjustment) ที่มีวันที่โพสต์และสกุลเงิน
- การจัดสรรไม่เกินเอกสารที่ถูกนำไปใช้ (ผลรวมการจัดสรรของการชำระเงิน ≤ ยอดการชำระเงิน; เหมือนกันกับเครดิต)
- ไม่มีการลบ ระเบียนผิดถูกย้อนกลับพร้อมเหตุผล แล้วแก้ไขด้วยแถวใหม่ที่โพสต์แล้ว
- ยอดคงเหลือที่เปิดคำนวณได้ ไม่เก็บเป็นข้อเท็จจริง (ยอดคงเหลือ = ยอดใบแจ้งหนี้ - การจัดสรรที่โพสต์และเครดิต)
- ยอดรวมของเอกสารตรงกับบรรทัดของมัน (ยอดหัวใบแจ้งหนี้เท่ากับผลรวมของบรรทัด ภาษี และค่าธรรมเนียมตามกฎการปัดเศษของคุณ)
ขั้นตอนถัดไปเพื่อส่งมอบของที่ใช้งานได้
เมื่อสคีมมั่นคงแล้ว สร้างเวิร์กโฟลว์ปฏิบัติการรอบ ๆ มัน:
- หน้าผู้ดูแลสำหรับสร้าง โพสต์ และย้อนกลับ ใบแจ้งหนี้ การชำระเงิน เครดิต และการปรับ พร้อมหมายเหตุที่จำเป็น
- มุมมองการกระทบยอดที่แสดงเอกสารและการจัดสรรข้างกัน รวมทั้งใครโพสต์เมื่อไหร่
- การส่งออกที่ฝ่ายการเงินต้องการ (ตามวันที่โพสต์ ตามลูกค้า ตามการแมป GL ถ้ามี)
- เวิร์กโฟลว์ปิดงวด: ล็อกวันที่โพสต์สำหรับเดือนที่ปิด และบังคับให้มีรายการย้อนกลับสำหรับการแก้ไขภายหลัง
- สถานการณ์ทดสอบ (refunds, partial payments, write-offs) ที่ต้องตรงกับยอดที่คาดหวัง
ถ้าคุณต้องการเส้นทางที่เร็วขึ้นในการได้พอร์ทัลการเงินภายใน AppMaster (appmaster.io) สามารถช่วยคุณโมเดลสคีม PostgreSQL สร้าง API และสร้างหน้าผู้ดูแลจากแหล่งเดียวกัน เพื่อให้กฎการโพสต์และการจัดสรรคงที่เมื่อแอปพลิเคชันเติบโต
คำถามที่พบบ่อย
การกระทบยอดหมายความว่าทุกตัวเลขรายงานสามารถสร้างขึ้นใหม่ได้จากระเบียนต้นทางและย้อนกลับไปยังรายการที่มีวันที่ได้ ถ้ารายงานบอกว่าคุณเก็บเงินได้ $12,430 คุณควรชี้ไปยังการชำระเงินและการคืนเงินที่โพสต์จริง ๆ และรวมกันเป็นตัวเลขนั้น โดยไม่ต้องพึ่งฟิลด์ที่ถูกเขียนทับ
สาเหตุที่พบบ่อยที่สุดคือการเก็บค่าที่เปลี่ยนแปลงได้ เช่น paid_amount หรือ balance_due ราวกับว่าเป็นข้อเท็จจริง ถ้าฟิลด์พวกนี้ถูกอัปเดตโดยการลองใหม่ ข้อบกพร่อง หรืองานแก้ไขด้วยมือ คุณจะสูญเสียประวัติและตัวรวมจะไม่ตรงกับสิ่งที่เกิดขึ้นจริง
เพราะแต่ละอย่างแทนเหตุการณ์ในโลกจริงที่มีความหมายทางบัญชีต่างกัน เมื่อยัดใส่เป็นระเบียน "transaction" เดียวที่มีฟิลด์เป็นอ็อปชัน รายงานจะกลายเป็นการเดา และการตรวจสอบจะกลายเป็นการถกเถียงกันว่าแถวควรหมายถึงอะไร
Credit memo ลดจำนวนที่ลูกค้าต้องจ่ายแต่ไม่ได้ย้ายเงินสด ส่วน refund คือเงินสดออกจากบริษัท มักจะเชื่อมโยงกับการชำระเงินก่อนหน้า การปฏิบัติรวมกันหรือทำให้เป็น "negative payments" จะทำให้การรายงานเงินสดและการจับคู่กับรายการธนาคารยากขึ้นมาก
โพสต์การย้อนกลับแทนการแก้ไขหรือลบ สร้างรายการใหม่ที่สะท้อนจำนวนเดิมด้วยเครื่องหมายตรงข้าม ลิงก์กลับไปยังการโพสต์ต้นฉบับ จากนั้นโพสต์การจัดสรรที่ถูกต้อง เพื่อให้ประวัติแสดงว่ามีอะไรเปลี่ยนแปลงและทำไม
ใช้ระเบียนการจัดสรร (applications) ที่ระบุการเชื่อมต่อระหว่างการชำระเงินหรือเครดิตกับใบแจ้งหนี้หนึ่งหรือหลายใบ โดยมียอดเงินที่จัดสรรและวันที่โพสต์ ยอดค้างของใบแจ้งหนี้ควรคำนวณได้จากยอดใบแจ้งหนี้ บวก/ลบการปรับ และหักด้วยการจัดสรรที่โพสต์แล้ว
เก็บทั้งวันที่ของเอกสารและวันที่โพสต์ วันที่ของเอกสารคือสิ่งที่ลูกค้าเห็น ขณะที่ posting date ควบคุมว่าเมื่อใดจะปรากฏในรายงานการเงินและการปิดงวด ดังนั้นตัวเลขเดือนสิ้นงวดจะไม่เปลี่ยนเพราะมีคนแก้ไขหลัง
เก็บรายละเอียดภาษีและค่าธรรมเนียมในระดับบรรทัดรายการ พร้อมกับยอดรวมที่แสดงให้ลูกค้าเห็น ถ้าคุณเก็บแค่ tax_total บนระดับใบแจ้งหนี้เดียว คุณจะเจอกรณีที่อธิบายไม่ได้ โดยเฉพาะเมื่อต้องจัดการอัตราภาษีต่างกันหรือการยกเว้นบางรายการ
เก็บยอดเงินในสกุลเงินรายการต้นทาง และเก็บยอดสำหรับการรายงานด้วยอัตราแลกเปลี่ยน (fx_rate) ที่ใช้เมื่อโพสต์ เลือกกฎการปัดเศษเดียว (ต่อบรรทัดหรือทั้งใบ) และบันทึกความแตกต่างจากการปัดเป็นรายการปรับแยกต่างหากเพื่อให้สามารถสร้างยอดใหม่ได้อย่างแม่นยำ
ใช้สถานะเป็นฉลากของเวิร์กโฟลว์ (Draft, Issued, Void, Paid) และใช้รายการที่โพสต์แล้วกับการจัดสรรเป็นข้อเท็จจริงทางบัญชี สถานะอาจผิดได้ แต่รายการที่โพสต์และไม่เปลี่ยนแปลงทำให้การเงินคำนวณตัวเลขได้เหมือนเดิมทุกครั้ง


