جدول الأقساط، والحارس الذي يمنع الفاتورة الملغاة من الانقلاب مسددة
الفوترة بالأقساط مطلب شائع في السوق: 40% مقدما، و30% عند التسليم، و30% بعد الاستلام.
الحساب: المصيدة الوحيدة فيه
اقسم المبلغ على النسب ثم اجمع النواتج، وسيظهر لك أن المجموع لا يساوي الأصل. ريال أو هللة تضيع في التقريب.
الحل أن تحسب كل قسط عدا الأخير، ثم تجعل الأخير هو الباقي:
القسط الأخير = المبلغ الكلي - مجموع ما سبق
بهذه الطريقة يخرج المجموع مساويا للأصل مهما كانت النسب.
واحسب بأصغر وحدة: بالهللات لا بالريالات، لأن خطأ الكسور العشرية في المال يتراكم مع كل عملية.
وعندي اختبار واحد يحرس هذا كله: مجموع الأقساط المخزنة يساوي إجمالي الفاتورة مهما كان التقسيم.
الحساب كما هو
// احسب بأصغر وحدة، واجعل الأخير هو الباقي، فيساوي المجموع الأصل دائماً
function plan(totalHalalas, percents) {
const parts = [];
let used = 0;
for (const p of percents.slice(0, -1)) {
const amount = Math.round(totalHalalas * p / 100);
parts.push(amount);
used += amount;
}
parts.push(totalHalalas - used); // الأخير يمتص التقريب
return parts;
}
plan(2000000, [40, 30, 30]); // 20,000 ريال
الخطر الحقيقي
الحساب أهون ما في الموضوع. الفاتورة تمر بحالات: مسودة، مرسلة، مسددة جزئيا، مسددة، ملغاة. وفي النظام عدة مسارات يمكنها إغلاق الفاتورة: دفعة تسجل يدويا، أو إشعار يصل من بوابة الدفع، أو المهمة المجدولة التي تسوّي المعلّق. وأي واحد من هذه المسارات قد يستقبل حدثا يخص فاتورة ألغيت بعد إرسالها.
ماذا يحدث بلا حارس
فاتورة ألغيت، ثم وصلها إشعار دفع متأخر. المسار يرى أن دفعة وصلت فيغلق الفاتورة، وتنقلب الملغاة إلى مسددة. والنتيجة إيراد يظهر في التقارير ولا يقابله شيء.
الحارس
كل تحديث يغلق فاتورة يشترط الحالة داخل جملة التحديث نفسها:
UPDATE invoices SET status = 'paid'
WHERE id = ? AND status IN ('sent','partial')
الفاتورة الملغاة لا تطابق الشرط، فيمر التحديث من دون أي أثر. وبهذا تستغني عن فحص منفصل قد ينساه من يكتب المسار الرابع.
القاعدة
اشترط الحالة داخل جملة التحديث نفسها. الفحص الذي يسبق التحديث في سطر مستقل يترك نافذة سباق، إذ قد تتغير الحالة بين لحظة القراءة ولحظة الكتابة. الشرط داخل الجملة تتحقق منه القاعدة وتنفذ الكتابة في خطوة واحدة، فتنغلق تلك النافذة.