import { inputText } from './input-text.ts'; 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; exactFields?: Record; reasons: string[]; }>; }; type Store = { version: 1; books: Record }; const excluded = ['03', '04', '05', '06', '07', '11', '12', '13']; export const acquisitionExcludedFields = excluded; export function acquisitionScope(draft: Record): string { if (!draft.context_opportunity) { try { const context = JSON.parse( inputText(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 inputText(draft.context_opportunity || 'general'); } export function readAcquisitionStore(raw: unknown): Store { if (!raw) return { version: 1, books: {} }; const value = JSON.parse(inputText(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, ): AcquisitionBook { return ( readAcquisitionStore(draft.modelo_a_snapshot).books[ acquisitionScope(draft) ] ?? { currency: inputText(draft.currency ?? ''), rows: [], periods: [], } ); } export function saveAcquisitionBook( draft: Record, 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[] { try { return monthlyIvaInput( draft, readMonthlyIvaSettings(inputText(draft.monthly_iva_settings ?? '')), ).monthly_sales.map((r) => r.date.slice(0, 7)); } catch { return []; } } export function acquisitionPlanReferences( draft: Record, ): Array<{ key: string; kind: 'inventory' | 'asset' | 'operating'; month: number; period: string; amount: string | null; currency: string; label: string; }> { const dates = periods(draft), plan = restoreBusinessPlanSnapshot( inputText(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, 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], inputText(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 { 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, ): 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, 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; } ).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 { const labels = draft.selection_labels as | Record | 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, 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, 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( inputText(draft.monthly_iva_snapshot ?? ''), ); let salesCurrent = false; try { salesCurrent = !!record && record.execution.status === 'completed' && monthlyIvaKey(record.input) === monthlyIvaKey( monthlyIvaInput( draft, readMonthlyIvaSettings( inputText(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 = {}; const declaredAmount = (key: string) => { const value = amount(inputText(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) && !( salesOperationsCurrent(draft, salesBook(draft)) && salesBook(draft).result?.periods.some( (p) => p.period === period && p.fields['01'] != null && p.fields['02'] != null, ) ) ) 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 = {}; 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, 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, ): 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', ], ]; } }