import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts'; export const reducedBaseVersion = 'proportional-bases/1'; export const reducedBaseSource = 'CIVA15(2)j/k/l/m,15(1)/(5)/(6)/(9),Lei13/2016PDF13–14/p1426(271–272);Lei22/2022PDF3mantém15(2)j–m;Lei10/2025não altera15 eadita18(6),PDF13:dedução do imposto suportado relativo a estas operações na mesma proporção da base reduzida. Recorte2026/MZN;base apenas,sem taxaIVA/IVA/dedução/campo11 ou totaisModeloA. Energia62%,taxas aeronáuticas85%,obras redução60%(base40%),água75%. Percentagem de base não é taxaIVA.'; export const reducedBaseTreatments = [ { id: 'regulated_energy', branch: 'P096', clause: 'j', label: 'P096 — Energia com preço fixado por Autoridade Pública', percent: 62, }, { id: 'aeronautical_fees', branch: 'P097', clause: 'k', label: 'P097 — Serviços com preço fixado por taxas aeronáuticas', percent: 85, }, { id: 'public_works', branch: 'P098', clause: 'l', label: 'P098 — Obras públicas abrangidas', percent: 40, }, { id: 'public_water', branch: 'P099', clause: 'm', label: 'P099 — Água potável por rede pública e preço fixado', percent: 75, }, ] as const; export const reducedBaseConditions = [ [ 'taxableOperation', 'Está identificada a operação sujeita a IVA e a correspondência com este tratamento', ], [ 'fullNetInvoice', 'O montante é a contraprestação total da operação/factura antes desta redução,sem o próprioIVA ecom composição15(1)/(5)/(6)conferida', ], [ 'scopeMatches', 'Documentos/referências ou pressupostos expressos correspondem ao objecto,local,operação eperíodo', ], ] as const; export const reducedOwnConditions = { regulated_energy: [ ['energySupply', 'Transmissão de energia identificada'], [ 'publicFixedPrice', 'Preço fixado pela Autoridade Pública identificada na referência aplicável', ], ], aeronautical_fees: [ [ 'aeronauticalPrice', 'Preço do serviço fixado através de taxas aeronáuticas;uma descrição de comunicações,saneamento ou telecomunicações não basta', ], ], public_works: [ [ 'publicWorks', 'Construção ou reabilitação de obras públicas abrangidas15(2)l/15(9)', ], [ 'workCategoryMatches', 'A categoria eâmbito da obra correspondem ao projecto público identificado', ], ], public_water: [ ['potableWater', 'Fornecimento de água potável'], ['publicNetwork', 'Fornecimento através de rede pública'], [ 'publicFixedPrice', 'Preço fixado pela Autoridade Pública identificada na referência aplicável', ], ], } as const; export const publicWorkCategories = [ { id: 'roads', label: 'Estradas' }, { id: 'bridges', label: 'Pontes' }, { id: 'water_infrastructure', label: 'Infra-estruturas de abastecimento de água/saneamento15(9)a', }, { id: 'rural_electricity', label: 'Electrificação rural em projectos públicos15(9)b', }, { id: 'agricultural_hydraulics', label: 'Hidráulica agrícola no âmbito definido15(9)c', }, ] as const; export function ownReducedConditions( treatment: string, ): ReadonlyArray { return ( reducedOwnConditions[treatment as keyof typeof reducedOwnConditions] ?? [] ); } type Choice = '' | 'yes' | 'no'; export type ReducedBaseRow = { 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; projectRef: string; workCategory: string; compositionRef: string; evidence: string; conditions: Partial>; }; type LineResult = { id: string; status: 'determined' | 'not_applicable' | 'indeterminate'; baseAmount: string | null; deductionProportion: string | null; vat: null; rate: null; reason: string; }; export type ReducedBaseCalculation = { version: string; revision: number; calculatedAt: string; opportunity: string; location: string; sourceKey: string; source: string; inputs: ReducedBaseRow[]; rows: LineResult[]; }; export type ReducedBaseBook = { rows: ReducedBaseRow[]; result?: ReducedBaseCalculation; history?: ReducedBaseCalculation[]; }; type Store = ReturnType & { regulatedReducedBases?: Record; }; export function newReducedBase(id: string, currency: string): ReducedBaseRow { return { id, treatment: '', description: '', period: '', origin: 'projected', currency, operationDate: '', priceRef: '', priceScope: '', priceFrom: '', priceTo: '', documentRef: '', documentDate: '', consideration: '', projectRef: '', workCategory: '', compositionRef: '', evidence: '', conditions: {}, }; } export function switchReducedTreatment( row: ReducedBaseRow, treatment: string, ): ReducedBaseRow { return { ...row, treatment, conditions: Object.fromEntries( reducedBaseConditions.map(([key]) => [ key, row.conditions[key] ?? '', ]), ), }; } function readStore(draft: Record): Store { const store = readAcquisitionStore(draft.modelo_a_snapshot) as Store; if ( store.regulatedReducedBases !== undefined && (!store.regulatedReducedBases || typeof store.regulatedReducedBases !== 'object' || Array.isArray(store.regulatedReducedBases)) ) throw new Error( 'Bases reduzidas não recuperáveis;originais preservados.', ); const fields = Object.keys(newReducedBase('', '')).filter( (key) => key !== 'conditions', ); for (const book of Object.values(store.regulatedReducedBases ?? {})) 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 ReducedBaseRow] !== 'string', ) || !row.conditions || typeof row.conditions !== 'object' || Array.isArray(row.conditions), ) ) throw new Error( 'Linhas de bases reduzidas não recuperáveis;originais preservados,sem substituição por lista vazia.', ); return store; } export function reducedBaseBook( draft: Record, ): ReducedBaseBook { return ( readStore(draft).regulatedReducedBases?.[acquisitionScope(draft)] ?? { rows: [], } ); } export function saveReducedBaseBook( draft: Record, book: ReducedBaseBook, ): string { const current = readStore(draft), encoded = JSON.stringify({ ...current, regulatedReducedBases: { ...current.regulatedReducedBases, [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 / 100000000n}.${(n % 100000000n).toString().padStart(8, '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 evaluateReducedBase( row: ReducedBaseRow, currency: unknown, ): LineResult { const result: LineResult = { id: row.id, status: 'indeterminate', baseAmount: null, deductionProportion: null, vat: null, rate: null, reason: '', }; const stop = ( reason: string, status: LineResult['status'] = 'indeterminate', ) => ({ ...result, status, reason }); const rule = reducedBaseTreatments.find((t) => t.id === row.treatment); if (!rule) return stop( 'Identifique energia,taxas aeronáuticas,obras públicas ou água;sem percentagem por defeito.', ); if (currency !== 'MZN' || row.currency !== currency) return stop( 'Moeda fora do âmbitoMZN 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ês2026 e dados efectivos ou pressupostos expressos de projecção.', ); const conditions = [ ...reducedBaseConditions, ...ownReducedConditions(row.treatment), ]; if (conditions.some(([key]) => row.conditions[key] === 'no')) return stop( 'Este tratamento de base reduzida não se aplica aos factos declarados;rever natureza/composição/âmbito,sem isenção ou IVAzero presumido.', 'not_applicable', ); if (conditions.some(([key]) => row.conditions[key] !== 'yes')) return stop( 'Condições por esclarecer;desconhecido não é não aplicável ou zero. Confirme o montante total antes da redução,sem próprioIVA.', ); if ( !row.description.trim() || !row.documentRef.trim() || !row.compositionRef.trim() || !row.evidence.trim() || !row.priceScope.trim() || !validDate(row.documentDate) || !validDate(row.operationDate) || row.operationDate.slice(0, 7) !== row.period ) return stop( 'Identifique operação,data/mês,documento/data,âmbito e composição ou pressupostos expressos;reutilize referências sem certificado/aprovador novo.', ); if (row.treatment === 'public_works') { if ( !row.projectRef.trim() || !publicWorkCategories.some((c) => c.id === row.workCategory) ) return stop( 'Identifique projecto/contrato público,categoria eâmbito da obra15(2)l/15(9);não aplicar a qualquer obra privada.', ); } else if ( !row.priceRef.trim() || !validDate(row.priceFrom) || row.operationDate < row.priceFrom || (row.priceTo !== '' && (!validDate(row.priceTo) || row.priceFrom > row.priceTo || row.operationDate > row.priceTo)) ) return stop( 'Identifique referência aplicável do preço/taxa e intervalo que abrange a operação;fim pode ficar vazio sem termo fixado,com correspondência temporal confirmada. Sem preço actual presumido para outro período.', ); const amount = decimal(row.consideration); if (amount === null) return stop( 'Indique contraprestação total antes da redução,sem próprioIVA;desconhecido/inválido não é zero. Se só existe total comIVA,separe a composição no percurso aplicável antes de usar este campo.', ); const base = money(amount * BigInt(rule.percent)); return { ...result, status: 'determined', baseAmount: base, deductionProportion: String(rule.percent) + '%', reason: `${rule.branch}:15(2)${rule.clause},base=${row.consideration}×${rule.percent}%=${base}MZN,${row.origin === 'projected' ? 'sob pressupostos expressos' : 'nos dados efectivos declarados'}. ${row.treatment === 'public_works' ? 'Redução de60% significa base40%,não base60%.' : 'Percentagem aplicada uma única vez ao montante total antes da redução,sem próprioIVA.'} Art18(6),Lei10/2025:dedução relativa a estas operações na mesma proporção(${rule.percent}%),sujeita às condições próprias;isto não calcula nem concede dedução/campo11. Base não é imposto;sem taxaIVA,IVA ou totaisModeloA. Decimal exacto,sem arredondamento fiscal presumido.`, }; } function key(draft: Record, book: ReducedBaseBook): string { return JSON.stringify({ version: reducedBaseVersion, scope: acquisitionScope(draft), location: draft.selection_labels ?? {}, currency: draft.currency, rows: book.rows, }); } export function reducedBaseCurrent( draft: Record, book: ReducedBaseBook, ): boolean { return !!book.result && book.result.sourceKey === key(draft, book); } export function recordReducedBases( draft: Record, book: ReducedBaseBook, ): ReducedBaseBook { 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: ReducedBaseCalculation = { version: reducedBaseVersion, revision: (book.result?.revision ?? 0) + 1, calculatedAt: new Date().toISOString(), opportunity: acquisitionScope(draft), location: JSON.stringify(draft.selection_labels ?? {}), sourceKey: key(draft, book), source: reducedBaseSource, inputs: structuredClone(book.rows), rows: book.rows.map((row) => evaluateReducedBase(row, draft.currency)), }; const next = { ...book, result, history: [ ...(book.history ?? []), ...(book.result ? [book.result] : []), ], }; saveReducedBaseBook(draft, next); return next; } export function reducedBaseReportRows( draft: Record, ): Array<[string, string, string, string]> { try { const book = reducedBaseBook(draft), result = book.result; if (!result) return []; return result.inputs.map((row, index) => [ 'IVA mensal', `Base ${reducedBaseTreatments.find((t) => t.id === row.treatment)?.branch ?? 'reduzida'} — ${row.period} — ${row.id}`, `${row.description};base tributável: ${result.rows[index].baseAmount ?? 'Indeterminada'} ${row.currency};IVA: Indeterminado;proporção18(6):${result.rows[index].deductionProportion ?? 'Indeterminada'}(sem dedução calculada);${result.rows[index].reason}`, `${result.opportunity};${result.location};${reducedBaseCurrent(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'};operação ${row.operationDate};âmbito ${row.priceScope};${row.treatment === 'public_works' ? `projecto/contrato ${row.projectRef};categoria ${row.workCategory}` : `preço/taxa ${row.priceRef};intervalo ${row.priceFrom} a ${row.priceTo || 'sem termo fixado,correspondência confirmada'}`};contraprestação total antes redução ${row.consideration} ${row.currency};composição ${row.compositionRef};documento ${row.documentRef},${row.documentDate};condições ${[...reducedBaseConditions, ...ownReducedConditions(row.treatment)].map(([key, label]) => label + ': ' + (row.conditions[key] === 'yes' ? 'Sim' : row.conditions[key] === 'no' ? 'Não' : 'Por indicar')).join('; ')};fundamento ${row.evidence};sem dedução/campo11/totaisModeloA.`, ]); } catch { return [ [ 'IVA mensal', 'Bases reduzidas', 'Dados não recuperáveis', 'Originais preservados;sem substituir por zero.', ], ]; } }