102 lines
17 KiB
TypeScript
102 lines
17 KiB
TypeScript
import type {Acquisition,AcquisitionResult} from './iva-acquisitions.ts';
|
||
import type {AcquisitionEligibility} from './iva-acquisition-right.ts';
|
||
import {acquisitionScope,readAcquisitionStore} from './iva-acquisitions.ts';
|
||
import {linkedOperationRight,linkedBeverageDeductionRight} from './iva-acquisition-right.ts';
|
||
import {acquisitionTreatmentIssue} from './iva-acquisition-continuity.ts';
|
||
import {decimalFraction,fraction,fractionPresentation,subtractFractions} from './fiscal-rational.ts';
|
||
export const acquisitionBCVersion='acquisition-imports-exclusions/1';
|
||
export const acquisitionBCSource='L2-B/C: CIVA18(1)(a/b),18(2),19,20(1)(a–f)/20(2)/20(3)(a/b),21. Lei13/2016 PDF14–15,p1426(272–273);Lei22/2022 PDF3,p2252(27),art20(2–3);Lei10/2025 PDF13,p2180(13),18(2)/19(2). Bebidas13(2):Lei13/2016 PDF12,p1426(270). MZN/2026,mesmo mês documental;sem pro rata18(6),regularizações ou restituiçãoL3.';
|
||
type Choice=''|'yes'|'no';
|
||
export const expenseOptions=[['ordinary','Despesa sem exclusão especial'],['vehicle','Viatura de turismo,barco de recreio,helicóptero,avião,moto ou outro veículo'],['fuel','Combustível normalmente utilizável em viaturas automóveis'],['travel','Transportes e viagens do sujeito passivo/pessoal'],['lodging_food','Alojamento e alimentação'],['beverages','Bebidas,tabaco e recepção'],['telephone','Comunicações telefónicas'],['luxury','Divertimento e luxo'],['other','Outro caso — por enquadrar']] as const;
|
||
export type AcquisitionBC={version:1;expense:string;reducedSupply:Choice;evidence:string;facts:Record<string,Choice>;fuelKind:''|'diesel'|'other';dieselUse:''|'heavy_passengers'|'public_passengers'|'machine'|'agriculture'|'other';beveragePath:''|'deduction'|'refund';importer:string;belongs:Choice;ownImportVat:Choice;vatBasis:''|'due'|'exempt'|'zero'|'unknown';vatRef:string;goodsRef:string;source?:{map:string;id:string;fingerprint:string}};
|
||
export function newAcquisitionBC():AcquisitionBC{return{version:1,expense:'',reducedSupply:'',evidence:'',facts:{},fuelKind:'',dieselUse:'',beveragePath:'',importer:'',belongs:'',ownImportVat:'',vatBasis:'',vatRef:'',goodsRef:''};}
|
||
export function acquisitionBCDetails(b:AcquisitionBC):string{
|
||
const labels:Record<string,string>={exigibleInPeriod:'IVA aduaneiro exigível no período de exercício confirmado pelo documento',tourism:'Bem abrangido20(1)(a)',saleOrExploit:'Venda/exploração como objecto da actividade',resale:'Combustível para revenda',dieselScope:'Âmbito específico do uso de gasóleo confirmado',commercialTraveller:'Viajante comercial por conta própria',ownLandline:'Telefone fixo em nome do sujeito passivo',beverageSpecial:'Bebidas no âmbito13(2)',onlyDeduction:'Parcela exclusivamente para dedução',notCoastalFishing:'Sem provisões de pesca costeira'};
|
||
const keys:Record<string,string[]>={vehicle:['tourism',...(b.facts.tourism==='yes'?['saleOrExploit']:[])],fuel:['resale',...(b.fuelKind==='diesel'&&b.dieselUse!=='other'?['dieselScope']:[])],lodging_food:['commercialTraveller'],telephone:['ownLandline'],beverages:['beverageSpecial',...(b.facts.beverageSpecial==='yes'?[...(b.beveragePath==='deduction'?['onlyDeduction']:[]),'notCoastalFishing']:[])]};
|
||
return [...(b.facts.exigibleInPeriod!==undefined?['exigibleInPeriod']:[]),...(keys[b.expense]??[])].map(k=>`${labels[k]}:${b.facts[k]==='yes'?'Sim':b.facts[k]==='no'?'Não':'Por indicar'}`).concat(b.expense==='fuel'?[`Combustível:${b.fuelKind==='diesel'?'Gasóleo':b.fuelKind==='other'?'Outro':'Por indicar'}`,`Utilização:${({heavy_passengers:'Pesado de passageiros',public_passengers:'Transporte público licenciado,excepto rent a car',machine:'Máquina,não veículo matriculado',agriculture:'Tractor agrícola,emprego exclusivo/predominante',other:'Outro uso50%','':'Por indicar'})[b.dieselUse]}`]:[],b.expense==='beverages'&&b.facts.beverageSpecial==='yes'?[`Via:${b.beveragePath==='deduction'?'DeduçãoL2':b.beveragePath==='refund'?'RestituiçãoL3':'Por indicar'}`]:[]).join('; ');
|
||
}
|
||
export function validAcquisitionBC(v:unknown):v is AcquisitionBC{
|
||
if(!v||typeof v!=='object'||Array.isArray(v))return false;const b=v as AcquisitionBC;
|
||
return b.version===1&&['expense','evidence','vatRef','goodsRef','importer','fuelKind','dieselUse','beveragePath','vatBasis'].every(k=>typeof b[k as keyof AcquisitionBC]==='string')&&[b.reducedSupply,b.belongs,b.ownImportVat].every(v=>['','yes','no'].includes(v))&&!!b.facts&&typeof b.facts==='object'&&!Array.isArray(b.facts)&&Object.values(b.facts).every(v=>['','yes','no'].includes(v))&&(!b.source||['map','id','fingerprint'].every(k=>typeof b.source?.[k as keyof typeof b.source]==='string'));
|
||
}
|
||
type ImportSource={id:string;treatment?:string;description?:string;documentRef?:string;documentDate?:string;period?:string;importDate?:string;importer?:string};
|
||
const importMaps=['importExemptions','objectiveImports','remissionImports'];
|
||
export function acquisitionImportSources(draft:Record<string,unknown>){const store=readAcquisitionStore(draft.modelo_a_snapshot) as unknown as Record<string,Record<string,{rows:ImportSource[]}>>;return importMaps.flatMap(map=>(store[map]?.[acquisitionScope(draft)]?.rows??[]).map(row=>({map,row,label:`${row.description||row.id} — ${row.period??''} — ${row.documentRef||'documento por indicar'}`})));}
|
||
export function reuseAcquisitionImport(draft:Record<string,unknown>,row:Acquisition,map:string,id:string):Acquisition{
|
||
const src=acquisitionImportSources(draft).find(v=>v.map===map&&v.row.id===id);if(!src)throw Error('Origem da importação indisponível;dados conservados.');
|
||
const bc=row.bc??newAcquisitionBC();return {...row,description:row.description||src.row.description||'',documentRef:row.documentRef||src.row.documentRef||'',documentDate:row.documentDate||src.row.documentDate||src.row.importDate||'',period:row.period||src.row.period||'',bc:{...bc,importer:bc.importer||src.row.importer||'',source:{map,id,fingerprint:JSON.stringify(src.row)}}};
|
||
}
|
||
export function acquisitionBCSourceKey(draft:Record<string,unknown>,rows:Acquisition[]):string{return rows.some(r=>r.bc)?JSON.stringify({version:acquisitionBCVersion,sources:rows.filter(r=>r.bc?.source).map(r=>acquisitionImportSources(draft).find(s=>s.map===r.bc!.source!.map&&s.row.id===r.bc!.source!.id)?.row??null)}):'';}
|
||
function validDate(value:string){const d=new Date(value+'T00:00:00Z');return /^\d{4}-\d{2}-\d{2}$/.test(value)&&Number.isFinite(d.getTime())&&d.toISOString().slice(0,10)===value;}
|
||
export function evaluateAcquisitionBC(row:Acquisition,currency:string,registered:Choice,draft:Record<string,unknown>|undefined,raw:AcquisitionResult,partial?:{right:AcquisitionEligibility;noExcludedPayments:Choice}):AcquisitionResult{
|
||
const bc=row.bc!;let right=partial?.right??(row.deductionLink&&draft?linkedOperationRight(draft,row.deductionLink):{status:'indeterminate' as const,reason:'Vínculo à operação de destino em falta.',source:acquisitionBCSource});
|
||
const stop=(reason:string,status:'eligible'|'no_right'|'indeterminate'='indeterminate',percent?:number):AcquisitionResult=>{
|
||
const exact=percent!==undefined&&raw.vat!==null?decimalFraction(raw.vat):null;
|
||
const deductibleExact=exact?fraction(BigInt(exact.numerator)*BigInt(percent!),BigInt(exact.denominator)*100n):undefined;
|
||
return {...raw,reason,deductible:deductibleExact?fractionPresentation(deductibleExact).decimal:null,...(deductibleExact?{deductibleExact,nonDeductibleExact:subtractFractions(exact!,deductibleExact)}:{}),eligibility:{...right,status,reason,source:acquisitionBCSource+'; '+right.source}};
|
||
};
|
||
if(!validAcquisitionBC(bc))return stop('CondiçõesB/C não recuperáveis;sem regra por defeito.');
|
||
if(currency!=='MZN'||row.sourceCurrency!==currency||!/^2026-(0[1-9]|1[0-2])$/.test(row.period)||raw.vat===null)return stop(raw.reason||'IVA/moeda/período em falta.');
|
||
if(row.domestic==='no'){
|
||
if(!/^\d{1,15}(\.\d{1,6})?$/.test(row.vat.trim()))return stop('Indique expressamente o IVA do bilhete;diferença entre custo líquido/bruto não constitui montante aduaneiro identificado.');
|
||
if(row.net.trim()&&raw.net===null||row.gross.trim()&&raw.gross===null)return stop('Valores de custo preenchidos inválidos;conservar e resolver a divergência.');
|
||
}else if(raw.net===null||raw.gross===null)return stop(raw.reason);
|
||
if(raw.net!==null&&raw.gross!==null&&fractionPresentation(subtractFractions(decimalFraction(raw.gross),decimalFraction(raw.net))).decimal!==raw.vat)return stop('Líquido + IVA diverge do bruto declarado;sem substituir valores.');
|
||
const treatmentIssue=acquisitionTreatmentIssue(row.acquisitionTreatment);if(treatmentIssue)return stop(treatmentIssue);
|
||
if(!['projected','effective'].includes(row.origin))return stop('Indique dados efectivos ou pressupostos de projecção.');
|
||
if(row.sourceMonth!==null&&(!row.sourceBasis||row.sourceAmount===null||(row.sourceBasis==='net'?raw.net:raw.gross)===null||JSON.stringify(decimalFraction(row.sourceAmount))!==JSON.stringify(decimalFraction((row.sourceBasis==='net'?raw.net:raw.gross)!))))return stop('Valor de origem divergente ou por classificar;sem sobrescrita.');
|
||
if(row.domestic==='no'&&row.field!=='11'||row.domestic==='yes'&&row.field==='11'||!['yes','no'].includes(row.domestic))return stop('Separe aquisição interna08–10 da importação de bens11.');
|
||
if(bc.source){const src=draft?acquisitionImportSources(draft).find(s=>s.map===bc.source!.map&&s.row.id===bc.source!.id):undefined;if(!src||JSON.stringify(src.row)!==bc.source.fingerprint)return stop('OrigemL1 da importação alterada:adopte explicitamente os dados actuais,conservando edições e histórico.');if(['service_in_customs_value','accessory_service_first_destination'].includes(src.row.treatment??''))return stop('Origem é serviço incluído na base aduaneira12(1)(e),não IVA próprio da importação de bens;identifique o bilhete dos bens,sem somar novamente IVA do serviço.');}
|
||
if(registered!=='yes')return stop('Enquadramento do adquirente no regime normal por confirmar.');
|
||
if(row.allocation==='no_right'||row.integrity==='no'||row.exclusion==='excluded')return stop('Sem direito pela afectação,exclusão expressa ou impedimento18(3–5) declarado.','no_right',0);
|
||
if(row.allocation==='mixed'&&!partial)return stop('Afectação parcial/pro rata depende deD;sem dedução integral presumida.');
|
||
if(!partial&&(!row.deductionLink||row.deductionLink.scopeConfirmed!=='yes'||!row.deductionLink.allocationRef.trim()||row.allocation!=='taxable_only'))return stop('Confirme afectação exclusiva e referência à operação ligada.');
|
||
if(bc.expense==='beverages'&&bc.facts.beverageSpecial==='yes'&&['deduction','refund'].includes(bc.beveragePath)&&draft&&row.deductionLink)right=linkedBeverageDeductionRight(draft,row.deductionLink,bc.facts.notCoastalFishing==='yes');
|
||
if(right.status==='no_right')return stop(right.reason,'no_right',0);
|
||
if(right.status!=='eligible')return stop(right.reason);
|
||
const noExcludedPayments=partial?.noExcludedPayments??row.deductionLink?.noExcludedPayments;
|
||
if(noExcludedPayments==='no')return stop('Pagamentos15(6)(c)/19(2) excluídos.','no_right',0);
|
||
if(noExcludedPayments!=='yes')return stop('Exclusão19(2) por esclarecer.');
|
||
if(!validDate(row.documentDate)||row.documentDate.slice(0,7)!==row.period)return stop('Documento e exercício no mesmo mês neste incremento;dedução posterior não presumida.');
|
||
if(!row.documentRef.trim()||row.legalDocument!=='yes'||row.held!=='yes'||row.integrity!=='yes')return stop('Referência,forma legal,posse e integridade do documento por completar.');
|
||
if(row.domestic==='no'){
|
||
if(row.documentType!=='customs'||!bc.importer.trim()||!bc.goodsRef.trim()||!bc.vatRef.trim()||bc.belongs!=='yes'||bc.ownImportVat!=='yes')return stop('Bilhete de despacho,importador/titularidade,bens/linha e IVA próprio devido na importação por completar;fornecedor inscrito no IVA interno não é exigido.');
|
||
if(bc.facts.exigibleInPeriod!=='yes')return stop('Exigibilidade do IVA aduaneiro no período de exercício por confirmar pelo documento21(1);não inferida só da emissão do bilhete.Dedução posterior conserva percurso próprio.');
|
||
if(!['due','exempt','zero'].includes(bc.vatBasis))return stop('IVA da importação desconhecido;não inferir montante da classificaçãoL1 ou calcular direitosL13.');
|
||
if(['exempt','zero'].includes(bc.vatBasis)&&decimalFraction(raw.vat).numerator!=='0')return stop('IVA positivo incompatível com isenção/zero declarado;resolver divergência sem substituir montante.');
|
||
}else if(!['invoice','equivalent'].includes(row.documentType)||!row.supplier.trim()||row.supplierRegistered!=='yes')return stop('Factura/documento equivalente e fornecedor interno inscrito por completar.');
|
||
if(row.domestic==='yes'&&bc.reducedSupply==='yes')return stop('IVA suportado em fornecimento17-A:exclusão20(2),Lei22/2022;distinta do destino19(2).','no_right',0);
|
||
if(row.domestic==='yes'&&bc.reducedSupply!=='no')return stop('Indique se o IVA suportado respeita a fornecimento17-A;sem inferir pela taxa ou valor.');
|
||
if(!expenseOptions.some(([id])=>id===bc.expense)||bc.expense==='other'||!bc.evidence.trim())return stop('Natureza da despesa e fundamento factual/documental por identificar.');
|
||
const f=bc.facts;
|
||
switch(bc.expense){
|
||
case 'ordinary':return stop('Elegível pelas condições declaradas18/19;sem exclusão especial20 identificada.','eligible',100);
|
||
case 'vehicle':
|
||
if(f.tourism==='')return stop('Classificação do veículo na definição20(1)(a) em falta.');
|
||
if(f.tourism==='no')return stop('Veículo fora da definição20(1)(a),segundo características/utilização documentadas.','eligible',100);
|
||
if(f.tourism!=='yes')return stop('Indique se o bem está abrangido por20(1)(a).');
|
||
if(f.saleOrExploit==='yes')return stop('Excepção20(3)(a):venda/exploração do bem constitui objecto da actividade;combustível conserva20(1)(b).','eligible',100);
|
||
return f.saleOrExploit==='no'?stop('Bem abrangido20(1)(a),sem excepção20(3)(a).','no_right',0):stop('Venda/exploração como objecto da actividade por esclarecer.');
|
||
case 'fuel':
|
||
if(f.resale==='yes')return stop('Combustível adquirido para revenda,conforme ressalva20(3)(a);fundamento específico documentado.','eligible',100);
|
||
if(f.resale!=='no')return stop('Destino de revenda do combustível por esclarecer.');
|
||
if(bc.fuelKind==='other')return stop('Combustível normalmente utilizável em viaturas,fora da excepção gasóleo20(1)(b).','no_right',0);
|
||
if(bc.fuelKind!=='diesel')return stop('Natureza do combustível por indicar.');
|
||
if(bc.dieselUse==='other')return stop('Gasóleo20(1)(b):dedução50%,não taxa de IVA nem pro rata geral.','eligible',50);
|
||
if(['heavy_passengers','public_passengers','machine','agriculture'].includes(bc.dieselUse)){
|
||
if(f.dieselScope!=='yes')return stop('Confirme o âmbito específico20(1)(b)(i–iv);não basta escolher a categoria.');
|
||
return stop('Gasóleo20(1)(b)(i–iv):dedução integral no uso específico confirmado;transporte público exclui rent a car,máquina não é veículo matriculado,tractor agrícola tem emprego exclusivo/predominante.','eligible',100);
|
||
}return stop('Utilização do gasóleo por indicar.');
|
||
case 'travel':return stop('Transportes/viagens do sujeito passivo e pessoal excluídos20(1)(c).','no_right',0);
|
||
case 'lodging_food':return f.commercialTraveller==='yes'?stop('Excepção20(3)(b):viajante comercial por conta própria,no quadro da actividade profissional.','eligible',100):f.commercialTraveller==='no'?stop('Alojamento/alimentação excluídos20(1)(d),sem excepção20(3)(b).','no_right',0):stop('Âmbito de viajante comercial por conta própria por esclarecer.');
|
||
case 'telephone':return f.ownLandline==='yes'?stop('Telefone fixo em nome do sujeito passivo:excepção expressa20(1)(e).','eligible',100):f.ownLandline==='no'?stop('Comunicações telefónicas fora da excepção20(1)(e).','no_right',0):stop('Telefone fixo e titularidade por esclarecer.');
|
||
case 'luxury':return stop('Despesa de divertimento/luxo20(1)(f),segundo natureza/montante e exploração normal declarados.','no_right',0);
|
||
case 'beverages':
|
||
if(bc.facts.beverageSpecial==='no')return stop('Bebidas/tabaco/recepção excluídos20(1)(d),fora do fundamento especial13(2).','no_right',0);
|
||
if(bc.facts.beverageSpecial==='yes'&&bc.beveragePath==='refund'&&right.reason.startsWith('Via de dedução13(2)'))return stop('Via restituição13(2):linha documental integral escolhida para acompanhamentoL3-E,sem dedução desta parcela. Não representa pedido apresentado,concessão ou recebimento.','no_right',0);
|
||
if(bc.facts.beverageSpecial==='yes'&&bc.beveragePath==='deduction'&&bc.facts.onlyDeduction==='yes'&&right.reason.startsWith('Via de dedução13(2)'))return stop(right.reason+' IVA suportado identificado neste documento;mesma parcela não segue restituiçãoL3.','eligible',100);
|
||
return stop('Via especial13(2) por delimitar:dedução ou restituiçãoL3,sem dupla contagem;tabaco/recepção não herdam a excepção de bebidas.');
|
||
}
|
||
return stop('Tratamento não implementado;rascunho preservado.');
|
||
}
|