import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts'; export const fuelBaseVersion = 'regulated-fuel-base/1'; export const fuelBaseSource = 'CIVA republicado Lei13/2016,15(2)(i),PDF13/p1426(271): nas transmissões de combustíveis cujo preço é fixado por Autoridade Pública,efectuadas por revendedores,o valor da contraprestação determinado nos termos dos números1 e5 não inclui a Taxa sobre os Combustíveis. Art15(5)/(6),PDF14/p1426(272),composição/exclusões,sem próprioIVA. Lei22/2022PDF3mantém15(2)(i)/(1)/(5)/(6)por remissão;Lei10/2025não altera15. Recorte2026/MZN;base apenas,sem presumir preço regulado,valorTC,incidência,taxaIVA,IVA,dedução/campo11 ou totaisModeloA.'; export const fuelBaseTreatments = [ { id: 'fuel_tax_included', label: 'Montante sem IVA ainda inclui a Taxa sobre Combustíveis', }, { id: 'fuel_tax_excluded', label: 'Montante sem IVA já exclui a Taxa sobre Combustíveis', }, ] as const; export const fuelBaseConditions = [ [ 'taxableFuelResale', 'Está identificada uma transmissão de combustíveis sujeita a IVA,efectuada por revendedor; não é qualquer aquisição ou prestação de serviços', ], [ 'publicFixedPrice', 'O preço deste combustível é fixado pela Autoridade Pública identificada na referência aplicável', ], [ 'priceScopeMatches', 'A referência do preço e o respectivo âmbito abrangem este combustível,operação,local e data; não se presume um preço actual para outro período', ], [ 'netComposition', 'O montante indicado exclui o próprio IVA e reflecte a composição aplicável do artigo15(1)/(5)/(6),sem omissões ou duplicações', ], [ 'fuelTaxTreatment', 'O tratamento escolhido corresponde à composição do montante:TC ainda incluída ou já excluída; a mesma TC não será subtraída duas vezes', ], ] as const; type Choice = '' | 'yes' | 'no'; type Condition = (typeof fuelBaseConditions)[number][0]; export type FuelBaseRow = { id: string; treatment: string; description: string; period: string; origin: 'projected' | 'effective'; currency: string; operationDate: string; priceRef: string; priceScope: string; priceFrom: string; priceTo: string; documentRef: string; documentDate: string; consideration: string; fuelTax: string; fuelTaxRef: string; compositionRef: string; evidence: string; conditions: Partial>; }; type LineResult = { id: string; status: 'determined' | 'not_applicable' | 'indeterminate'; baseAmount: string | null; vat: null; rate: null; reason: string; }; export type FuelBaseCalculation = { version: string; revision: number; calculatedAt: string; opportunity: string; location: string; sourceKey: string; source: string; inputs: FuelBaseRow[]; rows: LineResult[]; }; export type FuelBaseBook = { rows: FuelBaseRow[]; result?: FuelBaseCalculation; history?: FuelBaseCalculation[]; }; type Store = ReturnType & { regulatedFuelBases?: Record; }; export function newFuelBase(id: string, currency: string): FuelBaseRow { return { id, treatment: '', description: '', period: '', origin: 'projected', currency, operationDate: '', priceRef: '', priceScope: '', priceFrom: '', priceTo: '', documentRef: '', documentDate: '', consideration: '', fuelTax: '', fuelTaxRef: '', compositionRef: '', evidence: '', conditions: {}, }; } export function switchFuelTaxTreatment( row: FuelBaseRow, treatment: string, ): FuelBaseRow { return { ...row, treatment, conditions: { ...row.conditions, fuelTaxTreatment: '' }, }; } function readStore(draft: Record): Store { const store = readAcquisitionStore(draft.modelo_a_snapshot) as Store; if ( store.regulatedFuelBases !== undefined && (!store.regulatedFuelBases || typeof store.regulatedFuelBases !== 'object' || Array.isArray(store.regulatedFuelBases)) ) throw new Error( 'Bases de combustíveis não recuperáveis;originais preservados.', ); const fields = Object.keys(newFuelBase('', '')).filter( (key) => key !== 'conditions', ); for (const book of Object.values(store.regulatedFuelBases ?? {})) if ( !book || !Array.isArray(book.rows) || (book.history !== undefined && !Array.isArray(book.history)) || book.rows.some( (row) => !row || fields.some( (key) => typeof row[key as keyof FuelBaseRow] !== 'string', ) || !row.conditions || typeof row.conditions !== 'object' || Array.isArray(row.conditions), ) ) throw new Error( 'Linhas de combustíveis não recuperáveis;originais preservados,sem substituição por lista vazia.', ); return store; } export function fuelBaseBook(draft: Record): FuelBaseBook { return ( readStore(draft).regulatedFuelBases?.[acquisitionScope(draft)] ?? { rows: [], } ); } export function saveFuelBaseBook( draft: Record, book: FuelBaseBook, ): string { const current = readStore(draft), encoded = JSON.stringify({ ...current, regulatedFuelBases: { ...current.regulatedFuelBases, [acquisitionScope(draft)]: book, }, }); readStore({ ...draft, modelo_a_snapshot: encoded }); return encoded; } function decimal(raw: unknown): bigint | null { if (typeof raw !== 'string' || !/^\d{1,15}(\.\d{1,6})?$/.test(raw.trim())) return null; const [whole, fraction = ''] = raw.trim().split('.'); return BigInt(whole) * 1000000n + BigInt(fraction.padEnd(6, '0')); } function money(n: bigint): string { return `${n / 1000000n}.${(n % 1000000n).toString().padStart(6, '0').replace(/0+$/, '').padEnd(2, '0')}`; } function validDate(value: string): boolean { const d = new Date(value + 'T00:00:00Z'); return ( /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(d.getTime()) && d.toISOString().slice(0, 10) === value ); } export function evaluateFuelBase( row: FuelBaseRow, currency: unknown, ): LineResult { const result: LineResult = { id: row.id, status: 'indeterminate', baseAmount: null, vat: null, rate: null, reason: '', }; const stop = ( reason: string, status: LineResult['status'] = 'indeterminate', ) => ({ ...result, status, reason }); if (!fuelBaseTreatments.some((item) => item.id === row.treatment)) return stop( 'Indique se o montante sem IVA ainda inclui a TC ou já a exclui;não se presume a composição.', ); if (currency !== 'MZN' || row.currency !== currency) return stop( 'Moeda fora do âmbito MZN ou divergente do projecto;sem conversão silenciosa.', ); if ( !/^2026-(0[1-9]|1[0-2])$/.test(row.period) || !['projected', 'effective'].includes(row.origin) ) return stop( 'Indique mês em2026 e dados efectivos declarados ou pressupostos de projecção;sem inferir do orçamento.', ); const failed = fuelBaseConditions.filter( ([key]) => row.conditions[key] === 'no', ); if (failed.length) return stop( 'Não se aplica esta base especial de combustíveis: ' + failed.map(([, label]) => label).join('; ') + '. Não é isenção nem IVA zero.', 'not_applicable', ); const missing = fuelBaseConditions.filter( ([key]) => row.conditions[key] !== 'yes', ); if (missing.length) return stop( 'Condições por indicar: ' + missing.map(([, label]) => label).join('; ') + '. Desconhecido não significa não aplicável ou zero.', ); if ( !row.description.trim() || !row.priceRef.trim() || !row.priceScope.trim() || !row.documentRef.trim() || !row.compositionRef.trim() || !row.evidence.trim() || !validDate(row.documentDate) ) return stop( 'Identifique combustível/transmissão,referência e âmbito do preço,documento/data,composição e fundamento ou pressupostos expressos;reutilize referências existentes,sem novo certificado/aprovador.', ); if ( !validDate(row.operationDate) || row.operationDate.slice(0, 7) !== row.period || !validDate(row.priceFrom) || row.operationDate < row.priceFrom || (row.priceTo !== '' && (!validDate(row.priceTo) || row.priceFrom > row.priceTo || row.operationDate > row.priceTo)) ) return stop( 'Identifique a data da transmissão no mês escolhido e o intervalo aplicável do preço que a abrange. Sem aplicar preço de outro período;na projecção indique pressupostos temporais expressos. O fim pode ficar vazio quando não existe termo fixado,com a correspondência temporal confirmada.', ); const amount = decimal(row.consideration); if (amount === null) return stop( 'Indique o montante da contraprestação sem IVA;valor desconhecido ou inválido não é zero.', ); let base = amount, formula = 'montante já sem TC,sem nova subtracção'; if (row.treatment === 'fuel_tax_included') { const tc = decimal(row.fuelTax); if (tc === null || !row.fuelTaxRef.trim()) return stop( 'Identifique o montante exacto e a referência da TC incluída nesta contraprestação,ou pressuposto expresso;sem taxa TC presumida ou valor desconhecido igual a zero.', ); if (tc > amount) return stop( 'A TC indicada excede a contraprestação sem IVA;reveja a composição e os montantes,sem base negativa ou zero substituto.', ); base = amount - tc; formula = `${money(amount)} − ${money(tc)} de TC`; } return { ...result, status: 'determined', baseAmount: money(base), reason: `Base15(2)(i): ${formula} = ${money(base)}MZN,${row.origin === 'projected' ? 'nas condições expressamente assumidas na projecção' : 'nos dados efectivos declarados'}. Preço/âmbito/período e composição identificados;sem determinar automaticamente preço ou taxaTC. Base não é imposto;IVA/taxa,dedução/campo11 e totaisModeloA não são determinados por este cálculo.`, }; } function key(draft: Record, book: FuelBaseBook): string { return JSON.stringify({ version: fuelBaseVersion, scope: acquisitionScope(draft), location: draft.selection_labels ?? {}, currency: draft.currency, rows: book.rows, }); } export function fuelBaseCurrent( draft: Record, book: FuelBaseBook, ): boolean { return !!book.result && book.result.sourceKey === key(draft, book); } export function recordFuelBases( draft: Record, book: FuelBaseBook, ): FuelBaseBook { if ( !book.rows.length || new Set(book.rows.map((row) => row.id)).size !== book.rows.length ) throw new Error( 'Indique operações com identificadores distintos;originais conservados.', ); const result: FuelBaseCalculation = { version: fuelBaseVersion, revision: (book.result?.revision ?? 0) + 1, calculatedAt: new Date().toISOString(), opportunity: acquisitionScope(draft), location: JSON.stringify(draft.selection_labels ?? {}), sourceKey: key(draft, book), source: fuelBaseSource, inputs: structuredClone(book.rows), rows: book.rows.map((row) => evaluateFuelBase(row, draft.currency)), }; const next = { ...book, result, history: [ ...(book.history ?? []), ...(book.result ? [book.result] : []), ], }; saveFuelBaseBook(draft, next); return next; } export function fuelBaseReportRows( draft: Record, ): Array<[string, string, string, string]> { try { const book = fuelBaseBook(draft), result = book.result; if (!result) return []; return result.inputs.map((row, index) => [ 'IVA mensal', `Base P095 — ${row.period} — ${row.id}`, `${row.description};base tributável: ${result.rows[index].baseAmount ?? 'Indeterminada'} ${row.currency};IVA: Indeterminado;${result.rows[index].reason}`, `${result.opportunity};${result.location};${fuelBaseCurrent(draft, book) ? 'Resultado actual' : 'Entradas alteradas;resultado histórico conservado'};versão${result.revision};${result.calculatedAt};${result.source};${row.origin === 'projected' ? 'Projecção' : 'Dados efectivos declarados'};transmissão ${row.operationDate};preço ${row.priceRef};âmbito ${row.priceScope};intervalo ${row.priceFrom} a ${row.priceTo || 'sem termo fixado,correspondência temporal confirmada'};${fuelBaseTreatments.find((item) => item.id === row.treatment)?.label ?? 'Composição por indicar'};contraprestação ${row.consideration} ${row.currency};${row.treatment === 'fuel_tax_included' ? `TC incluída ${row.fuelTax} ${row.currency},referência ${row.fuelTaxRef}` : 'TC já excluída,sem segunda subtracção'};composição ${row.compositionRef};documento ${row.documentRef},${row.documentDate};condições ${fuelBaseConditions.map(([key, label]) => label + ': ' + (row.conditions[key] === 'yes' ? 'Sim' : row.conditions[key] === 'no' ? 'Não' : 'Por indicar')).join('; ')};fundamento ${row.evidence};não preenche totaisModeloA ou campo11.`, ]); } catch { return [ [ 'IVA mensal', 'Base de combustíveis', 'Dados não recuperáveis', 'Originais preservados;sem substituir por zero.', ], ]; } }