58 lines
12 KiB
TypeScript
58 lines
12 KiB
TypeScript
import {acquisitionScope,readAcquisitionStore} from './iva-acquisitions.ts';
|
|
export const goodsPresumptionVersion='goods-presumptions/1';
|
|
export const goodsPresumptionSource='CIVA Lei13/2016,3(3)d PDF6/p1426(264):mercadorias enviadas à consignação não devolvidas em180dias da entrega;3(4) PDF7/p1426(265):salvo prova em contrário,presunção de transmissão dos bens adquiridos/importados/produzidos não encontrados nos estabelecimentos e dos consumidos em quantidades excessivas face ao volume de produção;presunção de aquisição dos bens encontrados nesses locais.Lei22/2022 não altera3;Lei10/2025 PDF12 mantém3(3)d/3(4).Não são embalagensP102 nem aplicação automática da margemP092/P093/P125.';
|
|
export const goodsPresumptionTreatments=[{id:'consignment_180',branch:'P123',label:'Consignação — não devolução em180dias'},{id:'missing_goods',branch:'P126',label:'Presunção de transmissão — bens não localizados'},{id:'excess_consumption',branch:'P126',label:'Presunção de transmissão — consumo excessivo'},{id:'found_goods',branch:'P126',label:'Presunção de aquisição — bens encontrados'}] as const;
|
|
export const goodsPresumptionConditions={consignment_180:[['consignment','Mercadorias enviadas à consignação,com entrega ao destinatário identificado'],['notReturned','O conjunto identificado não foi devolvido até à data de conferência']],missing_goods:[['acquiredImportedProduced','Bens adquiridos,importados ou produzidos pelo sujeito passivo'],['notLocated','Não se encontram em nenhum dos estabelecimentos do sujeito passivo,segundo o levantamento identificado']],excess_consumption:[['acquiredImportedProduced','Bens adquiridos,importados ou produzidos pelo sujeito passivo'],['excessive','Consumo em quantidades excessivas,tendo em conta o volume de produção identificado']],found_goods:[['found','Bens encontrados em estabelecimento do sujeito passivo identificado']]} as const;
|
|
export function presumptionConditions(t:string):ReadonlyArray<readonly[string,string]>{return goodsPresumptionConditions[t as keyof typeof goodsPresumptionConditions]??[];}
|
|
type Choice=''|'yes'|'no';
|
|
export type GoodsPresumptionRow={id:string;treatment:string;description:string;period:string;origin:'projected'|'effective';operationDate:string;operationRef:string;documentRef:string;documentDate:string;goodsRef:string;scope:string;evidence:string;deliveryDate:string;recipientRef:string;inventoryRef:string;productionRef:string;quantity:string;quantityUnit:string;contraryProof:Choice;contraryProofRef:string;conditions:Partial<Record<string,Choice>>};
|
|
type LineResult={id:string;status:'determined'|'not_applicable'|'indeterminate';classification:'consignment_transmission'|'presumed_transmission'|'presumed_acquisition'|'rebutted'|'not_applicable'|'indeterminate';deadline:string|null;elapsedDays:number|null;baseAmount:null;vat:null;rate:null;reason:string};
|
|
export type GoodsPresumptionCalculation={version:string;revision:number;calculatedAt:string;opportunity:string;location:string;sourceKey:string;source:string;inputs:GoodsPresumptionRow[];rows:LineResult[]};
|
|
export type GoodsPresumptionBook={rows:GoodsPresumptionRow[];result?:GoodsPresumptionCalculation;history?:GoodsPresumptionCalculation[]};
|
|
type Store=ReturnType<typeof readAcquisitionStore>&{goodsPresumptions?:Record<string,GoodsPresumptionBook>};
|
|
export function newGoodsPresumption(id:string):GoodsPresumptionRow{return{id,treatment:'',description:'',period:'',origin:'projected',operationDate:'',operationRef:'',documentRef:'',documentDate:'',goodsRef:'',scope:'',evidence:'',deliveryDate:'',recipientRef:'',inventoryRef:'',productionRef:'',quantity:'',quantityUnit:'',contraryProof:'',contraryProofRef:'',conditions:{}};}
|
|
export function switchPresumptionTreatment(row:GoodsPresumptionRow,treatment:string):GoodsPresumptionRow{return{...row,treatment,conditions:{},contraryProof:''};}
|
|
function readStore(draft:Record<string,unknown>):Store{
|
|
const store=readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
|
if(store.goodsPresumptions!==undefined&&(!store.goodsPresumptions||typeof store.goodsPresumptions!=='object'||Array.isArray(store.goodsPresumptions)))throw new Error('Presunções não recuperáveis;originais preservados.');
|
|
const fields=Object.keys(newGoodsPresumption('')).filter(k=>k!=='conditions');
|
|
for(const book of Object.values(store.goodsPresumptions??{}))if(!book||!Array.isArray(book.rows)||(book.history!==undefined&&!Array.isArray(book.history))||book.rows.some(row=>!row||fields.some(k=>typeof row[k as keyof GoodsPresumptionRow]!=='string')||!row.conditions||typeof row.conditions!=='object'||Array.isArray(row.conditions)))throw new Error('Linhas não recuperáveis;originais conservados,sem lista vazia por defeito.');
|
|
return store;
|
|
}
|
|
export function goodsPresumptionBook(draft:Record<string,unknown>):GoodsPresumptionBook{return readStore(draft).goodsPresumptions?.[acquisitionScope(draft)]??{rows:[]};}
|
|
export function saveGoodsPresumptionBook(draft:Record<string,unknown>,book:GoodsPresumptionBook):string{const store=readStore(draft),encoded=JSON.stringify({...store,goodsPresumptions:{...store.goodsPresumptions,[acquisitionScope(draft)]:book}});readStore({...draft,modelo_a_snapshot:encoded});return encoded;}
|
|
function date(s:string):number|null{const n=Date.parse(s+'T00:00:00Z');return /^\d{4}-\d{2}-\d{2}$/.test(s)&&Number.isFinite(n)&&new Date(n).toISOString().slice(0,10)===s?n:null;}
|
|
export function evaluateGoodsPresumption(row:GoodsPresumptionRow):LineResult{
|
|
const result:LineResult={id:row.id,status:'indeterminate',classification:'indeterminate',deadline:null,elapsedDays:null,baseAmount:null,vat:null,rate:null,reason:''};
|
|
const stop=(reason:string,status:LineResult['status']='indeterminate',classification:LineResult['classification']=status==='not_applicable'?'not_applicable':result.classification)=>({...result,status,classification,reason});
|
|
const rule=goodsPresumptionTreatments.find(t=>t.id===row.treatment);if(!rule)return stop('Identifique o percurso;sem presunção por defeito.');
|
|
const operation=date(row.operationDate);
|
|
if(operation===null||!/^2026-(0[1-9]|1[0-2])$/.test(row.period)||row.period!==row.operationDate.slice(0,7)||!['projected','effective'].includes(row.origin))return stop('Identifique data de conferência/mês2026 e dados efectivos ou pressupostos expressos.');
|
|
if(![row.description,row.operationRef,row.documentRef,row.goodsRef,row.scope,row.evidence].every(v=>!!v.trim())||date(row.documentDate)===null)return stop('Identifique bens,conjunto/âmbito,operação,documento/data e factos ou pressupostos;reutilize dados disponíveis,sem certificado/aprovador novo.');
|
|
const conditions=presumptionConditions(row.treatment);
|
|
if(conditions.some(([k])=>row.conditions[k]==='no'))return stop('Os factos declarados afastam este percurso específico;isso não determina isenção,IVA zero ou a classificação de outra operação.','not_applicable');
|
|
if(conditions.some(([k])=>row.conditions[k]!=='yes'))return stop('Factos próprios por esclarecer;desconhecido não é não aplicável.');
|
|
if(row.treatment==='consignment_180'){
|
|
const delivery=date(row.deliveryDate);if(delivery===null||delivery>operation||!row.recipientRef.trim())return stop('Identifique entrega ao destinatário e data anterior ou igual à conferência;sem prazo presumido.');
|
|
result.elapsedDays=(operation-delivery)/86400000;result.deadline=new Date(delivery+180*86400000).toISOString().slice(0,10);
|
|
if(result.elapsedDays<=180)return stop(`Prazo de180dias ainda não ultrapassado na conferência;limite ${result.deadline},decorridos ${result.elapsedDays}dias.Sem transmissão por não devolução nesta data. O dia180 conserva o prazo até ao fim do dia.`,`not_applicable`);
|
|
return stop(`P123:mercadorias identificadas enviadas à consignação,não devolvidas após180dias desde entrega${row.deliveryDate};limite${result.deadline};decorridos${result.elapsedDays}dias. Transmissão3(3)d identificada no recorte de conferência;não são embalagensP102. Data de exigibilidade/base/IVA não calculadas neste percurso;devolução posterior requer tratamento próprio,sem apagar histórico.`,`determined`,'consignment_transmission');
|
|
}
|
|
if(!row.inventoryRef.trim())return stop('Identifique levantamento/inventário e os estabelecimentos abrangidos;ausência numa única localização não demonstra ausência em todos os estabelecimentos.');
|
|
if(row.treatment==='excess_consumption'&&(!row.productionRef.trim()||!row.quantityUnit.trim()||!/^\d{1,15}(\.\d{1,6})?$/.test(row.quantity)||!/[1-9]/.test(row.quantity)))return stop('Identifique quantidade/unidade consumida e volume de produção/comparação que fundamenta o excesso;sem percentagem ou limiar inventado.');
|
|
if(row.contraryProof==='yes')return row.contraryProofRef.trim()?stop('Prova em contrário identificada para o conjunto/âmbito declarado:presunção3(4) afastada,sem atribuir isenção ou IVA zero e sem apagar histórico.','not_applicable','rebutted'):stop('Identifique o âmbito e referência da prova em contrário declarada;sem afastar presunção só por seleccionar Sim.');
|
|
if(row.contraryProof!=='no')return stop('Existência de prova em contrário por esclarecer;presunção ilidível,não conclusão definitiva nem infracção presumida.');
|
|
return stop(`P126:presunção ilidível3(4) de ${row.treatment==='found_goods'?'aquisição dos bens encontrados':'transmissão dos bens '+(row.treatment==='missing_goods'?'não localizados nos estabelecimentos abrangidos':'consumidos em quantidades excessivas face ao volume de produção')};segundo factos/pressupostos e âmbito identificados,sem prova em contrário declarada. Não comprova factura,aquisição efectiva,isenção ou dedução.Nenhuma base/taxa/IVA calculada;manter a possibilidade de prova em contrário.`,`determined`,row.treatment==='found_goods'?'presumed_acquisition':'presumed_transmission');
|
|
}
|
|
function key(draft:Record<string,unknown>,book:GoodsPresumptionBook):string{return JSON.stringify({version:goodsPresumptionVersion,scope:acquisitionScope(draft),location:draft.selection_labels??{},rows:book.rows});}
|
|
export function goodsPresumptionCurrent(draft:Record<string,unknown>,book:GoodsPresumptionBook):boolean{return!!book.result&&book.result.sourceKey===key(draft,book);}
|
|
export function recordGoodsPresumptions(draft:Record<string,unknown>,book:GoodsPresumptionBook):GoodsPresumptionBook{
|
|
if(!book.rows.length||new Set(book.rows.map(r=>r.id)).size!==book.rows.length)throw new Error('Indique operações com identificadores distintos;originais conservados.');
|
|
const result:GoodsPresumptionCalculation={version:goodsPresumptionVersion,revision:(book.result?.revision??0)+1,calculatedAt:new Date().toISOString(),opportunity:acquisitionScope(draft),location:JSON.stringify(draft.selection_labels??{}),sourceKey:key(draft,book),source:goodsPresumptionSource,inputs:structuredClone(book.rows),rows:book.rows.map(evaluateGoodsPresumption)};
|
|
const next={...book,result,history:[...(book.history??[]),...(book.result?[book.result]:[])]};saveGoodsPresumptionBook(draft,next);return next;
|
|
}
|
|
export function goodsPresumptionReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]>{
|
|
try{const book=goodsPresumptionBook(draft),r=book.result;if(!r)return[];return r.inputs.map((row,i)=>['IVA mensal',`${goodsPresumptionTreatments.find(t=>t.id===row.treatment)?.branch??'Presunção'} — ${row.period} — ${row.id}`,`${row.description};${r.rows[i].reason};base/IVA:Indeterminados`,`${r.opportunity};${r.location};${goodsPresumptionCurrent(draft,book)?'Resultado actual':'Entradas alteradas;resultado histórico conservado'};versão${r.revision};${r.calculatedAt};${r.source};${row.origin};operação ${row.operationRef},${row.operationDate};documento ${row.documentRef},${row.documentDate};bens ${row.goodsRef};âmbito ${row.scope};${row.treatment==='consignment_180'?`entrega${row.deliveryDate},destinatário${row.recipientRef},limite${r.rows[i].deadline??'indeterminado'}`:`inventário ${row.inventoryRef};${row.treatment==='excess_consumption'?`consumo ${row.quantity} ${row.quantityUnit},produção/comparação ${row.productionRef};`:''}provaemcontrário ${row.contraryProof},${row.contraryProofRef}`};condições${JSON.stringify(row.conditions)};fundamento ${row.evidence};sem nova dedução/campo11/IVA/margem automática.`]);}
|
|
catch{return[['IVA mensal','Consignação/presunções','Dados não recuperáveis','Originais conservados;sem classificação por defeito.']];}
|
|
}
|