التقنية المالية والمدفوعات الأدلة التقنية

المطابقة Reconciliation: كشف البنك مقابل نظامك

كيف تقابل كل حركة في كشف البنك بقيد في دفترك على مراحل، ولماذا قد يتساوى الرصيدان وتحتهما دفع مكرر لا يكشفه إلا المرجع.

المطابقة Reconciliation: كشف البنك مقابل نظامك

في مقال رسائل البنوك تركنا بدر، مهندس الواجهات الخلفية في شركة ناشئة تخدم المتاجر الصغيرة، أمام ملف لم يكتبه هو: كشف حساب الشركة الذي يعطيه البنك كل يوم. والشركة تستقبل مبيعات المتاجر بالبطاقات عبر مزود دفع، يحول إليها كل يوم صافي المبيعات بعد رسومه، ثم تصرف هي لكل متجر مستحقاته الأسبوعية بتحويل بنكي. وفي آخر الشهر الأول جاءت المحاسبة بسؤال بسيط: الرصيد في البنك لا يساوي الرصيد في نظامكم، والفرق ألف وثلاثمئة ريال تقريباً، فأين ذهب؟

وكانت أول فكرة عند بدر أن يقارن المجموعين ويبحث عن رقم يساوي الفرق. لكنه اكتشف بسرعة أن الفرق ليس رقماً واحداً، بل مجموعة أشياء صغيرة اختلطت: حوالة رجعت لأن حساب متجر أغلق، ورسوم بنكية لم يسجلها أحد، وتحويل وصل من عميل دون أي مرجع، وتسوية يوم كامل ظهرت في كشف اليوم التالي. والأسوأ أنه حين بحث أعمق وجد أن مطابقة المجاميع قد تخفي أخطاء أكبر من الفرق نفسه، كما سنرى في التجربة. وهذا هو عمل المطابقة Reconciliation: أن يقابل كل سطر في كشف البنك سطراً في دفتر الشركة، أو سبباً مكتوباً لعدم وجوده.

وقد شرحنا في مقال عملية الدفع بالبطاقة مطابقة أحداث مزود الدفع مع ملف التسوية، وفي دراسة حالة المحفظة الرقمية دفتر القيد المزدوج والحساب الوسيط الذي يجب أن يقترب من الصفر. وهذا المقال يكمل الضلع الثالث: كشف البنك مقابل نظامك. وسوف نناقش في هذا المقال ما يلي:

  • لماذا لا تتطابق الأرقام وحدها، والمصادر الثلاثة للحقيقة نفسها.
  • ما الذي في كشف البنك: تاريخ القيد وتاريخ القيمة، والمراجع، وعناصر camt.053.
  • تصميم المطابقة: على مراحل من الدليل الأقوى إلى الأضعف، ومطابقة واحد بواحد وواحد بكثير.
  • تجربة عملية بسكربت Python على دفتر وكشف مثال، ثم ثلاث تجارب «ماذا لو»: القطع في يوم، والمطابقة بالمبلغ وحده، والدفع المكرر.
  • الاستثناءات: كيف تصنفها، ومن يعالجها، ومتى تكبر.
  • المطابقة كعملية يومية في شركة صغيرة، وأخطاء شائعة، وقائمة تحقق.

لماذا لا يكفي أن يتساوى الرصيدان؟

الحل البسيط الذي يخطر لأي شخص أن يقارن رصيد البنك برصيد الدفتر في آخر الشهر، فإذا تساويا فكل شيء بخير. وهذا يفشل من جهتين. الأولى أن الرصيدين نادراً ما يتساويان حتى حين لا يوجد أي خطأ، لأن البنك والدفتر يسجلان الحدث نفسه في أوقات مختلفة: تبيع المتاجر يوم الأحد، فيسجل الدفتر المبيعات الأحد، ويحولها المزود الإثنين، ويظهرها البنك في كشف الثلاثاء. والثانية أخطر: الرصيدان قد يتساويان وتحتهما أخطاء تلغي بعضها، كأن يدفع لمتجر مرتين ولا يدفع لآخر بالمبلغ نفسه، فيبقى المجموع صحيحاً والمتجران مظلومان.

ولهذا لا تبدأ المطابقة الجادة من الرصيد بل من السطور. فكل حركة في الكشف يجب أن تجد قيدها في الدفتر، وكل قيد يتوقع حركة في البنك يجب أن يجد حركته، وما لا يجد مقابله يصبح استثناء له سبب وصاحب وموعد. وحين تنتهي من السطور يصبح الفرق في الرصيد مجرد فحص أخير: يجب أن يساوي مجموع الاستثناءات المفتوحة، لا أكثر ولا أقل.

والصورة التالية تبين المصادر الثلاثة التي يقارن بينها بدر، وما يقوله كل منها:

ثلاثة مصادر: دفتر الشركة Ledger يقول ما نظن أنه حدث بقيود المبيعات والمستحقات والمراجع، وتقرير مزود الدفع يقول ما يعترف به المزود بدفعات التسوية والرسوم لكل يوم، وكشف البنك Statement يقول ما وصل وخرج فعلاً بصيغة camt.053 أو ملف CSV، وتحتها المطابقة: كل سطر في مصدر يقابله سطر في الآخر أو سبب مكتوب
ثلاثة سجلات للمال نفسه، والمطابقة تقابل سطورها أو تشرح ما لا يتقابل

ولاحظ أن تقرير مزود الدفع يقف بين الاثنين. فالبنك لا يرى مبيعات المتاجر واحدة واحدة، بل يرى تحويلاً واحداً كل يوم من المزود بصافي المبيعات بعد الرسوم والاستردادات. والدفتر يرى المبيعات واحدة واحدة. فبدون تقرير المزود الذي يقول «هذا التحويل يتكون من هذه العمليات ناقص هذه الرسوم»، لا يمكن ربط حركة البنك بمبيعات الدفتر. ومزودو الدفع يقدمون هذا التقرير لهذا الغرض بالضبط، فتصف Stripe مثلاً تقرير مطابقة التحويلات بأنه يساعد على ربط التحويلات التي تصل إلى حسابك البنكي بدفعات العمليات التي تتعلق بها. ولهذا يسجل بدر في الدفتر قيداً لكل دفعة تسوية بمبلغها الصافي ومرجعها من تقرير المزود، كما فعلنا في مختبر مقال البطاقة حين طابقنا الأحداث مع ملف التسوية.

ما الذي في كشف البنك؟

الكشف قائمة حركات Entries، وكل حركة فيها حقول قليلة لكن كل حقل يهم في المطابقة. وإذا كان البنك يعطي الكشف بصيغة ISO 20022 فهو رسالة camt.053 التي رأيناها في مقال رسائل البنوك، وإلا فملف CSV أو Excel من بوابة البنك بأعمدة تشبهها. وأهم ما فيه:

  • اتجاه الحركة: دائن Credit أي دخل الحساب، أو مدين Debit أي خرج منه، ويكتب في camt.053 في عنصر CdtDbtInd بقيمتين CRDT وDBIT، والمبلغ نفسه موجب دائماً. فأول خطأ يقع فيه المبرمج أن يقارن مبلغاً موجباً من الكشف بمبلغ سالب من الدفتر.
  • تاريخ القيد Booking Date: اليوم الذي سجل فيه البنك الحركة في الحساب، وتاريخ القيمة Value Date: اليوم الذي يحسب منه أثرها، وقد يختلفان، كما سنرى في التجربة.
  • المرجع من البداية إلى النهاية: إذا كانت الحركة تحويلاً أرسلته الشركة بمرجع في EndToEndId، يعود هذا المرجع في الكشف، وهو أقوى دليل للمطابقة.
  • النص الإضافي: وصف حر يكتب فيه البنك أو المرسل شيئاً مثل «تسوية المزود» ورقم الدفعة، أو اسم المرسل، أو سبب الإرجاع.
  • مرجع البنك: رقم يعطيه البنك للحركة نفسها، يفيد حين تتصل به تسأل عن حركة بعينها.

وفي حالة بدر، طلب من البنك الكشف اليومي بالصيغة الأغنى المتاحة، لأن كل عمود إضافي فيه دليل إضافي للمطابقة. والكشف الذي لا يحمل إلا التاريخ والمبلغ والوصف المختصر يجعل المطابقة الآلية مستحيلة تقريباً لكل ما ليس له مرجع مميز.

وماذا لو كان الكشف فقيراً؟

ليس كل بنك يعطي كل الحقول، وليست كل قناة تحمل المراجع. فقد تحصل شركة على كشف CSV فيه التاريخ والمبلغ ووصف مختصر من بضع كلمات فقط، ويضيع فيه المرجع الذي وضعته في أمر التحويل. وفي هذه الحالة تنقلب المطابقة: يصبح النص الحر هو الدليل الأقوى المتاح، وتحتاج قواعد تستخرج منه ما يمكن، مثل رقم الدفعة أو اسم المتجر، ويكبر عدد السطور التي تحتاج إنساناً. ولهذا كان من أول ما سأله بدر للبنك قبل فتح الحساب: هل يظهر المرجع الذي نضعه في التحويلات الصادرة في الكشف؟ وهل يظهر نص المرسل في التحويلات الواردة؟ فالجواب عن هذين السؤالين يحدد كم من المطابقة سيكون آلياً، وكم سيبقى عملاً يدوياً كل يوم.

الطبقة الأولى: الدفتر مقابل تقرير المزود

قبل أن يصل بدر إلى البنك، هناك مطابقة سابقة يجب أن تكون سليمة: مبيعات الدفتر مقابل تقرير التسوية من مزود الدفع. فالرقم الصافي لدفعة B-1026 الذي سجله في الدفتر جاء من تقرير المزود، وإذا كان التقرير نفسه لا يطابق المبيعات التي سجلها نظام الشركة، فمطابقة البنك ستنجح على رقم خاطئ. وهذه الطبقة شرحناها بالكود في مقال عملية الدفع بالبطاقة: أحداث مكررة، وأحداث وصلت بترتيب خاطئ، وتفويضات لم تحصل، ورسوم واعتراضات تنقص الصافي.

فالمطابقة الكاملة عند بدر سلسلة من حلقتين: العمليات في نظامه مقابل تقرير المزود، ثم دفعات التقرير مقابل كشف البنك. وكل حلقة لها استثناءاتها: الأولى تكشف عمليات سجلها النظام ولم يعترف بها المزود أو العكس، والثانية تكشف دفعات اعترف بها المزود ولم تصل إلى البنك. ولا تغني إحداهما عن الأخرى، فالدفعة التي وصلت البنك كاملة قد تخفي داخلها عملية حسبها المزود مرتين، والعملية التي طابقت في التقرير قد تكون في دفعة لم تصل.

تصميم المطابقة: من الدليل الأقوى إلى الأضعف

المطابقة في جوهرها بحث عن أزواج: سطر من الكشف وسطر من الدفتر يمثلان الحدث نفسه. والسؤال: ما الدليل على أنهما الحدث نفسه؟ وليست كل الأدلة بالقوة نفسها:

  1. مرجع وضعته أنت وعاد كما هو: مثل EndToEndId في تحويل أرسلته. هذا دليل يكاد يكون قاطعاً، لأن المرجع فريد ولم يكتبه أحد غيرك، ومع تطابق المبلغ لا يبقى شك.
  2. مرجع طرف ثالث في النص: مثل رقم دفعة مزود الدفع في وصف الحركة. قوي، لكنه يعتمد على أن الطرف الآخر يكتب المرجع بالشكل نفسه دائماً.
  3. المبلغ والتاريخ: مبلغ متساو في نافذة أيام قليلة. ضعيف، لأن مبلغين متساويين قد يكونان حدثين مختلفين، كما سنرى بالضبط في التجربة.

ولهذا تمر المطابقة على مراحل: تطابق أولاً بالدليل الأقوى، وتخرج ما طابق من البحث، ثم تنتقل إلى الدليل الأضعف على ما تبقى فقط. والسبب أن تطبيق الدليل الضعيف أولاً قد يربط سطراً بالشريك الخطأ، فيحرم السطر الصحيح من شريكه الحقيقي. وكل سطر يطابق مرة واحدة فقط، فإذا ظهر سطر ثان بالمرجع نفسه فهذا ليس مطابقة ثانية بل إنذار.

وهناك نوعان من الأزواج. واحد بواحد One-to-One، مثل مستحق متجر يقابله تحويل واحد في الكشف. وواحد بكثير One-to-Many، مثل حركة واحدة في الكشف تقابل مئات العمليات في الدفتر، وهي حال تسوية المزود. والحل العملي للثاني أن تجمع العمليات في الدفتر أولاً تحت قيد واحد بمرجع الدفعة كما يحددها تقرير المزود، فيصبح واحداً بواحد مع الكشف، وتبقى مطابقة العمليات داخل الدفعة مع تقرير المزود عملاً منفصلاً شرحناه في مقال البطاقة.

وهناك حالة ثالثة أصعب: كثير بكثير Many-to-Many، كأن يدفع عميل ثلاث فواتير بتحويلين، أو يجمع البنك عدة تحويلات صادرة في حركة واحدة. وهذه لا تحلها مطابقة آلية بسيطة، لأن عدد التركيبات الممكنة يكبر بسرعة كبيرة، وأي تركيبة مجموعها صحيح قد تكون صدفة. والحل العملي أن تمنعها من البداية حين تستطيع: مرجع واحد لكل تحويل، وتحويل واحد لكل مرجع، وطلب واضح للعملاء أن يدفعوا كل فاتورة وحدها أو أن يكتبوا كل أرقام الفواتير. وما لا تستطيع منعه يذهب إلى إنسان مع اقتراحات من النظام، لا إلى خوارزمية تختار تركيبة بنفسها.

واختيار المفتاح نفسه قرار تصميم. فالمرجع الذي يصلح مفتاحاً يجب أن يكون فريداً، وأن يولده نظامك لا المستخدم، وألا يتغير بين الإرسال والكشف. ورقم الفاتورة مفتاح جيد للتحويلات الواردة إذا كتبه العميل، لكنه ضعيف إذا كان العميل قد يكتبه بصيغ مختلفة، كأن يضيف مسافة أو يحذف الصفر الأول. ولهذا يوحد بدر صيغة المراجع قبل المقارنة، فيزيل المسافات ويوحد حالة الأحرف، لكنه يحفظ النص الأصلي كما جاء، لأن ما تغيره للمقارنة لا يجب أن يغير السجل.

تجربة عملية: دفتر وكشف ومطابقة

كتب بدر سكربتاً صغيراً بمكتبة Python القياسية وحدها، يقرأ ملفين: دفتر الشركة، وكشف البنك بأعمدة مأخوذة من عناصر camt.053. والبيانات كلها مثال كتبناه للتجربة، لا من شركة ولا بنك حقيقي، والمبالغ بالهللة لأن الكسور العشرية في الحسابات المالية تجلب أخطاء التقريب، كما شرحنا في دراسة حالة المحفظة. وهذا الدفتر:

id,date,type,ref,amount_halala,note
L1,2026-10-26,psp_settlement,B-1026,1235800,card sales of 25 Oct, net of fees (from the PSP report)
L2,2026-10-27,psp_settlement,B-1027,987400,card sales of 26 Oct, net of fees
L3,2026-10-26,payout,PO-0101,-150000,merchant 0101 weekly payout
L4,2026-10-26,payout,PO-0102,-150000,merchant 0102 weekly payout
L5,2026-10-26,payout,PO-0103,-420050,merchant 0103 weekly payout
L6,2026-10-26,payout,PO-0104,-98000,merchant 0104 weekly payout

فيه دفعتا تسوية من مزود الدفع بمبالغهما الصافية من تقريره، وأربعة مستحقات لمتاجر خرجت بتحويل بنكي، كل منها بمرجع وضعه بدر في EndToEndId عند إرسال أمر التحويل. وهذا كشف البنك للأيام نفسها:

entry,booking_date,value_date,cd,amount_halala,end_to_end_id,info
S1,2026-10-26,2026-10-26,CRDT,1235800,,PSP SETTLEMENT B-1026
S2,2026-10-26,2026-10-26,DBIT,150000,PO-0101,PAYOUT MERCHANT 0101
S3,2026-10-26,2026-10-26,DBIT,150000,PO-0102,PAYOUT MERCHANT 0102
S4,2026-10-26,2026-10-26,DBIT,420050,PO-0103,PAYOUT MERCHANT 0103
S5,2026-10-26,2026-10-26,DBIT,98000,PO-0104,PAYOUT MERCHANT 0104
S6,2026-10-27,2026-10-27,CRDT,98000,PO-0104,RETURN BENEFICIARY ACCOUNT CLOSED
S7,2026-10-27,2026-10-27,DBIT,1150,,BANK CHARGES OCT
S8,2026-10-27,2026-10-27,CRDT,35000,,TRANSFER FROM A CUSTOMER
S9,2026-10-28,2026-10-27,CRDT,987400,,PSP SETTLEMENT B-1027

وفيه عمداً ما يحدث في الحياة الحقيقية: المستحقات الأربعة خرجت، لكن مستحق المتجر 0104 رجع في اليوم التالي لأن حساب المستفيد مغلق، ورسوم بنكية شهرية لم يسجلها أحد في الدفتر، وتحويل من عميل بلا أي مرجع، وتسوية اليوم الثاني ظهرت في كشف 28 أكتوبر بتاريخ قيمة 27. وهذا السكربت:

# Bank statement vs ledger: match in passes from the strongest evidence to the weakest, then explain what is left.
#   python recon.py statement.csv [--until YYYY-MM-DD] [--naive]
import argparse, csv

ap = argparse.ArgumentParser()
ap.add_argument("statement"); ap.add_argument("--until"); ap.add_argument("--naive", action="store_true")
a = ap.parse_args()

ledger = [dict(l, amount=int(l["amount_halala"])) for l in csv.DictReader(open("ledger.csv", encoding="utf-8"))]
stmt = []
for s in csv.DictReader(open(a.statement, encoding="utf-8")):
    if a.until and s["booking_date"] > a.until:
        continue                                   # not on the statement yet at this cut-off
    s["signed"] = int(s["amount_halala"]) * (1 if s["cd"] == "CRDT" else -1)
    stmt.append(s)

matches, used_s, used_l = [], set(), set()
def match(l, s, rule):
    matches.append((l["id"], s["entry"], rule)); used_l.add(l["id"]); used_s.add(s["entry"])
def free(l, s):
    return l["id"] not in used_l and s["entry"] not in used_s

if a.naive:   # what many spreadsheets do: same amount = same payment
    for l in ledger:
        for s in stmt:
            if free(l, s) and s["signed"] == l["amount"]:
                match(l, s, "amount only")
else:
    for l in ledger:                               # pass 1: our end-to-end id came back
        for s in stmt:
            if free(l, s) and s["end_to_end_id"] == l["ref"] and s["signed"] == l["amount"]:
                match(l, s, "end-to-end id + amount")
    for l in ledger:                               # pass 2: PSP batch id in the text, net amount from the PSP report
        for s in stmt:
            if free(l, s) and l["ref"] in s["info"] and s["signed"] == l["amount"]:
                match(l, s, "batch id in text + amount")

def why(s):
    if "RETURN" in s["info"] and s["end_to_end_id"]:
        return f"returned payout {s['end_to_end_id']}: reverse the payout, contact the merchant"
    if "CHARGES" in s["info"]:
        return "bank fee not booked: post to bank charges"
    if s["end_to_end_id"] and any(m[1] != s["entry"] for m in matches if any(x["entry"] == m[1] and x["end_to_end_id"] == s["end_to_end_id"] for x in stmt)):
        return f"second debit with an id already matched: possible duplicate payment {s['end_to_end_id']}"
    return "no reference: find the sender before booking"

print("matched:")
for m in matches:
    print("  ", *m)
print("exceptions on the statement:")
for s in stmt:
    if s["entry"] not in used_s:
        print(f"   {s['entry']} {s['signed']:>9}  {why(s)}")
print("exceptions in the ledger:")
for l in ledger:
    if l["id"] not in used_l:
        print(f"   {l['id']} {l['amount']:>9}  {l['ref']}: expected, not on the statement yet")
bank, book = sum(s["signed"] for s in stmt), sum(l["amount"] for l in ledger)
open_s = sum(s["signed"] for s in stmt if s["entry"] not in used_s)
open_l = sum(l["amount"] for l in ledger if l["id"] not in used_l)
print(f"bank {bank}  ledger {book}  difference {bank - book}  explained by open items {open_s - open_l}")

ولماذا كتب بهذه الطريقة؟ يحول السكربت أولاً كل مبلغ في الكشف إلى مبلغ بإشارة، موجب للدائن وسالب للمدين، حتى يقارن بالدفتر الذي يسجل الخروج سالباً. ثم يمر بمرحلتين بترتيب قوة الدليل كما شرحنا: المرجع من البداية إلى النهاية مع المبلغ، ثم رقم دفعة المزود في النص مع المبلغ. والدالة free تضمن أن كل سطر يطابق مرة واحدة. وبعد المطابقة يحاول السكربت أن يشرح كل سطر بقي بقواعد بسيطة: إرجاع بمرجع معروف، أو رسوم، أو مدين ثان بمرجع سبق مطابقته، أو مجهول. وفي النهاية يتحقق من أن فرق الرصيد يساوي مجموع ما بقي مفتوحاً من الجهتين، فإذا لم يتساويا فهناك شيء لم يره السكربت أصلاً. والخيار --until يقطع الكشف عند يوم معين، والخيار --naive يطابق بالمبلغ وحده، وسنستخدمهما في التجارب. وشغله في Container مؤقت من python:3.13-slim، بـ Python 3.13.17:

docker run --rm -v "$PWD":/w -w /w python:3.13-slim python recon.py statement.csv
matched:
   L3 S2 end-to-end id + amount
   L4 S3 end-to-end id + amount
   L5 S4 end-to-end id + amount
   L6 S5 end-to-end id + amount
   L1 S1 batch id in text + amount
   L2 S9 batch id in text + amount
exceptions on the statement:
   S6     98000  returned payout PO-0104: reverse the payout, contact the merchant
   S7     -1150  bank fee not booked: post to bank charges
   S8     35000  no reference: find the sender before booking
exceptions in the ledger:
bank 1537000  ledger 1405150  difference 131850  explained by open items 131850

في المخرج أعلاه لاحظ التالي:

  • المرحلة الأولى طابقت المستحقات الأربعة بمرجعها ومبلغها، والثانية طابقت دفعتي التسوية برقم الدفعة في النص ومبلغها الصافي. فكل الدفتر وجد مقابله، ولا استثناء في الدفتر.
  • بقيت ثلاث حركات في الكشف، ولكل منها تفسير وإجراء: إرجاع مستحق المتجر 0104 يحتاج عكس القيد والتواصل مع المتجر لحساب صحيح، والرسوم البنكية تحتاج قيد مصروف، والتحويل المجهول يحتاج البحث عن مرسله قبل أي قيد.
  • السطر الأخير هو الفحص الأهم: الفرق بين البنك والدفتر 131,850 هللة، أي 1,318.50 ريالاً، وهو بالضبط الفرق الذي سألت عنه المحاسبة، ويساوي تماماً مجموع الاستثناءات المفتوحة: 980 + 350 - 11.50. فلا يوجد فرق غير مفسر.
ثلاث طبقات: المرحلة 1 بالمرجع من البداية إلى النهاية والمبلغ أعطت 4 مطابقات L3 إلى L6 مع S2 إلى S5 لمستحقات المتاجر، والمرحلة 2 برقم دفعة المزود في النص والمبلغ الصافي أعطت مطابقتين L1 مع S1 وL2 مع S9 لتسويات البطاقات، وما تبقى 3 استثناءات S6 مرتجع وS7 رسوم وS8 بلا مرجع
مخرج حقيقي: مرحلتان بالدليل الأقوى، وثلاثة استثناءات تحتاج سبباً مكتوباً

ولم يصل بدر إلى هذا الشكل من أول مرة. فالنسخة الأولى من السكربت كانت تطابق ثم تطبع ما بقي وفرق الرصيد فقط، فكان آخر مخرجها هكذا:

statement entries without a ledger line:
   S6 2026-10-27     98000 PO-0104  RETURN BENEFICIARY ACCOUNT CLOSED
   S7 2026-10-27     -1150 -        BANK CHARGES OCT
   S8 2026-10-27     35000 -        TRANSFER FROM A CUSTOMER
ledger lines without a statement entry:
bank movement 1537000  ledger movement 1405150  difference 131850

والأرقام صحيحة، لكنه حين عرضها على المحاسبة سألته سؤالين لم يجب عنهما المخرج: هل هذه الثلاثة هي كل الفرق؟ وماذا نفعل بكل منها؟ فأضاف السطر الذي يقارن الفرق بمجموع المفتوح، والتفسير الذي يقترح إجراء لكل استثناء. فالمطابقة التي تقول «هذه سطور لم تطابق» نصف عمل، والتي تقول «وهذا يفسر الفرق كله، وهذا ما يجب فعله» هي ما يحتاجه من سيغلق الشهر.

ثلاثة استثناءات وإجراءاتها: S6 دائن 980.00 مستحق مرتجع PO-0104 يعكس قيده ويتواصل مع المتجر لحساب صحيح، وS7 مدين 11.50 رسوم بنكية تقيد مصروفاً، وS8 دائن 350.00 تحويل بلا مرجع يبحث عن مرسله قبل أي قيد ويوضع في حساب معلق، والفرق بين البنك والدفتر 1,318.50 ريالاً تشرحه الثلاثة بالضبط
كل استثناء يحتاج إجراء وصاحباً، والثلاثة معاً تشرح الفرق كله

وأول سؤال سأله المتجر 0104 حين تواصل معه فريق الدعم كان: «لماذا لم يصلني مالي؟». والجواب الذي كان يعطيه الفريق قبل المطابقة اليومية: «خرج من عندنا، راجع بنكك». أما بعدها، فصار الفريق يتصل بالمتجر في يوم الإرجاع نفسه، قبل أن يسأل، ويطلب حساباً صحيحاً، ويعيد إرسال المستحق بمرجع جديد مرتبط بالأول. والفرق في تجربة صاحب المتجر كبير: بدل أن يكتشف بعد أسبوع أن ماله لم يصل ويضيع وقته بين بنكين، يعرف في يومه ماذا حدث وماذا يفعل.

ماذا لو طابقنا في نهاية يوم 27؟

المحاسبة تغلق كل يوم، لا آخر الشهر فقط. فشغل بدر السكربت كما لو كان في نهاية يوم 27 أكتوبر، فلا يرى من الكشف إلا ما سجله البنك حتى ذلك اليوم:

python recon.py statement.csv --until 2026-10-27
exceptions in the ledger:
   L2    987400  B-1027: expected, not on the statement yet
bank 549600  ledger 1405150  difference -855550  explained by open items -855550

ظهر في الدفتر قيد متوقع لم يظهر في الكشف: تسوية الدفعة B-1027 بمبلغ 9,874 ريالاً، وصار الفرق الإجمالي بالسالب. وهذا ليس خطأ بل فرق توقيت Timing Difference: الكشف يحمل الحركة بتاريخ قيمة 27 لكن بتاريخ قيد 28، والسكربت يقطع بتاريخ القيد. ولو قطع بتاريخ القيمة لظهرت الحركة في مطابقة يوم 27. والقرار هنا ليس تقنياً فقط: أي التاريخين تعتمد المحاسبة لإغلاق اليوم؟ المهم أن يختار الفريق واحداً، ويكتبه، ويطبقه كل يوم، وأن يعرف أن القيود المتوقعة التي لم تظهر بعد تبقى مفتوحة يوماً أو يومين ثم تطابق، فإذا بقيت أطول من ذلك صارت مشكلة حقيقية.

خط زمني: يوم 27 أكتوبر يتوقع الدفتر تسوية B-1027 وتاريخ القيمة في الكشف 27، ويوم 28 أكتوبر تاريخ القيد في الكشف حيث تظهر الحركة، وخط القطع في نهاية 27، فتبقى B-1027 متوقعة ولم تظهر بمبلغ 9,874.00 وهو فرق توقيت لا خطأ، والقرار: المطابقة بتاريخ القيد أم بتاريخ القيمة
مخرج حقيقي: الحركة نفسها تقع في يومين مختلفين بحسب التاريخ الذي تعتمده

ماذا لو طابقنا بالمبلغ وحده؟

وهنا التجربة التي غيرت رأي بدر في كل جداول المطابقة التي رآها. فكثير من الفرق تطابق في جدول بيانات بالمبلغ: تبحث عن المبلغ نفسه في العمودين وتلونه. فأنشأ نسخة من الكشف فيها خطأ يحدث في الحياة: أمر التحويل رفع مرتين بالخطأ، فخرج مستحق المتجر 0101 مرتين، ولم يخرج مستحق المتجر 0102 أبداً. والمبلغان متساويان، 1,500 ريال. ثم شغل المطابقة بالمبلغ وحده:

python recon.py statement_dup.csv --naive
matched:
   L1 S1 amount only
   L2 S9 amount only
   L3 S2 amount only
   L4 S3 amount only
   L5 S4 amount only
   L6 S5 amount only
exceptions on the statement:
   S6     98000  returned payout PO-0104: reverse the payout, contact the merchant
   S7     -1150  bank fee not booked: post to bank charges
   S8     35000  no reference: find the sender before booking
exceptions in the ledger:
bank 1537000  ledger 1405150  difference 131850  explained by open items 131850

المطابقة بالمبلغ تقول إن الدفتر كله مطابق، ستة من ستة، ولا استثناء في الدفتر. والحقيقة أن متجراً دفع له مرتين، ومتجراً لم يدفع له. ثم شغلها بالمراجع على الكشف نفسه:

python recon.py statement_dup.csv
matched:
   L3 S2 end-to-end id + amount
   L5 S4 end-to-end id + amount
   L6 S5 end-to-end id + amount
   L1 S1 batch id in text + amount
   L2 S9 batch id in text + amount
exceptions on the statement:
   S3   -150000  second debit with an id already matched: possible duplicate payment PO-0101
   S6     98000  returned payout PO-0104: reverse the payout, contact the merchant
   S7     -1150  bank fee not booked: post to bank charges
   S8     35000  no reference: find the sender before booking
exceptions in the ledger:
   L4   -150000  PO-0102: expected, not on the statement yet
bank 1537000  ledger 1405150  difference 131850  explained by open items 131850

ظهر الاثنان فوراً: مدين ثان بالمرجع PO-0101 الذي سبقت مطابقته، أي احتمال دفع مكرر، والمستحق PO-0102 الذي لم يظهر في الكشف. والملاحظة التي تستحق أن تعلق فوق مكتب كل فريق مالي في السطر الأخير من المخرجين: الفرق الإجمالي هو نفسه في الحالتين، 131,850 هللة. أي أن مطابقة المجاميع لم تكن لتكشف الخطأ أبداً، ولا المطابقة بالمبلغ، والذي كشفه هو المرجع الذي وضعه بدر في كل أمر تحويل قبل أن يرسله.

مقارنة: المطابقة بالمبلغ وحده تقول إن كل الدفتر مطابق ستة من ستة ولا استثناء، والحقيقة أن المتجر 0101 دفع له مرتين والمتجر 0102 لم يدفع له، والمطابقة بالمرجع تعطي استثناءين واضحين: S3 مدين ثان بالمرجع PO-0101 احتمال دفع مكرر، وL4 المستحق PO-0102 لم يظهر في الكشف، والفرق الإجمالي واحد في الحالتين 131,850 هللة
مخرج حقيقي: الأرقام نفسها، والحقيقة لا تظهر إلا بالمرجع

المطابقة في دفتر القيد المزدوج

في التجربة عاملنا الدفتر كقائمة سطور، لكن الدفتر الحقيقي في شركة بدر دفتر قيد مزدوج كما شرحنا في دراسة حالة المحفظة: كل حركة مال تسجل في حسابين، من حساب إلى حساب. وهذا يغير شكل المطابقة إلى الأفضل. فحين تبيع المتاجر بالبطاقات، لا يسجل بدر المال في حساب البنك مباشرة، بل في حساب وسيط اسمه مستحقات مزود الدفع، لأن المال لم يصل إلى البنك بعد. وحين تصل التسوية في الكشف وتطابق، يسجل قيداً ينقل المبلغ الصافي من الحساب الوسيط إلى حساب البنك، والرسوم من الحساب الوسيط إلى مصروف رسوم المزود. فإذا وصلت كل التسويات، اقترب الحساب الوسيط من الصفر، وكل رصيد يبقى فيه مال يقول الدفتر إنه في الطريق ولم يصل.

والشيء نفسه مع المستحقات. فحين يرسل بدر أمر تحويل لمتجر، يسجل المبلغ من حساب مستحقات المتجر إلى حساب وسيط اسمه تحويلات صادرة قيد التنفيذ، ثم حين يظهر الخصم في الكشف ويطابق بمرجعه، ينتقل من الوسيط إلى البنك. وإذا رجع التحويل كما حدث مع المتجر 0104، يعكس القيد فيعود المبلغ إلى مستحقات المتجر، لأن الشركة ما زالت مدينة له، ولم يتغير شيء إلا أن المال عاد. وهذا أدق بكثير من أن يحذف أحد القيد الأصلي كأنه لم يكن، لأن حذف القيد يمحو أن المال خرج ثم عاد، وهي حقيقة يحتاج المدقق والمتجر أن يروها.

وميزة هذا التصميم للمطابقة أن الحسابات الوسيطة نفسها تصبح تقرير استثناءات: رصيد في مستحقات المزود عمره أيام يعني تسوية لم تصل، ورصيد في تحويلات قيد التنفيذ يعني تحويلاً لم يظهر في الكشف. فحتى قبل أن تفتح السكربت، تخبرك أرصدة هذه الحسابات أين تبحث.

ماذا لو كبر الحجم؟

سكربت التجربة يقارن كل قيد بكل حركة في حلقتين متداخلتين، وهذا يكفي لتسعة سطور، لكن بدر يتوقع أن تصل المستحقات الأسبوعية إلى آلاف. فأراد أن يعرف متى يصبح هذا مشكلة، فكتب تجربة تولد آلاف المستحقات وحركاتها في الكشف بترتيب عشوائي، وتطابقها بطريقتين: الحلقتان المتداخلتان كما في السكربت، وفهرس Index من المرجع إلى الحركات يبنى مرة واحدة، فتصبح المطابقة بحثاً مباشراً لكل قيد:

# How the matching loop grows: nested loops versus an index by reference, on generated payouts.
import random, time
rnd = random.Random(1)
for n in (2000, 20000):
    ledger = [{"ref": f"PO-{i:06}", "amount": -rnd.randint(1000, 500000)} for i in range(n)]
    stmt = [{"e2e": l["ref"], "signed": l["amount"]} for l in ledger]
    rnd.shuffle(stmt)
    t = time.perf_counter()
    used, hits = set(), 0
    for l in ledger:
        for j, s in enumerate(stmt):
            if j not in used and s["e2e"] == l["ref"] and s["signed"] == l["amount"]:
                used.add(j); hits += 1; break
    nested = time.perf_counter() - t
    t = time.perf_counter()
    index = {}
    for j, s in enumerate(stmt):
        index.setdefault(s["e2e"], []).append(j)
    used, hits2 = set(), 0
    for l in ledger:
        for j in index.get(l["ref"], []):
            if j not in used and stmt[j]["signed"] == l["amount"]:
                used.add(j); hits2 += 1; break
    indexed = time.perf_counter() - t
    print(f"{n:>6} payouts: nested loops {nested:8.3f} s ({hits} matched)  index by reference {indexed:6.3f} s ({hits2} matched)")
  2000 payouts: nested loops    0.912 s (2000 matched)  index by reference  0.006 s (2000 matched)
 20000 payouts: nested loops  278.599 s (20000 matched)  index by reference  0.124 s (20000 matched)

الطريقتان طابقتا كل السطور في الحالتين، فالنتيجة نفسها، لكن الوقت مختلف تماماً. فحين تضاعف عدد المستحقات عشر مرات، من ألفين إلى عشرين ألفاً، زاد وقت الحلقتين المتداخلتين قرابة ثلاثمئة مرة، من أقل من ثانية إلى أكثر من أربع دقائق ونصف، لأن كل قيد يمر على كل الحركات تقريباً، فيكبر العمل بمربع العدد. أما الفهرس فزاد وقته بما يقارب حجم البيانات نفسه، من ستة أجزاء من الألف إلى ثمن ثانية. والأرقام من جهاز التجربة، فتختلف على جهازك، لكن النسبة بين الطريقتين هي ما يهم.

والدرس العملي لا يخص Python وحدها: المطابقة بحث عن أزواج، والبحث عن الأزواج يجب أن يقوم على فهرس بالمفتاح الأقوى، سواء كان قاموساً في الذاكرة أو فهرساً في قاعدة البيانات على عمود المرجع. ولهذا جعل بدر عمود المرجع في جدول القيود مفهرساً من اليوم الأول، فالمطابقة التي تعمل في ثانية اليوم لن تصبح ساعات بعد سنة لمجرد أن الشركة نمت.

الاستثناءات: التصنيف والمالك والعمر

المطابقة الآلية لا تنتهي بصفر استثناءات، ولا ينبغي أن تهدف إلى ذلك. فهدفها أن تطابق ما يمكن مطابقته بثقة، وأن تضع الباقي أمام إنسان مع ما يحتاجه ليقرر. وصنف بدر الاستثناءات في شركته إلى أنواع ثابتة، لكل نوع إجراء وصاحب:

النوعمثال من التجربةالإجراءصاحبه
فرق توقيتB-1027 ظهرت في كشف اليوم التاليانتظر يوماً أو يومين، ثم صعدالنظام آلياً
مرتجعمستحق المتجر 0104، حساب مغلقعكس القيد، وطلب حساب صحيح من المتجرفريق دعم المتاجر
رسوم لم تسجلرسوم بنكية شهريةقيد مصروف رسوم بنكيةالمحاسبة
دخل مجهولتحويل من عميل بلا مرجعحساب معلق، ثم البحث عن المرسلالمحاسبة
مكرر أو ناقصPO-0101 مرتين وPO-0102 لم يخرجإيقاف فوري، والتواصل مع البنك والمتجرينالمسؤول المالي

والعمر Aging هو ما يحول الاستثناء من ملاحظة إلى خطر. ففرق التوقيت الذي عمره يوم طبيعي، والذي عمره أسبوع يعني أن تسوية لم تصل فعلاً. والتحويل المجهول الذي بقي في الحساب المعلق شهراً قد يكون مالاً لعميل يبحث عنه، أو مالاً أرسل بالخطأ وسيطلب صاحبه رده. ولهذا يعرض تقرير بدر اليومي كل استثناء مع عمره بالأيام، ويرفع لوناً تحذيرياً لكل نوع بعد حد يتفق عليه الفريق. والحساب المعلق Suspense Account نفسه فكرة محاسبية مهمة: مال تعرف أنه دخل ولا تعرف لمن هو بعد، فلا تضعه في الإيراد ولا تتركه خارج الدفتر، بل في حساب يجب أن يفرغ مع الوقت، كما يجب أن يقترب الحساب الوسيط لمزود الدفع من الصفر في دراسة حالة المحفظة.

وأهم قاعدة في الاستثناءات أن لا يعدل أحد الدفتر ليطابق الكشف دون سبب. فالإغراء كبير في آخر الشهر: فرق صغير، وقيد «تسوية فروقات» يجعل الرقمين متساويين. لكن هذا القيد يخفي سبب الفرق، ويجعل المطابقة القادمة تبدأ من رقم غير صحيح. فكل قيد يضاف لإغلاق استثناء يجب أن يحمل سبب الاستثناء ومرجعه، حتى يستطيع أي شخص بعد سنة أن يعرف لماذا أضيف.

كيف تبحث عن مرسل مجهول؟

بقي التحويل المجهول بقيمة 350 ريالاً أصعب الاستثناءات الثلاثة، لأن لا شيء فيه يقود إلى صاحبه غير النص «تحويل من عميل». ورتب بدر مع المحاسبة خطوات ثابتة له ولأمثاله. أولاً: يبحث النظام في الفواتير المفتوحة عن مبلغ مساو، لكنه لا يطابق آلياً حتى لو وجد فاتورة واحدة بالمبلغ نفسه، بل يقترحها على إنسان، لأننا رأينا ما تفعله المطابقة بالمبلغ وحده. ثانياً: يطلب من البنك تفاصيل الحركة الكاملة، فبعض البنوك تعطي اسم المرسل أو حسابه عند الطلب حتى لو لم يظهر في الكشف المختصر. ثالثاً: إذا اتصل عميل يسأل لماذا لم تسجل دفعته، يطابق التاريخ والمبلغ مع ما في الحساب المعلق. وفي كل الأحوال لا يدخل المال في الإيراد حتى يعرف صاحبه، ولا يعاد إلى أي حساب دون تحقق، لأن رد المال إلى الشخص الخطأ خسارة مضاعفة.

ومع الوقت لاحظ بدر أن أغلب التحويلات المجهولة تأتي من عملاء جدد لم يعرفوا أن عليهم كتابة رقم الفاتورة، فأضاف إلى بريد الفاتورة نفسه سطراً واضحاً بالمرجع المطلوب ومكان كتابته، فقل عدد المجهولات. وهذا مثال على أن علاج الاستثناءات الحقيقي غالباً خارج المطابقة: في المكان الذي يولد فيه المال بلا مرجع.

المطابقة تبدأ قبل الكشف: صمم المراجع

أدرك بدر من التجربة أن أصعب جزء في المطابقة لا يحدث يوم المطابقة، بل يوم إرسال المال. فالمستحقات طابقت بسهولة لأنه وضع في كل أمر تحويل مرجعاً يعرفه، والتسويات طابقت لأن المزود يكتب رقم الدفعة في النص، والتحويل المجهول لم يطابق لأن مرسله لم يكتب شيئاً. فكل ما يستطيع أن يتحكم فيه بدر هو المراجع التي تخرج من نظامه، وكل ما لا يتحكم فيه يجب أن يتوقعه بقواعد.

فكتب ثلاث قواعد لفريقه. الأولى: لا يخرج تحويل من نظام الشركة دون مرجع فريد في EndToEndId يحفظ في الدفتر قبل الإرسال، لا بعده. والثانية: حين تطلب الشركة من عملائها أن يدفعوا بتحويل، تعطي كل عميل مرجعاً يكتبه في الحوالة، رقم فاتورة أو رقم عميل، وتشرح له ذلك بوضوح، لأن التحويل بلا مرجع يكلف المحاسبة وقتاً يفوق قيمته أحياناً. والثالثة: كل مرجع من طرف ثالث، مثل رقم دفعة المزود، يحفظ في الدفتر كما يكتبه الطرف الثالث حرفاً بحرف، لا كما يفضله النظام، لأن المطابقة تبحث عن النص كما هو.

وهذا يربط المطابقة بما رأيناه في مقال رسائل البنوك: المرجع من البداية إلى النهاية في أمر التحويل ليس حقلاً شكلياً في ملف XML، بل الخيط الذي يعود به المال إلى سجله. وكلما كان الكشف أغنى، كما في رسائل camt.053 التي تحمل المراجع والنصوص المنظمة، صارت المطابقة الآلية أسهل، وكلما كان أفقر، صار عمل الإنسان أكبر.

الفروق الصغيرة والتسامح Tolerance

في التجربة كانت كل المبالغ متطابقة بالهللة، لكن بدر يعرف أن الحياة لا تكون هكذا دائماً. فقد تختلف التسوية عن تقرير المزود بفرق صغير بسبب رسوم لم تذكر في التقرير، أو تصل حوالة من عميل في الخارج ناقصة رسوم بنك وسيط، أو يدفع عميل مبلغاً أقل من فاتورته بقليل. فهل يطابق السطران رغم الفرق؟

والجواب المعتاد في المطابقة الآلية هو التسامح Tolerance: تقبل المطابقة إذا كان الفرق أقل من حد معين، بشرط أن يكون الدليل الآخر قوياً، كمرجع مطابق. لكن بدر وضع لذلك ثلاثة قيود. الأول: التسامح لا يطبق إلا مع مرجع مطابق، فلا تطابق بالمبلغ التقريبي وحده، لأن المطابقة بالمبلغ الدقيق نفسها كانت خطيرة. والثاني: الفرق المقبول لا يختفي، بل يسجل في قيد منفصل بحساب معروف، مثل رسوم بنكية أو فروق تحصيل، حتى يرى الفريق مجموعه آخر الشهر. والثالث: الحد نفسه يقرره الفريق المالي ويكتبه، لا المبرمج في الكود، لأنه قرار مالي لا تقني. ولم نضع في السكربت تسامحاً لأن بيانات المثال لا تحتاجه، وهذا مقصود: ابدأ دون تسامح، وأضفه حين يظهر نوع فرق متكرر تعرف سببه.

وتظهر الحاجة نفسها مع العملات. فحين تستقبل الشركة دولارات وتحولها، يختلف المبلغ بالريال في الكشف عن المتوقع في الدفتر بفرق سعر الصرف، وهذا فرق يجب أن يسجل في حسابه الخاص لا أن يذوب في المطابقة. ولهذا يحمل كل قيد في دفتر بدر العملة والمبلغ بالعملة الأصلية، ولا يجمع مبالغ بعملات مختلفة أبداً.

من يطابق ومن يعتمد؟

وحين بدأت المطابقة تعمل يومياً، سألت المحاسبة سؤالاً يهم كل شركة تكبر: من يحق له أن يغلق الاستثناء؟ فالشخص الذي يرسل أوامر التحويل ويغلق استثناءاتها بنفسه يستطيع نظرياً أن يخفي خطأه أو أسوأ من ذلك. ولهذا يفصل الفريق المالي عادة بين الأدوار: من يرسل المال غير من يطابقه، ومن يقترح قيد إغلاق استثناء غير من يعتمده. وفي شركة صغيرة قد لا يتوفر هذا الفصل كاملاً، لكن بدر طبق منه ما يستطيع: لا يغلق استثناء من نوع مكرر أو ناقص إلا المسؤول المالي، وكل إغلاق يسجل في جدول الاستثناءات بمن أغلقه ومتى وبأي سبب.

وهذا السجل نفسه هو ما يطلبه المدقق في آخر السنة: لا يكفيه أن الرصيدين متساويان، بل يريد أن يرى كيف وصلت إلى ذلك، وأي الفروق ظهرت، ومن عالجها. والمطابقة التي تنتج هذا السجل تلقائياً كل يوم توفر أسابيع من التحضير للتدقيق، وتجعل الإجابة عن أي سؤال عن حركة قديمة بحثاً في جدول لا في الذاكرة.

المطابقة كعملية يومية في شركة صغيرة

في الشهر الأول كانت المطابقة عند بدر عملاً يدوياً في آخر الشهر استغرق يومين، ووجد فيه فروقاً عمرها أسابيع لم يعد أحد يتذكر سببها. فحولها إلى عملية يومية صغيرة بأربع خطوات ثابتة:

  1. كل صباح: يجلب النظام كشف الأمس من البنك وتقرير التسوية من المزود، بالصيغة الأغنى المتاحة.
  2. المطابقة الآلية: يمر السكربت بمراحله، ويطابق ما يستطيع، ويصنف ما بقي ويحسب عمر كل استثناء.
  3. التقرير: يصل إلى المحاسبة وإلى بدر تقرير قصير: عدد ما طابق، والاستثناءات الجديدة، والاستثناءات التي تجاوزت عمرها المسموح، والفحص الأخير بين الفرق ومجموع المفتوح.
  4. المعالجة: كل صاحب استثناء يعالج ما يخصه في يومه، ويسجل السبب مع القيد.

وأثر هذا التحويل لم يكن في توفير الوقت فقط. فالمرتجع الذي يكتشف في يومه يعني أن المتجر يأخذ مستحقه بعد يومين بدل أن يتصل غاضباً بعد أسبوعين، والدفع المكرر الذي يكتشف في يومه يمكن استرداده من المتجر بسهولة قبل أن يصرفه، والتسوية التي لم تصل تكتشف قبل أن تتراكم. فالمطابقة اليومية ليست عملاً محاسبياً بعد الحدث، بل إنذار مبكر لكل ما فات الكود، كما وصفناها في دراسة حالة المحفظة. وبعد شهرين من العمل بها، صارت المطابقة الشهرية التي كانت تأخذ يومين تأخذ ساعة واحدة، لأن كل يوم من أيام الشهر طوبق في يومه، ولم يبق في آخر الشهر إلا مراجعة الاستثناءات المفتوحة وأعمارها، والتأكد أن الحسابات الوسيطة قريبة من الصفر. والوقت الذي وفره الفريق ذهب إلى ما يستحقه: البحث في أسباب الاستثناءات المتكررة ومعالجتها من مصدرها، لا تكرار المطابقة نفسها كل شهر.

ولم يحتج بدر لهذا أداة تجارية في البداية. فسكربت مثل الذي رأيناه، مع جدول في قاعدة بيانات الشركة للاستثناءات وأعمارها، يكفي شركة بحجمها. وحين يكبر حجم الحركات إلى آلاف يومياً، أو تتعدد الحسابات والبنوك والعملات، يصبح الاستثمار في أداة مطابقة متخصصة معقولاً. والمبدأ لا يتغير مع الأداة: مراحل بقوة الدليل، وكل سطر مرة واحدة، واستثناءات بأصحاب وأعمار، وفحص أخير يساوي الفرق بالمفتوح.

ولاحظ أن كل خطوة في هذه العملية تنتج سجلاً: الكشف كما وصل، ونتيجة المطابقة، والاستثناءات وأصحابها، والقيود التي أغلقتها بأسبابها. فإذا سأل أحد بعد سنة عن مستحق متجر بعينه، يستطيع بدر أن يتتبعه من أمر التحويل إلى الكشف إلى الإرجاع إلى إعادة الإرسال، دون أن يعتمد على ذاكرة أحد في الفريق.

أخطاء شائعة Common Mistakes

  • الاكتفاء بمقارنة الرصيدين: رأينا أن الفرق الإجمالي كان واحداً مع دفع مكرر ومستحق ناقص، فالمجموع لا يكشف الأخطاء التي تلغي بعضها.
  • المطابقة بالمبلغ وحده: مبلغان متساويان ليسا الحدث نفسه، والمطابقة بالمبلغ أعلنت كل شيء مطابقاً والحقيقة غير ذلك.
  • تطبيق الدليل الضعيف أولاً: فيربط سطراً بالشريك الخطأ ويحرم السطر الصحيح من شريكه.
  • الخلط بين إشارة المبلغ واتجاهه: المبلغ في الكشف موجب دائماً مع مؤشر دائن أو مدين، والدفتر يسجل الخروج سالباً.
  • الكسور العشرية في المبالغ: استخدم أصغر وحدة للعملة بأعداد صحيحة، فالجمع بالكسور يصنع فروقاً من لا شيء.
  • قيد «تسوية فروقات» بلا سبب: يجعل الرقمين متساويين ويخفي الخطأ إلى الأبد.
  • مطابقة شهرية بدل يومية: كلما كبر عمر الاستثناء صعب تفسيره، وصعب استرداد ما دفع خطأ.
  • عدم اختيار تاريخ واحد للقطع: تاريخ القيد أو تاريخ القيمة، فخلط الاثنين يصنع فروق توقيت تظهر وتختفي بلا منطق.

قائمة تحقق للمطابقة

السؤاللماذا
هل لكل تحويل يخرج من نظامك مرجع فريد محفوظ قبل الإرسال؟هو الدليل الأقوى، وهو ما كشف الدفع المكرر في التجربة
هل تحصل على الكشف بأغنى صيغة متاحة، يومياً؟كل عمود إضافي دليل إضافي، وكل يوم تأخير يكبر عمر الاستثناءات
هل تحصل على تقرير تسوية المزود وتقيد الدفعات بمراجعها؟البنك يرى دفعة واحدة صافية، والدفتر يرى العمليات
هل تطابق على مراحل من الأقوى إلى الأضعف، وكل سطر مرة واحدة؟الدليل الضعيف أولاً يربط الأزواج الخطأ
هل يساوي فرق الرصيد مجموع الاستثناءات المفتوحة؟إذا لم يتساويا فهناك ما لم يره نظامك أصلاً
هل لكل استثناء نوع وإجراء وصاحب وعمر؟الاستثناء بلا صاحب لا يغلق
هل اخترت تاريخ القطع وكتبته؟تاريخ القيد وتاريخ القيمة يعطيان يومين مختلفين
هل يحمل كل قيد إغلاق سبب الاستثناء ومرجعه؟حتى يفهمه من يراجعه بعد سنة

الخلاصة

  • المطابقة تقابل كل حركة في كشف البنك بقيد في الدفتر، أو بسبب مكتوب، ولا تبدأ من الرصيد بل من السطور.
  • الأدلة ليست بالقوة نفسها: المرجع الذي وضعته أنت أقوى من مرجع طرف ثالث في النص، وكلاهما أقوى بكثير من المبلغ والتاريخ.
  • في التجربة طابقت مرحلتان ستة قيود، وبقيت ثلاثة استثناءات تشرح الفرق كله: 1,318.50 ريالاً.
  • القطع في يوم 27 أظهر فرق توقيت لتسوية بتاريخ قيمة 27 وتاريخ قيد 28، فاختر تاريخاً واحداً للقطع واكتبه.
  • المطابقة بالمبلغ وحده أعلنت كل شيء مطابقاً مع دفع مكرر ومستحق ناقص، والفرق الإجمالي كان واحداً في الحالتين، والمرجع وحده كشف الخطأ.
  • اجعل المطابقة يومية، بالاستثناءات وأصحابها وأعمارها، وصمم المراجع يوم إرسال المال لا يوم المطابقة.
  • ابن المطابقة على فهرس بالمرجع: عشرة أضعاف البيانات جعلت الحلقتين المتداخلتين أبطأ قرابة ثلاثمئة مرة، والفهرس بقي أقل من ثانية.

وعاد بدر إلى المحاسبة بجواب لسؤالها الأول: الفرق 1,318.50 ريالاً، وهذه ثلاثة أسبابه، وهذا ما يفعله كل فريق بكل منها. ثم أضاف ما لم تسأل عنه: أن المطابقة صارت تعمل كل صباح، وأن أي دفع مكرر أو مستحق ناقص سيظهر في اليوم التالي لا في آخر الشهر. وهذا ما يجعل المطابقة جزءاً من النظام، لا عملاً محاسبياً يأتي بعده.

وبقية مقالات قسم التقنية المالية والمدفوعات:

دليلكيف يعمل الدفع بالبطاقة؟ التفويض Authorization والتسويةمن ضغطة «ادفع» إلى آخر اعتراض: التفويض و3D Secure والتحصيل والتسوية والاسترداد والاعتراض، مع مختبر لأحداث مزود الدفع ومطابقتها.دليلالمحفظة الرقمية من الداخل: دفتر القيد المزدوج Ledgerكيف تبني محفظة رقمية لا يضيع فيها ريال ولا يخصم مرتين، بالقيد المزدوج والمعاملات والمطابقة مع مزود الدفع.دليلرسائل البنوك: ISO 8583 للبطاقات وISO 20022 للتحويلاتنفكك رسالة تفويض بطاقة بايتاً بايتاً، ونبني أمر تحويل بأسماء عربية ونتحقق منه بالمخطط الرسمي، ونرى ما يتعطل في كل معيار.دليلمنظومة المدفوعات في السعودية: سريع وSARIE ومدى وسدادالأنظمة الوطنية للمدفوعات في المملكة كما تصفها مصادر البنك المركزي، وما يعنيه كل نظام للمطور، مع تحليل تقارير نقاط البيع الأسبوعية.دليلالمصرفية المفتوحة Open Banking: إطار البنك المركزي والواجهاتما تتيحه المصرفية المفتوحة في المملكة وما تفرضه لائحتها، والموافقة من الطلب إلى الحذف، مع مختبر لتطبيق يقرأ بموافقة العميل فقط.

سجل التحديثات

  • أكتوبر 2026: كتابة المقال وتشغيل سكربت المطابقة يوم 27 أكتوبر 2026 على python:3.13-slim (Python 3.13.17) ببيانات مثال فقط، وتجربة الحجم بعشرين ألف مستحق مولد، ومراجعة توثيق تقرير مطابقة التحويلات في Stripe في التاريخ نفسه.
نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.