monregistraire/app/cca_pool.py
Daniel Allaire 691537400f Références législatives : URLs vers les textes officiels (Justice Canada / ARC)
Remplace les mentions textuelles par des URLs directes dans les docstrings de
cca_pool.py, cca_registry.py et le commentaire CCA_CATEGORIES dans store.py.

  - laws-lois.justice.gc.ca : LIR art. 13, 20 ; Règl. art. 1100
  - canada.ca : Guide T2 (T4012) pour l'Annexe 8 / Schedule 8

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 16:17:02 -04:00

180 lines
7.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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