feat(irpc): reconcile CBF16 cessation and documented prior quotas in QA

This commit is contained in:
2026-10-26 15:16:45 +02:00
parent ba83afe7d8
commit 992c97ad4e
7 changed files with 143 additions and 15 deletions
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,4 +1,4 @@
import {irpcSpecialVersion,irpcSpecialMonthlyVersion,irpcSpecialStartPeriodVersion,specialCalculationVersion,validateSpecial,calculateSpecial,specialReportRows,specialDependency,type SpecialInput,type SpecialModel} from './irpc-special.ts';
import {irpcSpecialVersion,irpcSpecialMonthlyVersion,irpcSpecialStartPeriodVersion,irpcSpecialCessationPriorVersion,specialCalculationVersion,validateSpecial,calculateSpecial,specialReportRows,specialDependency,type SpecialInput,type SpecialModel} from './irpc-special.ts';
import {normalFormPreparationVersion,validateNormalFormPreparation,prepareNormalForms,normalFormPreparationReport,type NormalFormPreparationInput,type NormalFormPreparationModel} from './irpc-normal-form-preparation.ts';
import {normalPeriodFxVersion,validateNormalPeriodFx,mapNormalPeriodFx,normalPeriodFxDependency,normalPeriodFxReport,type NormalPeriodFxInput,type NormalPeriodFxModel} from './irpc-normal-period-fx.ts';
import {normalDepreciationVersion,validateNormalDepreciation,calculateNormalDepreciation,normalDepreciationReport,type NormalDepreciationInput,type NormalDepreciationModel} from './irpc-normal-depreciation.ts';
@@ -41,7 +41,7 @@ export function validateNormal(b:NormalBook){
if(!b||b.version!==1||['year','mappingReference','scopeReference'].some(k=>typeof b[k as 'year']!=='string')||!['','resident_general','ep_general','other'].includes(b.scope)||!Array.isArray(b.rows)||b.history!==undefined&&!Array.isArray(b.history))throw Error('Preparação IRPC normal não recuperável.');
for(const r of b.rows)if(!r||Object.keys(newNormalAdjustment('')).some(k=>typeof r[k as keyof NormalAdjustment]!=='string')||!['','noncontractual_fine','compensatory_interest','other'].includes(r.rule)||!['','yes','no'].includes(r.included))throw Error('Ajustamento IRPC não recuperável;original conservado.');
if(new Set(b.rows.map(r=>r.id)).size!==b.rows.length)throw Error('Identificador de ajustamento duplicado.');
for(const r of [b.result,...(b.history??[])])if(r&&(!r.model||![irpcSpecialStartPeriodVersion,irpcSpecialMonthlyVersion,irpcSpecialVersion,irpcNormalVersion,normalProfitVersion,normalBaseVersion,normalAssessmentVersion,normalSchedulesVersion,normalCostsVersion,normalDepreciationVersion,normalPeriodFxVersion,normalFormPreparationVersion].includes(r.model.version)||typeof r.key!=='string'||typeof r.createdAt!=='string'||!Array.isArray(r.model.rows)||!Array.isArray(r.model.pending)))throw Error('Histórico IRPC normal não recuperável.');
for(const r of [b.result,...(b.history??[])])if(r&&(!r.model||![irpcSpecialStartPeriodVersion,irpcSpecialCessationPriorVersion,irpcSpecialMonthlyVersion,irpcSpecialVersion,irpcNormalVersion,normalProfitVersion,normalBaseVersion,normalAssessmentVersion,normalSchedulesVersion,normalCostsVersion,normalDepreciationVersion,normalPeriodFxVersion,normalFormPreparationVersion].includes(r.model.version)||typeof r.key!=='string'||typeof r.createdAt!=='string'||!Array.isArray(r.model.rows)||!Array.isArray(r.model.pending)))throw Error('Histórico IRPC normal não recuperável.');
}
export function normalBook(d:Record<string,unknown>):NormalBook{
const m=store(d).irpcNormal;if(m===undefined)return newNormalBook();if(!m||typeof m!=='object'||Array.isArray(m))throw Error('Mapa IRPC normal não recuperável.');const b=m[acquisitionScope(d)]??newNormalBook();validateNormal(b);return b;
+21 -10
View File
@@ -7,6 +7,8 @@ import {addFractions,subtractFractions,decimalFraction,fraction,fractionPresenta
export const irpcSpecialVersion='IRPC-normal-adjustments/10';
export const irpcSpecialMonthlyVersion='IRPC-normal-adjustments/11';
export const irpcSpecialStartPeriodVersion='IRPC-normal-adjustments/12';
export const irpcSpecialCessationPriorVersion='IRPC-normal-adjustments/13';
export const specialCessationPriorSource='CBF3/9/10/16;CIRPC7(4);Decreto72/2013 art3(3),9(1–3) e10:residente,ano civil2026 ou período curto de cessação com meses completosL6. Activo novo a custo histórico,entrada2025/2026,método constante/taxa normal máxima e duodécimos desde a entrada. Taxa ×1,5,limites contabilizado elegível e saldo fiscal;quotas anteriores e parcela temporariamente tributada documentadas emL6,sem recalcular o passado nem consumir saldos oficiais. Saída do activo só quando declarada,excluindo o mês do evento;cessação não presume transmissão. Sem cumulação.';
export const specialStartPeriodSource='CBF3/9/10/16;CIRPC7(4);Decreto72/2013 art3(3)/9(3):período curto por início de actividade de residente em2026. Taxa normal máxima ×1,5 ×meses efectivos/12 reutilizados de L6,sem anualização;activo novo emuso no período,método constante,sem saída/quotas anteriores/cumulação. Limites contabilizado elegível e saldo fiscal;apenas diferença adicional.';
export const specialMonthlySource='CBF16 com Decreto72/2013 art9:taxa normal máxima ×1,5 ×duodécimos/12,meses reutilizados do mapaL6. Exercício anual civil2026,entrada emuso no ano,método constante,sem saída/quotas anteriores/cumulação. Dedução limitada ao contabilizado elegível e saldo fiscal;apenas diferença adicional,não nova amortização contabilística.';
export const specialSources={expenses:'CIRPC,Lei34/2007,art61(4),PDF17/p768(71):35% sobre encargos não devidamente documentados e despesas confidenciais/ilícitas,sem prejuízo do36(1)g. Exercício2026;sem regra inferida da falta de anexo.',cooperative:'CIRPC,Lei34/2007,art12/14,PDF5/p768(59),e61(1):cooperativas agrárias,de artesanato e culturais;redução de50% da taxa geral32%=16%;rendimentos sujeitos a retenção excluídos deste benefício. Recorte2026,residente,parcela ordinária integralmente elegível.',acceleration:'CBF,Lei4/2009,art3/4/9/10/16;compilação oficial APIEX pp66–68/72:incremento50% das taxas normais nos imóveis novos do projecto e,nas mesmas condições,imóveis reabilitados/máquinas e equipamentos industriais ou agro-industriais. Decreto72/2013 art3(3):limite contabilizado e saldo fiscal. Recorte constante pela taxa normal máxima da tabela,primeiro exercício anual2026,sem cumulação presumida.'}as const;
@@ -14,9 +16,10 @@ export type Qualification={state:''|'known'|'absent'|'not_applicable';eligible:'
export const newQualification=():Qualification=>({state:'',eligible:'',from:'',to:'',reference:'',periodReference:''});
export type SpecialExpense={id:string;scheduleId:string;nature:''|'undocumented'|'confidential'|'illicit'|'other';eligible:''|'yes'|'no';reference:string};
export type AcceleratedAsset={id:string;assetId:string;kind:''|'new_project_building'|'rehabilitated_project_building'|'industrial_equipment'|'agroindustrial_equipment'|'other';eligible:''|'yes'|'no';reference:string};
export type SpecialMonthlyInput={version:1;reference:string;startPeriod?:{version:1;reference:string}};
export type SpecialContinuationInput={version:1;period:''|'annual'|'cessation';reference:string;historyReference:string;monthlyFromStart:''|'yes'|'no'};
export type SpecialMonthlyInput={version:1;reference:string;startPeriod?:{version:1;reference:string};continuation?:SpecialContinuationInput};
export type SpecialInput={version:1;expenses:{qualification:Qualification;rows:SpecialExpense[]};cooperative:{qualification:Qualification;kind:''|'agrarian'|'craft'|'cultural'|'other';allIncomeEligible:''|'yes'|'no';withheldIncome:''|'absent'|'known'};acceleration:{qualification:Qualification;cbfRequirements:''|'yes'|'no';recognition:''|'documented'|'not_required';recognitionReference:string;noOtherBenefits:''|'yes'|'no';rows:AcceleratedAsset[];monthly?:SpecialMonthlyInput}};
export const specialCalculationVersion=(i:SpecialInput)=>i.acceleration.monthly?.startPeriod?irpcSpecialStartPeriodVersion:i.acceleration.monthly?irpcSpecialMonthlyVersion:irpcSpecialVersion;
export const specialCalculationVersion=(i:SpecialInput)=>i.acceleration.monthly?.continuation?irpcSpecialCessationPriorVersion:i.acceleration.monthly?.startPeriod?irpcSpecialStartPeriodVersion:i.acceleration.monthly?irpcSpecialMonthlyVersion:irpcSpecialVersion;
export const newSpecialInput=():SpecialInput=>({version:1,expenses:{qualification:newQualification(),rows:[]},cooperative:{qualification:newQualification(),kind:'',allIncomeEligible:'',withheldIncome:''},acceleration:{qualification:newQualification(),cbfRequirements:'',recognition:'',recognitionReference:'',noOtherBenefits:'',rows:[]}});
export const newSpecialExpense=(id:string):SpecialExpense=>({id,scheduleId:'',nature:'',eligible:'',reference:''});
export const newAcceleratedAsset=(id:string):AcceleratedAsset=>({id,assetId:'',kind:'',eligible:'',reference:''});
@@ -25,6 +28,8 @@ export function validateSpecial(i:SpecialInput){
if(!i||i.version!==1||!i.expenses||!i.cooperative||!i.acceleration)throw Error('Qualificação L7 não recuperável;originais conservados.');
if(i.acceleration.monthly!==undefined&&(!i.acceleration.monthly||i.acceleration.monthly.version!==1||typeof i.acceleration.monthly.reference!=='string'))throw Error('Preparação CBF16 por duodécimos não recuperável;históricos conservados.');
if(i.acceleration.monthly?.startPeriod!==undefined&&(!i.acceleration.monthly.startPeriod||i.acceleration.monthly.startPeriod.version!==1||typeof i.acceleration.monthly.startPeriod.reference!=='string'))throw Error('Preparação CBF16 para início de actividade não recuperável;históricos conservados.');
const continuation=i.acceleration.monthly?.continuation;
if(continuation!==undefined&&(!continuation||continuation.version!==1||!choices(continuation.period,['','annual','cessation'])||typeof continuation.reference!=='string'||typeof continuation.historyReference!=='string'||!choices(continuation.monthlyFromStart,['','yes','no'])||i.acceleration.monthly?.startPeriod!==undefined))throw Error('Preparação CBF16 de cessação/quotas anteriores não recuperável ou incompatível com início de actividade;históricos conservados.');
for(const q of[i.expenses.qualification,i.cooperative.qualification,i.acceleration.qualification])if(!q||!choices(q.state,['','known','absent','not_applicable'])||!choices(q.eligible,['','yes','no'])||['from','to','reference','periodReference'].some(k=>typeof q[k as 'from']!=='string'))throw Error('Condição/período L7 não recuperável.');
for(const [rows,keys]of [[i.expenses.rows,Object.keys(newSpecialExpense(''))],[i.acceleration.rows,Object.keys(newAcceleratedAsset(''))]]as const){if(!Array.isArray(rows)||new Set(rows.map(r=>r.id)).size!==rows.length||rows.some(r=>!r||keys.some(k=>typeof(r as unknown as Record<string,unknown>)[k]!=='string')||!choices(r.eligible,['','yes','no'])))throw Error('Parcela L7 não recuperável ou identificador repetido.');}
if(i.expenses.rows.some(r=>!choices(r.nature,['','undocumented','confidential','illicit','other']))||i.acceleration.rows.some(r=>!choices(r.kind,['','new_project_building','rehabilitated_project_building','industrial_equipment','agroindustrial_equipment','other'])))throw Error('Natureza L7 desconhecida.');
@@ -81,6 +86,9 @@ export function calculateSpecial(draft:Record<string,unknown>,b:NormalBook,m:Nor
if(accDecision.status==='calculated'){
if(i.acceleration.monthly&&!i.acceleration.monthly.reference.trim())fail(accDecision,'Falta suporte da reutilização dos meses e da opção por duodécimosCBF16 no mapaL6.');
if(i.acceleration.monthly?.startPeriod&&!i.acceleration.monthly.startPeriod.reference.trim())fail(accDecision,'Falta suporte do período curto por início de actividadeCBF16;período e activosL6 conservados.');
const continuation=i.acceleration.monthly?.continuation;
if(continuation?.monthlyFromStart==='no')fail(accDecision,'Duodécimos desde a entrada emuso não aplicados:esta combinação de quotas/saída exige correspondência própria;L6 e os saldos declarados ficam preservados.');
if(continuation&&(!continuation.period||!continuation.reference.trim()||!continuation.historyReference.trim()||!continuation.monthlyFromStart))fail(accDecision,'CBF16 cessação/quotas anteriores:identifique período,suporte da vigência/condições e das quotas anteriores ou ausência;duodécimos desde a entrada emuso por documentar. Sem recalcular ou presumir saldos anteriores.');
if(i.acceleration.cbfRequirements!=='yes'||!i.acceleration.recognition||!i.acceleration.recognitionReference.trim())fail(accDecision,'Falta enquadramentoCBF3/9 e reconhecimento10 quando exigido;excepção deve ter norma/suporte,não localização.');
if(i.acceleration.noOtherBenefits!=='yes')fail(accDecision,'CumulaçãoCBF4 por corresponder;este recorte não combina outros benefícios positivos.');
if(coopDecision.status==='calculated')fail(accDecision,'Combinação de redução cooperativa comCBF16 ainda não correspondida;cada caso independente continua demonstrável.');
@@ -99,7 +107,10 @@ export function calculateSpecial(draft:Record<string,unknown>,b:NormalBook,m:Nor
if(base?.catalog&&cmp(decimalFraction(a.rate),decimalFraction(base.catalog.rate))!==0n)issues.push('RecorteCBF16 usa a taxa normal máxima da tabela;opção por quota inferior exige correspondência própria,sem a acelerar por aproximação.');
if(i.acceleration.monthly){
const p=specialPeriod(b,m),priorZero=[a.previousAccounting,a.previousFiscal,a.priorAvailable].every(v=>cmp(decimalFraction(v),z())===0n);
if(i.acceleration.monthly.startPeriod){
if(i.acceleration.monthly.continuation){
const c=i.acceleration.monthly.continuation,annual=c.period==='annual'&&p.from==='2026-01-01'&&p.to==='2026-12-31'&&!m.periodsFx?.short,cessation=c.period==='cessation'&&b.periodsFx?.kind==='cessation'&&m.periodsFx?.short&&p.from==='2026-01-01'&&iso(p.to)&&p.to<'2026-12-01';
if(b.scope!=='resident_general'||p.pending.length||!annual&&!cessation||a.method!=='constant'||a.timing!=='months'||!iso(a.start)||a.start<'2025-01-01'||a.start>p.to||a.start>='2026-01-01'&&!priorZero||a.exitState!=='absent'&&!(a.exitState==='known'&&iso(a.exitDate)&&a.exitDate>=p.from&&a.exitDate<=p.to)||base?.months===null||base?.months===undefined)issues.push('CBF16 cessação/quotas anteriores:residente,ano civil2026 ou cessaçãoL6 de meses completos desde1Janeiro,activo novo com entrada2025/2026 e saldosL6 reconciliados,método constante e duodécimos. Quotas anteriores positivas exigem entrada2025;saída só por evento documentado dentro do período. Outros anos/períodos/métodos mantêm correspondência própria.');
}else if(i.acceleration.monthly.startPeriod){
if(b.scope!=='resident_general'||b.periodsFx?.kind!=='start'||!m.periodsFx?.short||p.pending.length||!iso(p.from)||p.from<'2026-02-01'||p.from>'2026-12-01'||p.from.slice(-2)!=='01'||p.to!=='2026-12-31'||a.method!=='constant'||a.timing!=='months'||!iso(a.start)||a.start<p.from||a.start>p.to||a.exitState!=='absent'||!priorZero||base?.months===null||base?.months===undefined)issues.push('CBF16 início de actividade:residente,período curtoL6 de meses completos em2026 até31/12,activo novo emuso dentro do período,método constante,opção mensal e ausência documentada de saída/quotas anteriores;cessação/transição e demais situações permanecem separadas.');
}else if(a.method!=='constant'||a.timing!=='months'||!iso(a.start)||a.start<'2026-01-01'||a.start>'2026-12-31'||a.exitState!=='absent'||!priorZero||p.from!=='2026-01-01'||p.to!=='2026-12-31'||base?.months===null||base?.months===undefined)issues.push('CBF16 por duodécimos:exercício anual civil2026,método constante,entrada no ano,opção mensalL6 e ausência documentada de saída/quotas anteriores;outros períodos/casos permanecem separados.');
}else if(a.method!=='constant'||a.timing!=='annual'||a.start!=='2026-01-01'||a.exitState!=='absent'||a.previousAccounting!=='0'||a.previousFiscal!=='0'||a.priorAvailable!=='0'||m.fiscalPeriod?.from!=='2026-01-01'||m.fiscalPeriod?.to!=='2026-12-31')issues.push('CBF16 inicial:primeiro exercício anual civil2026,método constante,sem quotas anteriores ou saída;duodécimos e recuperação anterior continuam separados.');
@@ -111,9 +122,9 @@ export function calculateSpecial(draft:Record<string,unknown>,b:NormalBook,m:Nor
if(accDecision.status==='calculated'){
for(const r of accelerations)if(r.status==='indeterminate')fail(accDecision,(r.description||r.id)+': '+r.reason);
if(accDecision.status==='calculated'&&b.depreciation&&m.depreciation?.adjustment){
const startPeriod=!!i.acceleration.monthly?.startPeriod;
acceleratedDepreciation=calculateNormalDepreciation(b.depreciation,source,startPeriod?specialPeriod(b,m):{from:'2026-01-01',to:'2026-12-31'},[...b.rows,...(b.schedules?.rows??[]),...(b.costs?.rows??[])],startPeriod,new Set(accelerations.filter(r=>r.status==='calculated').map(r=>r.assetId)));
if(startPeriod)acceleratedDepreciation={...acceleratedDepreciation,source:specialStartPeriodSource};
const startPeriod=!!i.acceleration.monthly?.startPeriod,continuation=i.acceleration.monthly?.continuation;
acceleratedDepreciation=calculateNormalDepreciation(b.depreciation,source,startPeriod||continuation?specialPeriod(b,m):{from:'2026-01-01',to:'2026-12-31'},[...b.rows,...(b.schedules?.rows??[]),...(b.costs?.rows??[])],startPeriod||continuation?.period==='cessation',new Set(accelerations.filter(r=>r.status==='calculated').map(r=>r.assetId)));
if(startPeriod||continuation)acceleratedDepreciation={...acceleratedDepreciation,source:continuation?specialCessationPriorSource:specialStartPeriodSource};
if(acceleratedDepreciation.adjustment===null)fail(accDecision,acceleratedDepreciation.pending.join(' '));
else additionalDeduction=subtractFractions(m.depreciation.adjustment,acceleratedDepreciation.adjustment);
}else if(accDecision.status==='calculated')fail(accDecision,'Reconciliação da amortização normal em falta.');
@@ -125,12 +136,12 @@ export function calculateSpecial(draft:Record<string,unknown>,b:NormalBook,m:Nor
const assessment=assessmentInput?calculateNormalAssessment(assessmentInput,base?.taxableBase??null,{year:b.year,scope:b.scope,currency:m.currency,extended:!!b.schedules||!!b.periodsFx,short:!!m.periodsFx?.short,period:specialPeriod(b,m)}):null;
const normalColecta=assessment?.colecta??null,colecta=coopDecision.status==='indeterminate'?null:normalColecta?(coopDecision.status==='calculated'?mul(normalColecta,1n,2n):normalColecta):null;
const combinedColecta=colecta&&autonomousTax?addFractions([colecta,autonomousTax]):null;
const monthlySource=i.acceleration.monthly?.startPeriod?specialStartPeriodSource:specialMonthlySource;
const monthlySource=i.acceleration.monthly?.continuation?specialCessationPriorSource:i.acceleration.monthly?.startPeriod?specialStartPeriodSource:specialMonthlySource;
const monthlyModel=i.acceleration.monthly?{input:structuredClone(i.acceleration.monthly),period:specialPeriod(b,m),source:monthlySource,assets:accelerations.map(r=>{
const input=b.depreciation?.assets.find(a=>a.id===r.assetId),normal=m.depreciation?.assets.find(a=>a.id===r.assetId),accelerated=acceleratedDepreciation?.assets.find(a=>a.id===r.assetId),applied=accDecision.status==='calculated'&&r.status==='calculated'&&accelerated?.status==='calculated',rate=normal?.status==='calculated'&&input?decimalFraction(input.rate):null;
return{id:r.id,assetId:r.assetId,assetKey:r.assetKey,description:r.description,start:input?.start??'',months:normal?.months??null,taxBasis:normal?.taxBasis??null,normalRate:rate,acceleratedRate:applied&&rate?mul(rate,3n,2n):null,normalQuota:normal?.quota??null,acceleratedQuota:applied?accelerated.quota:null,normalDeduction:normal?.deduction??null,acceleratedDeduction:applied?accelerated.deduction:null,additionalDeduction:applied&&normal?.deduction&&accelerated.deduction?subtractFractions(accelerated.deduction,normal.deduction):r.status==='not_applicable'?z():null,applied,reason:r.reason};
return{...(i.acceleration.monthly?.continuation?{prior:{previousAccounting:input?.previousAccounting??'',previousFiscal:input?.previousFiscal??'',available:input?.priorAvailable??'',reference:input?.previousReference??'',normalUsed:normal?.priorUsed??null,normalRemaining:normal?.priorRemaining??null,acceleratedUsed:applied?accelerated.priorUsed:null,acceleratedRemaining:applied?accelerated.priorRemaining:null,normalFiscalRemaining:normal?.remainingFiscal??null,acceleratedFiscalRemaining:applied?accelerated.remainingFiscal:null},exit:{state:input?.exitState??'',date:input?.exitDate??''}}:{}),id:r.id,assetId:r.assetId,assetKey:r.assetKey,description:r.description,start:input?.start??'',months:normal?.months??null,taxBasis:normal?.taxBasis??null,normalRate:rate,acceleratedRate:applied&&rate?mul(rate,3n,2n):null,normalQuota:normal?.quota??null,acceleratedQuota:applied?accelerated.quota:null,normalDeduction:normal?.deduction??null,acceleratedDeduction:applied?accelerated.deduction:null,additionalDeduction:applied&&normal?.deduction&&accelerated.deduction?subtractFractions(accelerated.deduction,normal.deduction):r.status==='not_applicable'?z():null,applied,reason:r.reason};
})}:undefined;
return{input:structuredClone(i),context:structuredClone(specialContext(draft)),subjectNuit:b.base?.subjectNuit??'',period:specialPeriod(b,m),expenses:{...expenseDecision,rows:expenses,knownAutonomous,autonomousTax,source:specialSources.expenses},cooperative:{...coopDecision,rate:coopDecision.status==='calculated'?fraction(16n):null,source:specialSources.cooperative},acceleration:{...accDecision,rows:accelerations,additionalDeduction,normal:m.depreciation??null,accelerated:acceleratedDepreciation,...(monthlyModel?{monthly:monthlyModel}:{}),source:i.acceleration.monthly?.startPeriod?specialStartPeriodSource:i.acceleration.monthly?specialSources.acceleration+' '+specialMonthlySource:specialSources.acceleration},reference:{taxableProfit:m.taxableProfit,taxableBase:m.taxableBase,colecta:m.colecta??null},taxableProfit,taxableBase:base?.taxableBase??null,colecta,combinedColecta,pending:[...expenseDecision.pending,...coopDecision.pending,...accDecision.pending,...(base?.pending??[]),...(assessment?.pending??[])],limitations:['L7 parcial:apenas61(4),cooperativa integralmente elegível12 eCBF16 no recorte documentado.','Localização/sector só contextualizam;não activam benefícios.','Parcelas mistas,cumulação,mais-valias e demais famílias têm correspondência própria.','M20/M22 permanecem preparaçãoL6 de referência;efeitosL7 sem campo oficial presumido.','Colecta combinada não é saldo final a pagar/recuperar;retenções,pagamentos e créditos anuais emL8.'],finalBalance:{scope:'L8',payable:null,recoverable:null}};
return{input:structuredClone(i),context:structuredClone(specialContext(draft)),subjectNuit:b.base?.subjectNuit??'',period:specialPeriod(b,m),expenses:{...expenseDecision,rows:expenses,knownAutonomous,autonomousTax,source:specialSources.expenses},cooperative:{...coopDecision,rate:coopDecision.status==='calculated'?fraction(16n):null,source:specialSources.cooperative},acceleration:{...accDecision,rows:accelerations,additionalDeduction,normal:m.depreciation??null,accelerated:acceleratedDepreciation,...(monthlyModel?{monthly:monthlyModel}:{}),source:i.acceleration.monthly?.continuation?specialCessationPriorSource:i.acceleration.monthly?.startPeriod?specialStartPeriodSource:i.acceleration.monthly?specialSources.acceleration+' '+specialMonthlySource:specialSources.acceleration},reference:{taxableProfit:m.taxableProfit,taxableBase:m.taxableBase,colecta:m.colecta??null},taxableProfit,taxableBase:base?.taxableBase??null,colecta,combinedColecta,pending:[...expenseDecision.pending,...coopDecision.pending,...accDecision.pending,...(base?.pending??[]),...(assessment?.pending??[])],limitations:['L7 parcial:apenas61(4),cooperativa integralmente elegível12 eCBF16 no recorte documentado.','Localização/sector só contextualizam;não activam benefícios.','Parcelas mistas,cumulação,mais-valias e demais famílias têm correspondência própria.','M20/M22 permanecem preparaçãoL6 de referência;efeitosL7 sem campo oficial presumido.','Colecta combinada não é saldo final a pagar/recuperar;retenções,pagamentos e créditos anuais emL8.'],finalBalance:{scope:'L8',payable:null,recoverable:null}};
}
export type SpecialModel=ReturnType<typeof calculateSpecial>;
const money=(v:FiscalFraction|null)=>{if(!v)return'Indeterminado';const p=fractionPresentation(v);return(p.approximate?'≈':'')+p.decimal+' MZN (exacto '+v.numerator+'/'+v.denominator+')';};
@@ -148,5 +159,5 @@ export function specialReportRows(m:SpecialModel):Array<[string,string,string,st
];}
function specialMonthlyReport(m:NonNullable<SpecialModel['acceleration']['monthly']>,deduction:FiscalFraction|null):Array<[string,string,string,string]>{
return[['IRPC — L7','CBF16 — duodécimos reutilizados de L6','Período:'+m.period.from+'–'+m.period.to+';dedução adicional conciliada:'+money(deduction)+';sem nova dedução do activo de origem.',m.input.reference+(m.input.startPeriod?';início de actividade:'+m.input.startPeriod.reference:'')+';'+m.source],...m.assets.map(a=>['IRPC — L7','CBF16 por duodécimos — '+a.description,'Entrada emuso:'+(a.start||'Informação em falta')+';duodécimosL6:'+(a.months??'Indeterminados')+';base fiscal:'+money(a.taxBasis)+';taxa normal:'+(a.normalRate?fractionPresentation(a.normalRate).decimal+'%':'Indeterminada')+';taxa CBF16:'+(a.acceleratedRate?fractionPresentation(a.acceleratedRate).decimal+'%':'Não aplicada')+';quota normal:'+money(a.normalQuota)+';quota CBF16:'+money(a.acceleratedQuota)+';dedução normal:'+money(a.normalDeduction)+';dedução CBF16:'+money(a.acceleratedDeduction)+';diferença adicional:'+money(a.additionalDeduction)+';'+a.reason,m.source]as[string,string,string,string])];
return[['IRPC — L7','CBF16 — duodécimos reutilizados de L6','Período:'+m.period.from+'–'+m.period.to+';dedução adicional conciliada:'+money(deduction)+';sem nova dedução do activo de origem.',m.input.reference+(m.input.startPeriod?';início de actividade:'+m.input.startPeriod.reference:'')+(m.input.continuation?';período:'+(m.input.continuation.period==='cessation'?'Cessação da actividade':m.input.continuation.period==='annual'?'Ano civil2026':'Informação em falta')+';suporte:'+m.input.continuation.reference+';histórico:'+m.input.continuation.historyReference:'')+';'+m.source],...m.assets.map(a=>['IRPC — L7','CBF16 por duodécimos — '+a.description,'Entrada emuso:'+(a.start||'Informação em falta')+';duodécimosL6:'+(a.months??'Indeterminados')+';base fiscal:'+money(a.taxBasis)+';taxa normal:'+(a.normalRate?fractionPresentation(a.normalRate).decimal+'%':'Indeterminada')+';taxa CBF16:'+(a.acceleratedRate?fractionPresentation(a.acceleratedRate).decimal+'%':'Não aplicada')+';quota normal:'+money(a.normalQuota)+';quota CBF16:'+money(a.acceleratedQuota)+';dedução normal:'+money(a.normalDeduction)+';dedução CBF16:'+money(a.acceleratedDeduction)+';diferença adicional:'+money(a.additionalDeduction)+(a.prior?';quotas contabilísticas anteriores:'+a.prior.previousAccounting+';quotas fiscais anteriores:'+a.prior.previousFiscal+';parcela anterior recuperável declarada:'+a.prior.available+';recuperada normal:'+money(a.prior.normalUsed)+';recuperada CBF16:'+money(a.prior.acceleratedUsed)+';saldo anterior aritmético normal:'+money(a.prior.normalRemaining)+';saldo anterior aritmético CBF16:'+money(a.prior.acceleratedRemaining)+';saldo fiscal normal:'+money(a.prior.normalFiscalRemaining)+';saldo fiscal CBF16:'+money(a.prior.acceleratedFiscalRemaining)+';saída:'+(a.exit?.state==='absent'?'Ausência declarada':a.exit?.state==='known'?'Evento documentado':'Informação em falta')+' '+(a.exit?.date??'')+';sem consumo de saldos oficiais':'')+';'+a.reason,m.source+(a.prior?';suporte das quotas anteriores:'+a.prior.reference:'')]as[string,string,string,string])];
}