Files
Investidor-V2/resources/js/lib/iva-acquisitions.ts
T

283 lines
37 KiB
TypeScript
Raw 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.
import { modeloAPurchaseReferences } from './iva-modelo-a.ts';
import { monthlyIvaInput, monthlyIvaKey, readMonthlyIvaRecord, readMonthlyIvaSettings } from './monthly-iva.ts';
import { restoreBusinessPlanSnapshot } from './business-financial-plan.ts';
import { salesBook, salesCurrent as salesOperationsCurrent } from './iva-operations.ts';
import {evaluateAcquisitionAllocation,validAcquisitionAllocation,allocationSourceKey,allocationHasUnconsolidatedOutputs,acquisitionAllocationVersion,acquisitionAllocationSource,type AcquisitionAllocation} from './iva-acquisition-allocation.ts';
import {evaluateAcquisitionBC,validAcquisitionBC,acquisitionBCSource,acquisitionBCSourceKey,acquisitionBCDetails,expenseOptions,type AcquisitionBC} from './iva-acquisition-bc.ts';
import { linkedOperationRight, validAcquisitionRightLink, acquisitionRightVersion, acquisitionRightSource, type AcquisitionRightLink, type AcquisitionEligibility } from './iva-acquisition-right.ts';
import { acquisitionTreatmentIssue, acquisitionContinuityVersion, formerExplorationSource, type AcquisitionTreatment } from './iva-acquisition-continuity.ts';
import { addFractions, decimalFraction, fractionLabel, fractionPresentation, positiveFraction, subtractFractions, type FiscalFraction } from './fiscal-rational.ts';
import {regularizationFields,regularizationSourceKey,regularizationReportRows,frozenAssetRows,validateRegularizationStore} from './iva-regularizations.ts';
import {beverageParcelReserved} from './iva-beverage-parcels.ts';
export const acquisitionVersion = 'modelo-a-internal-acquisitions/1';
export const acquisitionBasis = 'CIVA arts.18(1)(a),18(2) (Lei10/2025),18(3–5),19(1)(a),19(2),20 (Lei22/2022),21(1–3); âmbito interno, afectação exclusiva, mesmo período, sem exclusões ou dedução parcial.';
export const acquisitionSources = 'Modelo A DOCX 62c198e9 / v1 63d45fa8; VAT Return.pdf SHA256 9c0b99a810c7db53eb75d63c22ee22fb86a50ba57bd5509041ed80ee10f75741, p.1; CIVA republicado Lei13/2016, PDF14–16; Lei22/2022, art.20; Lei10/2025, arts.18/19, PDF13.';
type Choice = '' | 'yes' | 'no';
export type Acquisition = {
id: string; field: '08'|'09'|'10'|'11'; period: string; origin: 'projected'|'effective';
sourceMonth: number|null; sourcePeriod?:string; sourceField?:'inventory'|'asset'|'operating'; sourceAmount: string|null; sourceCurrency: string; sourceBasis: ''|'net'|'gross';
net: string; vat: string; gross: string; documentType: ''|'invoice'|'equivalent'|'customs'; documentRef: string; documentDate: string; documentLine:string;
supplier: string; supplierRegistered: Choice; legalDocument: Choice; held: Choice; domestic: Choice;
allocation: ''|'taxable_only'|'mixed'|'no_right'; exclusion: ''|'none'|'excluded'|'special';
integrity: Choice; description: string;
acquisitionTreatment?: AcquisitionTreatment;
deductionLink?: AcquisitionRightLink;
bc?:AcquisitionBC;
d?:AcquisitionAllocation;
investmentAsset?:boolean;
acquisitionTreatmentOrigin?: {id:'former_exploration_exemption';source:string};
};
export type AcquisitionResult = {id:string; net:string|null; vat:string|null; gross:string|null; deductible:string|null; reason:string; eligibility?:AcquisitionEligibility;deductibleExact?:FiscalFraction;nonDeductibleExact?:FiscalFraction;allocationCalculation?:{version:string;ratio:FiscalFraction;rawRatio:FiscalFraction;before:FiscalFraction;source:string}};
type PeriodInput = {period:string; complete:boolean; absent:string[];importsComplete?:boolean};
export type AcquisitionBook = {currency:string; rightVersion?:string; taxpayerRegistered?:Choice; rows:Acquisition[]; periods:PeriodInput[]; result?:AcquisitionCalculation;history?:AcquisitionCalculation[]};
export type AcquisitionCalculation = {version:string; revision:number; calculatedAt:string; opportunity:string; location:string; currency:string; sourceKey:string; basis:string; sources:string;
declarations?:{taxpayerRegistered:Choice;periods:PeriodInput[]}; rows:AcquisitionResult[]; inputs:Acquisition[]; periods:Array<{period:string; fields:Record<string,string|null>; exactFields?:Record<string,FiscalFraction|null>; reasons:string[]}>};
type Store = {version:1; books:Record<string,AcquisitionBook>};
const excluded = ['03','04','05','06','07','11','12','13'];
export const acquisitionExcludedFields = excluded;
export function acquisitionScope(draft:Record<string,unknown>):string {
if(!draft.context_opportunity) {
try {const context=JSON.parse(String(draft.context_applicability_snapshot??''));if(context.opportunity_choices?.length>1)throw new Error('Escolha a oportunidade em tratamento antes de preparar as compras.');}
catch(cause) {if(cause instanceof Error && cause.message.startsWith('Escolha'))throw cause;}
}
return String(draft.context_opportunity || 'general');
}
export function readAcquisitionStore(raw:unknown):Store {
if (!raw) return {version:1,books:{}};
const value=JSON.parse(String(raw));
if (value?.version!==1 || !value.books || typeof value.books!=='object' || Array.isArray(value.books)) throw new Error('Compras fiscais guardadas não recuperáveis; dados preservados.');
validateRegularizationStore(value);
for(const book of Object.values(value.books) as AcquisitionBook[]) {
if(book?.rows?.some(row=>row.investmentAsset!==undefined&&typeof row.investmentAsset!=='boolean'))throw new Error('Classificação do activo não recuperável;originais preservados.');
if(book?.rows?.some(row=>row.d!==undefined&&!validAcquisitionAllocation(row.d)))throw new Error('AfectaçãoD não recuperável;originais preservados.');
if(book?.rows?.some(row=>row.bc!==undefined&&!validAcquisitionBC(row.bc))||book?.periods?.some(p=>p.importsComplete!==undefined&&typeof p.importsComplete!=='boolean'))throw new Error('CondiçõesB/C ou cobertura de importações inválidas;originais preservados.');
if(book?.rightVersion!==undefined&&book.rightVersion!==acquisitionRightVersion)throw new Error('Versão do direito da aquisição não recuperável; original preservado.');
if(book?.rows?.some(row=>row.deductionLink!==undefined&&!validAcquisitionRightLink(row.deductionLink)))throw new Error('Vínculo da aquisição não recuperável; original preservado.');
if(book?.history!==undefined&&!Array.isArray(book.history))throw new Error('Histórico de aquisições não recuperável; originais preservados, sem substituição.');
if(!book || !Array.isArray(book.rows) || !Array.isArray(book.periods) || typeof book.currency!=='string') throw new Error('Formato de compras não recuperável; sem substituição.');
const stringKeys=Object.keys(newAcquisition('','','')).filter(key=>!['sourceMonth','sourceAmount'].includes(key));
if(book.rows.some(row=>!row || stringKeys.some(key=>typeof row[key as keyof Acquisition]!=='string') || !(row.sourceMonth===null || Number.isInteger(row.sourceMonth)) || !(row.sourceAmount===null || typeof row.sourceAmount==='string')) || book.periods.some(p=>!p || typeof p.period!=='string' || typeof p.complete!=='boolean' || !Array.isArray(p.absent) || p.absent.some(f=>typeof f!=='string'))) throw new Error('Linhas ou períodos de compras não recuperáveis; os dados guardados foram preservados.');
}
return value;
}
export function acquisitionBook(draft:Record<string,unknown>):AcquisitionBook {
return readAcquisitionStore(draft.modelo_a_snapshot).books[acquisitionScope(draft)] ?? {currency:String(draft.currency ?? ''),rows:[],periods:[]};
}
export function saveAcquisitionBook(draft:Record<string,unknown>,book:AcquisitionBook):string {
const store=readAcquisitionStore(draft.modelo_a_snapshot);
return JSON.stringify({...store,books:{...store.books,[acquisitionScope(draft)]:book}});
}
export function newAcquisition(id:string,period:string,currency:string):Acquisition {
return {id,field:'09',period,origin:'projected',sourceMonth:null,sourceAmount:null,sourceCurrency:currency,sourceBasis:'',net:'',vat:'',gross:'',documentType:'',documentRef:'',documentDate:'',documentLine:'',supplier:'',supplierRegistered:'',legalDocument:'',held:'',domestic:'',allocation:'',exclusion:'',integrity:'',description:''};
}
function periods(draft:Record<string,unknown>):string[] {
try {return monthlyIvaInput(draft,readMonthlyIvaSettings(String(draft.monthly_iva_settings ?? ''))).monthly_sales.map(r=>r.date.slice(0,7));}
catch {return [];}
}
export function acquisitionPlanReferences(draft:Record<string,unknown>):Array<{key:string;kind:'inventory'|'asset'|'operating';month:number;period:string;amount:string|null;currency:string;label:string}> {
const dates=periods(draft),plan=restoreBusinessPlanSnapshot(String(draft.business_plan_snapshot??''));
if(!plan || !dates.length)return [];
if(plan.currency!==draft.currency)throw new Error('Moeda do plano divergente; não reutilizar como moeda do projecto.');
const inventory=modeloAPurchaseReferences(draft).map(r=>({key:`inventory:${r.month}`,kind:'inventory' as const,month:r.month,period:dates[r.month-1],amount:r.declared,currency:plan.currency,label:`Compras/encomendas — mês ${r.month}`}));
const asset={key:'asset:1',kind:'asset' as const,month:1,period:dates[0],amount:plan.assetInvestment?.trim()||null,currency:plan.currency,label:'Orçamento de investimento em activos — período a confirmar'};
const operating=dates.map((period,index)=>({key:`operating:${index+1}`,kind:'operating' as const,month:index+1,period,amount:(plan.monthlyOperatingExpensesByPeriod?plan.monthlyOperatingExpensesByPeriod[index]:plan.monthlyOperatingExpenses)?.trim()||null,currency:plan.currency,label:`Orçamento de despesas operacionais — mês ${index+1}`}));
return [...inventory,asset,...operating];
}
export function reuseAcquisitions(draft:Record<string,unknown>,book:AcquisitionBook):AcquisitionBook {
const dates=periods(draft); if(!dates.length) throw new Error('Defina o período mensal e resolva a moeda da projecção antes de reutilizar compras.');
const rows=[...book.rows];
for(const reference of modeloAPurchaseReferences(draft)) {
if(rows.some(r=>r.sourceMonth===reference.month)) continue;
rows.push({...newAcquisition(`plan-${reference.month}`,dates[reference.month-1],String(draft.currency ?? '')),sourceMonth:reference.month,sourcePeriod:dates[reference.month-1],sourceAmount:reference.declared,description:`Compras/encomendas do plano — mês ${reference.month}`});
}
return {...book,rows,periods:dates.map(period=>book.periods.find(p=>p.period===period) ?? {period,complete:false,absent:[]})};
}
// Decimal arithmetic for aggregation only. No binary rounding and no invented purchase tax rate.
function amount(raw:unknown):bigint|null {
if(typeof raw!=='string' || !/^\d{1,15}(\.\d{1,6})?$/.test(raw.trim())) return null;
const [a,b='']=raw.trim().split('.');return BigInt(a)*1000000n+BigInt(b.padEnd(6,'0'));
}
function money(value:bigint):string {const a=value/1000000n,b=(value%1000000n).toString().padStart(6,'0').replace(/0+$/,'').padEnd(2,'0');return `${a}.${b}`;}
function sum(values:Array<string|null>):string|null {const parsed=values.map(amount);return parsed.some(v=>v===null)?null:money((parsed as bigint[]).reduce((a,b)=>a+b,0n));}
function dateValid(raw:string):boolean {const date=new Date(`${raw}T00:00:00Z`);return /^\d{4}-\d{2}-\d{2}$/.test(raw) && Number.isFinite(date.getTime()) && date.toISOString().slice(0,10)===raw;}
function evaluateAcquisitionBase(row:Acquisition,currency:string,taxpayerRegistered:Choice=''):AcquisitionResult {
let n=amount(row.net),v=amount(row.vat),g=amount(row.gross);
if(n!==null && v!==null && g===null && !row.gross.trim()) g=n+v;
if(g!==null && v!==null && n===null && !row.net.trim() && g>=v) n=g-v;
if(n!==null && g!==null && v===null && !row.vat.trim() && g>=n) v=g-n;
const result:AcquisitionResult={id:row.id,net:n===null?null:money(n),vat:v===null?null:money(v),gross:g===null?null:money(g),deductible:null,reason:''};
const stop=(reason:string,deductible:string|null=null)=>({...result,reason,deductible});
if(currency!=='MZN' || row.sourceCurrency!==currency) return stop('Divergência ou moeda fora de MZN; sem conversão.');
if(!/^2026-(0[1-9]|1[0-2])$/.test(row.period)) return stop('Período fora do âmbito 2026 ou em falta.');
const treatmentIssue=acquisitionTreatmentIssue(row.acquisitionTreatment);
if(treatmentIssue)return stop(treatmentIssue);
if(n===null || v===null || g===null || n+v!==g) return stop('Indique dois valores válidos; líquido + IVA deve corresponder ao bruto.');
if(row.sourceMonth!==null && (!row.sourceBasis || amount(row.sourceAmount)!==(row.sourceBasis==='net'?n:g))) return stop('Classifique ou resolva a divergência do valor de origem; não foi reinterpretado.');
if(v===0n&&!row.deductionLink) return stop('IVA explicitamente indicado como zero; não existe imposto a deduzir nesta aquisição.','0.00');
if(taxpayerRegistered!=='yes') return stop('Confirme o enquadramento do adquirente como sujeito passivo de IVA no regime normal; não é inferido da organização.');
if(row.allocation==='no_right' || row.exclusion==='excluded' || row.supplierRegistered==='no' || row.legalDocument==='no' || row.integrity==='no') return stop('Sem dedução no âmbito declarado: afectação, exclusão, fornecedor ou documento não elegível.','0.00');
if(row.domestic!=='yes') return stop('Aquisição interna por confirmar; importações fora deste lote.');
if(row.allocation!=='taxable_only' || row.exclusion!=='none') return stop('Confirme afectação exclusiva a operações tributadas e ausência de exclusões; casos mistos/especiais não calculados.');
if(!['invoice','equivalent'].includes(row.documentType) || !row.documentRef.trim() || !row.supplier.trim() || row.supplierRegistered!=='yes' || row.legalDocument!=='yes' || row.held!=='yes' || row.integrity!=='yes') return stop('Documento, fornecedor inscrito, posse e condições de dedução ainda incompletos.');
if(!dateValid(row.documentDate) || row.documentDate.slice(0,7)!==row.period) return stop('Data do documento e período de dedução devem coincidir neste lote; dedução posterior não presumida.');
return stop(row.origin==='projected'?'Dedução projectada sob pressupostos explícitos; não certifica factura ou declaração.':'Dedução segundo dados e condições declarados; sem submissão ou certificação pela AT.',money(v));
}
export function evaluateAcquisition(row:Acquisition,currency:string,taxpayerRegistered:Choice='',draft?:Record<string,unknown>):AcquisitionResult {
const result=evaluateAcquisitionBase(row,currency,taxpayerRegistered);
if(row.d)return evaluateAcquisitionAllocation(row,currency,taxpayerRegistered,draft,result);
if(row.bc)return evaluateAcquisitionBC(row,currency,taxpayerRegistered,draft,result);
if(!row.deductionLink)return result;
const right=draft?linkedOperationRight(draft,row.deductionLink):{status:'indeterminate' as const,reason:'Contexto da operação em falta.',source:acquisitionRightSource};
const finish=(status:AcquisitionEligibility['status'],reason:string,deductible:string|null=null):AcquisitionResult=>({...result,deductible,reason,eligibility:{...right,status,reason,source:right.source+'; '+acquisitionBasis}});
if(currency!=='MZN'||row.sourceCurrency!==currency||!/^2026-(0[1-9]|1[0-2])$/.test(row.period)||acquisitionTreatmentIssue(row.acquisitionTreatment)||result.net===null||result.vat===null||result.gross===null||(amount(result.net)! + amount(result.vat)!)!==amount(result.gross))return finish('indeterminate',result.reason);
if(row.deductionLink.scopeConfirmed!=='yes'||!row.deductionLink.allocationRef.trim())return finish('indeterminate','Confirme a afectação exclusiva à operação ligada e indique o motivo/referência; não inferida da oportunidade.');
if(row.domestic!=='yes'||row.allocation==='mixed'||row.exclusion==='special')return finish('indeterminate','Importação, afectação parcial ou despesa especial: enquadramento específico B/C/D ainda necessário; sem dedução integral presumida.');
if(right.status==='indeterminate')return finish('indeterminate',right.reason);
if(right.status==='no_right')return finish('no_right',right.reason,'0.00');
if(row.allocation==='no_right'||row.exclusion==='excluded'||row.supplierRegistered==='no'||row.legalDocument==='no'||row.integrity==='no')return finish('no_right',result.reason,'0.00');
if(row.deductionLink.noExcludedPayments==='no')return finish('no_right','Afectação a pagamentos do artigo15(6)(c): exclusão19(2).','0.00');
if(row.deductionLink.noExcludedPayments!=='yes')return finish('indeterminate','Indique se a aquisição está fora dos pagamentos excluídos15(6)(c)/19(2).');
if(result.deductible===null)return finish('indeterminate',result.reason);
return finish('eligible',right.reason+' '+result.reason,result.deductible);
}
function sourceKey(draft:Record<string,unknown>,book:AcquisitionBook):string {
const raw=JSON.stringify([acquisitionScope(draft),draft.currency??'',draft.province_id??'',draft.district_id??'',draft.business_plan_snapshot,draft.monthly_iva_settings,draft.monthly_iva_snapshot,book.currency,book.taxpayerRegistered,book.rows,book.periods]);
// Change detection only, never an authorization or authenticity token.
let hash=14695981039346656037n;for(let i=0;i<raw.length;i++)hash=BigInt.asUintN(64,(hash^BigInt(raw.charCodeAt(i)))*1099511628211n);
const operations=(readAcquisitionStore(draft.modelo_a_snapshot) as Store & {salesOperations?:Record<string,unknown>}).salesOperations?.[acquisitionScope(draft)];
const e=regularizationSourceKey(draft);
return `${raw.length}:${hash.toString(16)}${operations?`:${JSON.stringify(operations)}`:''}${book.rows.some(r=>r.acquisitionTreatment||r.acquisitionTreatmentOrigin)?`:${acquisitionContinuityVersion}`:''}${book.rightVersion||book.rows.some(r=>r.deductionLink)?`:${acquisitionRightVersion}`:''}${book.rows.some(r=>r.bc)?`:${acquisitionBCSourceKey(draft,book.rows)}`:''}${book.rows.some(r=>r.d)?`:${acquisitionAllocationVersion}:${allocationSourceKey(draft,book.rows)}`:''}${e?`:E:${e}`:''}`;
}
function location(draft:Record<string,unknown>):string {
const labels=draft.selection_labels as Record<string,unknown>|undefined;
return ['province_id','district_id'].filter(key=>draft[key]).map(key=>String(draft[`${key}_label`]??labels?.[key]??draft[key])).join(' / ');
}
export function acquisitionCurrent(draft:Record<string,unknown>,book:AcquisitionBook):boolean {return !!book.result && book.result.version===acquisitionVersion && book.result.opportunity===acquisitionScope(draft) && book.result.sourceKey===sourceKey(draft,book);}
export function calculateAcquisitions(draft:Record<string,unknown>,book:AcquisitionBook,now=new Date().toISOString()):AcquisitionCalculation {
const scope=acquisitionScope(draft),dates=book.periods.map(p=>p.period);
if(book.currency!=='MZN' || draft.currency!==book.currency) throw new Error('Resolva a moeda de origem das compras; valores preservados sem conversão.');
if(!dates.length||dates.some(period=>!/^2026-(0[1-9]|1[0-2])$/.test(period))||new Set(dates).size!==dates.length) throw new Error('Indique os meses aplicáveis às aquisições em2026,sem períodos duplicados; não inferidos do orçamento ou das vendas.');
if(new Set(book.rows.map(r=>r.id)).size!==book.rows.length) throw new Error('Existem compras com identidade repetida.');
const documents=book.rows.filter(r=>r.documentRef.trim() && r.supplier.trim()).map(r=>JSON.stringify([r.origin,r.supplier.trim().toLowerCase(),r.documentRef.trim().toLowerCase(),r.documentDate,r.documentLine?.trim()??'']));
if(new Set(documents).size!==documents.length) throw new Error('Documento/linha repetido: discrimine linhas distintas ou remova a duplicação antes de deduzir.');
if(book.rows.some(r=>!['08','09','10','11'].includes(r.field) || r.field==='11'&&!r.bc || !dates.includes(r.period))) throw new Error('Classifique cada aquisição num campo08–10 ou importaçãoB no campo11 e num mês incluído na cobertura declarada.');
const references=acquisitionPlanReferences(draft);
for(const row of book.rows.filter(r=>r.sourceMonth!==null)) {
const current=references.find(p=>p.month===row.sourceMonth && p.kind===(row.sourceField??'inventory'));
if(!current || current.amount!==row.sourceAmount || (current.kind!=='asset' && row.sourcePeriod && current.period!==row.sourcePeriod)) throw new Error('As compras ou orçamentos do plano mudaram. Resolva a divergência de origem antes de calcular; histórico preservado.');
}
if(book.rightVersion||book.rows.some(r=>r.deductionLink||r.bc)){
const identities=book.rows.filter(r=>r.documentRef.trim()&&(r.supplier.trim()||r.field==='11')).map(r=>JSON.stringify([r.field==='11'?'customs':r.supplier.trim().toLowerCase(),r.documentRef.trim().toLowerCase(),r.documentDate,r.documentLine.trim()]));
if(new Set(identities).size!==identities.length)throw new Error('Documento/linha repetido entre projecção e efectivo; sem dupla dedução.');
}
const rows=book.rows.map(row=>{
const result=evaluateAcquisition(row,book.currency,book.taxpayerRegistered,draft);
if(result.deductible!==null&&(result.deductibleExact?BigInt(result.deductibleExact.numerator)>0n:Number(result.deductible)>0)&&beverageParcelReserved(draft,row))return {...result,deductible:null,deductibleExact:undefined,nonDeductibleExact:undefined,reason:'Parcela ligada a pedido/decisão/recebimento de restituição13(2);sem a voltar a deduzir. Reconciliar o procedimento conservando o histórico.',eligibility:{status:'indeterminate' as const,reason:'Parcela reservada para restituição13(2).',source:acquisitionBCSource}};
if(!row.deductionLink&&!row.d&&(book.rightVersion||book.rows.some(r=>r.deductionLink)))return {...result,deductible:null,reason:'Aquisição sem vínculo à operação de destino; direito indeterminado.',eligibility:{status:'indeterminate' as const,reason:'Aquisição sem vínculo à operação de destino; direito indeterminado.',source:acquisitionRightSource}};
return result;
});
const record=readMonthlyIvaRecord(String(draft.monthly_iva_snapshot ?? ''));
let salesCurrent=false;
try {salesCurrent=!!record && record.execution.status==='completed' && monthlyIvaKey(record.input)===monthlyIvaKey(monthlyIvaInput(draft,readMonthlyIvaSettings(String(draft.monthly_iva_settings ?? ''))));} catch { /* Preserve history, never fabricate 01/02. */ }
const projection=record?.execution.result_snapshot?.monthly_projection as {rows?:Array<{date:string;net_sales:string|null;vat:string|null}>}|undefined;
const calculated=dates.map(period=>{
const input=book.periods.find(p=>p.period===period), reasons:string[]=[], fields:Record<string,string|null>={};
const declaredAmount=(key:string)=>{const value=amount(String(draft[key]??''));return value!==null && value>0n;};
if(input?.absent.includes('11') && (declaredAmount('imports') || book.rows.some(r=>r.period===period && r.domestic==='no'))) throw new Error('Existem importações declaradas. Não confirme ausência no campo11; enquadramento fora deste lote.');
if(input?.absent.includes('07') && declaredAmount('sales_exempt')) throw new Error('Existem vendas isentas declaradas. Resolva o período e a classificação antes de confirmar ausência no campo07.');
if(input?.absent.includes('05') && (declaredAmount('exports')||declaredAmount('sales_zero_rate'))) throw new Error('Existem exportações ou vendas de outro enquadramento. Não confirme ausência no campo05 sem resolver a classificação.');
const sales=salesCurrent?projection?.rows?.find(r=>r.date.slice(0,7)===period):null;
fields['01']=sales?.net_sales??null;fields['02']=sales?.vat??null;
if((!sales || sales.net_sales===null || sales.vat===null)&&!(book.rows.some(r=>r.deductionLink||r.d)&&salesOperationsCurrent(draft,salesBook(draft))&&salesBook(draft).result?.periods.some(p=>p.period===period))) reasons.push('01/02: vendas ou IVA mensal em falta, execução incompleta ou desactualizada.');
for(const field of excluded) fields[field]=input?.absent.includes(field)?'0.00':null;
const operations=salesBook(draft);
if(operations.rows.length || operations.result) {
const classified=salesOperationsCurrent(draft,operations)?operations.result?.periods.find(p=>p.period===period):null;
for(const field of ['01','02','03','04','05','06','07'])fields[field]=classified?.fields[field]??null;
if(!classified)reasons.push('Operações L1 alteradas ou sem resultado correspondente: recalcule a classificação; não usar01/02 antigos nem ausência presumida.');
else reasons.push(...classified.reasons);
}
for(const field of ['08','09','10']) {
const matching=book.rows.filter(r=>r.period===period && r.field===field);
fields[field]=input?.complete ? sum(matching.map(r=>rows.find(x=>x.id===r.id)!.deductible)) : null;
if((operations.rows.length || operations.result) && operations.rows.some(r=>r.period===period && !['general','export_goods'].includes(r.treatment)) && matching.some(r=>!r.deductionLink&&!r.d&&rows.find(x=>x.id===r.id)!.deductible!=='0.00')) {
fields[field]=null;
reasons.push(`Campo${field}: operações reduzidas/isentas/outras exigem afectar cada aquisição; não reutilizar dedução exclusiva geral como se fosse universal (L2).`);
}
}
if(book.rows.some(r=>r.bc)||input?.importsComplete!==undefined){
const imported=book.rows.filter(r=>r.period===period&&r.field==='11');
if(imported.length||input?.importsComplete)fields['11']=input?.importsComplete?sum(imported.map(r=>rows.find(x=>x.id===r.id)!.deductible)):null;
if(imported.length&&!input?.importsComplete)reasons.push('Campo11:confirme a cobertura de todas as importações do período,distinta das compras08–10.');
}
if(!input?.complete) reasons.push('Aquisições08–10: confirme que todas as compras do período foram incluídas, mesmo quando não existam.');
for(const row of book.rows.filter(r=>r.period===period)) {const r=rows.find(x=>x.id===row.id)!;if(r.deductible===null) reasons.push(`${row.description||row.id}: ${r.reason}`);}
const adjustments=regularizationFields(draft,period,book.rows);
if(adjustments){
if(input?.absent.some(f=>f==='12'||f==='13'))throw Error('Existem regularizaçõesE neste período. Retire a declaração de ausência12/13 e confirme a coberturaE,sem apagar o histórico.');
for(const f of ['12','13'])fields[f]=adjustments.fields[f]?fractionPresentation(adjustments.fields[f]!).decimal:null;
reasons.push(...adjustments.reasons);
}
for(const field of excluded) if(fields[field]===null) reasons.push(`Campo${field}: sem apuramento correspondente ou declaração expressa de inexistência de operações.`);
fields['14']=sum(['01','03','05','06','07'].map(f=>fields[f]));
fields['15']=sum(['08','09','10','11','12'].map(f=>fields[f]));
fields['16']=sum(['02','04','13'].map(f=>fields[f]));
const state=amount(fields['16']),taxpayer=amount(fields['15']);
fields['17']=state===null||taxpayer===null?null:money(state>taxpayer?state-taxpayer:0n);
fields['18']=state===null||taxpayer===null?null:money(taxpayer>state?taxpayer-state:0n);
const classified=salesOperationsCurrent(draft,operations)?operations.result?.periods.find(p=>p.period===period):null;
if(classified?.exactFields||book.rows.some(r=>r.bc||r.d)||adjustments) {
const exactFields:Record<string,FiscalFraction|null>={};
for(const f of ['01','02','03','04','05','06','07','08','09','10','11','12','13'])exactFields[f]=classified?.exactFields?.[f]??(fields[f]===null?null:decimalFraction(fields[f]!));
for(const f of ['08','09','10','11'])if(fields[f]!==null){const matching=book.rows.filter(r=>r.period===period&&r.field===f).map(r=>rows.find(v=>v.id===r.id)!);exactFields[f]=addFractions(matching.map(r=>r.deductibleExact??decimalFraction(r.deductible!)));}
if(adjustments)for(const f of ['12','13'])exactFields[f]=adjustments.fields[f];
for(const [f,parts] of [['14',['01','03','05','06','07']],['15',['08','09','10','11','12']],['16',['02','04','13']]] as const)exactFields[f]=parts.some(k=>exactFields[k]===null)?null:addFractions(parts.map(k=>exactFields[k]!));
exactFields['17']=exactFields['16']&&exactFields['15']?positiveFraction(subtractFractions(exactFields['16'],exactFields['15'])):null;
exactFields['18']=exactFields['16']&&exactFields['15']?positiveFraction(subtractFractions(exactFields['15'],exactFields['16'])):null;
if((book.rows.some(r=>r.d)||adjustments)&&allocationHasUnconsolidatedOutputs(draft,period)){
for(const f of ['14','16','17','18'])exactFields[f]=null;
reasons.push('Origem18(6) identifica base/proporção,sem IVA liquidado consolidado no livro de operações. Não presumir impostozero nem duplicar a base:totais14/16/17/18 permanecem indeterminados;dedução15 conserva apuramento próprio.');
}
for(const f of Object.keys(exactFields))fields[f]=exactFields[f]?fractionPresentation(exactFields[f]!).decimal:null;
reasons.push('Totais derivados das fracções exactas, sem somar aproximações das linhas; decimais a6casas são apresentação técnica, não arredondamento declarativo.');
return {period,fields,exactFields,reasons};
}
return {period,fields,reasons};
});
return {version:acquisitionVersion,revision:(book.result?.revision??0)+1,calculatedAt:now,opportunity:scope,location:location(draft),currency:book.currency,sourceKey:sourceKey(draft,book),basis:book.rows.some(r=>r.d)?acquisitionAllocationSource:book.rows.some(r=>r.bc)?acquisitionBCSource:acquisitionBasis,sources:acquisitionSources+(book.rows.some(r=>r.acquisitionTreatment==='former_exploration_exemption'||r.acquisitionTreatmentOrigin)?`; ${formerExplorationSource}`:''),declarations:{taxpayerRegistered:book.taxpayerRegistered??'',periods:structuredClone(book.periods)},rows,inputs:structuredClone(book.rows),periods:calculated};
}
export function recordAcquisitions(draft:Record<string,unknown>,book:AcquisitionBook,now=new Date().toISOString()):AcquisitionBook {
const result=calculateAcquisitions(draft,book,now);
return {...book,...(book.result?{history:[...(book.history??[]),structuredClone(book.result)]}:{}),result};
}
export function acquisitionReportRows(draft:Record<string,unknown>):Array<[string,string,string,string]> {
if(!draft.modelo_a_snapshot) return [];
try {
const book=acquisitionBook(draft),result=book.result;
if(!result) return [['IVA mensal','Compras — Modelo A','Sem cálculo guardado para a oportunidade escolhida','Sem dedução presumida']];
const context=`${result.currency}; oportunidade ${result.opportunity}; localização ${result.location || 'não indicada'}; ${result.version}; revisão ${result.revision}; ${result.calculatedAt}`;
const out:Array<[string,string,string,string]>=[['IVA mensal','Apuramento parcial Modelo A',acquisitionCurrent(draft,book)?'Versão correspondente às entradas':'Histórico desactualizado; não aplicar à projecção actual',context],['IVA mensal','Fundamento das aquisições',result.basis,result.sources]];
for(const row of result.inputs) {
const r=result.rows.find(x=>x.id===row.id)!;
if(row.d)out.push(['IVA mensal',`AfectaçãoD — ${row.id}`,`${{actual:'Afectação real22(2)',pro_rata:'Pro rata provisório22',reduced:'Proporção específica18(6)','':'Método por identificar'}[row.d.method]};âmbito${row.d.scopeRef};quociente${r.allocationCalculation?fractionLabel(r.allocationCalculation.rawRatio):'Indeterminado'};proporção aplicada${r.allocationCalculation?fractionLabel(r.allocationCalculation.ratio):'Indeterminada'};IVA elegível antesD${r.allocationCalculation?fractionLabel(r.allocationCalculation.before):'Indeterminado'};dedutível${r.deductibleExact?fractionLabel(r.deductibleExact):'Indeterminado'};não dedutível${r.nonDeductibleExact?fractionLabel(r.nonDeductibleExact):'Indeterminado'}`,`${row.d.source};${row.d.method==='reduced'?'origem '+row.d.reducedId:'bases '+row.d.numerator+'/'+row.d.denominator+' '+(row.d.method==='actual'?row.d.unit:'MZN')+';ano '+(row.d.method==='pro_rata'?row.d.referenceYear:row.period.slice(0,4))+';comunicação '+row.d.communicationRef+';declaração '+row.d.declarationRef};${r.allocationCalculation?.source??r.eligibility?.source??''}`]);
if(row.bc)out.push(['IVA mensal',`CondiçõesB/C — ${row.id}`,`${row.field==='11'?'Bilhete de despacho de importação;importador '+row.bc.importer+';bens '+row.bc.goodsRef+';IVA '+({due:'devido identificado',exempt:'isento',zero:'zero expresso',unknown:'desconhecido','':'por indicar'}[row.bc.vatBasis])+';fonte '+row.bc.vatRef:'Despesa '+(expenseOptions.find(([id])=>id===row.bc!.expense)?.[1]??'por identificar')+';IVA suportado17-A:'+({yes:'Sim',no:'Não','':'Por indicar'}[row.bc.reducedSupply])};dedutível exacto:${r.deductibleExact?fractionLabel(r.deductibleExact):'Indeterminado'};não dedutível:${r.nonDeductibleExact?fractionLabel(r.nonDeductibleExact):'Indeterminado'}`,`${row.bc.evidence};${acquisitionBCDetails(row.bc)};origem${row.bc.source?'registo de importaçãoL1 '+row.bc.source.id:'introdução directa'};${acquisitionBCSource}`]);
if(r.eligibility)out.push(['IVA mensal',`Direito da aquisição ${row.id}`,`${{eligible:'Elegível',no_right:'Sem direito',indeterminate:'Indeterminado'}[r.eligibility.status]}: ${r.eligibility.reason}`,`${r.eligibility.source}; ${row.d?'âmbitoD '+row.d.scopeRef:'operação '+(r.eligibility.operationId??'por identificar')+'; '+(r.eligibility.operationPeriod??'')+'; '+(r.eligibility.operationArticle??'')+'; afectação '+(row.deductionLink?.allocationRef??'')}`]);
const source=row.sourceMonth===null?'Introduzido pelo utilizador':`Plano (${row.sourceField==='asset'?'orçamento de activos':row.sourceField==='operating'?'orçamento operacional':'compras/encomendas'}), mês ${row.sourceMonth}, ${row.sourcePeriod??'período de origem não indicado'}, ${row.sourceAmount??'valor em falta'} ${row.sourceCurrency}; ${row.sourceBasis==='net'?'sem IVA':row.sourceBasis==='gross'?'com IVA':'base por classificar'}`;
const allocation={taxable_only:row.deductionLink?'exclusiva à operação ligada':'exclusiva a operações tributadas',mixed:'mista',no_right:'sem direito à dedução','':'por indicar'}[row.allocation];
out.push(['IVA mensal',`Aquisição ${row.id} — campo${row.field} — ${row.period}`,`${row.description}; líquido ${r.net??'Indeterminado'}; IVA ${r.vat??'Indeterminado'}; bruto ${r.gross??'Indeterminado'}; dedutível ${r.deductible??'Indeterminado'}`,`${context}; ${row.origin==='projected'?'Projecção':'Dados efectivos declarados'}; ${source}; ${row.documentType==='invoice'?'Factura':row.documentType==='equivalent'?'Documento equivalente':row.documentType==='customs'?'Bilhete de despacho de importação':'Documento por indicar'} ${row.documentRef}, ${row.documentDate}, linha ${row.documentLine||'única'}; fornecedor ${row.supplier}; afectação ${allocation}; ${r.reason}`]);
if(row.acquisitionTreatment==='former_exploration_exemption'||row.acquisitionTreatmentOrigin)out.push(['IVA mensal',`Continuidade14(2)(c) — ${row.id}`,row.acquisitionTreatment==='former_exploration_exemption'?'Antiga isenção eliminada; dedução indeterminada por esta referência':'Continuidade normal expressamente escolhida; montantes e documento conservados; condições de dedução da aquisição, sem aplicar antiga isenção',`${context}; ${formerExplorationSource}; origem conservada ${row.acquisitionTreatmentOrigin?.id??'former_exploration_exemption'}`]);
}
for(const period of result.periods) {
const entries=Object.entries(period.fields).sort(([a],[b])=>Number(a)-Number(b));
const declaration=result.declarations?.periods.find(p=>p.period===period.period);
const declarationOrigin=declaration?`Aquisições08–10 ${declaration.complete?'declaradas completas':'por completar'};${declaration.importsComplete!==undefined?`importações11 ${declaration.importsComplete?'declaradas completas':'por completar'};`:''} ausência de operações expressamente declarada nos campos ${declaration.absent.join(', ')||'nenhum'}.`:'Declarações detalhadas não recuperáveis nesta versão histórica.';
out.push(['IVA mensal',`Modelo A — ${period.period} — parcelas01–13`,entries.filter(([field])=>Number(field)<=13).map(([field,value])=>`${field}: ${value??'Indeterminado'}`).join('; '),`${context}; ${declarationOrigin}`]);
out.push(['IVA mensal',`Modelo A — ${period.period} — totais14–18`,entries.filter(([field])=>Number(field)>=14).map(([field,value])=>`${field}: ${value??'Indeterminado'}`).join('; '),context]);
if(period.exactFields)out.push(['IVA mensal',`Modelo A — ${period.period} — precisão exacta`,Object.entries(period.exactFields).map(([f,v])=>`${f}: ${v?fractionLabel(v):'Indeterminado'}`).join('; '),'Fracções em MZN; apresentação decimal técnica, sem regra declarativa presumida.']);
if(period.reasons.length)out.push(['IVA mensal',`Modelo A — ${period.period} — pendências`,period.reasons.join(' '),result.sources]);
}
return [...out,...regularizationReportRows(draft),...frozenAssetRows(draft)];
} catch {return [['IVA mensal','Compras — Modelo A','Não foi possível recuperar os dados guardados','Preservados, sem substituição por zero']];}
}