import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts'; export const publicBaseVersion = 'public-authority-base/1'; export const publicBaseSource = 'CIVA republicado Lei13/2016,15(2)(d),PDF13/p1426(271): base das transmissões de bens e prestações de serviços resultantes de actos de autoridades públicas é a indemnização ou qualquer outra compensação. Art15(3)/(4),PDF14/p1426(272): componente não monetária pelo valor normal;15(5)/(6),composição/exclusões,incluindo exclusão do próprio IVA e indemnização judicial por incumprimento de contratos. Lei22/2022PDF3 mantém15(2)(d)/(3–6)por remissão;Lei10/2025não altera15. Recorte2026/MZN,determinação da base;sem inferir incidência,taxa,isenção,IVA,dedução/campo11 ou totaisModeloA.'; export const publicBaseTreatments = [ { id: 'public_authority_indemnity', label: 'Indemnização por acto de autoridade pública', }, { id: 'public_authority_compensation', label: 'Outra compensação por acto de autoridade pública', }, ] as const; export const publicBaseConditions = [ [ 'taxableDomesticSupply', 'Está identificada uma transmissão de bens ou prestação de serviços sujeita a IVA no território nacional; não basta existir um pagamento ou uma entidade pública', ], [ 'authorityAct', 'A transmissão ou prestação resulta do acto da autoridade pública identificado', ], [ 'compensationLink', 'O montante e as componentes indicadas correspondem à indemnização ou compensação desta operação', ], [ 'notJudicialContractBreach', 'Não se trata de quantia recebida a título de indemnização declarada judicialmente por incumprimento total ou parcial de contrato, excluída pelo artigo15(6)(a)', ], [ 'netComposition', 'Os valores indicados excluem o próprio IVA e reflectem a composição aplicável dos números5/6 do artigo15, sem omissões nem duplicações; a referência explica a composição', ], ] as const; type Condition = (typeof publicBaseConditions)[number][0]; type Choice = '' | 'yes' | 'no'; export type PublicBaseRow = { id: string; treatment: string; description: string; operationNature: '' | 'goods' | 'services'; period: string; origin: 'projected' | 'effective'; currency: string; authority: string; actRef: string; documentRef: string; documentDate: string; compensationForm: '' | 'money' | 'non_money' | 'mixed'; moneyAmount: string; nonMoneyValue: string; valuationRef: 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 PublicBaseCalculation = { version: string; revision: number; calculatedAt: string; opportunity: string; location: string; sourceKey: string; source: string; inputs: PublicBaseRow[]; rows: LineResult[]; }; export type PublicBaseBook = { rows: PublicBaseRow[]; result?: PublicBaseCalculation; history?: PublicBaseCalculation[]; }; type Store = ReturnType & { publicAuthorityBases?: Record; }; export function newPublicBase(id: string, currency: string): PublicBaseRow { return { id, treatment: '', description: '', operationNature: '', period: '', origin: 'projected', currency, authority: '', actRef: '', documentRef: '', documentDate: '', compensationForm: '', moneyAmount: '', nonMoneyValue: '', valuationRef: '', compositionRef: '', evidence: '', conditions: {}, }; } function readStore(draft: Record): Store { const store = readAcquisitionStore(draft.modelo_a_snapshot) as Store; if ( store.publicAuthorityBases !== undefined && (!store.publicAuthorityBases || typeof store.publicAuthorityBases !== 'object' || Array.isArray(store.publicAuthorityBases)) ) throw new Error( 'Bases de actos públicos não recuperáveis; originais preservados.', ); const fields = Object.keys(newPublicBase('', '')).filter( (key) => key !== 'conditions', ); for (const book of Object.values(store.publicAuthorityBases ?? {})) { 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 PublicBaseRow] !== 'string', ) || !row.conditions || typeof row.conditions !== 'object' || Array.isArray(row.conditions), ) ) throw new Error( 'Linhas de base tributável não recuperáveis; originais preservados,sem substituição por lista vazia.', ); } return store; } export function publicBaseBook(draft: Record): PublicBaseBook { return ( readStore(draft).publicAuthorityBases?.[acquisitionScope(draft)] ?? { rows: [], } ); } export function savePublicBaseBook( draft: Record, book: PublicBaseBook, ): string { const current = readStore(draft), encoded = JSON.stringify({ ...current, publicAuthorityBases: { ...current.publicAuthorityBases, [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 evaluatePublicBase( row: PublicBaseRow, 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 (!publicBaseTreatments.some((item) => item.id === row.treatment)) return stop( 'Escolha indemnização ou outra compensação por acto de autoridade pública; um pagamento genérico não determina esta base.', ); if (currency !== 'MZN' || row.currency !== currency) return stop( 'Moeda fora do âmbito MZN ou divergente do projecto; originais conservados,sem conversão.', ); if ( !/^2026-(0[1-9]|1[0-2])$/.test(row.period) || !['projected', 'effective'].includes(row.origin) ) return stop( 'Indique o período em2026 e dados efectivos declarados ou pressupostos de projecção; não são inferidos do orçamento.', ); const failed = publicBaseConditions.filter( ([key]) => row.conditions[key] === 'no', ); if (failed.length) return stop( 'Não se aplica esta determinação de base: ' + failed.map(([, label]) => label).join('; ') + '. Não é isenção nem IVA zero; conserve o enquadramento próprio da operação.', 'not_applicable', ); const missing = publicBaseConditions.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 ( !['goods', 'services'].includes(row.operationNature) || !row.description.trim() || !row.authority.trim() || !row.actRef.trim() || !row.documentRef.trim() || !row.compositionRef.trim() || !row.evidence.trim() || !validDate(row.documentDate) ) return stop( 'Identifique a operação,natureza,autoridade,acto,documento/data e fundamento da composição,ou pressupostos expressos; reutilize referências existentes,sem novo certificado.', ); if (!['money', 'non_money', 'mixed'].includes(row.compensationForm)) return stop( 'Indique se a compensação é em dinheiro,em espécie ou mista; ausência de resposta não elimina uma componente.', ); const cash = row.compensationForm === 'non_money' ? 0n : decimal(row.moneyAmount), kind = row.compensationForm === 'money' ? 0n : decimal(row.nonMoneyValue); if (cash === null || kind === null) return stop( 'Indique os valores sem IVA das componentes aplicáveis; montante em falta ou inválido não é zero.', ); if (row.compensationForm !== 'money' && !row.valuationRef.trim()) return stop( 'Identifique o fundamento do valor normal da componente não monetária,conforme15(3)/(4),ou o pressuposto expresso; não é avaliado pelo portal.', ); const base = money(cash + kind); return { ...result, status: 'determined', baseAmount: base, reason: `Base15(2)(d): componente monetária ${money(cash)} + valor normal não monetário ${money(kind)} = ${base}MZN,${row.origin === 'projected' ? 'nas condições expressamente assumidas na projecção' : 'nos dados efectivos declarados'}. Componentes sem IVA,não duplicadas,composição15(5)/(6) identificada. Base não é imposto; incidência/taxa/IVA/dedução/campo11 não são determinados por este cálculo.`, }; } function key(draft: Record, book: PublicBaseBook): string { return JSON.stringify({ version: publicBaseVersion, scope: acquisitionScope(draft), location: draft.selection_labels ?? {}, currency: draft.currency, rows: book.rows, }); } function natureLabel(value: string): string { return value === 'goods' ? 'Transmissão de bens' : value === 'services' ? 'Prestação de serviços' : 'Por indicar'; } function formLabel(value: string): string { return value === 'money' ? 'Em dinheiro' : value === 'non_money' ? 'Em espécie' : value === 'mixed' ? 'Mista — dinheiro e espécie' : 'Por indicar'; } export function publicBaseCurrent( draft: Record, book: PublicBaseBook, ): boolean { return !!book.result && book.result.sourceKey === key(draft, book); } export function recordPublicBases( draft: Record, book: PublicBaseBook, ): PublicBaseBook { 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: PublicBaseCalculation = { version: publicBaseVersion, revision: (book.result?.revision ?? 0) + 1, calculatedAt: new Date().toISOString(), opportunity: acquisitionScope(draft), location: JSON.stringify(draft.selection_labels ?? {}), sourceKey: key(draft, book), source: publicBaseSource, inputs: structuredClone(book.rows), rows: book.rows.map((row) => evaluatePublicBase(row, draft.currency)), }; const next = { ...book, result, history: [ ...(book.history ?? []), ...(book.result ? [book.result] : []), ], }; savePublicBaseBook(draft, next); return next; } export function publicBaseReportRows( draft: Record, ): Array<[string, string, string, string]> { try { const book = publicBaseBook(draft), result = book.result; if (!result) return []; return result.inputs.map((row, index) => [ 'IVA mensal', `Base P090 — ${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}; ${publicBaseCurrent(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'}; ${publicBaseTreatments.find((item) => item.id === row.treatment)?.label ?? row.treatment}; natureza ${natureLabel(row.operationNature)}; autoridade ${row.authority}; acto ${row.actRef}; documento ${row.documentRef},${row.documentDate}; forma ${formLabel(row.compensationForm)}; ${row.compensationForm !== 'non_money' ? `dinheiro ${row.moneyAmount} ${row.currency}; ` : ''}${row.compensationForm !== 'money' ? `valor normal não monetário ${row.nonMoneyValue} ${row.currency},fundamento ${row.valuationRef}; ` : ''}composição ${row.compositionRef}; condições ${publicBaseConditions.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 actos públicos', 'Dados não recuperáveis', 'Originais preservados;sem substituir por zero.', ], ]; } }