283 lines
37 KiB
TypeScript
283 lines
37 KiB
TypeScript
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']];}
|
||
}
|