64 lines
15 KiB
TypeScript
64 lines
15 KiB
TypeScript
import {acquisitionScope} from './iva-acquisitions.ts';
|
||
import {decimalFraction,fractionPresentation,type FiscalFraction} from './fiscal-rational.ts';
|
||
|
||
export const ispcEligibilitySource='L4-A:Lei9/2025,BR248/1.ºsuplemento,republicaçãoISPC,arts2–5,9-A/13;PDF10–11/p2180(10–11);vigência2026art6/PDF9. Enquadramento preliminar de actividade habitual;opção separada;sem liquidação trimestral,retenção,submissão ou alteração de IVA/IRPC/IRPS.';
|
||
export const ispcActivities=[['forestry','Silvícola'],['fishery','Pesqueira'],['livestock','Pecuária'],['agriculture','Agrícola'],['poultry','Avícola'],['beekeeping','Apícola'],['industry','Industrial'],['commerce','Comercial'],['craft','Artesanato'],['services12','Serviços do artigo8(1)b'],['liberal15','Profissão liberal do artigo8(1)c'],['other','Outra actividade,fora dos âmbitos identificados']] as const;
|
||
export type IspcActivity=typeof ispcActivities[number][0];
|
||
type Choice=''|'yes'|'no';
|
||
export type IspcEligibilityInput={entityRef:string;personType:''|'singular'|'collective';origin:'projected'|'effective';assessmentDate:string;currency:string;mode:'regular'|'occasional';activities:IspcActivity[];activityDescription:string;territory:Choice;establishments:string;basis:''|'previous_year'|'start_forecast';referenceYear:string;annualReference:string;annualReferenceRef:string;forecastConfirmed:Choice;forecastConfirmationRef:string;organizedRequired:Choice;sharesInIspc:Choice;sharesAnonymous:Choice;otherActivities:Choice;unlistedServices:Choice;liberalDaysSameEntity:string;liberalClientRef:string;generalRegimeChosen:Choice;generalThreeYearsCompleted:Choice;generalRegimeRef:string;option:'preparation'|'opted'|'not_opted';optionReference:string;optionDate:string};
|
||
export type IspcEligibilityResult={calculatedAt:string;key:string;scope:string;input:IspcEligibilityInput;status:'compatible'|'incompatible'|'indeterminate';reason:string;missing:string[];annualReference:FiscalFraction|null;optionStatus:'preparation'|'recorded'|'not_opted'|'indeterminate';optionReason:string;source:string};
|
||
export type IspcEligibilityBook={version:1;input:IspcEligibilityInput;result?:IspcEligibilityResult;history?:IspcEligibilityResult[]};
|
||
const scope=(d:Record<string,unknown>)=>acquisitionScope(d);
|
||
export function ispcReuseValues(d:Record<string,unknown>):Partial<IspcEligibilityInput>{
|
||
const p:Partial<IspcEligibilityInput>={},person=String(d.person_type??'');if(['singular','sole_trader'].includes(person))p.personType='singular';else if(person==='collective')p.personType='collective';
|
||
if(typeof d.fiscal_activity==='string'&&d.fiscal_activity.trim())p.activityDescription=d.fiscal_activity;
|
||
const establishments=String(d.fiscal_establishments??'');if(/^[1-9]\d{0,4}$/.test(establishments))p.establishments=establishments;
|
||
const entity=String(d.fiscal_entity_ref||d.organization_name||'');if(entity.trim())p.entityRef=entity;
|
||
return p;
|
||
}
|
||
export function newIspcEligibilityInput(d:Record<string,unknown>):IspcEligibilityInput{return{entityRef:'',personType:'',origin:'projected',assessmentDate:'2026-01-01',currency:d.currency==='MZN'?'MZN':'',mode:'regular',activities:[],activityDescription:'',territory:'',establishments:'',basis:'',referenceYear:'',annualReference:'',annualReferenceRef:'',forecastConfirmed:'',forecastConfirmationRef:'',organizedRequired:'',sharesInIspc:'',sharesAnonymous:'',otherActivities:'',unlistedServices:'',liberalDaysSameEntity:'',liberalClientRef:'',generalRegimeChosen:'',generalThreeYearsCompleted:'',generalRegimeRef:'',option:'preparation',optionReference:'',optionDate:'',...ispcReuseValues(d)};}
|
||
function store(d:Record<string,unknown>):Record<string,unknown>{const s=JSON.parse(String(d.modelo_a_snapshot||'{"version":1,"books":{}}'));if(s.version!==1||!s.books||typeof s.books!=='object'||Array.isArray(s.books))throw Error('Livros não recuperáveis;originais preservados.');return s;}
|
||
function map(d:Record<string,unknown>):Record<string,IspcEligibilityBook>{const value=store(d).ispcEligibility;if(value===undefined)return{};if(!value||typeof value!=='object'||Array.isArray(value))throw Error('PreparaçãoISPC não recuperável;originais preservados.');return value as Record<string,IspcEligibilityBook>;}
|
||
function validate(b:IspcEligibilityBook):void{
|
||
const sample=newIspcEligibilityInput({});if(!b||b.version!==1||!b.input||typeof b.input!=='object'||Object.keys(sample).some(k=>k==='activities'?!Array.isArray(b.input.activities)||new Set(b.input.activities).size!==b.input.activities.length||b.input.activities.some(a=>!ispcActivities.some(([kind])=>kind===a)):typeof b.input[k as keyof IspcEligibilityInput]!=='string')||!['','singular','collective'].includes(b.input.personType)||!['projected','effective'].includes(b.input.origin)||!['regular','occasional'].includes(b.input.mode)||!['','previous_year','start_forecast'].includes(b.input.basis)||!['preparation','opted','not_opted'].includes(b.input.option)||['territory','forecastConfirmed','organizedRequired','sharesInIspc','sharesAnonymous','otherActivities','unlistedServices','generalRegimeChosen','generalThreeYearsCompleted'].some(k=>!['','yes','no'].includes(String(b.input[k as keyof IspcEligibilityInput])))||b.history!==undefined&&!Array.isArray(b.history)||b.result!==undefined&&(!b.result.input||!Array.isArray(b.result.missing)||!['compatible','incompatible','indeterminate'].includes(b.result.status)))throw Error('EntradasISPC não recuperáveis;sem substituir por valores vazios.');
|
||
}
|
||
export function ispcEligibilityBook(d:Record<string,unknown>):IspcEligibilityBook{const b=map(d)[scope(d)]??{version:1,input:newIspcEligibilityInput(d)};validate(b);return b;}
|
||
export function saveIspcEligibilityBook(d:Record<string,unknown>,b:IspcEligibilityBook):string{validate(b);return JSON.stringify({...store(d),ispcEligibility:{...map(d),[scope(d)]:b}});}
|
||
const validDate=(v:string)=>/^20\d{2}-(0[1-9]|1[0-2])-\d{2}$/.test(v)&&!Number.isNaN(Date.parse(v))&&new Date(v).toISOString().slice(0,10)===v;
|
||
function option(i:IspcEligibilityInput):Pick<IspcEligibilityResult,'optionStatus'|'optionReason'>{
|
||
if(i.option==='not_opted')return{optionStatus:'not_opted',optionReason:'Sem opçãoISPC declarada;não aplicar a exclusão de outros impostos doart5.'};
|
||
if(i.option==='preparation')return{optionStatus:'preparation',optionReason:'Opção em preparação;compatibilidade de acesso não é adesão nem alteração automática de regime.'};
|
||
if(!i.optionReference.trim()||!validDate(i.optionDate)||!validDate(i.assessmentDate)||i.optionDate>i.assessmentDate)return{optionStatus:'indeterminate',optionReason:'Identifique referência e data da opção,até à data de avaliação;não inferir adesão pela selecçãoISPC.'};
|
||
return{optionStatus:'recorded',optionReason:'Opção registada segundo documento ou pressuposto declarado;sem submissãoAT ou reescrita dos livros deIVA/IRPC/IRPS.'};
|
||
}
|
||
function assess(i:IspcEligibilityInput):Pick<IspcEligibilityResult,'status'|'reason'|'missing'|'annualReference'>{
|
||
let annual:FiscalFraction|null=null;try{annual=decimalFraction(i.annualReference);}catch{ /* Missing or invalid is never zero. */ }
|
||
const stop=(status:IspcEligibilityResult['status'],reason:string,missing:string[]=[])=>({status,reason,missing,annualReference:annual});
|
||
if(!validDate(i.assessmentDate)||!i.assessmentDate.startsWith('2026-')||i.currency!=='MZN')return stop('indeterminate','Este incremento cobre apenas avaliação2026emMZN;identifique data e moeda sem conversão silenciosa.',['data2026','moedaMZN']);
|
||
if(i.mode==='occasional')return stop('indeterminate','Operação ocasional3(3–4):conservar dados;incidênciaIRPS e famílias próprias ainda não avaliadas neste incremento.',['enquadramento específico da operação ocasional']);
|
||
if(i.territory==='no')return stop('incompatible','O âmbito territorial declarado não corresponde ao exercício em território nacional de2(1);nacionalidade do investidor não substitui este facto.');
|
||
if(i.activities.includes('other')||i.otherActivities==='yes')return stop('incompatible','Actividade fora dos âmbitos2/3declarada;exclusão3(2)c,não aplicarISPC por nome do sector.');
|
||
if(i.organizedRequired==='yes')return stop('incompatible','Obrigação de contabilidade organizada declarada:4(1)b não permite este enquadramento. Manter contabilidade voluntária distinto de obrigação.');
|
||
if(i.sharesInIspc==='yes'||i.sharesAnonymous==='yes')return stop('incompatible','Participações abrangidas por3(2)a/b declaradas;enquadramentoISPC incompatível.');
|
||
if(/^[1-9]\d{0,4}$/.test(i.establishments)&&Number(i.establishments)>1)return stop('incompatible','Actividade distribuída por vários estabelecimentos:exclusão3(2)d;sem presumir um estabelecimento por oportunidade.');
|
||
const service=i.activities.some(a=>['services12','liberal15'].includes(a)),liberal=i.activities.includes('liberal15');
|
||
if(service&&i.unlistedServices==='yes')return stop('incompatible','Serviços fora dos previstos declarados:exclusão3(2)f. Categoria e natureza próprias devem ser identificadas.');
|
||
if(liberal&&/^\d{1,3}$/.test(i.liberalDaysSameEntity)&&Number(i.liberalDaysSameEntity)<=366&&Number(i.liberalDaysSameEntity)>183)return stop('incompatible','Serviços8(1)càmesma entidade por mais183dias durante o ano:exclusão3(2)e. Não estender esta condição automaticamente aos serviços8(1)b.');
|
||
if(i.generalRegimeChosen==='yes'&&i.generalThreeYearsCompleted==='no')return stop('incompatible','Permanência mínima de três anos nos impostos gerais5(3)ainda não cumprida,segundo factos declarados.');
|
||
if(annual&&(i.basis==='previous_year'&&i.referenceYear==='2025'||i.basis==='start_forecast'&&i.referenceYear==='2026')&&BigInt(annual.numerator)>4000000n*BigInt(annual.denominator))return stop('incompatible','Volume anual de referência superior4.000.000MZN:fora do acesso4(1)a/4(2). Excesso do próprio ano e20%de8(1)d têm tratamento separado;não liquidado aqui.');
|
||
const missing:string[]=[];if(!i.personType)missing.push('pessoa singular ou colectiva');if(i.territory!=='yes')missing.push('exercício em território nacional');if(!i.activities.length||!i.activityDescription.trim())missing.push('actividade/natureza identificadas');if(!/^[1-9]\d{0,4}$/.test(i.establishments))missing.push('número de estabelecimentos');
|
||
for(const[field,label]of [['organizedRequired','obrigação de contabilidade organizada'],['sharesInIspc','participações noutras sociedadesISPC'],['sharesAnonymous','participaçõesSA/sócios não identificáveis'],['otherActivities','actividades fora do âmbito'],['generalRegimeChosen','opção pelos impostos gerais']] as const)if(i[field]==='')missing.push(label);
|
||
if(!i.basis)missing.push('origem do volume anual de referência');if(!annual)missing.push('montante anual de referência');const expected=i.basis==='previous_year'?'2025':i.basis==='start_forecast'?'2026':'';if(expected&&i.referenceYear!==expected)missing.push(`ano${expected}da referência;não reinterpretar outro ano`);
|
||
if(i.basis==='start_forecast'&&(i.forecastConfirmed!=='yes'||i.origin==='effective'&&!i.forecastConfirmationRef.trim()))missing.push('previsão da declaração de início confirmadaAT4(2),ou pressuposto expresso na projecção');
|
||
if(service&&i.unlistedServices==='')missing.push('correspondência dos serviços aos âmbitos daLei');
|
||
if(liberal&&(!/^\d{1,3}$/.test(i.liberalDaysSameEntity)||Number(i.liberalDaysSameEntity)>366||!i.liberalClientRef.trim()))missing.push('maior duração anual por mesma entidade e suporte declarado');
|
||
if(i.generalRegimeChosen==='yes'&&(i.generalThreeYearsCompleted!=='yes'||!i.generalRegimeRef.trim()))missing.push('cumprimento dos três anos5(3)e referência da opção/registo');
|
||
if(missing.length)return stop('indeterminate','Condições específicas por esclarecer;desconhecido não significa não aplicável nemzero.',missing);
|
||
return stop('compatible','Condições de acesso da actividade habitual compatíveis com os dados de referência declarados;opção separada. Volume do ano em curso, trimestre, excesso e mínimo têm apuramento trimestral próprio; retenções têm registo separado.'+(liberal?' Serviçosliberais:limite5anos9-A,contagemtransitória13a partir da vigência2026;sem presumir permanência fora da cobertura.':''));
|
||
}
|
||
const key=(d:Record<string,unknown>,b:IspcEligibilityBook)=>JSON.stringify(['ispc-eligibility/1',scope(d),b.input]);
|
||
export function ispcEligibilityCurrent(d:Record<string,unknown>,b:IspcEligibilityBook):boolean{return!!b.result&&b.result.key===key(d,b);}
|
||
export function recordIspcEligibility(d:Record<string,unknown>,b:IspcEligibilityBook,now=new Date().toISOString()):IspcEligibilityBook{validate(b);const result:IspcEligibilityResult={calculatedAt:now,key:key(d,b),scope:scope(d),input:structuredClone(b.input),...assess(b.input),...option(b.input),source:ispcEligibilitySource};return{...b,...(b.result?{history:[...(b.history??[]),structuredClone(b.result)]}:{}),result};}
|
||
export function ispcReuseDiff(d:Record<string,unknown>,b:IspcEligibilityBook):Array<{field:keyof IspcEligibilityInput;before:string;after:string}>{return Object.entries(ispcReuseValues(d)).filter(([field,value])=>b.input[field as keyof IspcEligibilityInput]!==value).map(([field,value])=>({field:field as keyof IspcEligibilityInput,before:String(b.input[field as keyof IspcEligibilityInput]),after:String(value)}));}
|
||
export function ispcEligibilityReportRows(d:Record<string,unknown>):Array<[string,string,string,string]>{try{const b=ispcEligibilityBook(d),r=b.result;if(!r)return[];const i=r.input;return[['ISPC','L4-A — enquadramento2026',`${r.status==='compatible'?'Compatível com os dados de referência':r.status==='incompatible'?'Incompatível':'Indeterminado'};${r.reason};falta:${r.missing.join('; ')||'nenhum dado deste recorte'}`,`${r.scope};${ispcEligibilityCurrent(d,b)?'Entradas correspondentes':'Histórico preservado;rever entradas'};${r.calculatedAt};${r.source}`],['ISPC','Origem e âmbito',`${i.entityRef||'Entidade por identificar'};${i.personType};${i.origin==='projected'?'Pressupostos de simulação':'Dados efectivos declarados'};${i.activities.map(a=>ispcActivities.find(([kind])=>kind===a)?.[1]??a).join('; ')};${i.activityDescription};estabelecimentos${i.establishments};territórionacional${i.territory}`,`Referênciaanual:${i.basis}/${i.referenceYear};${r.annualReference?fractionPresentation(r.annualReference).decimal:'Indeterminado'}MZN;${i.annualReferenceRef};previsãoAT${i.forecastConfirmed}/${i.forecastConfirmationRef};liberal${i.liberalDaysSameEntity}dias/${i.liberalClientRef};factos:${JSON.stringify({organizedRequired:i.organizedRequired,sharesInIspc:i.sharesInIspc,sharesAnonymous:i.sharesAnonymous,otherActivities:i.otherActivities,unlistedServices:i.unlistedServices,generalRegimeChosen:i.generalRegimeChosen,generalThreeYearsCompleted:i.generalThreeYearsCompleted,generalRegimeRef:i.generalRegimeRef})}`],['ISPC','Opção e próximo passo',`${r.optionReason};referência${i.optionReference};data${i.optionDate}`,`Prossiga no apuramento trimestral; esta avaliação não adopta regime nem calcula imposto. ${r.source}`]];}catch(error){return[['ISPC','Enquadramento','Dados não recuperáveis;originais preservados',String(error)]];}}
|