import { acquisitionScope } from './iva-acquisitions.ts'; import { operationTreatments, salesBook, salesCurrent, type SalesOperation, type SalesLineResult, } from './iva-operations.ts'; export const acquisitionRightVersion = 'acquisition-operation-right/1'; export const acquisitionRightSource = 'L2-A — CIVA19(1)(a)/(b)(i/iv/v),19(2),Lei13/2016 PDF15/p1426(273);Lei10/2025 PDF13/p2180(13):18(2),19(2),exclusão17-A. Condições da aquisição18/20/21 continuam próprias. Sem resolverP00105/06,importações,afectação parcial,excepções ou regularizações.'; export type AcquisitionRightLink = { version: 1; scopeId: string; operationId: string; fingerprint: string; scopeConfirmed: '' | 'yes' | 'no'; allocationRef: string; noExcludedPayments: '' | 'yes' | 'no'; }; export type AcquisitionEligibility = { status: 'eligible' | 'no_right' | 'indeterminate'; reason: string; source: string; operationId?: string; operationPeriod?: string; operationTreatment?: string; operationDescription?: string; operationArticle?: string; scopeId?: string; }; export function validAcquisitionRightLink( value: unknown, ): value is AcquisitionRightLink { if (!value || typeof value !== 'object' || Array.isArray(value)) return false; const v = value as AcquisitionRightLink; return ( v.version === 1 && ['scopeId', 'operationId', 'fingerprint', 'allocationRef'].every( (k) => typeof v[k as keyof AcquisitionRightLink] === 'string', ) && ['', 'yes', 'no'].includes(v.scopeConfirmed) && ['', 'yes', 'no'].includes(v.noExcludedPayments) ); } function fingerprint(row: SalesOperation, result: SalesLineResult | undefined) { return JSON.stringify({ version: acquisitionRightVersion, input: row, classification: result ? { article: result.article, source: result.source, status: result.status, } : null, }); } export function linkedBeverageDeductionRight( draft: Record, link: AcquisitionRightLink, notCoastalFishing: boolean, ): AcquisitionEligibility { const base = linkedOperationRight(draft, link), b = salesBook(draft), row = b.rows.find((r) => r.id === link.operationId), line = b.result?.rows.find((r) => r.id === link.operationId), rule = operationTreatments.find((t) => t.id === row?.treatment); if ( link.scopeId !== acquisitionScope(draft) || !salesCurrent(draft, b) || !row || !line || !rule || link.fingerprint !== fingerprint(row, line) || row.supplyCategory !== 'beverages' || ![ 'onboard_seagoing_ship', 'onboard_international_aircraft', 'onboard_rescue_coastal_ship', ].includes(row.treatment) || !row.description.trim() || !row.establishment.trim() || !row.evidence.trim() || rule.conditions.some((_, i) => !row.confirmed.includes(i)) || !/^2026-(0[1-9]|1[0-2])$/.test(row.period) || row.sourceCurrency !== 'MZN' || !/^\d{1,15}(\.\d{1,6})?$/.test(row.amount) || !['net', 'included'].includes(row.basis) || (row.treatment === 'onboard_rescue_coastal_ship' && !notCoastalFishing) ) return { ...base, status: 'indeterminate', reason: 'Correspondência própria13(2) por demonstrar em origemL1 actual:c/d/h,bebidas,condições e documento;pesca costeira conserva exclusão de provisões.', }; return { ...base, status: 'eligible', reason: 'Via de dedução13(2) de bebidas em13(1)(c/d/h),nas condições demonstradas;20(1)(d) não se aplica para este efeito. Não concede isenção directa nem restituição.', source: base.source + ';CIVA13(2–3),Lei13/2016 PDF12/p1426(270)', }; } export function acquisitionOperationChoices(draft: Record) { const book = salesBook(draft); return book.rows.map((row) => ({ id: row.id, label: `${row.description || row.id} — ${row.period} — ${operationTreatments.find((t) => t.id === row.treatment)?.label ?? row.treatment}`, row, current: salesCurrent(draft, book), })); } export function createAcquisitionRightLink( draft: Record, operationId: string, ): AcquisitionRightLink { const b = salesBook(draft), row = b.rows.find((r) => r.id === operationId); if (!row) throw Error( 'Operação de origem não encontrada nesta oportunidade;dados preservados.', ); return { version: 1, scopeId: acquisitionScope(draft), operationId, fingerprint: fingerprint( row, b.result?.rows.find((r) => r.id === operationId), ), scopeConfirmed: '', allocationRef: '', noExcludedPayments: '', }; } export function linkedOperationRight( draft: Record, link: AcquisitionRightLink, ): AcquisitionEligibility { const base: AcquisitionEligibility = { status: 'indeterminate', reason: '', source: acquisitionRightSource, operationId: link.operationId, scopeId: link.scopeId, }; const stop = ( reason: string, status: AcquisitionEligibility['status'] = 'indeterminate', ) => ({ ...base, status, reason }); if ( !validAcquisitionRightLink(link) || link.scopeId !== acquisitionScope(draft) ) return stop( 'Vínculo inválido ou pertencente a outra oportunidade;sem direito presumido.', ); const b = salesBook(draft), row = b.rows.find((r) => r.id === link.operationId), line = b.result?.rows.find((r) => r.id === link.operationId); if (!row) return stop( 'Operação ligada removida/não recuperável;escolha uma origem disponível,sem apagar o histórico.', ); base.operationPeriod = row.period; base.operationTreatment = row.treatment; base.operationDescription = row.description; base.operationArticle = line?.article; base.source += ';' + (line?.source ?? 'Fonte da classificação por recuperar'); if (!salesCurrent(draft, b) || !line || line.status === 'indeterminate') return stop( 'Classificação L1 de origem incompleta ou alterada;recupere/actualize a classificação,sem dedução por rótulo.', ); if (link.fingerprint !== fingerprint(row, line)) return stop( 'Operação de origem alterada:reveja e adopte explicitamente a origem actual;montantes e histórico da aquisição conservados.', ); const rule = operationTreatments.find((t) => t.id === row.treatment); if (!rule) return stop( 'Correspondência do direito ainda não identificada para esta operação.', ); if (/17-A/.test(rule.article)) return stop( 'Operação abrangida por17-A:19(2),redacçãoLei10/2025,não permite dedução apesar de ter IVA liquidado.', 'no_right', ); if (/19\(1\)\(b\)\((i|iv|v)\)/.test(rule.article)) return stop( 'Direito da operação identificado em' + rule.article + ';a aquisição continua sujeita às suas condições próprias.', 'eligible', ); if (line.status === 'calculated' && rule.rate === 16) return stop( 'Operação sujeita e não isenta:direito19(1)(a),sujeito a19(2) e condições próprias da aquisição.', 'eligible', ); // This is a bounded match to the already implemented article9 catalogue, after // checking its express article19 exceptions. Never use the ModeloA box as law. if (line.status === 'exempt' && /\b9\(/.test(rule.article)) return stop( 'Operação isenta do artigo9 fora das correspondências com direito identificadas19(1)(b);não confere dedução neste fundamento.', 'no_right', ); return stop( 'Classificação disponível,mas correspondência do direito não implementada neste percurso;não inferir direito pela caixa05/06/07 ou por IVAzero.', ); }