import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts'; import { goodsLocationBook, goodsLocationCurrent, } from './iva-goods-location.ts'; import { serviceLocationBook, serviceLocationCurrent, } from './iva-service-location.ts'; import { listedServiceBook, listedServiceCurrent, } from './iva-listed-services.ts'; import { currencyEventBook, currencyEventCurrent, } from './iva-currency-events.ts'; import { digitalBook, digitalCurrent } from './iva-digital.ts'; export const foreignOperationVersion = 'foreign-operations/1'; export const foreignOperationSource = 'CIVA6,Lei13/2016PDF7–8 e redacção2025:reutilizar localização já apurada.19(1)bii,2016PDF15/p1426(273),mantidoLei10/2025PDF13:operações no estrangeiro que seriam tributáveis se efectuadas no território nacional;19(2)alterado2025 mantém exclusões próprias. Enquadramento potencial,não dedução concedida/campo11.Não confundir fora do território com isenção/IVAzero,moeda estrangeira,cliente estrangeiro ou caixa06.'; type Choice = '' | 'yes' | 'no'; export type ForeignOperationRow = { id: string; sourceKind: '' | 'goods' | 'services' | 'listed' | 'digital'; sourceRowId: string; territorialPart: '' | 'outside' | 'national'; eventRowId: string; hypotheticalTaxable: Choice; hypotheticalRef: string; excluded19_2: Choice; scope: string; evidence: string; }; type Source = { id: string; description: string; period: string; operationRef: string; scope: string; status: string; territory: string | null; current: boolean; revision: number; source: string; reason: string; }; type LineResult = { id: string; status: 'determined' | 'not_applicable' | 'indeterminate'; territory: string | null; relation19: | 'potential' | 'not_demonstrated' | 'excluded_19_2' | 'indeterminate'; source: Source | null; event: Record | null; deduction: null; baseAmount: null; vat: null; rate: null; reason: string; }; export type ForeignOperationCalculation = { version: string; revision: number; calculatedAt: string; opportunity: string; sourceKey: string; source: string; inputs: ForeignOperationRow[]; rows: LineResult[]; }; export type ForeignOperationBook = { rows: ForeignOperationRow[]; result?: ForeignOperationCalculation; history?: ForeignOperationCalculation[]; }; type Store = ReturnType & { foreignOperations?: Record; }; export function newForeignOperation(id: string): ForeignOperationRow { return { id, sourceKind: '', sourceRowId: '', territorialPart: '', eventRowId: '', hypotheticalTaxable: '', hypotheticalRef: '', excluded19_2: '', scope: '', evidence: '', }; } export function switchForeignSource( row: ForeignOperationRow, sourceKind: ForeignOperationRow['sourceKind'], sourceRowId = '', ): ForeignOperationRow { return { ...row, sourceKind, sourceRowId, territorialPart: '', eventRowId: '', hypotheticalTaxable: '', excluded19_2: '', }; } export function foreignSources( draft: Record, kind: ForeignOperationRow['sourceKind'], ): Source[] { if (kind === 'digital') { const b = digitalBook(draft), r = b.result; return r ? r.inputs.map((input, i) => ({ id: input.id, description: input.description, period: input.period, operationRef: input.operationRef, scope: input.scope, status: r.rows[i].status, territory: r.rows[i].territory, current: digitalCurrent(draft, b), revision: r.revision, source: r.source, reason: r.rows[i].reason, })) : []; } if (kind === 'goods') { const b = goodsLocationBook(draft), r = b.result; return r ? r.inputs.map((input, i) => ({ id: input.id, description: input.description, period: input.period, operationRef: input.operationRef, scope: input.scope, status: r.rows[i].status, territory: r.rows[i].territory, current: goodsLocationCurrent(draft, b), revision: r.revision, source: r.source, reason: r.rows[i].reason, })) : []; } if (kind === 'services') { const b = serviceLocationBook(draft), r = b.result; return r ? r.inputs.map((input, i) => ({ id: input.id, description: input.description, period: input.period, operationRef: input.operationRef, scope: input.scope, status: r.rows[i].status, territory: r.rows[i].territory, current: serviceLocationCurrent(draft, b), revision: r.revision, source: r.source, reason: r.rows[i].reason, })) : []; } if (kind === 'listed') { const b = listedServiceBook(draft), r = b.result; return r ? r.inputs.map((input, i) => ({ id: input.id, description: input.description, period: input.period, operationRef: input.operationRef, scope: input.scope, status: r.rows[i].status, territory: r.rows[i].territory, current: listedServiceCurrent(draft, b), revision: r.revision, source: r.source, reason: r.rows[i].reason, })) : []; } return []; } export function foreignEvents(draft: Record): Array<{ id: string; description: string; operationRef: string; period: string; current: boolean; revision: number; status: string; eventDate: string | null; dueEvents: unknown[]; remainingPending: boolean; }> { const b = currencyEventBook(draft), r = b.result; return r ? r.inputs .filter((x) => x.treatment !== 'foreign_scenario') .map((input) => { const line = r.rows[r.inputs.findIndex((x) => x.id === input.id)]; return { id: input.id, description: input.description, operationRef: input.operationRef, period: input.period, current: currencyEventCurrent(draft, b), revision: r.revision, status: line.status, eventDate: line.eventDate, dueEvents: line.dueEvents, remainingPending: line.remainingPending, }; }) : []; } function readStore(draft: Record): Store { const s = readAcquisitionStore(draft.modelo_a_snapshot) as Store; if ( s.foreignOperations !== undefined && (!s.foreignOperations || typeof s.foreignOperations !== 'object' || Array.isArray(s.foreignOperations)) ) throw new Error( 'Operações no estrangeiro não recuperáveis;originais conservados.', ); const fields = Object.keys(newForeignOperation('')); for (const b of Object.values(s.foreignOperations ?? {})) if ( !b || !Array.isArray(b.rows) || (b.history !== undefined && !Array.isArray(b.history)) || b.rows.some( (r) => !r || fields.some( (k) => typeof r[k as keyof ForeignOperationRow] !== 'string', ), ) ) throw new Error( 'Linhas de operações no estrangeiro não recuperáveis;sem lista vazia por defeito.', ); return s; } export function foreignOperationBook( draft: Record, ): ForeignOperationBook { return ( readStore(draft).foreignOperations?.[acquisitionScope(draft)] ?? { rows: [], } ); } export function saveForeignOperationBook( draft: Record, book: ForeignOperationBook, ): string { const s = readStore(draft), encoded = JSON.stringify({ ...s, foreignOperations: { ...s.foreignOperations, [acquisitionScope(draft)]: book, }, }); readStore({ ...draft, modelo_a_snapshot: encoded }); return encoded; } export function evaluateForeignOperation( draft: Record, row: ForeignOperationRow, ): LineResult { const result: LineResult = { id: row.id, status: 'indeterminate', territory: null, relation19: 'indeterminate', source: null, event: null, deduction: null, baseAmount: null, vat: null, rate: null, reason: '', }; const stop = ( reason: string, status: LineResult['status'] = 'indeterminate', ) => ({ ...result, status, reason }); try { const source = foreignSources(draft, row.sourceKind).find( (s) => s.id === row.sourceRowId, ); if (!source) return stop( 'Escolha uma classificação de localização já guardada nesta oportunidade. Prepare-a nas secções existentes;sem nova classificação por endereço,nacionalidade ou moeda.', ); result.source = source; if ( !source.current || source.status !== 'determined' || !source.territory ) return stop( 'Localização alterada ou indeterminada;resultado anterior conservado,sem o tratar como classificação actual.', ); result.territory = source.territory; if ( !/^2026-(0[1-9]|1[0-2])$/.test(source.period) || !row.scope.trim() || !row.evidence.trim() ) return stop( 'Identifique âmbito da análise e factos/pressupostos;reutilize operação/período/documentos da localização.', ); if (source.territory === 'national') return stop( 'Localização reutilizada aponta para Moçambique;P002operação no estrangeiro não se aplica neste âmbito.Não equivale a isenção nem decide tributação interna.', 'not_applicable', ); if (source.territory === 'partial') { if (!['outside', 'national'].includes(row.territorialPart)) return stop( 'Transporte tem parcela nacional e exterior;delimite a parcela desta análise,sem transformar todo o trajecto em estrangeiro.', ); if (row.territorialPart === 'national') { result.territory = 'national'; return stop( 'Análise limitada à parcela nacional do trajecto;P002estrangeiro não se aplica a essa parcela.', 'not_applicable', ); } result.territory = 'outside_portion'; } if (row.eventRowId) { const event = foreignEvents(draft).find( (e) => e.id === row.eventRowId, ); if ( !event || !event.current || event.status !== 'determined' || event.operationRef !== source.operationRef || event.period !== source.period ) return stop( 'Evento escolhido não está actual ou não corresponde à mesma operação/período;conserve localização e corrija apenas a associação.Não inferir evento pela data do documento.', ); result.event = event; } if ( !row.hypotheticalRef.trim() || !['yes', 'no'].includes(row.hypotheticalTaxable) ) return stop( 'Operação/parcela estrangeira identificada;fundamente se seria tributável caso efectuada em Moçambique19(1)bii.Não presumir esse facto pela localização ou pela taxa estrangeira.', ); if (row.hypotheticalTaxable === 'no') { result.relation19 = 'not_demonstrated'; return stop( 'Operação no estrangeiro identificada;pressuposto de tributabilidade interna19(1)bii não demonstrado pelos factos declarados.Isto não decide outros enquadramentos de dedução.', 'determined', ); } if (!['yes', 'no'].includes(row.excluded19_2)) return stop( 'Tributabilidade interna hipotética indicada;conferir exclusões19(2),incluindo17-A e pagamentos15(6)c.Sem presumir dedução.', ); if (row.excluded19_2 === 'yes') { result.relation19 = 'excluded_19_2'; return stop( 'Operação no estrangeiro identificada;exclusão19(2) declarada e fundamentada afasta este enquadramento potencial.Não calcula ou regulariza dedução.', 'determined', ); } result.relation19 = 'potential'; return stop( 'Operação/parcela no estrangeiro reutilizada da localização;seria tributável em Moçambique segundo fundamento declarado,sem exclusão19(2) indicada.Enquadramento potencial19(1)bii,dependente das demais condições18/20 e âmbito dos bens/serviços utilizados.Não é concessão/cálculo de dedução,campo11,isenção ou IVA zero.', 'determined', ); } catch { return stop( 'Localização/eventos de origem não recuperáveis;originais preservados,sem inferir classificação ou valores.', ); } } function key( draft: Record, book: ForeignOperationBook, ): string { return JSON.stringify({ version: foreignOperationVersion, scope: acquisitionScope(draft), location: draft.selection_labels ?? {}, rows: book.rows, dependencies: book.rows.map((row) => ({ source: foreignSources(draft, row.sourceKind).find( (s) => s.id === row.sourceRowId, ), event: row.eventRowId ? foreignEvents(draft).find((e) => e.id === row.eventRowId) : null, })), }); } export function foreignOperationCurrent( draft: Record, book: ForeignOperationBook, ): boolean { try { return !!book.result && book.result.sourceKey === key(draft, book); } catch { return false; } } export function recordForeignOperations( draft: Record, book: ForeignOperationBook, ): ForeignOperationBook { if ( !book.rows.length || new Set(book.rows.map((r) => r.id)).size !== book.rows.length ) throw new Error('Indique identidades distintas;originais conservados.'); const result: ForeignOperationCalculation = { version: foreignOperationVersion, revision: (book.result?.revision ?? 0) + 1, calculatedAt: new Date().toISOString(), opportunity: acquisitionScope(draft), sourceKey: key(draft, book), source: foreignOperationSource, inputs: structuredClone(book.rows), rows: book.rows.map((r) => evaluateForeignOperation(draft, r)), }; const next = { ...book, result, history: [ ...(book.history ?? []), ...(book.result ? [book.result] : []), ], }; saveForeignOperationBook(draft, next); return next; } export function foreignOperationReportRows( draft: Record, ): Array<[string, string, string, string]> { try { const b = foreignOperationBook(draft), r = b.result; if (!r) return []; return r.inputs.map((row, i) => { const line = r.rows[i]; return [ 'IVA mensal', `P002 — ${line.source?.period ?? 'Período indeterminado'} — ${row.id}`, `${line.source?.description ?? 'Origem por escolher'};${line.reason};dedução/IVA:Indeterminados`, `${r.opportunity};${foreignOperationCurrent(draft, b) ? 'Resultado actual' : 'Origens/entradas alteradas;resultado histórico conservado'};versão${r.revision};${r.calculatedAt};${r.source};localização${row.sourceKind}/${row.sourceRowId},versão${line.source?.revision ?? 'indeterminada'};${line.source?.reason ?? ''};operação${line.source?.operationRef ?? ''};âmbito origem${line.source?.scope ?? ''};parcela${row.territorialPart};evento${row.eventRowId || 'Sem associação necessária'},${line.event ? JSON.stringify(line.event) : 'Não apurado por esta análise'};hipótese interna${row.hypotheticalTaxable},fundamento${row.hypotheticalRef};exclusão19(2)${row.excluded19_2};âmbito${row.scope};factos${row.evidence};sem base/IVA/dedução/campo11/totaisModeloA.`, ]; }); } catch { return [ [ 'IVA mensal', 'P002', 'Dados não recuperáveis', 'Originais preservados;sem classificação por defeito.', ], ]; } }