import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts'; export const valueReviewVersion = 'value-review-base/1'; export const valueReviewSource = 'CIVA republicado Lei13/2016,15(7),PDF14/p1426(272):para efeitos15(1),quando a contraprestação é inferior à resultante dos preços correntes ou normais de venda à porta da fábrica,por grosso ou a retalho,ou aos preços correntes/normais do serviço,tendo em conta a natureza das transmissões,a Administração Tributária pode proceder à correcção. Lei22/2022PDF3mantém15(7);Lei10/2025não altera15. Recorte2026/MZN;comparação não exerce o poder da AT,nem substitui automaticamente a contraprestação pelo valor de referência. Sem incidência,taxa,IVA,dedução/campo11,regularizações ou totaisModeloA.'; export const valueReviewTreatments = [ { id: 'price_comparison', label: 'Comparar sem correcção automática' }, { id: 'referenced_correction', label: 'Registar correcção referenciada / cenário condicionado', }, ] as const; export const priceContexts = [ { id: 'factory', label: 'Venda à porta da fábrica' }, { id: 'wholesale', label: 'Venda por grosso' }, { id: 'retail', label: 'Venda a retalho' }, { id: 'service', label: 'Preço corrente ou normal do serviço' }, ] as const; export const valueReviewConditions = [ [ 'taxableOperation', 'A operação e a contraprestação para efeitos15(1)estão identificadas', ], [ 'referenceCorresponds', 'O preço corrente ou normal e o contexto escolhido correspondem à natureza desta transmissão ou serviço,no período e âmbito identificados', ], [ 'netComposition', 'Os montantes comparados têm composição equivalente,sem próprioIVA,com inclusões/exclusões aplicáveis15(5)/(6)consideradas', ], ] as const; type Choice = '' | 'yes' | 'no'; type Condition = (typeof valueReviewConditions)[number][0]; export type ValueReviewRow = { id: string; treatment: string; priceContext: string; description: string; period: string; origin: 'projected' | 'effective'; currency: string; operationRef: string; documentRef: string; documentDate: string; declaredAmount: string; referenceAmount: string; referenceRef: string; referenceScope: string; compositionRef: string; evidence: string; correctedAmount: string; correctionRef: string; correctionScope: string; correctionDate: string; correctionApplies: Choice; conditions: Partial>; }; type LineResult = { id: string; status: 'determined' | 'not_applicable' | 'indeterminate'; baseAmount: string | null; vat: null; rate: null; comparison: 'below' | 'not_below' | null; reason: string; }; export type ValueReviewCalculation = { version: string; revision: number; calculatedAt: string; opportunity: string; location: string; sourceKey: string; source: string; inputs: ValueReviewRow[]; rows: LineResult[]; }; export type ValueReviewBook = { rows: ValueReviewRow[]; result?: ValueReviewCalculation; history?: ValueReviewCalculation[]; }; type Store = ReturnType & { valueReviews?: Record; }; export function newValueReview(id: string, currency: string): ValueReviewRow { return { id, treatment: '', priceContext: '', description: '', period: '', origin: 'projected', currency, operationRef: '', documentRef: '', documentDate: '', declaredAmount: '', referenceAmount: '', referenceRef: '', referenceScope: '', compositionRef: '', evidence: '', correctedAmount: '', correctionRef: '', correctionScope: '', correctionDate: '', correctionApplies: '', conditions: {}, }; } export function switchValueReview( row: ValueReviewRow, treatment: string, ): ValueReviewRow { return { ...row, treatment, correctionApplies: '' }; } export function switchPriceContext( row: ValueReviewRow, priceContext: string, ): ValueReviewRow { return { ...row, priceContext, conditions: { ...row.conditions, referenceCorresponds: '' }, correctionApplies: '', }; } export function switchValueOrigin( row: ValueReviewRow, origin: ValueReviewRow['origin'], ): ValueReviewRow { return { ...row, origin, correctionApplies: '' }; } function readStore(draft: Record): Store { const store = readAcquisitionStore(draft.modelo_a_snapshot) as Store; if ( store.valueReviews !== undefined && (!store.valueReviews || typeof store.valueReviews !== 'object' || Array.isArray(store.valueReviews)) ) throw new Error( 'Bases de contraprestação não recuperáveis;originais preservados.', ); const fields = Object.keys(newValueReview('', '')).filter( (key) => key !== 'conditions', ); for (const book of Object.values(store.valueReviews ?? {})) 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 ValueReviewRow] !== 'string', ) || !row.conditions || typeof row.conditions !== 'object' || Array.isArray(row.conditions), ) ) throw new Error( 'Linhas de contraprestação não recuperáveis;originais preservados,sem substituição por lista vazia.', ); return store; } export function valueReviewBook( draft: Record, ): ValueReviewBook { return ( readStore(draft).valueReviews?.[acquisitionScope(draft)] ?? { rows: [] } ); } export function saveValueReviewBook( draft: Record, book: ValueReviewBook, ): string { const current = readStore(draft), encoded = JSON.stringify({ ...current, valueReviews: { ...current.valueReviews, [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 evaluateValueReview( row: ValueReviewRow, currency: unknown, ): LineResult { const result: LineResult = { id: row.id, status: 'indeterminate', baseAmount: null, vat: null, rate: null, comparison: null, reason: '', }; const stop = ( reason: string, status: LineResult['status'] = 'indeterminate', ) => ({ ...result, status, reason }); if ( !valueReviewTreatments.some((item) => item.id === row.treatment) || !priceContexts.some((item) => item.id === row.priceContext) ) return stop( 'Identifique o percurso e o contexto do preço de referência;desconhecido não permite presumir correcçã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 expressos de projecção.', ); const failed = valueReviewConditions.filter( ([key]) => row.conditions[key] === 'no', ); if (failed.length) return stop( 'Não se aplica esta comparação: ' + failed.map(([, label]) => label).join('; ') + '. Não é isenção nem IVA zero.', 'not_applicable', ); if (valueReviewConditions.some(([key]) => row.conditions[key] !== 'yes')) return stop( 'Confirme as condições próprias da comparação;desconhecido não é zero,não aplicável ou correcção.', ); if ( !row.description.trim() || !row.operationRef.trim() || !row.documentRef.trim() || !validDate(row.documentDate) || !row.referenceRef.trim() || !row.referenceScope.trim() || !row.compositionRef.trim() || !row.evidence.trim() ) return stop( 'Identifique operação,documento/data,preço corrente/normal,referência/âmbito e composição,ou pressupostos expressos;sem avaliação automática/certificado/aprovador novo.', ); const declared = decimal(row.declaredAmount), reference = decimal(row.referenceAmount); if (declared === null || reference === null) return stop( 'Indique contraprestação e preço de referência comparáveis;valor desconhecido ou inválido não é zero.', ); result.comparison = declared < reference ? 'below' : 'not_below'; const comparison = declared < reference ? `Contraprestação ${money(declared)} inferior à referência ${money(reference)} por ${money(reference - declared)}MZN;AT pode proceder à correcção,sem automatismo.` : `Contraprestação ${money(declared)} não inferior à referência ${money(reference)}MZN;não se demonstra este pressuposto15(7).`; if (row.treatment === 'price_comparison') return { ...result, status: 'determined', baseAmount: money(declared), reason: comparison + ' Contraprestação declarada conservada como base neste recorte,sem correcção pelo portal;IVA/taxa,dedução/campo11,regularizações e totaisModeloA não determinados.', }; if (declared >= reference) return stop( comparison + ' A correcção deste percurso15(7)não é aplicada. Registo e referências conservados.', 'not_applicable', ); if (row.correctionApplies === 'no') return stop( comparison + ' A correcção referenciada não abrange esta operação/período/condições;sem aplicação automática.', 'not_applicable', ); if ( row.correctionApplies !== 'yes' || !row.correctionRef.trim() || !row.correctionScope.trim() || !validDate(row.correctionDate) ) return stop( comparison + ' Identifique referência,data e âmbito da correcção AT aplicável ou pressuposto expresso do cenário condicionado;comparar não substitui uma correcção.', ); const corrected = decimal(row.correctedAmount); if (corrected === null) return stop( comparison + ' Indique o montante da correcção referenciada ou pressuposto condicionado;sem usar automaticamente o maior valor ou a referência.', ); return { ...result, status: 'determined', baseAmount: money(corrected), reason: comparison + ` ${row.origin === 'projected' ? 'Cenário condicionado à correcção expressamente assumida,não decisão AT concedida' : 'Correcção AT nos dados efectivos declarados,com referência/data/âmbito identificados'}:base ${money(corrected)}MZN. Montante da referência não substituído automaticamente. Sem IVA/taxa,dedução/campo11,regularizações ou totaisModeloA.`, }; } function key(draft: Record, book: ValueReviewBook): string { return JSON.stringify({ version: valueReviewVersion, scope: acquisitionScope(draft), location: draft.selection_labels ?? {}, currency: draft.currency, rows: book.rows, }); } export function valueReviewCurrent( draft: Record, book: ValueReviewBook, ): boolean { return !!book.result && book.result.sourceKey === key(draft, book); } export function recordValueReviews( draft: Record, book: ValueReviewBook, ): ValueReviewBook { 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: ValueReviewCalculation = { version: valueReviewVersion, revision: (book.result?.revision ?? 0) + 1, calculatedAt: new Date().toISOString(), opportunity: acquisitionScope(draft), location: JSON.stringify(draft.selection_labels ?? {}), sourceKey: key(draft, book), source: valueReviewSource, inputs: structuredClone(book.rows), rows: book.rows.map((row) => evaluateValueReview(row, draft.currency)), }; const next = { ...book, result, history: [ ...(book.history ?? []), ...(book.result ? [book.result] : []), ], }; saveValueReviewBook(draft, next); return next; } export function valueReviewReportRows( draft: Record, ): Array<[string, string, string, string]> { try { const book = valueReviewBook(draft), result = book.result; if (!result) return []; return result.inputs.map((row, index) => [ 'IVA mensal', `Base P103 — ${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};${valueReviewCurrent(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'};${valueReviewTreatments.find((t) => t.id === row.treatment)?.label ?? 'Percurso por indicar'};contexto ${priceContexts.find((t) => t.id === row.priceContext)?.label ?? 'Por indicar'};operação ${row.operationRef};contraprestação ${row.declaredAmount};preço referência ${row.referenceAmount},${row.referenceRef};âmbito ${row.referenceScope};${row.treatment === 'referenced_correction' ? `correcção ${row.correctedAmount},referência ${row.correctionRef},data ${row.correctionDate},âmbito ${row.correctionScope},aplica:${row.correctionApplies === 'yes' ? 'Sim' : row.correctionApplies === 'no' ? 'Não' : 'Por indicar'}` : 'Sem correcção automática'};composição ${row.compositionRef};documento ${row.documentRef},${row.documentDate};condições ${valueReviewConditions.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', 'Comparação/correcção da contraprestação', 'Dados não recuperáveis', 'Originais preservados;sem substituir por zero.', ], ]; } }