609 lines
24 KiB
TypeScript
609 lines
24 KiB
TypeScript
import { acquisitionScope, readAcquisitionStore } from './iva-acquisitions.ts';
|
||
import {
|
||
newCurrencyEvent,
|
||
evaluateCurrencyEvent,
|
||
type CurrencyEventRow,
|
||
} from './iva-currency-events.ts';
|
||
import {
|
||
travelBook,
|
||
travelCurrent,
|
||
travelDecimal,
|
||
travelMoney,
|
||
type TravelLine,
|
||
type TravelRow,
|
||
} from './iva-travel.ts';
|
||
export const travelEventVersion = 'travel-suspensive-events/1';
|
||
export const travelEventSource =
|
||
'CIVA7(8)/(9),8,5(2),14(1),Lei13/2016PDF7/9/13/p1426(265/267/271); RIVA38/40,PDF6–7/p104–105.7(8) defere facto gerador/exigibilidade até cessar regime; mudança de local sem cessação não é saída fiscal.7(9)/40: primeiro entre pagamento integral e imediatamente antes do início/primeiro serviço. Facto gerador separado de factura/recebimento8; reutiliza motor entregue. Sem base/IVA/dedução, desembaraço ou pagamento automáticos.';
|
||
export type TravelEventRow = CurrencyEventRow & {
|
||
travelRowId: string;
|
||
fullPaymentState: string;
|
||
fullPaymentDate: string;
|
||
fullPaymentRef: string;
|
||
startState: string;
|
||
startDate: string;
|
||
startRef: string;
|
||
regimeClause: string;
|
||
entryDate: string;
|
||
entryRef: string;
|
||
regimeStatus: string;
|
||
exitDate: string;
|
||
exitRef: string;
|
||
cessationConfirmed: string;
|
||
};
|
||
type TravelOrigin = {
|
||
id: string;
|
||
period: string;
|
||
operatorRef: string;
|
||
operationRef: string;
|
||
description: string;
|
||
scope: string;
|
||
revision: number;
|
||
current: boolean;
|
||
input: TravelRow;
|
||
classification: TravelLine;
|
||
};
|
||
type DueEvent = { date: string; amount: string | null; reason: string };
|
||
export type TravelEventLine = {
|
||
id: string;
|
||
status: 'determined' | 'not_applicable' | 'indeterminate' | 'deferred';
|
||
eventDate: string | null;
|
||
eventTiming: string | null;
|
||
dueEvents: DueEvent[];
|
||
remainingPending: boolean;
|
||
source: TravelOrigin | null;
|
||
baseAmount: null;
|
||
vat: null;
|
||
deduction: null;
|
||
reason: string;
|
||
};
|
||
export type TravelEventCalculation = {
|
||
version: string;
|
||
revision: number;
|
||
calculatedAt: string;
|
||
opportunity: string;
|
||
sourceKey: string;
|
||
source: string;
|
||
inputs: TravelEventRow[];
|
||
rows: TravelEventLine[];
|
||
};
|
||
export type TravelEventBook = {
|
||
rows: TravelEventRow[];
|
||
result?: TravelEventCalculation;
|
||
history?: TravelEventCalculation[];
|
||
};
|
||
type Store = ReturnType<typeof readAcquisitionStore> & {
|
||
travelSpecialEvents?: Record<string, TravelEventBook>;
|
||
};
|
||
export function newTravelEvent(id: string, currency: string): TravelEventRow {
|
||
return {
|
||
...newCurrencyEvent(id, currency),
|
||
travelRowId: '',
|
||
fullPaymentState: '',
|
||
fullPaymentDate: '',
|
||
fullPaymentRef: '',
|
||
startState: '',
|
||
startDate: '',
|
||
startRef: '',
|
||
regimeClause: '',
|
||
entryDate: '',
|
||
entryRef: '',
|
||
regimeStatus: '',
|
||
exitDate: '',
|
||
exitRef: '',
|
||
cessationConfirmed: '',
|
||
};
|
||
}
|
||
export function switchTravelEvent(
|
||
row: TravelEventRow,
|
||
treatment: string,
|
||
): TravelEventRow {
|
||
return { ...row, treatment, factsConfirmed: '', invoiceRequired: '' };
|
||
}
|
||
export function travelEventSources(
|
||
draft: Record<string, unknown>,
|
||
): TravelOrigin[] {
|
||
const b = travelBook(draft),
|
||
r = b.result;
|
||
return r
|
||
? r.inputs.map((input, i) => ({
|
||
id: input.id,
|
||
period: input.period,
|
||
operatorRef: input.operatorRef,
|
||
operationRef: input.operationRef,
|
||
description: input.description,
|
||
scope: input.scope,
|
||
revision: r.revision,
|
||
current: travelCurrent(draft, b),
|
||
input,
|
||
classification: r.rows[i],
|
||
}))
|
||
: [];
|
||
}
|
||
function readStore(draft: Record<string, unknown>): Store {
|
||
const s = readAcquisitionStore(draft.modelo_a_snapshot) as Store;
|
||
if (
|
||
s.travelSpecialEvents !== undefined &&
|
||
(!s.travelSpecialEvents ||
|
||
typeof s.travelSpecialEvents !== 'object' ||
|
||
Array.isArray(s.travelSpecialEvents))
|
||
)
|
||
throw new Error(
|
||
'Eventos especiais não recuperáveis; originais conservados.',
|
||
);
|
||
const fields = Object.keys(newTravelEvent('', ''));
|
||
for (const b of Object.values(s.travelSpecialEvents ?? {}))
|
||
if (
|
||
!b ||
|
||
!Array.isArray(b.rows) ||
|
||
(b.history !== undefined && !Array.isArray(b.history)) ||
|
||
b.rows.some(
|
||
(r) =>
|
||
!r ||
|
||
fields.some(
|
||
(k) => typeof r[k as keyof TravelEventRow] !== 'string',
|
||
),
|
||
)
|
||
)
|
||
throw new Error(
|
||
'Linhas de eventos especiais não recuperáveis; sem lista vazia por defeito.',
|
||
);
|
||
return s;
|
||
}
|
||
export function travelEventBook(
|
||
draft: Record<string, unknown>,
|
||
): TravelEventBook {
|
||
return (
|
||
readStore(draft).travelSpecialEvents?.[acquisitionScope(draft)] ?? {
|
||
rows: [],
|
||
}
|
||
);
|
||
}
|
||
export function saveTravelEventBook(
|
||
draft: Record<string, unknown>,
|
||
book: TravelEventBook,
|
||
): string {
|
||
const s = readStore(draft),
|
||
encoded = JSON.stringify({
|
||
...s,
|
||
travelSpecialEvents: {
|
||
...s.travelSpecialEvents,
|
||
[acquisitionScope(draft)]: book,
|
||
},
|
||
});
|
||
readStore({ ...draft, modelo_a_snapshot: encoded });
|
||
return encoded;
|
||
}
|
||
function date(s: string): number | null {
|
||
const n = Date.parse(s + 'T00:00:00Z');
|
||
return /^\d{4}-\d{2}-\d{2}$/.test(s) &&
|
||
Number.isFinite(n) &&
|
||
new Date(n).toISOString().slice(0, 10) === s
|
||
? n
|
||
: null;
|
||
}
|
||
export function evaluateTravelEvent(
|
||
draft: Record<string, unknown>,
|
||
row: TravelEventRow,
|
||
): TravelEventLine {
|
||
const result: TravelEventLine = {
|
||
id: row.id,
|
||
status: 'indeterminate',
|
||
eventDate: null,
|
||
eventTiming: null,
|
||
dueEvents: [],
|
||
remainingPending: false,
|
||
source: null,
|
||
baseAmount: null,
|
||
vat: null,
|
||
deduction: null,
|
||
reason: '',
|
||
};
|
||
const stop = (
|
||
reason: string,
|
||
status: TravelEventLine['status'] = 'indeterminate',
|
||
) => ({ ...result, status, reason });
|
||
if (
|
||
!['travel_special', 'suspensive_exit'].includes(row.treatment) ||
|
||
!/^2026-(0[1-9]|1[0-2])$/.test(row.period) ||
|
||
!['projected', 'effective'].includes(row.origin) ||
|
||
date(row.documentDate) === null ||
|
||
date(row.reviewDate) === null ||
|
||
![
|
||
row.description,
|
||
row.operationRef,
|
||
row.documentRef,
|
||
row.scope,
|
||
row.evidence,
|
||
].every((v) => !!v.trim())
|
||
)
|
||
return stop(
|
||
'Identifique percurso, período2026, operação, âmbito, documentos/pressupostos e data de conferência.',
|
||
);
|
||
if (row.factsConfirmed === 'no')
|
||
return stop(
|
||
'Factos não correspondem ao percurso especial; manter enquadramento aplicável, sem isenção ou IVAzero presumidos.',
|
||
'not_applicable',
|
||
);
|
||
if (row.factsConfirmed !== 'yes')
|
||
return stop(
|
||
'Factos próprios do evento por esclarecer; não inferir pela factura, licenciamento ou viagem anunciada.',
|
||
);
|
||
const review = date(row.reviewDate)!;
|
||
if (row.treatment === 'suspensive_exit') {
|
||
const entry = date(row.entryDate);
|
||
if (
|
||
!['a', 'b', 'c', 'd'].includes(row.regimeClause) ||
|
||
entry === null ||
|
||
entry > review ||
|
||
!row.entryRef.trim()
|
||
)
|
||
return stop(
|
||
'Identifique bens/operação e regime ou procedimento14(1)/5(2), sua colocação/data e referência; uma localização em armazém não demonstra regime suspensivo.',
|
||
);
|
||
if (row.regimeStatus === 'still')
|
||
return stop(
|
||
'CIVA7(8): bens/operação continuam abrangidos pelo regime; facto gerador e exigibilidade diferidos. Factura ou recebimento não são tratados como saída fiscal por este percurso.',
|
||
'deferred',
|
||
);
|
||
if (row.regimeStatus !== 'ceased')
|
||
return stop(
|
||
'Situação actual do regime por esclarecer; ausência de documento não prova permanência nem saída.',
|
||
);
|
||
const exit = date(row.exitDate);
|
||
if (
|
||
exit === null ||
|
||
exit < entry ||
|
||
!row.exitRef.trim() ||
|
||
row.cessationConfirmed !== 'yes'
|
||
)
|
||
return stop(
|
||
'Identifique cessação efectiva do regime/procedimento e sua data/âmbito; transferência entre regimes ou mudança física de armazém não demonstram por si cessação.',
|
||
);
|
||
result.eventDate = row.exitDate;
|
||
result.eventTiming = 'cessação efectiva do regime/procedimento';
|
||
if (exit > review)
|
||
return stop(
|
||
'Cessação futura na conferência; não declarar facto gerador/exigibilidade já ocorridos.',
|
||
);
|
||
result.dueEvents = [
|
||
{
|
||
date: row.exitDate,
|
||
amount: null,
|
||
reason: '7(8): cessação do regime/procedimento identificado; montante não apurado',
|
||
},
|
||
];
|
||
return stop(
|
||
'P133: facto gerador e exigibilidade pelo momento de cessação efectiva7(8). Não concede isenção14, liquida importação, determina valor aduaneiro, direito, ICE ou dedução.',
|
||
'determined',
|
||
);
|
||
}
|
||
let source: TravelOrigin | undefined;
|
||
try {
|
||
source = travelEventSources(draft).find(
|
||
(s) => s.id === row.travelRowId,
|
||
);
|
||
} catch {
|
||
return stop(
|
||
'Origem de viagens não recuperável; conservar originais e histórico.',
|
||
);
|
||
}
|
||
if (!source)
|
||
return stop(
|
||
'Associe uma operação de viagens já classificada nesta oportunidade; não copiar de outro projecto.',
|
||
);
|
||
result.source = source;
|
||
if (!source.current || source.classification.regime === 'indeterminate')
|
||
return stop(
|
||
'Origem alterada ou natureza do regime indeterminada; rever apenas a associação/versão afectada.',
|
||
);
|
||
if (
|
||
!['special', 'mixed'].includes(source.classification.regime) ||
|
||
source.classification.territory !== 'national'
|
||
)
|
||
return stop(
|
||
'Origem segue regime geral ou outro âmbito; evento específico da parte especial não se aplica. Reutilizar eventos gerais próprios sem os substituir.',
|
||
'not_applicable',
|
||
);
|
||
if (source.input.domesticTaxable !== 'yes' || !source.input.taxRef.trim())
|
||
return stop(
|
||
'Enquadramento tributável da parcela especial por esclarecer. Montantes da margem não são pré-condição do evento, mas o âmbito fiscal deve estar identificado.',
|
||
);
|
||
if (
|
||
row.operationRef !== source.operationRef ||
|
||
row.currency !== source.input.currency
|
||
)
|
||
return stop(
|
||
'Referência da operação ou moeda diverge da origem; conservar dados e conferir a associação. O período deste evento pode diferir do período do registo da margem.',
|
||
);
|
||
if (
|
||
!['full', 'partial', 'none'].includes(row.fullPaymentState) ||
|
||
!['started', 'not_started'].includes(row.startState)
|
||
)
|
||
return stop(
|
||
'Distinga pagamento integral, parcial ou inexistente e início/primeiro serviço ocorrido ou ainda não iniciado. Desconhecido não é ausência de pagamento.',
|
||
);
|
||
const full =
|
||
row.fullPaymentState === 'full' ? date(row.fullPaymentDate) : null,
|
||
start = row.startState === 'started' ? date(row.startDate) : null;
|
||
if (
|
||
row.fullPaymentState === 'full' &&
|
||
(full === null || !row.fullPaymentRef.trim() || full > review)
|
||
)
|
||
return stop(
|
||
'Identifique pagamento integral efectivamente ocorrido ou no cenário de conferência declarado, sem o inferir de sinal/adiantamento.',
|
||
);
|
||
if (
|
||
row.startState === 'started' &&
|
||
(start === null || !row.startRef.trim() || start > review)
|
||
)
|
||
return stop(
|
||
'Identifique o primeiro serviço ao cliente/início da viagem ou alojamento, até à conferência. Não subtrair um dia à data: a regra é imediatamente antes desse momento.',
|
||
);
|
||
if (full !== null && (start === null || full < start)) {
|
||
result.eventDate = row.fullPaymentDate;
|
||
result.eventTiming = 'acto do pagamento integral';
|
||
} else if (start !== null) {
|
||
result.eventDate = row.startDate;
|
||
result.eventTiming =
|
||
full === start
|
||
? 'pagamento integral e início na mesma data; ordem intradiária não apurada'
|
||
: 'imediatamente antes do início/primeiro serviço';
|
||
}
|
||
if (row.fullPaymentState === 'partial' && row.paymentState !== 'received')
|
||
return stop(
|
||
'Pagamento parcial declarado exige identificar a parcela recebida; não o transformar em pagamento integral.',
|
||
);
|
||
if (row.fullPaymentState === 'none' && row.paymentState !== 'none')
|
||
return stop(
|
||
'Estado sem pagamentos diverge da parcela recebida/por esclarecer; reconciliar factos sem sobrescrever referências.',
|
||
);
|
||
if (row.fullPaymentState === 'full' && row.paymentState === 'none')
|
||
return stop(
|
||
'Pagamento integral diverge de ausência de recebimento; conferir estados.',
|
||
);
|
||
if (row.fullPaymentState === 'partial') {
|
||
const amount = travelDecimal(row.amount),
|
||
part = travelDecimal(row.paymentAmount);
|
||
if (amount === null || part === null || part <= 0n || part >= amount)
|
||
return stop(
|
||
'Parcial exige parcela positiva inferior à contraprestação total identificada; igualdade não pode ser tratada como simples sinal.',
|
||
);
|
||
}
|
||
if (row.fullPaymentState === 'full' && row.invoiceRequired === 'yes') {
|
||
const amount = travelDecimal(row.amount),
|
||
part = travelDecimal(row.paymentAmount),
|
||
payment = date(row.paymentDate);
|
||
if (
|
||
amount === null ||
|
||
part === null ||
|
||
payment === null ||
|
||
payment > full! ||
|
||
part <= 0n ||
|
||
part > amount ||
|
||
(payment === full && part !== amount) ||
|
||
(payment < full! && part >= amount)
|
||
)
|
||
return stop(
|
||
'Reconcilie parcela anterior e liquidação integral: data/valor do recebimento deve corresponder ao total na data integral ou a parcela anterior inferior ao total.',
|
||
);
|
||
}
|
||
if (result.eventDate === null) {
|
||
if (row.invoiceRequired === 'no')
|
||
return stop(
|
||
'Sem pagamento integral e sem início: facto gerador7(9) ainda não ocorrido no âmbito declarado; sem antecipação automática por adiantamento.',
|
||
'deferred',
|
||
);
|
||
if (row.invoiceRequired === 'yes' && row.paymentState === 'none') {
|
||
const deadline = date(row.legalDeadline),
|
||
invoice =
|
||
row.invoiceState === 'issued'
|
||
? date(row.invoiceDate)
|
||
: null;
|
||
if (
|
||
deadline === null ||
|
||
!row.deadlineRef.trim() ||
|
||
!['issued', 'not_issued'].includes(row.invoiceState) ||
|
||
(row.invoiceState === 'issued' &&
|
||
(invoice === null ||
|
||
invoice > review ||
|
||
!row.invoiceRef.trim()))
|
||
)
|
||
return stop(
|
||
'Facto gerador7(9) ainda não ocorrido; identificar factura e prazo/disposição aplicáveis antes de avaliar8a/b, sem inventar data de viagem.',
|
||
);
|
||
const due =
|
||
invoice !== null && invoice <= deadline
|
||
? row.invoiceDate
|
||
: row.legalDeadline;
|
||
if (date(due)! > review) {
|
||
result.remainingPending = true;
|
||
return stop(
|
||
'Facto gerador7(9) não ocorrido e nenhuma parcela8a/b exigível até à conferência declarada.',
|
||
'deferred',
|
||
);
|
||
}
|
||
const amount = travelDecimal(row.amount);
|
||
result.dueEvents = [
|
||
{
|
||
date: due,
|
||
amount: amount === null ? null : travelMoney(amount),
|
||
reason:
|
||
invoice !== null && invoice <= deadline
|
||
? '8a: factura antecipada emitida no prazo; facto gerador especial ainda não ocorrido'
|
||
: '8b: termo do prazo expressamente identificado; facto gerador especial ainda não ocorrido',
|
||
},
|
||
];
|
||
return stop(
|
||
'Exigibilidade8a/b por factura/prazo identificados, sem usar essa data como pagamento integral ou início7(9).',
|
||
'determined',
|
||
);
|
||
}
|
||
if (row.invoiceRequired !== 'yes' || row.paymentState !== 'received')
|
||
return stop(
|
||
'Facto gerador especial ainda não ocorrido; conferir eventual factura ou recebimento8 sem inventar data do início.',
|
||
);
|
||
const early = evaluateCurrencyEvent({
|
||
...row,
|
||
treatment: 'advance_payment',
|
||
eventDate: '',
|
||
eventRef: 'CIVA7(9)/RIVA40 — sem evento integral/início ocorrido',
|
||
});
|
||
result.dueEvents = early.dueEvents;
|
||
result.remainingPending = early.remainingPending;
|
||
return stop(
|
||
'Facto gerador7(9) ainda não ocorrido. Parcela8 avaliada pelo motor existente: ' +
|
||
early.reason,
|
||
early.status === 'determined'
|
||
? 'determined'
|
||
: early.status === 'not_applicable'
|
||
? 'not_applicable'
|
||
: 'indeterminate',
|
||
);
|
||
}
|
||
const existing = evaluateCurrencyEvent({
|
||
...row,
|
||
treatment: 'service_completed',
|
||
eventDate: result.eventDate!,
|
||
eventRef: 'CIVA7(9)/RIVA40: ' + result.eventTiming,
|
||
});
|
||
result.dueEvents = existing.dueEvents;
|
||
result.remainingPending = existing.remainingPending;
|
||
if (
|
||
existing.status === 'determined' &&
|
||
row.fullPaymentState === 'full' &&
|
||
row.invoiceRequired === 'yes'
|
||
) {
|
||
const total = travelDecimal(row.amount)!;
|
||
const before = result.dueEvents.filter((e) => date(e.date)! < full!);
|
||
let already = 0n;
|
||
for (const e of before) {
|
||
const value = e.amount === null ? null : travelDecimal(e.amount);
|
||
if (value === null)
|
||
return stop(
|
||
'Parcela anterior com montante indeterminado; conservar evento e esclarecer total antes de reconciliar pagamento integral.',
|
||
);
|
||
already += value;
|
||
}
|
||
if (already > total)
|
||
return stop(
|
||
'Parcelas anteriores excedem total; sem dupla contagem.',
|
||
);
|
||
result.dueEvents = [
|
||
...before,
|
||
...(total > already
|
||
? [
|
||
{
|
||
date: row.fullPaymentDate,
|
||
amount: travelMoney(total - already),
|
||
reason: '8c: saldo recebido na liquidação integral, descontando parcelas já exigíveis',
|
||
},
|
||
]
|
||
: []),
|
||
];
|
||
result.remainingPending = false;
|
||
}
|
||
return stop(
|
||
'P133:7(9)/RIVA40, ' +
|
||
result.eventTiming +
|
||
' em ' +
|
||
result.eventDate +
|
||
'. Exigibilidade8 separada por factura/recebimento, sem duplicação de parcela e restante. ' +
|
||
existing.reason,
|
||
existing.status === 'determined'
|
||
? 'determined'
|
||
: existing.status === 'not_applicable'
|
||
? 'not_applicable'
|
||
: 'indeterminate',
|
||
);
|
||
}
|
||
function key(draft: Record<string, unknown>, book: TravelEventBook): string {
|
||
return JSON.stringify({
|
||
version: travelEventVersion,
|
||
scope: acquisitionScope(draft),
|
||
location: draft.selection_labels ?? {},
|
||
rows: book.rows,
|
||
dependencies: book.rows.map((row) =>
|
||
row.treatment === 'travel_special'
|
||
? travelEventSources(draft).find(
|
||
(s) => s.id === row.travelRowId,
|
||
)
|
||
: null,
|
||
),
|
||
});
|
||
}
|
||
export function travelEventCurrent(
|
||
draft: Record<string, unknown>,
|
||
book: TravelEventBook,
|
||
): boolean {
|
||
try {
|
||
return !!book.result && book.result.sourceKey === key(draft, book);
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
export function recordTravelEvents(
|
||
draft: Record<string, unknown>,
|
||
book: TravelEventBook,
|
||
): TravelEventBook {
|
||
if (
|
||
!book.rows.length ||
|
||
new Set(book.rows.map((r) => r.id)).size !== book.rows.length
|
||
)
|
||
throw new Error(
|
||
'Identifique eventos distintos; originais conservados.',
|
||
);
|
||
const result: TravelEventCalculation = {
|
||
version: travelEventVersion,
|
||
revision: (book.result?.revision ?? 0) + 1,
|
||
calculatedAt: new Date().toISOString(),
|
||
opportunity: acquisitionScope(draft),
|
||
sourceKey: key(draft, book),
|
||
source: travelEventSource,
|
||
inputs: structuredClone(book.rows),
|
||
rows: book.rows.map((row) => evaluateTravelEvent(draft, row)),
|
||
};
|
||
const next = {
|
||
...book,
|
||
result,
|
||
history: [
|
||
...(book.history ?? []),
|
||
...(book.result ? [book.result] : []),
|
||
],
|
||
};
|
||
saveTravelEventBook(draft, next);
|
||
return next;
|
||
}
|
||
function eventFacts(row: TravelEventRow, line: TravelEventLine): string {
|
||
if (row.treatment === 'suspensive_exit')
|
||
return `regime14(1)${row.regimeClause};entrada${row.entryDate},${row.entryRef};situação${row.regimeStatus};${row.regimeStatus === 'ceased' ? `cessação${row.exitDate},${row.exitRef},confirmada${row.cessationConfirmed}` : 'dados de cessação sem aplicação conservados no rascunho/histórico, não usados neste resultado'}`;
|
||
return `origem viagem${row.travelRowId},versão${line.source?.revision ?? 'Indeterminada'},${line.source?.scope ?? ''};pagamento${row.fullPaymentState}${row.fullPaymentState === 'full' ? ',' + row.fullPaymentDate + ',' + row.fullPaymentRef : ''};início${row.startState}${row.startState === 'started' ? ',' + row.startDate + ',' + row.startRef : ''};factura${row.invoiceRequired};${row.invoiceRequired === 'yes' ? `emissão${row.invoiceState}${row.invoiceState === 'issued' ? ',' + row.invoiceDate + ',' + row.invoiceRef : ''};prazo${row.legalDeadline},${row.deadlineRef};` : ''}recebimento${row.paymentState}${row.paymentState === 'received' ? ',' + row.paymentDate + ',' + row.paymentAmount + ',' + row.paymentRef : ''};total${row.amount}${row.currency};referências sem aplicação conservadas no rascunho/histórico`;
|
||
}
|
||
export function travelEventReportRows(
|
||
draft: Record<string, unknown>,
|
||
): Array<[string, string, string, string]> {
|
||
try {
|
||
const b = travelEventBook(draft),
|
||
r = b.result;
|
||
if (!r) return [];
|
||
return r.inputs.map((row, i) => {
|
||
const line = r.rows[i];
|
||
return [
|
||
'IVA mensal',
|
||
`P133 — ${row.period} — ${row.id}`,
|
||
`${row.description};${line.reason};facto gerador:${line.eventDate ?? 'Não apurado/ainda não ocorrido'};momento:${line.eventTiming ?? 'Indeterminado'};parcelas:${line.dueEvents.map((p) => p.date + ':' + (p.amount ?? 'Montante indeterminado') + ' ' + row.currency + ' (' + p.reason + ')').join('; ') || 'Nenhuma apurada'};restante:${line.status === 'indeterminate' ? 'Indeterminado' : line.remainingPending ? 'Pendente' : 'Sem restante apurado'};base/IVA/dedução:Indeterminados`,
|
||
`${r.opportunity};${travelEventCurrent(draft, b) ? 'Resultado actual' : 'Origem/entradas alteradas; resultado histórico conservado'};versão${r.revision};${r.calculatedAt};${r.source};${row.origin};operação${row.operationRef};âmbito${row.scope};documento${row.documentRef},${row.documentDate};conferência${row.reviewDate};${eventFacts(row, line)};factos${row.evidence};sem lançamento fiscal, nova dedução/campo11 ou totaisModeloA.`,
|
||
];
|
||
});
|
||
} catch {
|
||
return [
|
||
[
|
||
'IVA mensal',
|
||
'P133',
|
||
'Dados não recuperáveis',
|
||
'Originais conservados; nenhum evento por defeito.',
|
||
],
|
||
];
|
||
}
|
||
}
|