""" Module fiscal — Mécanique du pool de catégorie (DPA dégressive). L'actif fiscal est un FLUX, pas un objet. La DPA se calcule sur le solde global de la catégorie (FNACC), pas pièce par pièce. Mécanique annuelle (déterministe) : FNACC_ouverture + ajouts_annee - dispositions_annee (produit de disposition, souvent 0 $) = solde_avant_ajustement - ajustement (coeff * ajouts_nets) ; demi-année => coeff 0.5 = base_calcul * taux (0.55) = DPA_annee (bornée >= 0) FNACC_cloture = solde_avant_ajustement - DPA_annee Perte finale : si l'inventaire PHYSIQUE de la catégorie est vide (plus aucun actif) mais que la FNACC reste positive, ce solde devient une perte finale déductible à 100 % (DPA = solde, FNACC clôture = 0). Récupération d'amortissement : si la FNACC devient négative (dispositions > solde), ce négatif est un revenu (récupération) ; la FNACC clôture est ramenée à 0 et le montant est signalé. Le coefficient d'ajustement de l'année d'acquisition est PARAMÉTRABLE (demi-année standard = 0.5 ; incitatif accéléré éventuel saisi par l'usager sur avis de son CPA). Aucun taux d'accélération n'est câblé en dur. Références législatives : - LIR 20(1)(a) — Déduction pour amortissement du revenu d'entreprise ou de bien - LIR 13(21) — Définition de la FNACC (fraction non amortie du coût en capital) - LIR 13(1) — Récupération d'amortissement (inclusion dans le revenu) - LIR 20(16) — Perte finale (déduction du revenu) - Règl. 1100(1) — Méthode dégressive ; taux par catégorie (Règl. Annexe II) - Règl. 1100(2) — Règle de la demi-année : ajustement sur ajouts nets de l'année d'acquisition (coefficient 0,5 par défaut) Textes officiels (Justice Canada) : https://laws-lois.justice.gc.ca/fra/lois/I-3.3/section-13.html https://laws-lois.justice.gc.ca/fra/lois/I-3.3/section-20.html https://laws-lois.justice.gc.ca/fra/reglements/C.R.C.,_ch._945/section-1100.html """ from __future__ import annotations from dataclasses import dataclass, field from decimal import Decimal, ROUND_HALF_UP TWOPLACES = Decimal("0.01") def _q(x: Decimal) -> Decimal: return x.quantize(TWOPLACES, rounding=ROUND_HALF_UP) @dataclass class YearResult: year: int opening_ucc: Decimal # FNACC d'ouverture additions: Decimal # ajouts de l'année dispositions: Decimal # produit des dispositions de l'année adjustment_coeff: Decimal # coeff sur ajouts nets (0.5 = demi-année) adjustment: Decimal # montant retiré de la base de calcul cca_rate: Decimal # 0.55 base_for_cca: Decimal # base après ajustement cca_claimed: Decimal # DPA de l'année closing_ucc: Decimal # FNACC de clôture recapture: Decimal # récupération (revenu) si > 0 terminal_loss: Decimal # perte finale (déductible) si > 0 note: str = "" def to_dict(self) -> dict: return { "year": self.year, "opening_ucc": str(self.opening_ucc), "additions": str(self.additions), "dispositions": str(self.dispositions), "adjustment_coeff": str(self.adjustment_coeff), "adjustment": str(self.adjustment), "cca_rate": str(self.cca_rate), "base_for_cca": str(self.base_for_cca), "cca_claimed": str(self.cca_claimed), "closing_ucc": str(self.closing_ucc), "recapture": str(self.recapture), "terminal_loss": str(self.terminal_loss), "note": self.note, } @dataclass class YearInput: year: int additions: Decimal = Decimal("0") dispositions: Decimal = Decimal("0") adjustment_coeff: Decimal = Decimal("0.5") # demi-année par défaut pool_physically_empty: bool = False # plus aucun actif physique claim_full_cca: bool = True # demander la DPA max cette année def compute_pool_year(opening_ucc: Decimal, yi: YearInput, cca_rate: Decimal = Decimal("0.55")) -> YearResult: """Calcule une année du pool de façon déterministe.""" opening = _q(opening_ucc) additions = _q(yi.additions) dispositions = _q(yi.dispositions) solde_avant = opening + additions - dispositions recapture = Decimal("0") terminal_loss = Decimal("0") note = "" # LIR 13(1) — récupération : le produit de disposition excède la FNACC ; # le solde négatif est inclus dans le revenu imposable de l'année. if solde_avant < 0: recapture = -solde_avant result = YearResult( year=yi.year, opening_ucc=opening, additions=additions, dispositions=dispositions, adjustment_coeff=yi.adjustment_coeff, adjustment=Decimal("0.00"), cca_rate=cca_rate, base_for_cca=Decimal("0.00"), cca_claimed=Decimal("0.00"), closing_ucc=Decimal("0.00"), recapture=_q(recapture), terminal_loss=Decimal("0.00"), note="Solde négatif : récupération d'amortissement (revenu imposable). FNACC ramenée à 0.", ) return result # LIR 20(16) — perte finale : catégorie vide physiquement avec FNACC positive ; # le solde résiduel est déductible en totalité (pas de plafond de taux). if yi.pool_physically_empty and solde_avant > 0: terminal_loss = solde_avant return YearResult( year=yi.year, opening_ucc=opening, additions=additions, dispositions=dispositions, adjustment_coeff=yi.adjustment_coeff, adjustment=Decimal("0.00"), cca_rate=cca_rate, base_for_cca=Decimal("0.00"), cca_claimed=Decimal("0.00"), closing_ucc=Decimal("0.00"), recapture=Decimal("0.00"), terminal_loss=_q(terminal_loss), note="Catégorie vidée physiquement avec solde positif : perte finale déductible à 100 %.", ) # Règl. 1100(2) — règle de la demi-année : l'ajustement (coeff × ajouts nets) # réduit la base de calcul pour l'année d'acquisition (coeff 0,5 par défaut). net_additions = additions - dispositions if net_additions < 0: net_additions = Decimal("0") # pas d'ajustement négatif adjustment = _q(yi.adjustment_coeff * net_additions) base = solde_avant - adjustment if base < 0: base = Decimal("0") cca = _q(base * cca_rate) if yi.claim_full_cca else Decimal("0.00") # La DPA ne peut excéder le solde disponible. if cca > solde_avant: cca = solde_avant closing = _q(solde_avant - cca) return YearResult( year=yi.year, opening_ucc=opening, additions=additions, dispositions=dispositions, adjustment_coeff=yi.adjustment_coeff, adjustment=adjustment, cca_rate=cca_rate, base_for_cca=_q(base), cca_claimed=cca, closing_ucc=closing, recapture=Decimal("0.00"), terminal_loss=Decimal("0.00"), note=note or "Calcul standard : DPA dégressive sur le solde global.", ) def compute_pool_timeline(years: list[YearInput], opening_ucc: Decimal = Decimal("0"), cca_rate: Decimal = Decimal("0.55")) -> list[YearResult]: """Enchaîne plusieurs années : la clôture d'une année ouvre la suivante.""" results: list[YearResult] = [] ucc = opening_ucc for yi in sorted(years, key=lambda y: y.year): r = compute_pool_year(ucc, yi, cca_rate) results.append(r) ucc = r.closing_ucc return results