643 lines
23 KiB
TypeScript
643 lines
23 KiB
TypeScript
import { inputText } from './input-text.ts';
|
||
import type {
|
||
AcquisitionCalculation,
|
||
Acquisition,
|
||
AcquisitionResult,
|
||
} from './iva-acquisitions.ts';
|
||
import {
|
||
addFractions,
|
||
decimalFraction,
|
||
fraction,
|
||
fractionLabel,
|
||
subtractFractions,
|
||
positiveFraction,
|
||
type FiscalFraction,
|
||
} from './fiscal-rational.ts';
|
||
|
||
export const regularizationSource =
|
||
'L2-E: CIVA22(4–8),Lei13/2016 PDF16/p1426(274),acerto anual no último período do ano;CIVA51(1/4),PDF21/p1426(279),rectificação da dedução pelo adquirente até ao fim do período seguinte à recepção. Decreto7/2008,RCIVA27,BR16 PDF5/p103,registo individual de activos: data,IVA suportado e deduzido. Caixas ModeloA12/13;divergência de remissões textuais11/12 conservada,sem alterar P001/05–06.';
|
||
type Choice = '' | 'yes' | 'no';
|
||
export type RegularizationEvent = {
|
||
id: string;
|
||
kind: 'annual' | 'purchase_correction';
|
||
period: string;
|
||
source: AcquisitionCalculation | null;
|
||
rowIds: string[];
|
||
numerator: string;
|
||
denominator: string;
|
||
reference: string;
|
||
receivedDate: string;
|
||
correctedDeductible: string;
|
||
reason: string;
|
||
facts: Record<string, Choice>;
|
||
};
|
||
export type RegularizationResult = {
|
||
id: string;
|
||
period: string;
|
||
status: 'determined' | 'indeterminate';
|
||
reason: string;
|
||
delta: FiscalFraction | null;
|
||
rawRatio?: FiscalFraction;
|
||
ratio?: FiscalFraction;
|
||
details: string;
|
||
source: string;
|
||
};
|
||
type Calculation = {
|
||
version: 1;
|
||
calculatedAt: string;
|
||
key: string;
|
||
events: RegularizationResult[];
|
||
};
|
||
export type RegularizationBook = {
|
||
version: 1;
|
||
events: RegularizationEvent[];
|
||
completePeriods: string[];
|
||
result?: Calculation;
|
||
history?: Calculation[];
|
||
};
|
||
type FrozenLine = { input: Acquisition; result: AcquisitionResult };
|
||
const scope = (draft: Record<string, unknown>) =>
|
||
inputText(draft.context_opportunity || 'general');
|
||
function store(draft: Record<string, unknown>): Record<string, unknown> {
|
||
const s = JSON.parse(
|
||
inputText(draft.modelo_a_snapshot || '{"version":1,"books":{}}'),
|
||
);
|
||
if (s.version !== 1 || !s.books)
|
||
throw Error('Origem fiscal não recuperável;conserve os originais.');
|
||
return s;
|
||
}
|
||
export function validateRegularizationStore(s: Record<string, unknown>): void {
|
||
if (s.regularizations === undefined) return;
|
||
if (
|
||
!s.regularizations ||
|
||
typeof s.regularizations !== 'object' ||
|
||
Array.isArray(s.regularizations)
|
||
)
|
||
throw Error(
|
||
'Mapa de regularizações não recuperável;originais preservados.',
|
||
);
|
||
for (const value of Object.values(s.regularizations))
|
||
assertRegularizationBook(value as RegularizationBook);
|
||
}
|
||
function assertRegularizationBook(value: RegularizationBook): void {
|
||
const badSource = (s: AcquisitionCalculation | null) =>
|
||
s !== null &&
|
||
(!s ||
|
||
typeof s !== 'object' ||
|
||
!Array.isArray(s.inputs) ||
|
||
!Array.isArray(s.rows) ||
|
||
!Array.isArray(s.periods) ||
|
||
!Number.isInteger(s.revision) ||
|
||
!['currency', 'opportunity', 'calculatedAt', 'sourceKey'].every(
|
||
(k) => typeof s[k as keyof AcquisitionCalculation] === 'string',
|
||
));
|
||
if (
|
||
!value ||
|
||
value.version !== 1 ||
|
||
!Array.isArray(value.events) ||
|
||
!Array.isArray(value.completePeriods) ||
|
||
value.completePeriods.some((p) => typeof p !== 'string') ||
|
||
(value.history !== undefined && !Array.isArray(value.history)) ||
|
||
value.events.some(
|
||
(e) =>
|
||
!e ||
|
||
!['annual', 'purchase_correction'].includes(e.kind) ||
|
||
![
|
||
'id',
|
||
'period',
|
||
'numerator',
|
||
'denominator',
|
||
'reference',
|
||
'receivedDate',
|
||
'correctedDeductible',
|
||
'reason',
|
||
].every(
|
||
(k) =>
|
||
typeof e[k as keyof RegularizationEvent] === 'string',
|
||
) ||
|
||
!Array.isArray(e.rowIds) ||
|
||
e.rowIds.some((id) => typeof id !== 'string') ||
|
||
!e.facts ||
|
||
typeof e.facts !== 'object' ||
|
||
Array.isArray(e.facts) ||
|
||
Object.values(e.facts).some(
|
||
(v) => !['', 'yes', 'no'].includes(v),
|
||
) ||
|
||
badSource(e.source),
|
||
)
|
||
)
|
||
throw Error(
|
||
'Regularizações não recuperáveis;originais preservados,sem substituir por zero.',
|
||
);
|
||
if (
|
||
value.result &&
|
||
(!Array.isArray(value.result.events) ||
|
||
value.result.version !== 1 ||
|
||
typeof value.result.key !== 'string' ||
|
||
typeof value.result.calculatedAt !== 'string' ||
|
||
value.result.events.some(
|
||
(e) =>
|
||
!e ||
|
||
typeof e.id !== 'string' ||
|
||
typeof e.period !== 'string' ||
|
||
!['determined', 'indeterminate'].includes(e.status) ||
|
||
(e.delta !== null &&
|
||
(!e.delta ||
|
||
!/^-?\d{1,80}$/.test(e.delta.numerator) ||
|
||
!/^\d{1,80}$/.test(e.delta.denominator) ||
|
||
BigInt(e.delta.denominator) === 0n)),
|
||
))
|
||
)
|
||
throw Error(
|
||
'Resultado de regularizações não recuperável;originais preservados.',
|
||
);
|
||
}
|
||
export function regularizationBook(
|
||
draft: Record<string, unknown>,
|
||
): RegularizationBook {
|
||
const value = (
|
||
store(draft).regularizations as
|
||
| Record<string, RegularizationBook>
|
||
| undefined
|
||
)?.[scope(draft)];
|
||
if (!value) return { version: 1, events: [], completePeriods: [] };
|
||
assertRegularizationBook(value);
|
||
return value;
|
||
}
|
||
export function saveRegularizationBook(
|
||
draft: Record<string, unknown>,
|
||
book: RegularizationBook,
|
||
): string {
|
||
const s = store(draft);
|
||
return JSON.stringify({
|
||
...s,
|
||
regularizations: {
|
||
...(s.regularizations as object),
|
||
[scope(draft)]: book,
|
||
},
|
||
});
|
||
}
|
||
export function regularizationSources(
|
||
draft: Record<string, unknown>,
|
||
): AcquisitionCalculation[] {
|
||
const b = (
|
||
store(draft).books as Record<
|
||
string,
|
||
{
|
||
result?: AcquisitionCalculation;
|
||
history?: AcquisitionCalculation[];
|
||
}
|
||
>
|
||
)[scope(draft)];
|
||
return b
|
||
? [...(b.history ?? []), ...(b.result ? [b.result] : [])].filter(
|
||
(v, i, a) =>
|
||
a.findIndex(
|
||
(x) => JSON.stringify(x) === JSON.stringify(v),
|
||
) === i,
|
||
)
|
||
: [];
|
||
}
|
||
export function newRegularization(
|
||
id: string,
|
||
kind: RegularizationEvent['kind'],
|
||
source: AcquisitionCalculation | null,
|
||
): RegularizationEvent {
|
||
return {
|
||
id,
|
||
kind,
|
||
period: source ? source.inputs[0]?.period.slice(0, 4) + '-12' : '',
|
||
source: source ? structuredClone(source) : null,
|
||
rowIds:
|
||
source?.inputs
|
||
.filter((r) =>
|
||
kind === 'annual' ? r.d?.method === 'pro_rata' : true,
|
||
)
|
||
.map((r) => r.id) ?? [],
|
||
numerator: '',
|
||
denominator: '',
|
||
reference: '',
|
||
receivedDate: '',
|
||
correctedDeductible: '',
|
||
reason: '',
|
||
facts: {},
|
||
};
|
||
}
|
||
function frozenLines(e: RegularizationEvent): FrozenLine[] {
|
||
return e.rowIds.map((id) => ({
|
||
input: e.source!.inputs.find((r) => r.id === id)!,
|
||
result: e.source!.rows.find((r) => r.id === id)!,
|
||
}));
|
||
}
|
||
function sourceAvailable(
|
||
draft: Record<string, unknown>,
|
||
e: RegularizationEvent,
|
||
): boolean {
|
||
return (
|
||
!!e.source &&
|
||
regularizationSources(draft).some(
|
||
(s) => JSON.stringify(s) === JSON.stringify(e.source),
|
||
)
|
||
);
|
||
}
|
||
function multiply(a: FiscalFraction, b: FiscalFraction) {
|
||
return fraction(
|
||
BigInt(a.numerator) * BigInt(b.numerator),
|
||
BigInt(a.denominator) * BigInt(b.denominator),
|
||
);
|
||
}
|
||
const validDate = (s: string) =>
|
||
/^2026-\d{2}-\d{2}$/.test(s) &&
|
||
!Number.isNaN(Date.parse(s)) &&
|
||
new Date(s).toISOString().slice(0, 10) === s;
|
||
function identity(e: RegularizationEvent, id: string): string {
|
||
const r = e.source?.inputs.find((r) => r.id === id);
|
||
return JSON.stringify([
|
||
e.kind,
|
||
r?.supplier.trim().toLowerCase(),
|
||
r?.documentType,
|
||
r?.documentRef.trim().toLowerCase(),
|
||
r?.documentDate,
|
||
r?.documentLine.trim(),
|
||
e.kind === 'annual' ? e.period.slice(0, 4) : '',
|
||
]);
|
||
}
|
||
export function evaluateRegularization(
|
||
draft: Record<string, unknown>,
|
||
e: RegularizationEvent,
|
||
): RegularizationResult {
|
||
const stop = (reason: string): RegularizationResult => ({
|
||
id: e.id,
|
||
period: e.period,
|
||
status: 'indeterminate',
|
||
reason,
|
||
delta: null,
|
||
details: 'Histórico preservado;sem lançamento automático.',
|
||
source: regularizationSource,
|
||
});
|
||
if (
|
||
!/^2026-(0[1-9]|1[0-2])$/.test(e.period) ||
|
||
draft.currency !== 'MZN' ||
|
||
e.source?.currency !== 'MZN' ||
|
||
e.source.opportunity !== scope(draft) ||
|
||
!sourceAvailable(draft, e)
|
||
)
|
||
return stop(
|
||
'Âmbito2026/MZN e resultado original congelado na mesma oportunidade por identificar;uma edição actual não substitui o resultado original.',
|
||
);
|
||
if (!e.rowIds.length || new Set(e.rowIds).size !== e.rowIds.length)
|
||
return stop('Seleccione parcelas originais distintas.');
|
||
const lines = frozenLines(e);
|
||
if (
|
||
lines.some(
|
||
(l) =>
|
||
!l.input ||
|
||
!l.result ||
|
||
l.result.deductibleExact === undefined ||
|
||
l.result.eligibility?.status !== 'eligible' ||
|
||
!l.input.documentRef.trim(),
|
||
)
|
||
)
|
||
return stop(
|
||
'Parcela original sem dedução exacta/elegível/documento;não presumir dedução inicial.',
|
||
);
|
||
if (
|
||
!e.reference.trim() ||
|
||
!e.reason.trim() ||
|
||
e.facts.scope !== 'yes' ||
|
||
e.facts.recorded !== 'yes'
|
||
)
|
||
return stop(
|
||
'Identifique documento/fonte,motivo,âmbito e montante originalmente deduzido (efectivo ou pressuposto da projecção).',
|
||
);
|
||
let delta: FiscalFraction,
|
||
details: string,
|
||
rawRatio: FiscalFraction | undefined,
|
||
ratio: FiscalFraction | undefined;
|
||
if (e.kind === 'annual') {
|
||
if (
|
||
!e.period.endsWith('-12') ||
|
||
lines.some(
|
||
(l) =>
|
||
l.input.period.slice(0, 4) !== e.period.slice(0, 4) ||
|
||
l.input.d?.method !== 'pro_rata' ||
|
||
!l.result.allocationCalculation,
|
||
)
|
||
)
|
||
return stop(
|
||
'Acerto22(6):apenas pro rata provisório do mesmo ano,no último período;afectação real e18(6) têm regras próprias.',
|
||
);
|
||
if (e.facts.annual !== 'yes' || e.facts.exclusions !== 'yes')
|
||
return stop(
|
||
'Confirme bases efectivas anuais22(4),semIVA,incluindo fora de campo,e exclusões22(5);não usar apenas vendas do mês.',
|
||
);
|
||
if (
|
||
!/^\d{1,15}(\.\d{1,6})?$/.test(e.numerator) ||
|
||
!/^\d{1,15}(\.\d{1,6})?$/.test(e.denominator)
|
||
)
|
||
return stop('Bases anuais finais em falta ou inválidas.');
|
||
const n = decimalFraction(e.numerator),
|
||
d = decimalFraction(e.denominator);
|
||
if (BigInt(d.numerator) === 0n)
|
||
return stop('Denominador anual zero;sem percentagem por defeito.');
|
||
rawRatio = fraction(
|
||
BigInt(n.numerator) * BigInt(d.denominator),
|
||
BigInt(n.denominator) * BigInt(d.numerator),
|
||
);
|
||
if (BigInt(rawRatio.numerator) > BigInt(rawRatio.denominator))
|
||
return stop('Numerador anual excede denominador.');
|
||
ratio = fraction(
|
||
(BigInt(rawRatio.numerator) * 100n +
|
||
BigInt(rawRatio.denominator) -
|
||
1n) /
|
||
BigInt(rawRatio.denominator),
|
||
100n,
|
||
);
|
||
delta = addFractions(
|
||
lines.map((l) =>
|
||
subtractFractions(
|
||
multiply(l.result.allocationCalculation!.before, ratio!),
|
||
l.result.deductibleExact!,
|
||
),
|
||
),
|
||
);
|
||
details =
|
||
lines
|
||
.map(
|
||
(l) =>
|
||
`${l.input.documentRef}/linha${l.input.documentLine || 'única'}:IVA elegível antesD${fractionLabel(l.result.allocationCalculation!.before)},deduzido${fractionLabel(l.result.deductibleExact!)},final${fractionLabel(multiply(l.result.allocationCalculation!.before, ratio!))}`,
|
||
)
|
||
.join('; ') +
|
||
`;quociente final${fractionLabel(rawRatio)};aplicado${fractionLabel(ratio)}`;
|
||
} else {
|
||
if (lines.length !== 1 || !validDate(e.receivedDate))
|
||
return stop(
|
||
'Rectificação51(4):seleccione uma linha e a data de recepção do documento rectificativo.',
|
||
);
|
||
const received = e.receivedDate.slice(0, 7),
|
||
next = new Date(e.receivedDate + 'T00:00:00Z');
|
||
next.setUTCMonth(next.getUTCMonth() + 1, 1);
|
||
const last = next.toISOString().slice(0, 7);
|
||
if (
|
||
e.receivedDate < lines[0].input.documentDate ||
|
||
e.period < received ||
|
||
e.period > last ||
|
||
e.period < lines[0].input.period
|
||
)
|
||
return stop(
|
||
'Período fora da recepção/seguinte51(4),ou anterior à dedução original;sem alteração retroactiva.',
|
||
);
|
||
if (
|
||
e.facts.supplierCorrection !== 'yes' ||
|
||
e.facts.rightUnchanged !== 'yes'
|
||
)
|
||
return stop(
|
||
'Confirme rectificação pelo fornecedor e manutenção do âmbito/condições da dedução. Mudança de direito exige enquadramento próprio.',
|
||
);
|
||
let corrected: FiscalFraction;
|
||
try {
|
||
corrected = decimalFraction(e.correctedDeductible);
|
||
} catch {
|
||
return stop(
|
||
'Indique a dedução corrigida documentada,sem taxa ou dedução presumida.',
|
||
);
|
||
}
|
||
if (
|
||
BigInt(
|
||
subtractFractions(corrected, lines[0].result.deductibleExact!)
|
||
.numerator,
|
||
) > 0n
|
||
)
|
||
return stop(
|
||
'Este percurso51(4) trata anulação/redução da dedução pelo adquirente;acréscimos ou erros têm fundamento/prazo próprios.',
|
||
);
|
||
delta = subtractFractions(corrected, lines[0].result.deductibleExact!);
|
||
details = `${lines[0].input.documentRef};deduzido${fractionLabel(lines[0].result.deductibleExact!)};corrigido${fractionLabel(corrected)};recebido${e.receivedDate};limite${last}`;
|
||
}
|
||
const sign = BigInt(delta.numerator);
|
||
return {
|
||
id: e.id,
|
||
period: e.period,
|
||
status: 'determined',
|
||
delta,
|
||
...(rawRatio && ratio ? { rawRatio, ratio } : {}),
|
||
reason:
|
||
sign > 0n
|
||
? 'A favor do sujeito passivo — campo12'
|
||
: sign < 0n
|
||
? 'A favor do Estado — campo13'
|
||
: 'Sem diferença;evento conservado,sem nova dedução',
|
||
details: `${details};revisão original${e.source.revision}/${e.source.calculatedAt};${lines.some((l) => l.input.origin === 'projected') ? 'Pressuposto de simulação;sem declaração fiscal submetida' : 'Dados efectivos declarados;submissão fora do portal'};${e.reference};${e.reason}`,
|
||
source: regularizationSource,
|
||
};
|
||
}
|
||
// Change detection only. Keep frozen source values, without embedding them again in every calculation key.
|
||
function fingerprint(value: unknown): string {
|
||
const raw = JSON.stringify(value);
|
||
let hash = 14695981039346656037n;
|
||
for (let i = 0; i < raw.length; i++)
|
||
hash = BigInt.asUintN(
|
||
64,
|
||
(hash ^ BigInt(raw.charCodeAt(i))) * 1099511628211n,
|
||
);
|
||
return `${raw.length}:${hash.toString(16)}`;
|
||
}
|
||
function key(book: RegularizationBook) {
|
||
return fingerprint([book.version, book.events, book.completePeriods]);
|
||
}
|
||
export function recordRegularizations(
|
||
draft: Record<string, unknown>,
|
||
book: RegularizationBook,
|
||
now = new Date().toISOString(),
|
||
): RegularizationBook {
|
||
if (new Set(book.events.map((e) => e.id)).size !== book.events.length)
|
||
throw Error('Evento repetido;sem dupla regularização.');
|
||
const identities = book.events.flatMap((e) =>
|
||
e.rowIds.map((id) => identity(e, id)),
|
||
);
|
||
if (new Set(identities).size !== identities.length)
|
||
throw Error(
|
||
'Mesma aquisição/ano ou documento já incluído noutra regularização deste motivo;consolide o evento original.',
|
||
);
|
||
// Mixing an annual correction and a credit note on the same deduction needs sequencing, not two independent deltas.
|
||
const seen = new Map<string, string>();
|
||
for (const e of book.events)
|
||
for (const id of e.rowIds) {
|
||
const k = identity({ ...e, kind: 'purchase_correction' }, id),
|
||
prior = seen.get(k);
|
||
if (prior && prior !== e.kind)
|
||
throw Error(
|
||
'Acerto anual e documento rectificativo sobre a mesma parcela exigem reconciliação sequencial;sem dupla correcção.',
|
||
);
|
||
seen.set(k, e.kind);
|
||
}
|
||
const result: Calculation = {
|
||
version: 1,
|
||
calculatedAt: now,
|
||
key: key(book),
|
||
events: book.events.map((e) => evaluateRegularization(draft, e)),
|
||
};
|
||
return {
|
||
...book,
|
||
...(book.result
|
||
? {
|
||
history: [
|
||
...(book.history ?? []),
|
||
structuredClone(book.result),
|
||
],
|
||
}
|
||
: {}),
|
||
result,
|
||
};
|
||
}
|
||
export function regularizationsCurrent(
|
||
draft: Record<string, unknown>,
|
||
b: RegularizationBook,
|
||
): boolean {
|
||
return (
|
||
!!b.result &&
|
||
b.result.version === 1 &&
|
||
b.result.key === key(b) &&
|
||
b.result.events.length === b.events.length &&
|
||
b.events.every(
|
||
(e) =>
|
||
sourceAvailable(draft, e) &&
|
||
b.result!.events.filter(
|
||
(r) => r.id === e.id && r.period === e.period,
|
||
).length === 1,
|
||
)
|
||
);
|
||
}
|
||
export function regularizationFields(
|
||
draft: Record<string, unknown>,
|
||
period: string,
|
||
currentRows?: Acquisition[],
|
||
): { fields: Record<string, FiscalFraction | null>; reasons: string[] } | null {
|
||
const b = regularizationBook(draft);
|
||
if (
|
||
!b.events.some((e) => e.period === period) &&
|
||
!b.completePeriods.includes(period)
|
||
)
|
||
return null;
|
||
const events = b.result?.events.filter((e) => e.period === period) ?? [];
|
||
if (
|
||
currentRows &&
|
||
b.events.some(
|
||
(e) =>
|
||
e.period === period &&
|
||
e.source &&
|
||
frozenLines(e).some(
|
||
(l) =>
|
||
l.input &&
|
||
currentRows.some(
|
||
(r) =>
|
||
r.id === l.input.id &&
|
||
JSON.stringify(r) !== JSON.stringify(l.input),
|
||
),
|
||
),
|
||
)
|
||
)
|
||
return {
|
||
fields: { '12': null, '13': null },
|
||
reasons: [
|
||
'A aquisição actual difere da parcela congelada usada emE;reconcilie a diferença antes de somar a regularização,sem recalcular o histórico.',
|
||
],
|
||
};
|
||
if (
|
||
!b.completePeriods.includes(period) ||
|
||
!regularizationsCurrent(draft, b) ||
|
||
events.some((e) => !e.delta)
|
||
)
|
||
return {
|
||
fields: { '12': null, '13': null },
|
||
reasons: [
|
||
'RegularizaçõesE:complete/reconcilie o período e guarde o cálculo correspondente;sem zero presumido nem uso de resultado histórico.',
|
||
],
|
||
};
|
||
return {
|
||
fields: {
|
||
'12': addFractions(events.map((e) => positiveFraction(e.delta!))),
|
||
'13': addFractions(
|
||
events.map((e) =>
|
||
positiveFraction(
|
||
fraction(
|
||
-BigInt(e.delta!.numerator),
|
||
BigInt(e.delta!.denominator),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
},
|
||
reasons: [
|
||
'E:apenas eventos documentados declarados completos;12/13 separados,sem recalcular aquisições históricas.',
|
||
],
|
||
};
|
||
}
|
||
export function regularizationSourceKey(
|
||
draft: Record<string, unknown>,
|
||
): string {
|
||
const b = regularizationBook(draft);
|
||
const reduced =
|
||
(
|
||
store(draft).regulatedReducedBases as
|
||
| Record<string, { rows: unknown[] }>
|
||
| undefined
|
||
)?.[scope(draft)]?.rows ?? [];
|
||
return b.events.length || b.completePeriods.length
|
||
? fingerprint([b, regularizationsCurrent(draft, b), reduced])
|
||
: '';
|
||
}
|
||
export function regularizationReportRows(
|
||
draft: Record<string, unknown>,
|
||
): Array<[string, string, string, string]> {
|
||
const b = regularizationBook(draft);
|
||
if (!b.events.length) return [];
|
||
return [
|
||
[
|
||
'IVA mensal',
|
||
'RegularizaçõesE',
|
||
regularizationsCurrent(draft, b)
|
||
? 'Resultado correspondente às entradas'
|
||
: 'Histórico preservado;rever entradas/origem',
|
||
regularizationSource,
|
||
],
|
||
...(b.result?.events ?? []).map(
|
||
(e) =>
|
||
[
|
||
'IVA mensal',
|
||
`Regularização ${e.id} — ${e.period}`,
|
||
`${e.status === 'determined' ? e.reason : 'Indeterminado: ' + e.reason};delta${e.delta ? fractionLabel(e.delta) : 'indeterminado'}`,
|
||
e.details + ';' + e.source,
|
||
] as [string, string, string, string],
|
||
),
|
||
];
|
||
}
|
||
export function frozenAssetRows(
|
||
draft: Record<string, unknown>,
|
||
): Array<[string, string, string, string]> {
|
||
const sources = regularizationSources(draft),
|
||
seen = new Set<string>(),
|
||
out: Array<[string, string, string, string]> = [];
|
||
for (const s of [...sources].reverse())
|
||
for (const r of s.inputs.filter(
|
||
(r) => r.field === '08' || r.investmentAsset === true,
|
||
)) {
|
||
const k = JSON.stringify([
|
||
r.supplier,
|
||
r.documentRef,
|
||
r.documentDate,
|
||
r.documentLine,
|
||
]);
|
||
if (seen.has(k)) continue;
|
||
seen.add(k);
|
||
const v = s.rows.find((v) => v.id === r.id);
|
||
out.push([
|
||
'IVA mensal',
|
||
`Activo — ${r.description || r.id}`,
|
||
`${r.documentRef};linha${r.documentLine || 'única'};aquisição${r.documentDate};IVA suportado${v?.vat ?? 'indeterminado'};IVA deduzido${v?.deductibleExact ? fractionLabel(v.deductibleExact) : (v?.deductible ?? 'indeterminado')}`,
|
||
`RCIVA27,Decreto7/2008 PDF5/p103;${s.currency};revisão${s.revision}/${s.calculatedAt};${r.origin === 'projected' ? 'Pressuposto de simulação' : 'Dados efectivos declarados'};sem amortização fiscal ou regularização plurianual presumidas.`,
|
||
]);
|
||
}
|
||
return out;
|
||
}
|