Skip to content

8. تمرين تطبيقي – الإصدار 1

8.1. المشكلة

Image

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

  • تحديد سقف الحصة العائلية الذي ينطبق على الدخل المرتفع؛
  • الخصم والتخفيض الضريبي اللذين ينطبقان على الدخل المنخفض؛

وبالتالي، يتضمن حساب الضريبة الخطوات التالية [http://impotsurlerevenu.org/comprendre-le-calcul-de-l-impot/1217-calcul-de-l-impot-2019.php]:

Image

نقترح كتابة برنامج يسمح بحساب الضريبة المستحقة على المكلف في الحالة المبسطة التي يكون فيها المكلف لا يعلن سوى راتبه الوحيد:

8.1.1. حساب الضريبة الإجمالية

يمكن حساب الضريبة الإجمالية بالطريقة التالية:

نحسب أولاً عدد الحصص الخاصة بالمكلف:

  • يُحسب لكل والد حصة واحدة؛
  • يُضاف لكل من الطفلين الأولين نصف حصة؛
  • يُحسب لكل طفل من الأطفال التاليين حصة واحدة:

وبالتالي، فإن عدد الحصص هو:

  • nbParts=1+nbEnfants*0,5+(عدد_الأطفال-2)*0,5 إذا كان الموظف غير متزوج؛
  • nbParts=2+nbEnfants*0,5+(nbEnfants-2)*0,5 إذا كان متزوجًا؛
    • حيث nbEnfants هو عدد أطفاله؛
  • يُحسب الدخل الخاضع للضريبة R=0.9*S حيث S هو الراتب السنوي؛
  • يُحسب الحصّة العائلية QF=R/nbParts؛
  • يُحسب الضريبة الإجمالية I بناءً على البيانات التالية (2019):
9964
0
0
27519
0.14
1394.96
73779
0.3
5798
156244
0.4
13913.69
0
0.45
20163.45

يحتوي كل سطر على 3 حقول: champ1، champ2، champ3. لحساب الضريبة I، نبحث عن السطر الأول الذي يكون فيه QF <= الحقل 1 ونأخذ قيم هذا السطر. على سبيل المثال، بالنسبة لموظف متزوج ولديه طفلان وراتب سنوي S يبلغ 50000 يورو:

الدخل الخاضع للضريبة: R=0,9*S=45000

عدد الحصص: nbParts=2+2*0,5=3

الحصة العائلية: QF=45000/3=15000

السطر الأول الذي يكون فيه QF <= الحقل 1 هو التالي:

    27519    0.14    1394.96

وبالتالي، فإن الضريبة I تساوي 0.14*R – 1394,96*nbParts=[0,14*45000-1394,96*3]=2115. ويتم تقريب الضريبة إلى أقرب يورو أقل.

إذا كانت العلاقة QF <= حقل1 صحيحة منذ السطر الأول، فإن الضريبة تكون صفرًا.

إذا كان QF بحيث لا تتحقق العلاقة QF<=champ1 أبدًا، فسيتم استخدام معاملات السطر الأخير. هنا:

    0    0.45    20163.45

مما يعطي الضريبة الإجمالية I=0.45*R – 20163,45*nbParts.

8.1.2. الحد الأقصى للمنحة العائلية

Image

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

الدخل الخاضع للضريبة: R=0,9*S=45000

عدد الحصص: nbParts=2 (لم يعد يتم احتساب الأطفال)

الحصة العائلية: QF=45000/2=22500

السطر الأول الذي يكون فيه QF <= الحقل 1 هو التالي:

    27519    0.14    1394.96

وبالتالي، فإن الضريبة I تساوي 0.14*R – 1394,96*nbParts=[0,14*45000-1394,96*2]=3510.

الربح الأقصى المرتبط بالأطفال: 1551 * 2 = 3102 يورو

الضريبة الدنيا: 3510 - 3102 = 408 يورو

الضريبة الإجمالية مع 3 حصص التي تم حسابها مسبقًا وتبلغ 2115 يورو أعلى من الضريبة الدنيا البالغة 408 يورو، وبالتالي لا ينطبق سقف الإعفاء العائلي في هذه الحالة.

بشكل عام، الضريبة الإجمالية تساوي (الضريبة 1، الضريبة 2) حيث:

  • [impôt1]: هو الضريبة الإجمالية المحسوبة مع الأطفال؛
  • [impôt2]: هو الضريبة الإجمالية المحسوبة بدون احتساب الأطفال، مطروحًا منها الحد الأقصى للخصم (وهو هنا 1551 يورو لكل نصف حصة) المرتبط بالأطفال؛

8.1.3. حساب الخصم

Image

بالنسبة للموظف المتزوج ولديه طفلان ويبلغ راتبه السنوي S 50000 يورو:

الضريبة الإجمالية (2115) الناتجة عن الخطوة السابقة أقل من 2627 يورو للزوجين (1595 يورو للعازب): وبالتالي يُطبق الخصم. ويتم الحصول عليه من خلال الحساب التالي:

التخفيض = الحد الأدنى (للزوجين = 1970 / للعازب = 1196) - 0,75 * الضريبة الإجمالية

الخصم = 1970 - 0,75 × 2115 = 383,75، مقربًا إلى 384 يورو.

الضريبة الإجمالية الجديدة = 2115 - 384 = 1731 يورو

8.1.4. حساب التخفيض الضريبي

Image

تحت حد معين، يتم تطبيق تخفيض بنسبة 20% على الضريبة الإجمالية الناتجة عن الحسابات السابقة. في عام 2019، كانت الحدود كما يلي:

  • الأعزب: 21037 يورو؛
  • الأزواج: 42074 يورو؛ (يبدو أن الرقم 37968 المستخدم في المثال أعلاه خاطئ)؛

يُضاف إلى هذا الحد القيمة التالية: 3797 * (عدد نصف الحصص التي يضيفها الأبناء).

وبالنسبة للموظف المتزوج ولديه طفلان ويبلغ راتبه السنوي S 50000 يورو:

  • دخله الخاضع للضريبة (45000 يورو) أقل من الحد الأدنى (42074+2*3797)=49668 يورو؛
  • وبالتالي يحق له الحصول على تخفيض بنسبة 20٪ من ضريبته: 1731 * 0,2 = 346,2 يورو، مقربًا إلى 347 يورو؛
  • يصبح الضريبة الإجمالية للمكلف: 1731 - 347 = 1384 يورو؛

8.1.5. حساب الضريبة الصافية

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

8.1.6. حالة ذوي الدخل المرتفع

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

8.1.6.1. الحد الأقصى للتخفيض بنسبة 10% على الدخل السنوي

في معظم الحالات، يتم حساب الدخل الخاضع للضريبة بالصيغة التالية: R=0,9*S حيث S هو الراتب السنوي. ويُطلق على ذلك اسم التخفيض بنسبة 10٪. ويخضع هذا التخفيض لسقف أقصى. في عام 2019:

  • لا يمكن أن يتجاوز 12502 يورو؛
  • لا يمكن أن يقل عن 437 يورو؛

لنأخذ مثالاً لموظف غير متزوج وليس لديه أطفال ويبلغ راتبه السنوي 200000 يورو:

  • يبلغ التخفيض بنسبة 10% 20000 يورو > 12502 يورو. وبالتالي يتم تخفيضه إلى 12502 يورو؛

8.1.6.2. الحد الأقصى للمعامل الأسري

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

  • الخصم بنسبة 10٪ يبلغ 10000 يورو < 12502 يورو. وبالتالي، فإن الدخل الخاضع للضريبة R يساوي 100000 - 10000 = 90000 يورو؛
  • يمتلك الزوجان nbParts = 2 + 0,5*2 + 1 = 4 حصص؛
  • وبالتالي فإن نصيب الأسرة هو QF = R/nbParts = 90000/4 = 22500 يورو؛
  • ضريبه الإجمالي I1 مع وجود أطفال هو I1=0,14*90000-1394,96*4= 7020 يورو؛
  • ضريبته الإجمالية I2 بدون أطفال:
  • QF=90000/2=45000 يورو؛
  • I2=0,3*90000-5798*2=15404 يورو؛
  • تنص قاعدة الحد الأقصى للحصة العائلية على أن المكسب الناتج عن وجود أطفال لا يمكن أن يتجاوز (1551 × 4 نصف حصص) = 6204 يورو. لكن في هذه الحالة، يبلغ المكسب I2-I1 = 15404 - 7020 = 8384 يورو، وهو بذلك أعلى من 6204 يورو؛
  • وبالتالي يُعاد حساب الضريبة الإجمالية على النحو التالي: I3 = I2-6204 = 15404 - 6204 = 9200 يورو؛

لن يحصل هذا الزوج على أي خصم أو تخفيض، وستكون ضريبتهما النهائية 9200 يورو.

8.1.7. الأرقام الرسمية

يعد حساب الضريبة أمرًا معقدًا. وستُجرى الاختبارات طوال هذا المستند باستخدام الأمثلة التالية. النتائج مأخوذة من أداة المحاكاة الخاصة بإدارة الضرائب |https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm|:

المكلف
النتائج الرسمية
نتائج الخوارزمية الواردة في الوثيقة
زوجان ولديهما طفلان ودخلهما السنوي يبلغ 55555 يورو
الضريبة = 2815 يورو
معدل الضريبة = 14%
الضريبة = 2814 يورو
معدل الضريبة = 14%
زوجان ولديهما طفلان ودخلهما السنوي 50000 يورو
الضريبة = 1385 يورو
الخصم = 720 يورو
التخفيض = 0 يورو
معدل الضريبة = 14%
الضريبة = 1384 يورو
الخصم = 384 يورو
الخصم = 347 يورو
معدل الضريبة = 14%
زوجان لديهما 3 أطفال ودخل سنوي يبلغ 50000 يورو
الضريبة = 0 يورو
الخصم = 384 يورو
الخصم = 346 يورو
معدل الضريبة = 14%
الضريبة = 0 يورو
الخصم = 720 يورو
الخصم = 0 يورو
معدل الضريبة = 14%
أعزب ولديه طفلان ودخله السنوي 100000 يورو
الضريبة = 19884 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 41%
الضريبة = 19884 يورو
الزيادة = 4480 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 41%
أعزب ولديه 3 أطفال ودخل سنوي قدره 100000 يورو
الضريبة = 16782 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 41%
الضريبة = 16782 يورو
الزيادة = 7176 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 41%
زوجان لديهما 3 أطفال ودخل سنوي قدره 100000 يورو
الضريبة = 9200 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 30%
الضريبة = 9200 يورو
الزيادة = 2180 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 30%
زوجان لديهما 5 أطفال ودخل سنوي قدره 100000 يورو
الضريبة = 4230 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 14%
الضريبة = 4230 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 14%
أعزب بدون أطفال ودخل سنوي قدره 100000 يورو
الضريبة = 22986 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 41%
الضريبة = 22986 يورو
الزيادة = 0 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 41%
زوجان ولديهما طفلان ودخلهما السنوي 30000 يورو
الضريبة = 0 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 0%
الضريبة = 0 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 0%
أعزب بدون أطفال ودخل سنوي قدره 200000 يورو
الضريبة = 64211 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 45%
الضريبة = 64210 يورو
الزيادة = 7498 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 45%
زوجان لديهما 3 أطفال ودخل سنوي يبلغ 200000 يورو
الضريبة = 42843 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 41%
الضريبة = 42842 يورو
الزيادة = 17283 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 41%

فيما سبق، يُطلق مصطلح «الزيادة» على المبلغ الإضافي الذي يدفعه أصحاب الدخل المرتفع بسبب ظاهرتين:

  • تحديد سقف للخصم بنسبة 10% على الدخل السنوي؛
  • تحديد سقف للمعامل الأسري؛

لم يتسن التحقق من هذا المؤشر لأن أداة المحاكاة الخاصة بإدارة الضرائب لا توفره.

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

  • إلى اليورو الأعلى بالنسبة للخصومات والتخفيضات؛
  • إلى اليورو الأدنى بالنسبة للزيادة الضريبية والضريبة النهائية؛

سنقوم بتطوير عدة إصدارات لتطبيق حساب الضريبة.

8.2. الإصدار 1

Image

8.2.1. النص البرمجي الرئيسي

نقدم برنامجًا أوليًا حيث:

  • البيانات اللازمة لحساب الضريبة مكتوبة بشكل ثابت في الكود على شكل قوائم وثوابت؛
  • توجد بيانات دافعي الضرائب (الحالة الاجتماعية، عدد الأبناء، الراتب) في ملف نصي أول [taxpayersdata.txt]؛
  • يتم تخزين نتائج حساب الضريبة (المتزوج، والأطفال، والراتب، والضريبة) في ملف نصي ثانٍ [résultats.txt

النص البرمجي [v-01/main.py] هو كما يلي:


# الوحدات النمطية
import sys

from impots.v01.shared.impôts_module_01 import *

# main -----------------------

# الثوابت
# ملف دافعي الضرائب
DATA = "./data/taxpayersdata.txt"
# ملف النتائج
RESULTATS = "./data/résultats.txt"

try:
    # قراءة بيانات دافعي الضرائب
    tax_payers = get_taxpayers_data(DATA)
    # قائمة النتائج
    results = []
    # حساب ضريبة المكلفين
    for tax_payer in tax_payers:
        # يُرجع حساب الضريبة قاموسًا من المفاتيح
        # ['marié', 'enfants', 'salaire', 'impôt', 'surcôte', 'décôte', 'réduction', 'taux']
        result = calcul_impôt(tax_payer['marié'], tax_payer['enfants'], tax_payer['salaire'])
        # تمت إضافة القاموس إلى قائمة النتائج
        results.append(result)
    # يتم تسجيل النتائج
    record_results(RESULTATS, results)
except BaseException as erreur:
    # قد تحدث أخطاء مختلفة: عدم وجود الملف، أو محتوى الملف غير صحيح
    # يتم عرض الخطأ والخروج من التطبيق
    print(f"l'erreur suivante s'est produite : {erreur}]\n")
    sys.exit()

ملاحظات

  • السطر 4: يتم استخدام الوحدة النمطية [impots.v01.modules.impôts_module_01]. تجدر الإشارة إلى أن هذا المسار يُحسب انطلاقًا من جذر المشروع PyCharm؛
  • السطر 10: الملف [data/taxpayersdata.txt] هو كما يلي:
oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3,100000
oui,3,100000
oui,5,100000
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000

يمثل كل سطر مجموعة مكونة من ثلاثة عناصر [marié / pacsé ou pas, nombre d'enfants, salaire annuel en euros].

  • السطر 12: الملف الذي سيتم فيه وضع نتائج حساب الضريبة لكل دافع ضرائب في الملف [taxpayersdata.txt]. وسيكون محتواه كما يلي:
{'marié': 'oui', 'enfants': 2, 'salaire': 55555, 'impôt': 2814, 'surcôte': 0, 'décôte': 0, 'réduction': 0, 'taux': 0.14}
{'marié': 'oui', 'enfants': 2, 'salaire': 50000, 'impôt': 1384, 'surcôte': 0, 'décôte': 384, 'réduction': 347, 'taux': 0.14}
{'marié': 'oui', 'enfants': 3, 'salaire': 50000, 'impôt': 0, 'surcôte': 0, 'décôte': 720, 'réduction': 0, 'taux': 0.14}
{'marié': 'non', 'enfants': 2, 'salaire': 100000, 'impôt': 19884, 'surcôte': 4480, 'décôte': 0, 'réduction': 0, 'taux': 0.41}
{'marié': 'non', 'enfants': 3, 'salaire': 100000, 'impôt': 16782, 'surcôte': 7176, 'décôte': 0, 'réduction': 0, 'taux': 0.41}
{'marié': 'oui', 'enfants': 3, 'salaire': 100000, 'impôt': 9200, 'surcôte': 2180, 'décôte': 0, 'réduction': 0, 'taux': 0.3}
{'marié': 'oui', 'enfants': 5, 'salaire': 100000, 'impôt': 4230, 'surcôte': 0, 'décôte': 0, 'réduction': 0, 'taux': 0.14}
{'marié': 'non', 'enfants': 0, 'salaire': 100000, 'impôt': 22986, 'surcôte': 0, 'décôte': 0, 'réduction': 0, 'taux': 0.41}
{'marié': 'oui', 'enfants': 2, 'salaire': 30000, 'impôt': 0, 'surcôte': 0, 'décôte': 0, 'réduction': 0, 'taux': 0}
{'marié': 'non', 'enfants': 0, 'salaire': 200000, 'impôt': 64210, 'surcôte': 7498, 'décôte': 0, 'réduction': 0, 'taux': 0.45}
{'marié': 'oui', 'enfants': 3, 'salaire': 200000, 'impôt': 42842, 'surcôte': 17283, 'décôte': 0, 'réduction': 0, 'taux': 0.41}
  • السطر 16: يتم استرداد بيانات المكلفين الواردة في [taxpayersdata.txt]. يتم استرداد قائمة بقواميس المفاتيح [marié, enfants, salaire حيث يمثل كل قاموس مكلفًا واحدًا؛
  • الأسطر 17-25: يتم حساب الضريبة المستحقة على دافعي الضرائب الواردة في القائمة [taxPayers]. يتم استرداد قائمة [results]، حيث يمثل كل عنصر فيها مرة أخرى قاموسًا للمفاتيح [marié, enfants, salaire, impôt, surcôte, décôte, réduction, taux]؛
  • السطر 27: يتم تسجيل قائمة النتائج [results] في الملف [résultats.txt] بالشكل الموضح أعلاه؛
  • الأسطر 28-32: يتم إيقاف جميع الاستثناءات التي قد تنشأ من الوحدة النمطية [impots.v01.modules.impôts_module_01]؛

سنقوم الآن بتفصيل الوظائف الثلاث التي يستخدمها البرنامج النصي [main]:

  • [get_taxpayers_data]: لقراءة بيانات دافعي الضرائب؛
  • [calcul_impôt]: لحساب الضرائب المستحقة عليهم؛
  • [record_results]: لتسجيل النتائج في ملف نصي؛

توجد جميع هذه الوظائف في الوحدة النمطية [impots.modules.impôts_module_01].

8.2.2. الوحدة النمطية [impots.v01.shared.impôts_module_01]

تم تجميع الوظائف اللازمة لحساب الضريبة في الوحدة النمطية [impots.v01.shared.impôts_module_01]:

Image

  • في [1]: تعريف الثوابت الخاصة بحساب الضريبة؛
  • في الوحدة النمطية [2]: قائمة وظائف الوحدة النمطية؛

8.2.3. الوظيفة [get_taxpayers_data]

الوظيفة [get_taxpayers_data] هي كما يلي:


# عمليات الاستيراد
import codecs


# قراءة بيانات دافعي الضرائب
# ----------------------------------------
def get_taxpayers_data(taxpayers_filename: str) -> list:
    # قراءة بيانات دافعي الضرائب
    file = None
    try:
        # قائمة دافعي الضرائب
        taxpayers = []
        # فتح الملف
        file = codecs.open(taxpayers_filename, "r", "utf8")
        # قراءة السطر الأول من ملف دافعي الضرائب
        ligne = file.readline().strip()
        # ما دام هناك سطر متبقي للاستخدام
        while ligne != '':
            # يتم استرداد الحقول الثلاثة «متزوج» و«أطفال» و«الراتب» التي تشكل السطر
            (marié, enfants, salaire) = ligne.split(",")
            # إضافتها إلى قائمة دافعي الضرائب
            taxpayers.append({'marié': marié.strip().lower(), 'enfants': int(enfants), 'salaire': int(salaire)})
            # يتم قراءة سطر جديد من ملف دافعي الضرائب
            ligne = file.readline().strip()
        # نُرجع النتيجة
        return taxpayers
    finally:
        # يتم إغلاق الملف إذا كان مفتوحًا
        if file:
            file.close()

ملاحظات

  • السطر 7: [taxpayers_filename] هو اسم الملف المراد معالجته. تُرجع الدالة قائمة؛
  • الأسطر 18-24: حلقة معالجة الأسطر [marié, enfants, salaire] من الملف النصي؛
  • السطر 20: يتم استرداد العناصر الثلاثة للسطر. نفترض هنا أن السطر صحيح من الناحية النحوية، أي أنه يحتوي بالفعل على العناصر الثلاثة المتوقعة؛
  • السطر 22: يتم إنشاء قاموس بالمفاتيح [marié, enfants, salaire] ويتم إضافة هذا القاموس إلى القائمة [taxPayers]؛
  • السطر 26: بمجرد معالجة الملف، يتم إرجاع القائمة [taxPayers]؛
  • الأسطر 10-30: نلاحظ أنه لم يتم وضع جملة [catch] في [try] في السطر 10. جملة [catch] ليست إلزامية. في السطر 27، تم إدراج جملة [finally] لإغلاق الملف النصي في جميع الحالات، سواء حدث خطأ أم لا؛
  • تسمح بنية try / finally هذه بمرور استثناء محتمل (لا يوجد أمر catch). سيتم تمرير هذا الاستثناء إلى البرنامج النصي الرئيسي [main] الذي سيتوقف ويعرض الاستثناء (انظر الفقرة |البرنامج النصي الرئيسي|). تم استخدام هذه الآلية في معظم وظائف الوحدة النمطية؛

8.2.4. الوظيفة [calcul_impôt]

الوظيفة [calcul_impôt] هي كما يلي:


# الاستيرادات
import codecs
import math

# شرائح الضريبة لعام 2019
limites = [9964, 27519, 73779, 156244, 0]
coeffr = [0, 0.14, 0.3, 0.41, 0.45]
coeffn = [0, 1394.96, 5798, 13913.69, 20163.45]

# الثوابت لحساب الضريبة لعام 2019
PLAFOND_QF_DEMI_PART = 1551
PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION = 21037
PLAFOND_REVENUS_COUPLE_POUR_REDUCTION = 42074
VALEUR_REDUC_DEMI_PART = 3797
PLAFOND_DECOTE_CELIBATAIRE = 1196
PLAFOND_DECOTE_COUPLE = 1970
PLAFOND_IMPOT_COUPLE_POUR_DECOTE = 2627
PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE = 1595
ABATTEMENT_DIXPOURCENT_MAX = 12502
ABATTEMENT_DIXPOURCENT_MIN = 437


# حساب الضريبة
# ----------------------------------------
def calcul_impôt(marié: str, enfants: int, salaire: int) -> dict:
    # متزوج: نعم، لا
    # الأطفال: عدد الأطفال
    # الراتب: الراتب السنوي
    # الحدود، المعامل، المعامل: جداول البيانات التي تسمح بحساب الضريبة
    #
    # حساب الضريبة مع وجود أطفال
    result1 = calcul_impôt_2(marié, enfants, salaire)
    impot1 = result1["impôt"]
    # حساب الضريبة بدون أطفال
    if enfants != 0:
        result2 = calcul_impôt_2(marié, 0, salaire)
        impot2 = result2["impôt"]
        # تطبيق الحد الأقصى للمعامل الأسري
        if enfants < 3:
            # PLAFOND_QF_DEMI_PART يورو للطفلين الأولين
            impot2 = impot2 - enfants * PLAFOND_QF_DEMI_PART
        else:
            # PLAFOND_QF_DEMI_PART يورو للطفلين الأولين، ضعف هذا المبلغ للأطفال التاليين
            impot2 = impot2 - 2 * PLAFOND_QF_DEMI_PART - (enfants - 2) * 2 * PLAFOND_QF_DEMI_PART
    else:
        impot2 = impot1
        result2 = result1

    # يُحتسب الضريبة الأعلى مع المعدل والزيادة المرتبطة بها
    if impot1 > impot2:
        impot = impot1
        taux = result1["taux"]
        surcôte = result1["surcôte"]
    else:
        surcôte = impot2 - impot1 + result2["surcôte"]
        impot = impot2
        taux = result2["taux"]

    # حساب أي خصم محتمل
    décôte = get_décôte(marié, salaire, impot)
    impot -= décôte
    # حساب أي تخفيض محتمل في الضرائب
    réduction = get_réduction(marié, salaire, enfants, impot)
    impot -= réduction
    # النتيجة
    return {"marié": marié, "enfants": enfants, "salaire": salaire, "impôt": math.floor(impot), "surcôte": surcôte,
            "décôte": décôte, "réduction": réduction, "taux": taux}

ملاحظات

  • الأسطر 6-8: شرائح الضريبة (انظر الفقرة |حساب الضريبة الإجمالية|)؛
  • الأسطر 11-20: الثوابت المستخدمة في حساب الضريبة؛
  • تجدر الإشارة إلى أن العناصر التي تم تهيئتها في الأسطر 5-20 ستكون عامة بالنسبة للدوال التي سنصفها. وبالتالي فهي معروفة طالما أن الدالة التي تستخدمها لا تعلن عن متغيرات تحمل نفس الأسماء؛
  • تتغير الأرقام الواردة في الأسطر 5-20 كل عام. والأرقام الواردة هنا هي أرقام عام 2019؛
  • السطر 25: تتلقى الدالة [calcul_impôt] ثلاثة معلمات:
    • [marié]: نعم/لا، تشير إلى ما إذا كان المكلف متزوجًا أو في علاقة شراكة مدنية؛
    • [enfants]: عدد أطفاله؛
    • [salaire]: راتبه السنوي باليورو؛
  • الأسطر 31-33: حساب الضريبة مع أخذ عدد الأطفال في الاعتبار؛
  • الأسطر 34-47: تُطبق هذه الأسطر الحد الأقصى للحصّة العائلية (انظر الفقرة |الحد الأقصى للحصّة العائلية|)؛
  • الأسطر 49-57: تحسب هذه الأسطر معدل الضريبة على المكلف وكذلك أي زيادة محتملة (انظر الفقرة |حالة الدخل المرتفع|)؛
  • الأسطر 59-61: حساب أي خصم محتمل (انظر الفقرة |حساب الخصم|)؛
  • الأسطر 62-64: حساب أي تخفيض محتمل في الضريبة المستحقة (انظر الفقرة |حساب التخفيض الضريبي|)؛

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

8.2.5. الدالة [calcul_impôt_2]

تستدعي الدالة [calcul_impôt] الدالة التالية [calcul_impôt_2]:


def calcul_impôt_2(marié: str, enfants: int, salaire: int) -> list:
    # متزوج: نعم، لا
    # الأطفال: عدد الأطفال
    # الراتب: الراتب السنوي
    # الحدود، المعامل، المعامل: جداول البيانات التي تسمح بحساب الضريبة
    #
    # عدد الحصص
    marié = marié.strip().lower()
    if marié == "oui":
        nb_parts = enfants / 2 + 2
    else:
        nb_parts = enfants / 2 + 1

    # حصة واحدة لكل طفل ابتداءً من الطفل الثالث
    if enfants >= 3:
        # نصف حصة إضافية لكل طفل ابتداءً من الطفل الثالث
        nb_parts += 0.5 * (enfants - 2)

    # الدخل الخاضع للضريبة
    revenu_imposable = get_revenu_imposable(salaire)
    # الزيادة
    surcôte = math.floor(revenu_imposable - 0.9 * salaire)
    # بسبب مشاكل التقريب
    if surcôte < 0:
        surcôte = 0

    # الحصة العائلية
    quotient = revenu_imposable / nb_parts
    #في نهاية جدول الحدود لإنهاء الحلقة التالية
    limites[len(limites) - 1] = quotient
    # حساب الضريبة
    i = 0
    while quotient > limites[i]:
        i += 1
        # نظرًا لوضع الحصيلة في نهاية جدول الحدود، فإن الحلقة السابقة
    # لا يمكن أن تتجاوز حدود الجدول

    # يمكننا الآن حساب الضريبة
    impôt = math.floor(revenu_imposable * coeffr[i] - nb_parts * coeffn[i])
    # النتيجة
    return {"impôt": impôt, "surcôte": surcôte, "taux": coeffr[i]}

وقد تم وصف هذه الخوارزمية في الفقرة 8.1.1.

8.2.6. الدالة [get_décôte]

تقوم الدالة [get_décôte] بتنفيذ حساب الخصم المحتمل من الضريبة (الفقرة |حساب الخصم|):


# يحسب الخصم المحتمل
def get_décôte(marié: str, salaire: int, impots: int) -> int:
    # في البداية، الخصم يساوي صفرًا
    décôte = 0
    # الحد الأقصى لمبلغ الضريبة للحصول على الخصم
    plafond_impôt_pour_décôte = PLAFOND_IMPOT_COUPLE_POUR_DECOTE if marié == "oui" else PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE
    if impots < plafond_impôt_pour_décôte:
        # الحد الأقصى لمبلغ الخصم
        plafond_décôte = PLAFOND_DECOTE_COUPLE if marié == "oui" else PLAFOND_DECOTE_CELIBATAIRE
        # الخصم النظري
        décôte = plafond_décôte - 0.75 * impots
        # لا يمكن أن يتجاوز الخصم مبلغ الضريبة
        if décôte > impots:
            décôte = impots

        # لا يوجد خصم <0
        if décôte < 0:
            décôte = 0

    # النتيجة
    return math.ceil(décôte)

8.2.7. الدالة [get_réduction]

تقوم الدالة [get_réduction] بتنفيذ حساب التخفيض المحتمل للضريبة المستحقة (الفقرة |حساب التخفيض الضريبي|):


# يحسب التخفيض المحتمل
def get_réduction(marié: str, salaire: int, enfants: int, impots: int) -> int:
    # الحد الأقصى للدخل للحصول على التخفيض بنسبة 20%
    plafond_revenu_pour_réduction = PLAFOND_REVENUS_COUPLE_POUR_REDUCTION if marié == "oui" else PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION
    plafond_revenu_pour_réduction += enfants * VALEUR_REDUC_DEMI_PART
    if enfants > 2:
        plafond_revenu_pour_réduction += (enfants - 2) * VALEUR_REDUC_DEMI_PART

    # الدخل الخاضع للضريبة
    revenu_imposable = get_revenu_imposable(salaire)
    # التخفيض
    réduction = 0
    if revenu_imposable < plafond_revenu_pour_réduction:
        # تخفيض بنسبة 20%
        réduction = 0.2 * impots

    # النتيجة
    return math.ceil(réduction)

8.2.8. الدالة [get_revenu_imposable]

تقوم الدالة [get_revenu_imposable] بحساب الدخل الخاضع للضريبة استنادًا إلى الراتب السنوي:


# revenu_imposable = salaireAnnuel - الخصم
# الخصم له حد أدنى وحد أقصى
# ----------------------------------------
def get_revenu_imposable(salaire: int) -> int:
    # خصم بنسبة 10% من الراتب
    abattement = 0.1 * salaire
    # لا يمكن أن يتجاوز هذا الخصم ABATTEMENT_DIXPOURCENT_MAX
    if abattement > ABATTEMENT_DIXPOURCENT_MAX:
        abattement = ABATTEMENT_DIXPOURCENT_MAX

    # لا يمكن أن يقل الخصم عن ABATTEMENT_DIXPOURCENT_MIN
    if abattement < ABATTEMENT_DIXPOURCENT_MIN:
        abattement = ABATTEMENT_DIXPOURCENT_MIN

    # الدخل الخاضع للضريبة
    revenu_imposable = salaire - abattement
    # النتيجة
    return math.floor(revenu_imposable)

8.2.9. الدالة [record_results]

تقوم الدالة [record_results] بتسجيل نتائج حساب الضريبة في ملف نصي:


# تسجيل النتائج في ملف نصي
# ----------------------------------------
def record_results(results_filename: str, results: list):
    # results_filename: اسم الملف النصي الذي سيتم تسجيل النتائج فيه
    # results: قائمة النتائج في شكل قائمة من القواميس
    # يتم كتابة كل قاموس في سطر نصي واحد
    résultats = None
    try:
        # فتح ملف النتائج
        résultats = codecs.open(results_filename, "w", "utf8")
        # معالجة البيانات
        for result in results:
            # يتم تسجيل النتيجة في ملف النتائج
            résultats.write(f"{result}\n")
            # المساهم التالي
    finally:
        # إغلاق الملف إذا كان مفتوحًا
        if résultats:
            résultats.close()

8.2.10. النتائج

كما سبق ذكره، باستخدام ملف دافعي الضرائب [taxpayersdata.txt] التالي:

oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3,100000
oui,3,100000
oui,5,100000
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000

يقوم البرنامج النصي [main.py] بإنشاء الملف [résultats.txt] التالي:

{'marié': 'oui', 'enfants': 2, 'salaire': 55555, 'impôt': 2814, 'surcôte': 0, 'décôte': 0, 'réduction': 0, 'taux': 0.14}
{'marié': 'oui', 'enfants': 2, 'salaire': 50000, 'impôt': 1384, 'surcôte': 0, 'décôte': 384, 'réduction': 347, 'taux': 0.14}
{'marié': 'oui', 'enfants': 3, 'salaire': 50000, 'impôt': 0, 'surcôte': 0, 'décôte': 720, 'réduction': 0, 'taux': 0.14}
{'marié': 'non', 'enfants': 2, 'salaire': 100000, 'impôt': 19884, 'surcôte': 4480, 'décôte': 0, 'réduction': 0, 'taux': 0.41}
{'marié': 'non', 'enfants': 3, 'salaire': 100000, 'impôt': 16782, 'surcôte': 7176, 'décôte': 0, 'réduction': 0, 'taux': 0.41}
{'marié': 'oui', 'enfants': 3, 'salaire': 100000, 'impôt': 9200, 'surcôte': 2180, 'décôte': 0, 'réduction': 0, 'taux': 0.3}
{'marié': 'oui', 'enfants': 5, 'salaire': 100000, 'impôt': 4230, 'surcôte': 0, 'décôte': 0, 'réduction': 0, 'taux': 0.14}
{'marié': 'non', 'enfants': 0, 'salaire': 100000, 'impôt': 22986, 'surcôte': 0, 'décôte': 0, 'réduction': 0, 'taux': 0.41}
{'marié': 'oui', 'enfants': 2, 'salaire': 30000, 'impôt': 0, 'surcôte': 0, 'décôte': 0, 'réduction': 0, 'taux': 0}
{'marié': 'non', 'enfants': 0, 'salaire': 200000, 'impôt': 64210, 'surcôte': 7498, 'décôte': 0, 'réduction': 0, 'taux': 0.45}
{'marié': 'oui', 'enfants': 3, 'salaire': 200000, 'impôt': 42842, 'surcôte': 17283, 'décôte': 0, 'réduction': 0, 'taux': 0.41}

تتوافق هذه النتائج مع الأرقام الرسمية الواردة في الفقرة |الأرقام الرسمية|.

الآن، لنقم بتشغيل هذه النسخة في نافذة سطر الأوامر:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\v01>python main.py
Traceback (most recent call last):
  File "main.py", line 4, in <module>
    from impots.v01.shared.impôts_module_01 import *
ModuleNotFoundError: No module named 'impots'

نلاحظ ظهور خطأ سبق أن واجهناه: وهو عدم العثور على وحدة نمطية، وهي في هذه الحالة الوحدة [impots]. نذكر أن هذا يعني أن:

  • أن مترجم Python قد بحث واحدًا تلو الآخر في المجلدات الموجودة في مسار Python؛
  • ولم يعثر في أي منها على مجلد يحتوي على البرنامج النصي [impots.py]؛

وستقدم النسخة [v02] حلاً لهذه المشكلة.