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

483 lines
17 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 { inputText } from './input-text.ts';
import { acquisitionBook, acquisitionCurrent } from './iva-acquisitions.ts';
import { beverageParcel } from './iva-beverage-parcels.ts';
import { closureFingerprint } from './iva-closure.ts';
import {
assessFiscalClaim,
newFiscalClaimEvents,
fiscalClaimDate,
type FiscalClaimEvents,
type FiscalClaimState,
} from './fiscal-claim-events.ts';
import {
decimalFraction,
fractionPresentation,
type FiscalFraction,
} from './fiscal-rational.ts';
import {
beverageRange,
beverageRangesOverlap,
beverageDeductionEvidence,
reconcileBeverageAllocation,
type BeverageAllocation,
} from './iva-beverage-allocation.ts';
export const beverageRestitutionSource =
'L3-E:CIVA13(1)(c/d/h),13(2–3),Lei13/2016 PDF12/p1426(270). Bebidas:dedução ou restituição,sem aplicar20(1)(d);não isenção directa. Provisões de pesca costeira excluídas. Linha integral ou parcelas documentadas sem sobreposição;dedução efectiva anterior reconciliada por montante e suporte,sem apagar históricos. Procedimento/formulário específico não automatizado;sem importar limites/prazos do reembolso geral21.';
export type BeverageRestitutionSource = {
parcel: string;
acquisitionId: string;
scope: string;
period: string;
origin: 'projected' | 'effective';
documentRef: string;
documentDate: string;
documentLine: string;
supplier: string;
vat: FiscalFraction;
fingerprint: string;
operationId: string;
operationRef: string;
calculatedAt: string;
};
export type BeverageRestitutionInput = FiscalClaimEvents & {
id: string;
source: BeverageRestitutionSource | null;
entityRef: string;
exclusive: '' | 'yes' | 'no';
allocation?: BeverageAllocation;
};
export type BeverageRestitutionResult = FiscalClaimState & {
id: string;
inputKey: string;
requested: FiscalFraction | null;
source: string;
};
type Calc = {
calculatedAt: string;
key: string;
inputs: BeverageRestitutionInput[];
rows: BeverageRestitutionResult[];
};
export type BeverageRestitutionBook = {
version: 1;
rows: BeverageRestitutionInput[];
result?: Calc;
history?: Calc[];
};
const scope = (d: Record<string, unknown>) =>
inputText(d.context_opportunity || 'general'),
norm = (s: string) => s.trim().toLocaleLowerCase();
function store(d: Record<string, unknown>): Record<string, unknown> {
const s = JSON.parse(
inputText(d.modelo_a_snapshot || '{"version":1,"books":{}}'),
);
if (s.version !== 1 || !s.books)
throw Error('Livros não recuperáveis;originais preservados.');
return s;
}
function maps(
d: Record<string, unknown>,
): Record<string, BeverageRestitutionBook> {
const m = store(d).ivaBeverageRestitutions;
if (m === undefined) return {};
if (!m || typeof m !== 'object' || Array.isArray(m))
throw Error(
'Mapa de restituições não recuperável;originais preservados.',
);
return m as Record<string, BeverageRestitutionBook>;
}
function validate(b: BeverageRestitutionBook): void {
if (
!b ||
b.version !== 1 ||
!Array.isArray(b.rows) ||
(b.history !== undefined && !Array.isArray(b.history)) ||
(b.result !== undefined &&
(!Array.isArray(b.result.rows) || !Array.isArray(b.result.inputs)))
)
throw Error('LivroE não recuperável;originais preservados.');
for (const r of b.rows)
if (
!r ||
![
'id',
'entityRef',
'requestReference',
'requestDate',
'decisionReference',
'decisionDate',
'decisionIssuer',
'approvedAmount',
].every(
(k) =>
typeof r[k as keyof BeverageRestitutionInput] === 'string',
) ||
(r.allocation !== undefined &&
(!r.allocation ||
![
'from',
'to',
'reference',
'deductionFrom',
'deductionTo',
'deductionReference',
].every(
(k) =>
typeof r.allocation![
k as keyof BeverageAllocation
] === 'string',
))) ||
!['', 'yes', 'no'].includes(r.exclusive) ||
!['preparation', 'submitted'].includes(r.stage) ||
!['pending', 'approved', 'partial', 'rejected'].includes(
r.decision,
) ||
!Array.isArray(r.receipts) ||
r.receipts.some(
(p) =>
!p ||
!['id', 'reference', 'date', 'amount'].every(
(k) => typeof p[k as keyof typeof p] === 'string',
),
)
)
throw Error('EntradasE não recuperáveis;sem apagar documentos.');
}
export function beverageRestitutionBook(
d: Record<string, unknown>,
): BeverageRestitutionBook {
const b = maps(d)[scope(d)] ?? { version: 1, rows: [] };
validate(b);
return b;
}
export function saveBeverageRestitutionBook(
d: Record<string, unknown>,
b: BeverageRestitutionBook,
): string {
validate(b);
const s = store(d);
return JSON.stringify({
...s,
ivaBeverageRestitutions: { ...maps(d), [scope(d)]: b },
});
}
export function beverageRestitutionSources(
d: Record<string, unknown>,
): BeverageRestitutionSource[] {
const b = acquisitionBook(d);
if (!acquisitionCurrent(d, b)) return [];
return b.rows.flatMap((row) => {
const result = b.result!.rows.find((r) => r.id === row.id);
if (
row.bc?.expense !== 'beverages' ||
row.bc.beveragePath !== 'refund' ||
!result?.reason.startsWith('Via restituição13(2)') ||
result.deductible !== '0.00' ||
!result.vat ||
!row.deductionLink
)
return [];
const vat = decimalFraction(result.vat);
if (BigInt(vat.numerator) <= 0n) return [];
return [
{
parcel: beverageParcel(row),
acquisitionId: row.id,
scope: scope(d),
period: row.period,
origin: row.origin,
documentRef: row.documentRef,
documentDate: row.documentDate,
documentLine: row.documentLine,
supplier: row.supplier,
vat,
fingerprint: closureFingerprint([row, result]),
operationId: row.deductionLink.operationId,
operationRef: row.deductionLink.allocationRef,
calculatedAt: b.result!.calculatedAt,
},
];
});
}
export function newBeverageRestitution(
id: string,
source: BeverageRestitutionSource | null,
entityRef = '',
): BeverageRestitutionInput {
return {
...newFiscalClaimEvents(),
id,
source: source ? structuredClone(source) : null,
entityRef,
exclusive: '',
};
}
function sameSource(
d: Record<string, unknown>,
r: BeverageRestitutionInput,
): boolean {
return (
!!r.source &&
beverageRestitutionSources(d).some(
(s) =>
s.scope === r.source!.scope &&
s.parcel === r.source!.parcel &&
s.fingerprint === r.source!.fingerprint,
)
);
}
function allUses(
d: Record<string, unknown>,
b: BeverageRestitutionBook,
): BeverageRestitutionInput[] {
return [
...b.rows,
...Object.entries(maps(d))
.filter(([id]) => id !== scope(d))
.flatMap(([, v]) => {
validate(v);
return v.rows.filter((r) => r.source?.origin === 'effective');
}),
];
}
function past(
d: Record<string, unknown>,
b: BeverageRestitutionBook,
): BeverageRestitutionInput[] {
return Object.entries({ ...maps(d), [scope(d)]: b }).flatMap(
([context, v]) =>
[...(v.history ?? []), ...(v.result ? [v.result] : [])].flatMap(
(c) =>
c.inputs.filter(
(r) =>
(context === scope(d) ||
r.source?.origin === 'effective') &&
c.rows.some(
(v) =>
v.id === r.id &&
v.status === 'determined' &&
v.requested &&
BigInt(v.requested.numerator) > 0n,
),
),
),
);
}
function committedParcel(r: BeverageRestitutionInput): string {
if (!r.source) return '';
try {
const range = beverageRange(r.source, r.allocation);
return closureFingerprint([
r.source.parcel,
r.source.origin,
r.source.vat,
range.from,
range.to,
]);
} catch {
return 'invalid';
}
}
const receiptKey = (
r: BeverageRestitutionInput,
p: FiscalClaimEvents['receipts'][number],
) => JSON.stringify([norm(r.entityRef), r.source?.origin, norm(p.reference)]);
function key(d: Record<string, unknown>, b: BeverageRestitutionBook): string {
return closureFingerprint([
b.rows,
b.rows.map((r) => [
sameSource(d, r),
r.source ? beverageDeductionEvidence(d, r.source) : null,
]),
allUses(d, b).map((r) => [
r.id,
r.source,
r.allocation,
r.receipts.map((p) => receiptKey(r, p)),
]),
past(d, b)
.filter(
(r) =>
r.stage === 'submitted' ||
r.decision !== 'pending' ||
r.receipts.length,
)
.map((r) => [
r.id,
r.source,
r.allocation,
r.receipts.map((p) => receiptKey(r, p)),
]),
]);
}
export function beverageRestitutionCurrent(
d: Record<string, unknown>,
b: BeverageRestitutionBook,
): boolean {
return (
!!b.result &&
b.result.key === key(d, b) &&
b.rows.length === b.result.rows.length &&
b.rows.every((r) =>
b.result!.rows.some(
(v) => v.id === r.id && v.inputKey === closureFingerprint(r),
),
)
);
}
export function recordBeverageRestitution(
d: Record<string, unknown>,
b: BeverageRestitutionBook,
now = new Date().toISOString(),
): BeverageRestitutionBook {
validate(b);
if (new Set(b.rows.map((r) => r.id)).size !== b.rows.length)
throw Error('IdentidadeE repetida.');
const uses = allUses(d, b),
history = past(d, b);
const rows = b.rows.map((row) => {
const r: BeverageRestitutionResult = {
id: row.id,
inputKey: closureFingerprint(row),
status: 'indeterminate',
reason: '',
requested: null,
approved: null,
received: null,
awaitingReceipt: null,
notGranted: null,
source: beverageRestitutionSource,
},
stop = (reason: string) => ({ ...r, reason }),
s = row.source;
if (
!s ||
!sameSource(d, row) ||
d.currency !== 'MZN' ||
s.scope !== scope(d)
)
return stop(
'OrigemL1/L2 alterada ou ausente;sem restituição presumida.',
);
if (!row.entityRef.trim() || row.exclusive !== 'yes')
return stop(
'Identifique entidade e confirme a parcela exclusivamente para restituição;sem dupla utilização.',
);
const reconciled = reconcileBeverageAllocation(d, s, row.allocation);
if (!reconciled.range) return stop(reconciled.reason);
const related = (v: BeverageRestitutionInput) =>
v.source?.origin === s.origin && v.source?.parcel === s.parcel;
const overlaps = (v: BeverageRestitutionInput) => {
try {
return beverageRangesOverlap(
reconciled.range!,
beverageRange(v.source!, v.allocation),
);
} catch {
return true;
}
};
if (
uses.some((v) => v !== row && related(v) && overlaps(v)) ||
history.some(
(v) =>
related(v) &&
(v.stage === 'submitted' ||
v.decision !== 'pending' ||
v.receipts.length) &&
(v.id !== row.id
? overlaps(v)
: committedParcel(v) !== committedParcel(row)),
)
)
return stop(
'Parcela documental já ligada a pedido ou histórico comprometido;limites sobrepostos ou alterados. Sem duplicar nem libertar IVA por apagar a preparação.',
);
r.requested = reconciled.range.amount;
if (
!fiscalClaimDate(row.requestDate) ||
row.requestDate < s.documentDate
)
return stop(
'Data do pedido/preparação anterior ao documento ou por identificar.',
);
const events = assessFiscalClaim(
row,
reconciled.range.amount,
(p) =>
uses.flatMap((v) =>
v.receipts.filter(
(q) => receiptKey(v, q) === receiptKey(row, p),
),
).length > 1 ||
history.some(
(v) =>
v.id !== row.id &&
v.receipts.some(
(q) => receiptKey(v, q) === receiptKey(row, p),
),
),
);
Object.assign(r, events);
if (r.status === 'determined')
r.reason +=
' Linha13(2) fora da deduçãoL2 e do crédito geralD;pedido não é isenção directa nem concessão automática. Decisão e recebimento registados fora doInvesteMoz;procedimento/formulário específico e prazos não automatizados,sem os importar deD.';
return r;
});
const result = {
calculatedAt: now,
key: '',
inputs: structuredClone(b.rows),
rows,
};
const next = {
...b,
...(b.result
? { history: [...(b.history ?? []), structuredClone(b.result)] }
: {}),
result,
};
result.key = key(d, next);
return next;
}
export function beverageRestitutionReportRows(
d: Record<string, unknown>,
): Array<[string, string, string, string]> {
try {
const b = beverageRestitutionBook(d);
if (!b.rows.length) return [];
const out: Array<[string, string, string, string]> = [
[
'IVA mensal',
'L3-E — restituição de bebidas',
beverageRestitutionCurrent(d, b)
? 'Resultado correspondente às entradas'
: 'Histórico preservado;rever origem/parcela',
beverageRestitutionSource,
],
],
fmt = (v: FiscalFraction | null) =>
v ? fractionPresentation(v).decimal : 'Indeterminado';
for (const r of b.result?.rows ?? []) {
const i = b.result!.inputs.find((v) => v.id === r.id),
s = i?.source;
out.push([
'IVA mensal',
`Restituição de bebidas — ${s?.period ?? ''}`,
`IVA documental:${fmt(s?.vat ?? null)};parcelaIVA:${fmt(r.requested)};dedução desta parcela:${r.requested ? '0pela via exclusivaE' : 'Indeterminada;ver origem'};reconhecido:${fmt(r.approved)};recebido:${fmt(r.received)};aguarda recebimento:${fmt(r.awaitingReceipt)}`,
`${i?.entityRef ?? ''};${scope(d)};${s?.origin === 'effective' ? 'Dados efectivos declarados' : 'Pressuposto de simulação'};${s?.documentRef ?? ''};${s?.documentDate ?? ''};linha${s?.documentLine ?? ''};operaçãoL1${s?.operationId ?? ''};${s?.operationRef ?? ''};pedido${i?.requestReference ?? ''},${i?.requestDate ?? ''};decisão${i?.decisionReference ?? ''},${i?.decisionDate ?? ''},${i?.decisionIssuer ?? ''};recibos:${i?.receipts.map((p) => `${p.reference};${p.date};${p.amount}MZN`).join(' | ') || 'nenhum registado'};${i?.allocation ? `repartiçãoIVA[${i.allocation.from},${i.allocation.to}[;suporte${i.allocation.reference};dedução anterior[${i.allocation.deductionFrom},${i.allocation.deductionTo}[;${i.allocation.deductionReference};` : ''}${r.reason}`,
]);
}
return out;
} catch {
return [
[
'IVA mensal',
'L3-E',
'Restituição não recuperável',
'Originais preservados;sem dedução/restituição presumidas.',
],
];
}
}