Skip to content

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

8.1. المشكلة

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

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

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

Image

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

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

يمكن حساب الضريبة الإجمالية على النحو التالي:

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

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

وبالتالي، يكون عدد الأسهم:

  • nbParts=1+nbChildren*0.5+(nbChildren-2)*0.5 إذا كان الموظف غير متزوج؛
  • nbParts=2+nbChildren*0.5+(nbChildren-2)*0.5 إذا كان متزوجًا؛
    • حيث nbChildren هو عدد الأطفال؛
  • نحسب الدخل الخاضع للضريبة R = 0.9 * S، حيث S هو الراتب السنوي؛
  • يُحسب معامل الأسرة QF على النحو التالي: QF = R / nbParts؛
  • نحسب الضريبة الإجمالية I بناءً على البيانات التالية (2019):
9964
0
0
27,519
0.14
1,394.96
73,779
0.3
5,798
156,244
0.4
13,913.69
0
0.45
20163.45

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

الدخل الخاضع للضريبة: R=0.9*S=45,000

عدد الحصص: nbParts=2+2*0.5=3

نسبة الأسرة: QF = 45,000 / 3 = 15,000

الصف الأول الذي يكون فيه QF <= field1 هو كما يلي:

    27519    0.14    1394.96

وبالتالي، فإن الضريبة I تساوي 0.14*R – 1394.96*عدد_الأسهم=[0.14*45000-1394.96*3]=2115. يتم تقريب الضريبة لأسفل إلى أقرب يورو.

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

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

    0    0.45    20163.45

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

8.1.2. الحد الأقصى لنسبة الأسرة

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

الدخل الخاضع للضريبة: R = 0.9 * S = 45,000

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

معدل الأسرة: QF = 45,000 / 2 = 22,500

السطر الأول حيث QF <= field1 هو كما يلي:

    27519    0.14    1394.96

وبالتالي، فإن الضريبة الأولى تساوي 0.14*R – 1394.96*عدد الأسهم = [0.14*45,000 – 1394.96*2] = 3,510.

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

الضريبة الدنيا: 3,510 – 3,102 = 408 يورو

الضريبة الإجمالية مع 3 شرائح ضريبية، التي تم حسابها بالفعل بمبلغ 2,115 يورو، أعلى من الحد الأدنى للضريبة البالغ 408 يورو، لذا لا ينطبق سقف الأسرة هنا.

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

  • [الضريبة 1]: هي الضريبة الإجمالية المحسوبة بما في ذلك الأطفال؛
  • [الضريبة 2]: هي الضريبة الإجمالية المحسوبة بدون الأطفال ومخصومة منها الائتمان الأقصى (هنا 1,551 يورو لكل نصف حصة) المتعلق بالأطفال؛

8.1.3. حساب التخفيض

Image

باستخدام المثال نفسه لموظف متزوج ولديه طفلان وراتب سنوي S قدره 50,000 يورو:

الضريبة الإجمالية (2,115) من الخطوة السابقة أقل من 2,627 يورو للزوجين (1,595 يورو للشخص العازب): وبالتالي ينطبق التخفيض. ويتم حسابه على النحو التالي:

الخصم = الحد الأدنى (للزوجين = 1,970 / للأعزب = 1,196) - 0.75 * الضريبة الإجمالية

الخصم = 1,970 – 0.75 * 2,115 = 383.75، مقربًا إلى 384 يورو.

الضريبة الإجمالية الجديدة = 2,115 – 384 = 1,731 يورو

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

Image

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

  • الأفراد: 21,037 يورو؛
  • الأزواج: 42,074 يورو؛ (يبدو أن الرقم 37,968 المستخدم في المثال أعلاه غير صحيح)؛

يتم زيادة هذا الحد بقيمة: 3,797 * (عدد نصف الحصص التي يساهم بها الأبناء).

مرة أخرى، بالنسبة للموظف المتزوج الذي لديه طفلان وراتب سنوي S قدره 50,000 يورو:

  • دخله الخاضع للضريبة (45,000 يورو) أقل من الحد الأدنى (42,074 + 2 × 3,797) = 49,668 يورو؛
  • وبالتالي يحق له الحصول على تخفيض بنسبة 20% في ضريبته: 1,731 * 0.2 = 346.2 يورو، مقربًا إلى 347 يورو؛
  • يصبح إجمالي الضريبة المستحقة على المكلف: 1,731 – 347 = 1,384 يورو؛

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

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

8.1.6. حالات الدخل المرتفع

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

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

في معظم الحالات، يُحسب الدخل الخاضع للضريبة باستخدام الصيغة: R = 0.9 × S، حيث S هو الراتب السنوي. يُعرف هذا باسم التخفيض بنسبة 10٪. هذا التخفيض محدد بسقف. في عام 2019:

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

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

  • تبلغ نسبة التخفيض البالغة 10% 200,000 يورو > 12,502 يورو. وبالتالي، فإن الحد الأقصى هو 12,502 يورو؛

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

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

  • الخصم بنسبة 10% هو 10,000 يورو < 12,502 يورو. وبالتالي، فإن الدخل الخاضع للضريبة R هو 100,000 - 10,000 = 90,000 يورو؛
  • لدى الزوجين nbParts = 2 + 0.5 × 2 + 1 = 4 حصص؛
  • وبالتالي، فإن حاصل الأسرة هو QF = R / nbParts = 90,000 / 4 = 22,500 يورو؛
  • ضريبتهم الإجمالية I1 مع أطفال هي I1 = 0.14 × 90,000 – 1,394.96 × 4 = 7,020 يورو؛
  • ضريبة الإجمالي I2 بدون أطفال:
  • QF = 90,000 / 2 = 45,000 يورو؛
  • I2 = 0.3 × 90,000 – 5,798 × 2 = 15,404 يورو؛
  • تنص قاعدة سقف الحصة العائلية على أن المزايا التي يوفرها الأطفال لا يمكن أن تتجاوز (1,551 × 4 نصف حصص) = 6,204 يورو. ومع ذلك، في هذه الحالة، فإن I2 – I1 = 15,404 – 7,020 = 8,384 يورو، وهو مبلغ أكبر من 6,204 يورو؛
  • وبالتالي، يُعاد حساب الضريبة الإجمالية على النحو التالي: I3 = I2 - 6,204 = 15,404 - 6,204 = 9,200 يورو؛

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

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

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

المكلف
النتائج الرسمية
النتائج المستمدة من خوارزمية الوثيقة
زوجان لديهما طفلان ودخلهما السنوي 55,555 يورو
الضريبة = 2,815 يورو
معدل الضريبة = 14%
الضريبة = 2,814 يورو
معدل الضريبة = 14%
زوجان ولديهما طفلان ودخل سنوي يبلغ 50,000 يورو ( )
الضريبة = 1,385 يورو
الائتمان الضريبي = 720 يورو
التخفيض = 0 يورو
معدل الضريبة = 14%
الضريبة = 1,384 يورو
الخصم = 384 يورو
الائتمان = 347 يورو
معدل الضريبة = 14%
زوجان ولديهما 3 أطفال ودخلهما السنوي 50,000 يورو
الضريبة = 0 يورو
الائتمان الضريبي = 384 يورو
التخفيض = 346 يورو
معدل الضريبة = 14%
الضريبة = 0 يورو
الخصم = 720 يورو
الخصم = 0 يورو
معدل الضريبة = 14%
أعزب ولديه طفلان ودخله السنوي 100,000 يورو
الضريبة = 19,884 يورو
الائتمان الضريبي = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 41%
الضريبة = 19,884 يورو
الرسوم الإضافية = 4,480 يورو
الخصم = 0 يورو
التخفيض = 0 يورو
معدل الضريبة = 41%
أعزب ولديه 3 أطفال ودخله السنوي 100,000 يورو
الضريبة = 16,782 يورو
الائتمان الضريبي = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 41%
الضريبة = 16,782 يورو
الرسوم الإضافية = 7,176 يورو
الخصم = 0 يورو
التخفيض = 0 يورو
معدل الضريبة = 41%
زوجان ولديهما 3 أطفال ودخلهما السنوي 100,000 يورو
الضريبة = 9,200 يورو
الائتمان الضريبي = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 30%
الضريبة = 9,200 يورو
الرسوم الإضافية = 2,180 يورو
الخصم = 0 يورو
التخفيض = 0 يورو
معدل الضريبة = 30%
زوجان لديهما 5 أطفال ودخل سنوي قدره 100,000 يورو
الضريبة = 4,230 يورو
الائتمان الضريبي = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 14%
الضريبة = 4,230 يورو
الخصم = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 14%
أعزب، بدون أطفال، ودخل سنوي قدره 100,000 يورو
الضريبة = 22,986 يورو
الائتمان الضريبي = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 41%
الضريبة = 22,986 يورو
الرسوم الإضافية = 0 يورو
الخصم = 0 يورو
التخفيض = 0 يورو
معدل الضريبة = 41%
زوجان ولديهما طفلان ودخلهما السنوي 30,000 يورو
الضريبة = 0 يورو
الائتمان الضريبي = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 0%
الضريبة = 0 يورو
الخصم = 0 يورو
التخفيض = 0 يورو
معدل الضريبة = 0%
أعزب بدون أطفال ودخل سنوي قدره 200,000 يورو
الضريبة = 64,211 يورو
الائتمان الضريبي = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 45%
الضريبة = 64,210 يورو
الرسوم الإضافية = 7,498 يورو
الخصم = 0 يورو
التخفيض = 0 يورو
معدل الضريبة = 45%
زوجان ولديهما 3 أطفال ودخلهما السنوي 200,000 يورو
الضريبة = 42,843 يورو
الائتمان الضريبي = 0 يورو
الخصم = 0 يورو
معدل الضريبة = 41%
الضريبة = 42,842 يورو
الرسوم الإضافية = 17,283 يورو
الخصم = 0 يورو
التخفيض = 0 يورو
معدل الضريبة = 41%

في المثال أعلاه، تشير "الرسوم الإضافية" إلى المبلغ الإضافي الذي يدفعه أصحاب الدخل المرتفع بسبب عاملين:

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

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

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

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

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

8.2. الإصدار 1

Image

8.2.1. النص الرئيسي

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

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

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

#  modules
import sys

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

#  hand -----------------------

#  constants
#  taxpayer file
DATA = "./data/taxpayersdata.txt"
#  results file
RESULTATS = "./data/résultats.txt"

try:
    #  reading taxpayer data
    tax_payers = get_taxpayers_data(DATA)
    #  results list
    results = []
    #  taxpayers' taxes are calculated
    for tax_payer in tax_payers:
        #  tax calculation returns a dictionary of keys
        #  ['married', 'children', 'salary', 'tax', 'surcôte', 'décôte', 'réduction', 'taux']
        result = calcul_impôt(tax_payer['marié'], tax_payer['enfants'], tax_payer['salaire'])
        #  the dictionary is added to the list of results
        results.append(result)
    #  we record the results
    record_results(RESULTATS, results)
except BaseException as erreur:
    #  there may be various errors: no file, incorrect file content
    #  display the error and exit the application
    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

يمثل كل سطر مجموعة مكونة من ثلاثة عناصر [متزوج/في شراكة مدنية أم لا، عدد الأبناء، الراتب السنوي باليورو].

  • السطر 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]. نسترد قائمة من القواميس ذات المفاتيح [married, children, salaryحيث يمثل كل قاموس دافع ضرائب؛
  • الأسطر 17-25: يتم حساب الضريبة للمكلفين في قائمة [taxPayers]. يتم إرجاع قائمة [results]، حيث يكون كل عنصر عبارة عن قاموس بمفاتيح [married, children, salary, tax, surcharge, discount, reduction, rate]؛
  • السطر 27: يتم حفظ قائمة [results] في الملف [results.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] هي كما يلي:

#  imports
import codecs


#  reading taxpayer data
# ----------------------------------------
def get_taxpayers_data(taxpayers_filename: str) -> list:
    #  reading taxpayer data
    file = None
    try:
        #  list of taxpayers
        taxpayers = []
        #  open file
        file = codecs.open(taxpayers_filename, "r", "utf8")
        #  read the first line of the taxpayer file
        ligne = file.readline().strip()
        #  as long as there's a line left to operate
        while ligne != '':
            #  we retrieve the 3 fields married,children,salary which form the line
            (marié, enfants, salaire) = ligne.split(",")
            #  we add them to the list of taxpayers
            taxpayers.append({'marié': marié.strip().lower(), 'enfants': int(enfants), 'salaire': int(salaire)})
            #  a new line is read from the taxpayer file
            ligne = file.readline().strip()
        #  we return the result
        return taxpayers
    finally:
        #  close the file if it has been opened
        if file:
            file.close()

ملاحظات

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

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

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

#  imports
import codecs
import math

#  2019 tax brackets
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]

#  constant for 2019 tax calculation
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


#  tAX CALCULATION
# ----------------------------------------
def calcul_impôt(marié: str, enfants: int, salaire: int) -> dict:
    #  married: yes, no
    #  children: number of children
    #  salary: annual salary
    #  limits, coeffr, coeffn: data tables for tax calculation
    #
    #  tax calculation with children
    result1 = calcul_impôt_2(marié, enfants, salaire)
    impot1 = result1["impôt"]
    #  tax calculation without children
    if enfants != 0:
        result2 = calcul_impôt_2(marié, 0, salaire)
        impot2 = result2["impôt"]
        #  application of the family allowance ceiling
        if enfants < 3:
            #  PLAFOND_QF_DEMI_PART euros for the first 2 children
            impot2 = impot2 - enfants * PLAFOND_QF_DEMI_PART
        else:
            #  PLAFOND_QF_DEMI_PART euros for the first 2 children, double for subsequent children
            impot2 = impot2 - 2 * PLAFOND_QF_DEMI_PART - (enfants - 2) * 2 * PLAFOND_QF_DEMI_PART
    else:
        impot2 = impot1
        result2 = result1

    #  we take the highest tax with the rate and surcharge that go with it
    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"]

    #  calculation of any discount
    décôte = get_décôte(marié, salaire, impot)
    impot -= décôte
    #  calculation of any tax reduction
    réduction = get_réduction(marié, salaire, enfants, impot)
    impot -= réduction
    #  result
    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: تأخذ الدالة [calculate_tax] ثلاثة معلمات:
    • [married]: نعم/لا، تشير إلى ما إذا كان دافع الضرائب متزوجًا أو في شراكة مدنية؛
    • [children]: عدد الأطفال؛
    • [salary]: راتبهم السنوي باليورو؛
  • الأسطر 31-33: حساب الضريبة مع أخذ عدد الأطفال في الاعتبار؛
  • الأسطر 34-47: تطبق هذه الأسطر الحد الأقصى لمعدل الأسرة (انظر القسم |الحد الأقصى لمعدل الأسرة|)؛
  • الأسطر 49-57: تحسب هذه الأسطر معدل الضريبة على المكلف بالضريبة بالإضافة إلى أي رسوم إضافية (انظر القسم |حالات الدخل المرتفع|)؛
  • الأسطر 59-61: حساب أي ائتمان ضريبي (انظر القسم |حساب الائتمان الضريبي|)؛
  • الأسطر 62–64: حساب أي تخفيض في الضريبة المستحقة (انظر القسم |حساب التخفيض الضريبي|)؛

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

8.2.5. دالة [calcul_tax_2]

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

def calcul_impôt_2(marié: str, enfants: int, salaire: int) -> list:
    #  married: yes, no
    #  children: number of children
    #  salary: annual salary
    #  limits, coeffr, coeffn: data tables for tax calculation
    #
    #  number of shares
    marié = marié.strip().lower()
    if marié == "oui":
        nb_parts = enfants / 2 + 2
    else:
        nb_parts = enfants / 2 + 1

    #  1 part per child from the 3rd
    if enfants >= 3:
        #  an additional half share for each child from the 3rd onwards
        nb_parts += 0.5 * (enfants - 2)

    #  taxable income
    revenu_imposable = get_revenu_imposable(salaire)
    #  surcharge
    surcôte = math.floor(revenu_imposable - 0.9 * salaire)
    #  for rounding problems
    if surcôte < 0:
        surcôte = 0

    #  family quotient
    quotient = revenu_imposable / nb_parts
    #  is set at the end of the limit table to stop the following loop
    limites[len(limites) - 1] = quotient
    #  tAX CALCULATION
    i = 0
    while quotient > limites[i]:
        i += 1
        #  because we've placed quotient at the end of the limit array, the previous loop
    #  cannot go beyond the limit table

    #  now we can calculate the tax
    impôt = math.floor(revenu_imposable * coeffr[i] - nb_parts * coeffn[i])
    #  result
    return {"impôt": impôt, "surcôte": surcôte, "taux": coeffr[i]}

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

8.2.6. دالة [get_discount]

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

#  calculates any discount
def get_décôte(marié: str, salaire: int, impots: int) -> int:
    #  at the outset, a zero discount
    décôte = 0
    #  maximum tax amount to qualify for discount
    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:
        #  maximum discount
        plafond_décôte = PLAFOND_DECOTE_COUPLE if marié == "oui" else PLAFOND_DECOTE_CELIBATAIRE
        #  theoretical discount
        décôte = plafond_décôte - 0.75 * impots
        #  the discount cannot exceed the amount of tax due
        if décôte > impots:
            décôte = impots

        #  no discount <0
        if décôte < 0:
            décôte = 0

    #  result
    return math.ceil(décôte)

8.2.7. دالة [get_reduction]

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

#  calculates any reduction
def get_réduction(marié: str, salaire: int, enfants: int, impots: int) -> int:
    #  the income ceiling to qualify for the 20% reduction
    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

    #  taxable income
    revenu_imposable = get_revenu_imposable(salaire)
    #  reduction
    réduction = 0
    if revenu_imposable < plafond_revenu_pour_réduction:
        #  20% discount
        réduction = 0.2 * impots

    #  result
    return math.ceil(réduction)

8.2.8. دالة [get_taxable_income]

تحسب الدالة [get_taxable_income] الدخل الخاضع للضريبة بناءً على الراتب السنوي:

#  revenu_imposable = salaireAnnuel - allowance
#  the allowance has a minimum and a maximum
# ----------------------------------------
def get_revenu_imposable(salaire: int) -> int:
    #  10% salary deduction
    abattement = 0.1 * salaire
    #  this allowance may not exceed ABATTEMENT_DIXPOURCENT_MAX
    if abattement > ABATTEMENT_DIXPOURCENT_MAX:
        abattement = ABATTEMENT_DIXPOURCENT_MAX

    #  the allowance cannot be less than ABATTEMENT_DIXPOURCENT_MIN
    if abattement < ABATTEMENT_DIXPOURCENT_MIN:
        abattement = ABATTEMENT_DIXPOURCENT_MIN

    #  taxable income
    revenu_imposable = salaire - abattement
    #  result
    return math.floor(revenu_imposable)

8.2.9. وظيفة [record_results]

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

#  writing results to a text file
# ----------------------------------------
def record_results(results_filename: str, results: list):
    #  results_filename: the name of the text file in which to place the results
    #  results: the results list in the form of a dictionary list
    #  each dictionary is written on a line of text
    résultats = None
    try:
        #  opening the results file
        résultats = codecs.open(results_filename, "w", "utf8")
        #  taxpayer exploitation
        for result in results:
            #  enter the result in the results file
            résultats.write(f"{result}\n")
            #  next taxpayer
    finally:
        #  close the file if it has been opened
        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] الملف التالي [results.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] حلاً لهذه المشكلة.