import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts'; export const exclusionBaseVersion = 'consideration-exclusions-base/1'; export const exclusionBaseSource = 'CIVA republicado Lei13/2016,15(6)(a–d),PDF14/p1426(272):exclui indemnizações declaradas judicialmente por incumprimento total/parcial de contratos;descontos,abatimentos ou bónus concedidos;quantias pagas em nome e por conta do adquirente/destinatário registadas em adequadas contas de terceiros;embalagens não efectivamente transaccionadas com elementos27(5)(b)na factura/documento equivalente.27(5)(b),PDF17/p1426(275):indicação separada e menção expressa da devolução acordada. Lei22/2022PDF3mantém15(6);Lei10/2025PDF14mantém27(5)(b),altera27(10),não15. Recorte2026/MZN,base apenas,sem IVA/dedução/campo11/regularizações ou totaisModeloA.'; export const exclusionTreatments = [ { id: 'judicial_indemnity', label: 'Indemnização declarada judicialmente por incumprimento contratual', }, { id: 'granted_discount', label: 'Desconto,abatimento ou bónus concedido' }, { id: 'third_party_payment', label: 'Quantia paga em nome e por conta do adquirente/destinatário', }, { id: 'returnable_packaging', label: 'Embalagem não efectivamente transaccionada,com devolução acordada', }, ] as const; export const exclusionConditions = [ [ 'operationIdentified', 'A operação e os montantes relacionados estão identificados', ], [ 'initialNet', 'O montante inicial exclui o próprio IVA e tem a composição aplicável15(5)identificada,antes das exclusões ainda não consideradas', ], [ 'completeComposition', 'As exclusões aplicáveis estão reconciliadas com o montante inicial,sem omissões ou dupla subtracção', ], [ 'specialRules', 'Esta composição respeita a regra aplicável da operação,sem substituir bases especiais ou regularizações de outros períodos', ], ] as const; export const exclusionPartConditions = { judicial_indemnity: [ [ 'judicialDeclaration', 'A indemnização recebida foi declarada judicialmente', ], [ 'contractBreach', 'Corresponde ao incumprimento total ou parcial de contrato', ], ], granted_discount: [ [ 'granted', 'O desconto,abatimento ou bónus está concedido e corresponde a esta operação', ], ], third_party_payment: [ [ 'paidInName', 'A quantia foi paga em nome do adquirente/destinatário identificado', ], [ 'paidForAccount', 'A quantia foi paga por conta desse adquirente/destinatário', ], [ 'thirdPartyAccounts', 'Está registada pelo contribuinte em adequadas contas de terceiros', ], ], returnable_packaging: [ [ 'notTransacted', 'As embalagens não foram efectivamente transaccionadas', ], [ 'separateIndication', 'Constam separadamente na factura ou documento equivalente', ], [ 'returnMention', 'Consta menção expressa de que foi acordada a devolução das embalagens', ], ], } as const; export function partConditions( treatment: string, ): ReadonlyArray { return ( exclusionPartConditions[ treatment as keyof typeof exclusionPartConditions ] ?? [] ); } type Choice = '' | 'yes' | 'no'; type Condition = (typeof exclusionConditions)[number][0]; export type ExclusionPart = { id: string; treatment: string; description: string; amount: string; included: Choice; reference: string; scope: string; accountRef: string; beneficiaryRef: string; documentExcerpt: string; conditions: Partial>; }; export type ExclusionRow = { id: string; description: string; period: string; origin: 'projected' | 'effective'; currency: string; initialAmount: string; initialRef: string; operationRef: string; documentRef: string; documentDate: string; exclusionRef: string; evidence: string; conditions: Partial>; parts: ExclusionPart[]; }; type LineResult = { id: string; status: 'determined' | 'not_applicable' | 'indeterminate'; baseAmount: string | null; vat: null; rate: null; reason: string; }; export type ExclusionCalculation = { version: string; revision: number; calculatedAt: string; opportunity: string; location: string; sourceKey: string; source: string; inputs: ExclusionRow[]; rows: LineResult[]; }; export type ExclusionBook = { rows: ExclusionRow[]; result?: ExclusionCalculation; history?: ExclusionCalculation[]; }; type Store = ReturnType & { considerationExclusions?: Record; }; export function newExclusionPart(id: string): ExclusionPart { return { id, treatment: '', description: '', amount: '', included: '', reference: '', scope: '', accountRef: '', beneficiaryRef: '', documentExcerpt: '', conditions: {}, }; } export function newExclusionBase(id: string, currency: string): ExclusionRow { return { id, description: '', period: '', origin: 'projected', currency, initialAmount: '', initialRef: '', operationRef: '', documentRef: '', documentDate: '', exclusionRef: '', evidence: '', conditions: {}, parts: [], }; } export function switchExclusionPart( part: ExclusionPart, treatment: string, ): ExclusionPart { return { ...part, treatment, conditions: {} }; } function readStore(draft: Record): Store { const store = readAcquisitionStore(draft.modelo_a_snapshot) as Store; if ( store.considerationExclusions !== undefined && (!store.considerationExclusions || typeof store.considerationExclusions !== 'object' || Array.isArray(store.considerationExclusions)) ) throw new Error('Exclusões não recuperáveis;originais preservados.'); const fields = Object.keys(newExclusionBase('', '')).filter( (key) => !['conditions', 'parts'].includes(key), ); const partFields = Object.keys(newExclusionPart('')).filter( (key) => key !== 'conditions', ); for (const book of Object.values(store.considerationExclusions ?? {})) 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 ExclusionRow] !== 'string', ) || !row.conditions || typeof row.conditions !== 'object' || Array.isArray(row.conditions) || !Array.isArray(row.parts) || row.parts.some( (part) => !part || partFields.some( (key) => typeof part[key as keyof ExclusionPart] !== 'string', ) || !part.conditions || typeof part.conditions !== 'object' || Array.isArray(part.conditions), ), ) ) throw new Error( 'Linhas de exclusão não recuperáveis;originais preservados,sem substituição por lista vazia.', ); return store; } export function exclusionBook(draft: Record): ExclusionBook { return ( readStore(draft).considerationExclusions?.[acquisitionScope(draft)] ?? { rows: [], } ); } export function saveExclusionBook( draft: Record, book: ExclusionBook, ): string { const current = readStore(draft), encoded = JSON.stringify({ ...current, considerationExclusions: { ...current.considerationExclusions, [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 evaluateExclusion( row: ExclusionRow, 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 (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.', ); const failed = exclusionConditions.filter( ([key]) => row.conditions[key] === 'no', ); if (failed.length) return stop( 'Não se aplica esta composição: ' + failed.map(([, label]) => label).join('; ') + '. Não é isenção nem IVA zero.', 'not_applicable', ); const missing = exclusionConditions.filter( ([key]) => row.conditions[key] !== 'yes', ); if (missing.length) return stop( 'Condições por indicar: ' + missing.map(([, label]) => label).join('; ') + '. Desconhecido não é zero.', ); if ( !row.description.trim() || !row.initialRef.trim() || !row.operationRef.trim() || !row.documentRef.trim() || !row.exclusionRef.trim() || !row.evidence.trim() || !validDate(row.documentDate) ) return stop( 'Identifique operação,montante inicial,documento/data,composição e fundamento ou pressupostos expressos;reutilize referências existentes,sem certificado/aprovador novo.', ); const initial = decimal(row.initialAmount); if (initial === null) return stop( 'Indique o montante inicial sem próprio IVA,com as inclusões aplicáveis consideradas e antes das exclusões ainda não realizadas;desconhecido não é zero.', ); if ( !row.parts.length || new Set(row.parts.map((p) => p.id)).size !== row.parts.length || row.parts.some((p) => !p.id.trim()) ) return stop( 'Identifique as parcelas desta operação com identificadores distintos;sem subtracção de linhas vazias ou duplicadas.', ); let exclusions = 0n; const formulas: string[] = []; for (const part of row.parts) { const rule = exclusionTreatments.find((t) => t.id === part.treatment); if (!rule) return stop( 'Identifique o tratamento de cada parcela;uma descrição genérica não demonstra exclusão.', ); const own = partConditions(part.treatment), failed = own.filter(([key]) => part.conditions[key] === 'no'); if (failed.length) return stop( 'Não se aplica a exclusão da parcela ' + part.id + ': ' + failed.map(([, label]) => label).join('; ') + '. Sem subtracção automática ou IVA zero.', 'not_applicable', ); if ( own.some(([key]) => part.conditions[key] !== 'yes') || !['yes', 'no'].includes(part.included) ) return stop( 'Confirme as condições próprias da parcela ' + part.id + ' e se ainda está incluída no montante inicial;desconhecido não permite presumir exclusão ou zero.', ); if ( !part.description.trim() || !part.reference.trim() || !part.scope.trim() ) return stop( 'Identifique descrição,referência e âmbito da parcela ' + part.id + ',ou pressuposto expresso. Na indemnização,a referência identifica a declaração judicial e o contrato;no desconto,a concessão.', ); if ( part.treatment === 'third_party_payment' && (!part.accountRef.trim() || !part.beneficiaryRef.trim()) ) return stop( 'Identifique as adequadas contas de terceiros e o adquirente/destinatário em nome e por conta de quem se pagou,ou pressupostos expressos;despesa própria não equivale a pagamento por conta alheia.', ); if ( part.treatment === 'returnable_packaging' && !part.documentExcerpt.trim() ) return stop( 'Identifique na referência da factura/documento a indicação separada e a menção expressa da devolução acordada,ou pressuposto expresso;sem exigir devolução já realizada.', ); const amount = decimal(part.amount); if (amount === null) return stop( 'Indique o montante exacto da parcela ' + part.id + ';desconhecido não é zero.', ); if (part.included === 'yes') exclusions += amount; formulas.push( `${part.id}:${rule.label},${money(amount)}MZN,${part.included === 'yes' ? 'excluída uma vez' : 'já excluída,sem segunda subtracção'}`, ); } if (exclusions > initial) return stop( 'As exclusões ainda incluídas excedem o montante inicial;reveja a composição,sem base negativa ou piso zero presumido.', ); const base = initial - exclusions; return { ...result, status: 'determined', baseAmount: money(base), reason: `Base15(6):montante inicial ${money(initial)} − exclusões ainda incluídas ${money(exclusions)} = ${money(base)}MZN;${formulas.join('; ')},${row.origin === 'projected' ? 'nas condições expressamente assumidas na projecção' : 'nos dados efectivos declarados'}. Base única por operação. Base não é imposto;IVA/taxa,dedução/campo11,regularizações e totaisModeloA não são determinados por esta composição.`, }; } function key(draft: Record, book: ExclusionBook): string { return JSON.stringify({ version: exclusionBaseVersion, scope: acquisitionScope(draft), location: draft.selection_labels ?? {}, currency: draft.currency, rows: book.rows, }); } export function exclusionCurrent( draft: Record, book: ExclusionBook, ): boolean { return !!book.result && book.result.sourceKey === key(draft, book); } export function recordExclusions( draft: Record, book: ExclusionBook, ): ExclusionBook { 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: ExclusionCalculation = { version: exclusionBaseVersion, revision: (book.result?.revision ?? 0) + 1, calculatedAt: new Date().toISOString(), opportunity: acquisitionScope(draft), location: JSON.stringify(draft.selection_labels ?? {}), sourceKey: key(draft, book), source: exclusionBaseSource, inputs: structuredClone(book.rows), rows: book.rows.map((row) => evaluateExclusion(row, draft.currency)), }; const next = { ...book, result, history: [ ...(book.history ?? []), ...(book.result ? [book.result] : []), ], }; saveExclusionBook(draft, next); return next; } export function exclusionReportRows( draft: Record, ): Array<[string, string, string, string]> { try { const book = exclusionBook(draft), result = book.result; if (!result) return []; return result.inputs.map((row, index) => [ 'IVA mensal', `Base P102 — ${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};${exclusionCurrent(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.operationRef};montante inicial ${row.initialAmount} ${row.currency},referência ${row.initialRef};parcelas ${row.parts .map( (p) => `${p.id}/${p.treatment},${p.description},${p.amount} ${row.currency},já incluída:${p.included === 'yes' ? 'Sim' : p.included === 'no' ? 'Não' : 'Por indicar'},referência ${p.reference},âmbito ${p.scope}${p.treatment === 'third_party_payment' ? `,contas ${p.accountRef},adquirente/destinatário ${p.beneficiaryRef}` : ''}${p.treatment === 'returnable_packaging' ? `,menção documental ${p.documentExcerpt}` : ''},condições ${partConditions( p.treatment, ) .map( ([key, label]) => label + ': ' + (p.conditions[key] === 'yes' ? 'Sim' : p.conditions[key] === 'no' ? 'Não' : 'Por indicar'), ) .join('; ')}`, ) .join( '; ', )};composição ${row.exclusionRef};documento ${row.documentRef},${row.documentDate};condições ${exclusionConditions.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', 'Composição da contraprestação', 'Dados não recuperáveis', 'Originais preservados;sem substituir por zero.', ], ]; } }