Files
Investidor-V2/resources/js/lib/iva-acquisition-right.ts
T

213 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string, unknown>,
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<string, unknown>) {
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<string, unknown>,
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<string, unknown>,
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.',
);
}