Files
Investidor-V2/resources/js/lib/iva-beverage-allocation.ts
T

202 lines
7.5 KiB
TypeScript

import { inputText } from './input-text.ts';
import type { AcquisitionBook } from './iva-acquisitions.ts';
import { beverageParcel } from './iva-beverage-parcels.ts';
import {
addFractions,
decimalFraction,
fraction,
fractionLabel,
subtractFractions,
type FiscalFraction,
} from './fiscal-rational.ts';
/** Technical representation of disjoint VAT portions, not an additional fiscal condition. */
export type BeverageAllocation = {
from: string;
to: string;
reference: string;
deductionFrom: string;
deductionTo: string;
deductionReference: string;
};
type Source = { parcel: string; origin: string; vat: FiscalFraction };
export type BeverageRange = {
from: FiscalFraction;
to: FiscalFraction;
amount: FiscalFraction;
};
function boundary(raw: string): FiscalFraction {
const parts = /^(\d{1,40})\/(\d{1,40})$/.exec(raw);
if (!parts) return decimalFraction(raw);
if (BigInt(parts[2]) === 0n)
throw Error('Denominador nulo nos limites da parcela.');
return fraction(BigInt(parts[1]), BigInt(parts[2]));
}
export function beverageRange(
source: Source,
allocation?: BeverageAllocation,
): BeverageRange {
const from = allocation ? boundary(allocation.from) : fraction(0n),
to = allocation ? boundary(allocation.to) : source.vat,
amount = subtractFractions(to, from);
if (
BigInt(amount.numerator) <= 0n ||
BigInt(subtractFractions(source.vat, to).numerator) < 0n
)
throw Error(
'Limites da parcela fora do IVA documental; indique início inferior ao fim e fim até ao total da linha.',
);
if (allocation && !allocation.reference.trim())
throw Error('Identifique o suporte da repartição da linha.');
return { from, to, amount };
}
export function beverageRangesOverlap(
a: BeverageRange,
b: BeverageRange,
): boolean {
return (
BigInt(subtractFractions(a.to, b.from).numerator) > 0n &&
BigInt(subtractFractions(b.to, a.from).numerator) > 0n
);
}
export function beverageDeductionEvidence(
d: Record<string, unknown>,
source: Source,
): {
amount: FiscalFraction | null;
reason: string;
records: Array<{
context: string;
revision: number;
calculatedAt: string;
amount: FiscalFraction;
documents: string[];
}>;
} {
const store = JSON.parse(inputText(d.modelo_a_snapshot || '{}')),
books = (store.books ?? {}) as Record<string, AcquisitionBook>,
records: Array<{
context: string;
revision: number;
calculatedAt: string;
amount: FiscalFraction;
documents: string[];
}> = [];
for (const [context, book] of Object.entries(books))
for (const calc of [
...(book.history ?? []),
...(book.result ? [book.result] : []),
]) {
const inputs = calc.inputs.filter(
(row) =>
row.origin === 'effective' &&
source.origin === 'effective' &&
beverageParcel(row) === source.parcel,
);
const amounts: FiscalFraction[] = [];
for (const input of inputs) {
const result = calc.rows.find((r) => r.id === input.id);
if (!result || result.deductible === null) {
if (input.bc?.beveragePath === 'deduction')
return {
amount: null,
reason: 'Dedução efectiva anterior indeterminada; identificar montante exacto e documento antes da repartição.',
records,
};
continue;
}
const amount =
result.deductibleExact ??
decimalFraction(result.deductible);
if (BigInt(amount.numerator) > 0n) amounts.push(amount);
}
if (amounts.length)
records.push({
context,
revision: calc.revision,
calculatedAt: calc.calculatedAt,
amount: addFractions(amounts),
documents: inputs.map(
(row) =>
`${row.documentRef};linha${row.documentLine};${row.period}`,
),
});
}
const unique = [
...new Map(
records.map((r) => [fractionLabel(r.amount), r.amount]),
).values(),
];
if (unique.length > 1)
return {
amount: null,
reason: 'Montantes de dedução efectiva divergentes nas versões recuperadas; reconciliar a sequência e o fundamento das regularizações. Uma correcção preparada não liberta IVA para E.',
records,
};
return {
amount: unique[0] ?? fraction(0n),
reason: records.length
? 'Dedução efectiva declarada preservada; a sua afectação à parcela exige suporte expresso.'
: 'Sem dedução efectiva positiva identificada nos livros recuperados; não é consulta à conta corrente AT.',
records,
};
}
export function reconcileBeverageAllocation(
d: Record<string, unknown>,
source: Source,
allocation?: BeverageAllocation,
): {
range: BeverageRange | null;
prior: FiscalFraction | null;
reason: string;
} {
const evidence = beverageDeductionEvidence(d, source);
if (!evidence.amount)
return { range: null, prior: null, reason: evidence.reason };
try {
const range = beverageRange(source, allocation);
if (BigInt(evidence.amount.numerator) > 0n) {
if (!allocation || !allocation.deductionReference.trim())
return {
range: null,
prior: evidence.amount,
reason: 'Dedução efectiva anterior identificada; indique limites e suporte da parcela já deduzida, sem a apagar ou repetir em restituição.',
};
const deduction = beverageRange(source, {
...allocation,
from: allocation.deductionFrom,
to: allocation.deductionTo,
});
if (
BigInt(
subtractFractions(deduction.amount, evidence.amount)
.numerator,
) !== 0n ||
beverageRangesOverlap(range, deduction)
)
return {
range: null,
prior: evidence.amount,
reason: 'Parcela da dedução não coincide com o montante efectivo anterior ou sobrepõe-se à restituição; benefício duplicado impedido. Se toda a linha foi deduzida, é necessária regularização com fundamento próprio, ainda não automatizada nesta via.',
};
} else if (
allocation &&
(allocation.deductionFrom ||
allocation.deductionTo ||
allocation.deductionReference)
)
return {
range: null,
prior: evidence.amount,
reason: 'Foi indicada dedução anterior sem correspondência nos livros efectivos recuperados; resolver a divergência antes de calcular.',
};
return { range, prior: evidence.amount, reason: evidence.reason };
} catch (error) {
return {
range: null,
prior: evidence.amount,
reason: String(error instanceof Error ? error.message : error),
};
}
}