import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts'; export const goodsAllocationVersion = 'goods-allocation-base/1'; export const goodsAllocationSource = 'CIVA3(3)e/f,Lei13/2016PDF6–7/p1426(264–265):afectação permanente de bens a uso próprio/particular/alheio,transmissão gratuita,afectação a sector isento ou ao activo imobilizado de bens20(1)a,com dedução total/parcial anterior nos bens/elementos;excepção de amostras/ofertas de pequeno valor na alíneae. Art15(2)b,PDF13/p1426(271):preço de aquisição ou,na sua falta,preço de custo,reportados ao momento da operação. Art20(1)a,PDF15/p1426(273). Lei22/2022PDF3mantém15(2)b e20(1)a;Lei10/2025mantém3(3)e/f e não altera15/20. Recorte2026/MZN,sem nova dedução/campo11,regularização,taxa,IVA ou totaisModeloA. Uso temporário de bens como serviçoP089 é distinto.'; export const goodsAllocationTreatments = [ { id: 'permanent_private_goods', label: 'Afectação permanente a uso próprio,particular ou fins alheios', clause: 'e', }, { id: 'free_goods_transfer', label: 'Transmissão gratuita de bens', clause: 'e', }, { id: 'exempt_sector_goods', label: 'Afectação de bens a sector de actividade isento', clause: 'f', }, { id: 'fixed_asset_20a', label: 'Afectação ao activo imobilizado de bens20(1)a', clause: 'f', }, ] as const; export const goodsAllocationConditions = [ [ 'businessGoods', 'São bens da empresa identificados,não uma prestação de serviços ou venda onerosa normal', ], [ 'priorDeduction', 'Houve dedução total ou parcial anterior nos bens ou elementos que os constituem,com referência histórica', ], [ 'factsCorrespond', 'Objecto,destino,operação,data eâmbito correspondem ao percurso identificado', ], ] as const; export const allocationOwnConditions = { permanent_private_goods: [ [ 'permanentAllocation', 'A afectação dos bens é permanente,não mero uso temporário', ], [ 'privatePurpose', 'Uso próprio do titular,pessoal ou fins alheios à empresa identificado', ], [ 'notSmallGift', 'Não se trata de amostra ou oferta de pequeno valor abrangida pela excepção3(3)e', ], ], free_goods_transfer: [ [ 'gratuitousTransfer', 'Está identificada uma transmissão gratuita dos bens', ], [ 'notSmallGift', 'Não se trata de amostra ou oferta de pequeno valor abrangida pela excepção3(3)e', ], ], exempt_sector_goods: [ [ 'exemptSector', 'Os bens são afectados ao sector de actividade isento identificado', ], ], fixed_asset_20a: [ ['fixedAsset', 'Está identificada a afectação ao activo imobilizado'], [ 'goods20a', 'Os bens correspondem aos referidos em20(1)a,com âmbito e referência identificados', ], ], } as const; export function allocationSpecificConditions( treatment: string, ): ReadonlyArray { return ( allocationOwnConditions[ treatment as keyof typeof allocationOwnConditions ] ?? [] ); } type Choice = '' | 'yes' | 'no'; export type GoodsAllocationRow = { id: string; treatment: string; description: string; period: string; origin: 'projected' | 'effective'; currency: string; operationDate: string; operationRef: string; documentRef: string; documentDate: string; goodsRef: string; deductionRef: string; destinationRef: string; exceptionRef: string; asset20aRef: string; valuationMode: string; acquisitionPrice: string; acquisitionRef: string; currentCost: string; costRef: string; valuationDate: string; valuationScope: string; evidence: string; conditions: Partial>; }; type LineResult = { id: string; status: 'determined' | 'not_applicable' | 'indeterminate'; classification: 'assimilated_goods' | 'not_applicable' | 'indeterminate'; baseAmount: string | null; vat: null; rate: null; reason: string; }; export type GoodsAllocationCalculation = { version: string; revision: number; calculatedAt: string; opportunity: string; location: string; sourceKey: string; source: string; inputs: GoodsAllocationRow[]; rows: LineResult[]; }; export type GoodsAllocationBook = { rows: GoodsAllocationRow[]; result?: GoodsAllocationCalculation; history?: GoodsAllocationCalculation[]; }; type Store = ReturnType & { regulatedGoodsAllocations?: Record; }; export function newGoodsAllocation( id: string, currency: string, ): GoodsAllocationRow { return { id, treatment: '', description: '', period: '', origin: 'projected', currency, operationDate: '', operationRef: '', documentRef: '', documentDate: '', goodsRef: '', deductionRef: '', destinationRef: '', exceptionRef: '', asset20aRef: '', valuationMode: '', acquisitionPrice: '', acquisitionRef: '', currentCost: '', costRef: '', valuationDate: '', valuationScope: '', evidence: '', conditions: {}, }; } export function switchAllocationTreatment( row: GoodsAllocationRow, treatment: string, ): GoodsAllocationRow { return { ...row, treatment, conditions: Object.fromEntries( goodsAllocationConditions.map(([key]) => [ key, row.conditions[key] ?? '', ]), ), }; } export function switchAllocationValuation( row: GoodsAllocationRow, valuationMode: string, ): GoodsAllocationRow { return { ...row, valuationMode, conditions: { ...row.conditions, valuationConfirmed: '', acquisitionUnavailable: '', }, }; } function readStore(draft: Record): Store { const store = readAcquisitionStore(draft.modelo_a_snapshot) as Store; if ( store.regulatedGoodsAllocations !== undefined && (!store.regulatedGoodsAllocations || typeof store.regulatedGoodsAllocations !== 'object' || Array.isArray(store.regulatedGoodsAllocations)) ) throw new Error( 'Afectações de bens não recuperáveis;originais preservados.', ); const fields = Object.keys(newGoodsAllocation('', '')).filter( (key) => key !== 'conditions', ); for (const book of Object.values(store.regulatedGoodsAllocations ?? {})) 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 GoodsAllocationRow] !== 'string', ) || !row.conditions || typeof row.conditions !== 'object' || Array.isArray(row.conditions), ) ) throw new Error( 'Linhas de afectações de bens não recuperáveis;originais preservados,sem substituição por lista vazia.', ); return store; } export function goodsAllocationBook( draft: Record, ): GoodsAllocationBook { return ( readStore(draft).regulatedGoodsAllocations?.[ acquisitionScope(draft) ] ?? { rows: [] } ); } export function saveGoodsAllocationBook( draft: Record, book: GoodsAllocationBook, ): string { const current = readStore(draft), encoded = JSON.stringify({ ...current, regulatedGoodsAllocations: { ...current.regulatedGoodsAllocations, [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 evaluateGoodsAllocation( row: GoodsAllocationRow, currency: unknown, ): LineResult { const result: LineResult = { id: row.id, status: 'indeterminate', classification: 'indeterminate', baseAmount: null, vat: null, rate: null, reason: '', }; const stop = ( reason: string, status: LineResult['status'] = 'indeterminate', ) => ({ ...result, status, classification: status === 'not_applicable' ? ('not_applicable' as const) : result.classification, reason, }); const rule = goodsAllocationTreatments.find((t) => t.id === row.treatment); if (!rule) return stop( 'Identifique afectação permanente,transmissão gratuita,sector isento ou activo imobilizado20(1)a.', ); if ( !/^2026-(0[1-9]|1[0-2])$/.test(row.period) || !['projected', 'effective'].includes(row.origin) || !validDate(row.operationDate) || row.operationDate.slice(0, 7) !== row.period ) return stop( 'Identifique mês2026,data correspondente e dados efectivos ou pressupostos expressos.', ); const conditions = [ ...goodsAllocationConditions, ...allocationSpecificConditions(row.treatment), ]; if (conditions.some(([key]) => row.conditions[key] === 'no')) return stop( 'Este percurso de transmissão assimilada não se aplica aos factos declarados;conferir natureza,dedução histórica ou excepção,não presumir isenção/IVAzero.', 'not_applicable', ); if (conditions.some(([key]) => row.conditions[key] !== 'yes')) return stop( 'Factos próprios ou dedução anterior por esclarecer;desconhecido não é não aplicável ou zero. Dedução histórica não é nova dedução concedida.', ); if ( !row.description.trim() || !row.operationRef.trim() || !row.documentRef.trim() || !validDate(row.documentDate) || !row.goodsRef.trim() || !row.deductionRef.trim() || !row.destinationRef.trim() || !row.evidence.trim() ) return stop( 'Identifique bens,operação,documento/data,dedução histórica,destino eâmbito factual ou pressupostos expressos;reutilize referências,sem certificado/aprovador novo.', ); if (rule.clause === 'e' && !row.exceptionRef.trim()) return stop( 'Identifique os factos/referência que distinguem a operação da excepção de amostras/ofertas de pequeno valor,ou pressuposto expresso;sem limiar inventado.', ); if (row.treatment === 'fixed_asset_20a' && !row.asset20aRef.trim()) return stop( 'Identifique enquadramento dos bens20(1)a eafectação ao activo imobilizado,com referência/âmbito.', ); result.classification = 'assimilated_goods'; if (currency !== 'MZN' || row.currency !== currency) return stop( 'Operação3(3)' + rule.clause + ' identificada;moeda fora do recorteMZN ou divergente,base indeterminada,sem conversão silenciosa.', ); if (!['acquisition', 'cost'].includes(row.valuationMode)) return stop( 'Operação3(3)' + rule.clause + ' identificada;base15(2)b por esclarecer:preço aquisição ou,na sua falta demonstrada,preço custo,reportados ao momento da operação.', ); if ( row.conditions.valuationConfirmed !== 'yes' || !validDate(row.valuationDate) || row.valuationDate !== row.operationDate || !row.valuationScope.trim() ) return stop( 'Operação assimilada identificada;base por esclarecer. Confirme preço de aquisição/custo reportado ao momento desta operação,data/âmbito ecomposição sem próprioIVA;não usar custo histórico automaticamente.', ); if ( row.valuationMode === 'cost' && row.conditions.acquisitionUnavailable !== 'yes' ) return stop( 'Operação assimilada identificada;não substituir aquisição desconhecida por custo. O custo15(2)b só é usado na falta demonstrada de preço aquisição;conferir esse facto.', ); const raw = row.valuationMode === 'acquisition' ? row.acquisitionPrice : row.currentCost, ref = row.valuationMode === 'acquisition' ? row.acquisitionRef : row.costRef; const amount = decimal(raw); if (amount === null || !ref.trim()) return stop( 'Operação assimilada identificada;indique o preço e referência correspondentes ao modo escolhido,reportados ao momento da operação;desconhecido não é zero.', ); return { ...result, status: 'determined', baseAmount: money(amount), reason: `P124:transmissão assimilada3(3)${rule.clause};P088:base15(2)b=${money(amount)}MZN,${row.valuationMode === 'acquisition' ? 'preço aquisição' : 'preço custo,na falta demonstrada de preço aquisição'},reportado a${row.operationDate},${row.origin === 'projected' ? 'sob pressupostos expressos' : 'segundo dados efectivos declarados'}. Dedução anterior total/parcial é facto histórico dos bens/elementos,não nova dedução/campo11. Não confundir afectação/transmissão de bens comserviço por uso temporárioP089. Sem calcular IVA,taxa,regularização ou totaisModeloA.`, }; } function key( draft: Record, book: GoodsAllocationBook, ): string { return JSON.stringify({ version: goodsAllocationVersion, scope: acquisitionScope(draft), location: draft.selection_labels ?? {}, currency: draft.currency, rows: book.rows, }); } export function goodsAllocationCurrent( draft: Record, book: GoodsAllocationBook, ): boolean { return !!book.result && book.result.sourceKey === key(draft, book); } export function recordGoodsAllocations( draft: Record, book: GoodsAllocationBook, ): GoodsAllocationBook { 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: GoodsAllocationCalculation = { version: goodsAllocationVersion, revision: (book.result?.revision ?? 0) + 1, calculatedAt: new Date().toISOString(), opportunity: acquisitionScope(draft), location: JSON.stringify(draft.selection_labels ?? {}), sourceKey: key(draft, book), source: goodsAllocationSource, inputs: structuredClone(book.rows), rows: book.rows.map((row) => evaluateGoodsAllocation(row, draft.currency), ), }; const next = { ...book, result, history: [ ...(book.history ?? []), ...(book.result ? [book.result] : []), ], }; saveGoodsAllocationBook(draft, next); return next; } export function goodsAllocationReportRows( draft: Record, ): Array<[string, string, string, string]> { try { const book = goodsAllocationBook(draft), result = book.result; if (!result) return []; return result.inputs.map((row, index) => { const line = result.rows[index], rule = goodsAllocationTreatments.find( (t) => t.id === row.treatment, ); return [ 'IVA mensal', `Afectação P124/baseP088 — ${row.period} — ${row.id}`, `${row.description};classificação:${line.classification === 'assimilated_goods' ? 'Transmissão assimilada de bens' : line.classification === 'not_applicable' ? 'Percurso não aplicável' : 'Indeterminada'};base:${line.baseAmount ?? 'Indeterminada'} ${row.currency};IVA: Indeterminado;${line.reason}`, `${result.opportunity};${result.location};${goodsAllocationCurrent(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'};${rule?.label ?? 'Percurso por indicar'};operação ${row.operationRef},${row.operationDate};bens ${row.goodsRef};dedução histórica ${row.deductionRef};destino ${row.destinationRef};${rule?.clause === 'e' ? `excepção amostras/ofertas ${row.exceptionRef};` : ''}${row.treatment === 'fixed_asset_20a' ? `enquadramento20(1)a ${row.asset20aRef};` : ''}base ${row.valuationMode};${row.valuationMode === 'cost' ? `custo ${row.currentCost},${row.costRef};falta aquisição ${row.conditions.acquisitionUnavailable ?? 'por indicar'}` : `aquisição ${row.acquisitionPrice},${row.acquisitionRef}`};data/âmbito base ${row.valuationDate},${row.valuationScope};documento ${row.documentRef},${row.documentDate};condições ${[...goodsAllocationConditions, ...allocationSpecificConditions(row.treatment)].map(([key, label]) => label + ': ' + (row.conditions[key] === 'yes' ? 'Sim' : row.conditions[key] === 'no' ? 'Não' : 'Por indicar')).join('; ')};fundamento ${row.evidence};sem nova dedução/campo11/IVA/regularização/totaisModeloA.`, ]; }); } catch { return [ [ 'IVA mensal', 'Afectação de bens', 'Dados não recuperáveis', 'Originais preservados;sem base ou classificação por defeito.', ], ]; } }