254 lines
12 KiB
TypeScript
254 lines
12 KiB
TypeScript
import { closureBook, closureCurrent } from './iva-closure.ts';
|
||
import { substitutionBook, substitutionCurrent } from './iva-substitution.ts';
|
||
import { refundBook, refundCurrent } from './iva-refund.ts';
|
||
import {
|
||
beverageRestitutionBook,
|
||
beverageRestitutionCurrent,
|
||
} from './iva-beverage-restitution.ts';
|
||
import {
|
||
fractionPresentation,
|
||
fractionLabel,
|
||
type FiscalFraction,
|
||
} from './fiscal-rational.ts';
|
||
export const l3FormReference = {
|
||
file: 'VAT Return.pdf',
|
||
sha256: '9c0b99a810c7db53eb75d63c22ee22fb86a50ba57bd5509041ed80ee10f75741',
|
||
page: 2,
|
||
scope: 'Preparação dos campos identificados dos quadros8–11 do exemplar recebido;edição/aceitação electrónica não estabelecida.',
|
||
};
|
||
export const l3FormalitiesDependencies = [
|
||
{
|
||
branch: 'A/B',
|
||
identified:
|
||
'Quadros8/10/11 e campos19/20/22–27 do exemplar recebido,PDF2;CIVA21(2/5)/23(1);RCIVA12/15.',
|
||
missing:
|
||
'Identificação da edição electrónica aplicável e dados de autenticação/meio de pagamento não inferidos dos recibos.',
|
||
trigger:
|
||
'Exemplar/instrução da edição electrónica ou dados do sujeito passivo;nenhuma assinatura ou submissão automática.',
|
||
},
|
||
{
|
||
branch: 'C',
|
||
identified:
|
||
'Quadro9/campo21 e transcrição positiva para22 do exemplar recebido,PDF2.',
|
||
missing:
|
||
'RCIVA15 refereModeloB;exemplar recebido assinala substituiçãoModeloA. Correspondência de edição/formulário regulamentar por resolver.',
|
||
trigger:
|
||
'ModeloB/instrução que esclareça a correspondência com a edição recebida. Preservar cálculo e preparação de21–24.',
|
||
},
|
||
{
|
||
branch: 'D',
|
||
identified:
|
||
'Quadro11:25=26+27;pedido27 não volta a19 sem comunicação. Remissãoart20 impressa conservada como divergência;regime2026 assenta em21.',
|
||
missing:
|
||
'Exemplares e instruções regulamentares específicos de reembolso:Decretos78/2017,30/2022 e52/2025 identificados na análise anterior;sem correspondência de campos demonstrada.',
|
||
trigger:
|
||
'Texto/modelo/instrução integral novo ou já fornecido;sem repetir a pesquisa anterior sem nova evidência. Preparação26/27 não equivale ao pedido regulamentar completo.',
|
||
},
|
||
{
|
||
branch: 'E',
|
||
identified:
|
||
'CIVA13(1)c/d/h e13(2–3);pedido,decisão e recebimento separados no dossier.',
|
||
missing:
|
||
'Modelo e procedimento específico de restituição de bebidas por identificar. Não usar27 como se fosse a restituição13(2).',
|
||
trigger:
|
||
'Exemplar ou instrução específica de13(2);sem importar limiares,prazos ou modelos do reembolso geralD.',
|
||
},
|
||
] as const;
|
||
export type L3FormPreparation = {
|
||
id: string;
|
||
branch: string;
|
||
period: string;
|
||
entity: string;
|
||
current: boolean;
|
||
reference: string;
|
||
status: string;
|
||
fields: Array<{
|
||
field: string;
|
||
label: string;
|
||
value: string;
|
||
exact: string;
|
||
}>;
|
||
notes: string[];
|
||
};
|
||
const fmt = (v: FiscalFraction | null | undefined) =>
|
||
v ? fractionPresentation(v).decimal : 'Indeterminado';
|
||
const cell = (
|
||
field: string,
|
||
label: string,
|
||
v: FiscalFraction | null | undefined,
|
||
) => ({
|
||
field,
|
||
label,
|
||
value: fmt(v),
|
||
exact: v ? fractionLabel(v) : 'Indeterminado',
|
||
});
|
||
export function l3FormPreparations(
|
||
d: Record<string, unknown>,
|
||
): L3FormPreparation[] {
|
||
const out: L3FormPreparation[] = [],
|
||
ab = closureBook(d),
|
||
c = substitutionBook(d),
|
||
rd = refundBook(d),
|
||
e = beverageRestitutionBook(d);
|
||
for (const r of ab.result?.rows ?? []) {
|
||
const input = ab.result!.inputs.find((i) => i.id === r.id);
|
||
out.push({
|
||
id: `AB:${r.id}`,
|
||
branch: 'A/B',
|
||
period: r.period,
|
||
entity: r.entityRef,
|
||
current: closureCurrent(d, ab),
|
||
reference: `${l3FormReference.file};SHA256${l3FormReference.sha256};PDF2/quadros8,10,11`,
|
||
status: r.status,
|
||
fields: [
|
||
['19', 'Excesso do período anterior'],
|
||
['20', 'Créditos comunicados'],
|
||
['22', 'IVA a entregar'],
|
||
['23', 'Juros compensatórios'],
|
||
['24', 'Importância a pagar'],
|
||
['25', 'Crédito de imposto'],
|
||
['26', 'Reporte seguinte'],
|
||
['27', 'Pedido de reembolso'],
|
||
].map(([f, label]) => cell(f, label, r.fields[f])),
|
||
notes: [
|
||
`Versão congelada${ab.result!.calculatedAt};${input?.origin ?? ''};fonteL2${input?.source?.revision ?? ''}.`,
|
||
'Cabeçalho refere18/19,mas caixas19/20;original não renumerado. Meio de pagamento,assinatura e uso dos serviços ficam por preencher.',
|
||
'27 é afectação a pedido geral,não decisão nem recebimento;05/06 permanecem fora desta preparaçãoL3.',
|
||
],
|
||
});
|
||
}
|
||
for (const r of c.result?.rows ?? []) {
|
||
const input = c.result!.inputs.find((i) => i.id === r.id);
|
||
out.push({
|
||
id: `C:${r.id}`,
|
||
branch: 'C',
|
||
period: input?.prior?.period ?? '',
|
||
entity: input?.prior?.entityRef ?? '',
|
||
current: substitutionCurrent(d, c),
|
||
reference: `${l3FormReference.file};PDF2/quadro9;${l3FormalitiesDependencies[1].missing}`,
|
||
status: r.status,
|
||
fields: [
|
||
['21', 'Diferencial'],
|
||
['22', 'Diferencial devedor'],
|
||
['23', 'Juros adicionais'],
|
||
['24', 'Importância adicional a pagar'],
|
||
].map(([f, label]) => cell(f, label, r.fields[f])),
|
||
notes: [
|
||
`Anterior${input?.priorReference ?? ''};${input?.priorDate ?? ''};substituição${input?.reference ?? ''};${input?.date ?? ''};${input?.stage ?? ''}.`,
|
||
r.reason,
|
||
'Crédito credor aguarda comunicação20;pagamentos anteriores conservam-se e não voltam a ser inscritos como pagamento adicional.',
|
||
],
|
||
});
|
||
}
|
||
for (const r of rd.result?.rows ?? []) {
|
||
const input = rd.result!.inputs.find((i) => i.id === r.id);
|
||
out.push({
|
||
id: `D:${r.id}`,
|
||
branch: 'D',
|
||
period: input?.source?.period ?? '',
|
||
entity: input?.source?.entityRef ?? '',
|
||
current: refundCurrent(d, rd),
|
||
reference: `${l3FormReference.file};PDF2/quadro11;${l3FormalitiesDependencies[2].missing}`,
|
||
status: r.status,
|
||
fields: [
|
||
cell('25', 'Crédito de imposto', input?.source?.credit),
|
||
cell('26', 'Reporte separado', r.reported),
|
||
cell('27', 'Pedido de reembolso', r.requested),
|
||
],
|
||
notes: [
|
||
`Pedido${input?.requestReference ?? ''};${input?.requestDate ?? ''};${input?.stage ?? ''}.`,
|
||
r.reason,
|
||
`Acompanhamento separado:reconhecido${fmt(r.approved)};recebido${fmt(r.received)};por receber${fmt(r.awaitingReceipt)}MZN. Estes valores não substituem27.`,
|
||
],
|
||
});
|
||
}
|
||
for (const r of e.result?.rows ?? []) {
|
||
const input = e.result!.inputs.find((i) => i.id === r.id);
|
||
out.push({
|
||
id: `E:${r.id}`,
|
||
branch: 'E',
|
||
period: input?.source?.period ?? '',
|
||
entity: input?.entityRef ?? '',
|
||
current: beverageRestitutionCurrent(d, e),
|
||
reference:
|
||
'Dossier de apoio13(2);modelo oficial específico ainda não identificado.',
|
||
status: r.status,
|
||
fields: [
|
||
cell('Dossier', 'IVA documental', input?.source?.vat),
|
||
cell('Dossier', 'Parcela pedida', r.requested),
|
||
{
|
||
field: 'Dossier',
|
||
label: 'Dedução desta parcela',
|
||
value: r.requested ? '0.00' : 'Indeterminado',
|
||
exact: r.requested ? '0/1' : 'Indeterminado',
|
||
},
|
||
cell('Dossier', 'Reconhecido', r.approved),
|
||
cell('Dossier', 'Recebido', r.received),
|
||
cell('Dossier', 'Por receber', r.awaitingReceipt),
|
||
],
|
||
notes: [
|
||
`Documento${input?.source?.documentRef ?? ''};linha${input?.source?.documentLine ?? ''};operação${input?.source?.operationId ?? ''};pedido${input?.requestReference ?? ''};${input?.requestDate ?? ''}.`,
|
||
r.reason,
|
||
'Sem campo27 nem formulárioD presumidos;IVA do fornecimento mantém cálculo próprio.',
|
||
],
|
||
});
|
||
}
|
||
return out;
|
||
}
|
||
export const l3PreparationStatus = (status: string) =>
|
||
status === 'determined'
|
||
? 'Cálculo determinado'
|
||
: status === 'indeterminate'
|
||
? 'Indeterminado'
|
||
: status;
|
||
const escape = (s: string) =>
|
||
s.replace(
|
||
/[&<>"']/g,
|
||
(c) =>
|
||
({
|
||
'&': '&',
|
||
'<': '<',
|
||
'>': '>',
|
||
'"': '"',
|
||
"'": ''',
|
||
})[c]!,
|
||
);
|
||
export function l3FormPreparationHtml(p: L3FormPreparation): string {
|
||
return `<!doctype html><html lang="pt"><meta charset="utf-8"><title>Preparação L3 ${escape(p.branch)}</title><style>body{font:16px system-ui;max-width:960px;margin:32px auto;padding:0 24px;line-height:1.5}table{border-collapse:collapse;width:100%}th,td{border:1px solid #999;padding:8px;text-align:left}small{display:block}h1{font-size:26px}@media print{body{margin:0}}</style><h1>Preparação L3 ${escape(p.branch)} — ${escape(p.period)}</h1><p>${escape(p.entity)};${escape(l3PreparationStatus(p.status))};${p.current ? 'Correspondente às entradas' : 'Histórico preservado;rever entradas/origem'}. Submissão externa;preparação para conferência,não declaração AT submetida.</p><p>${escape(p.reference)}</p><table><thead><tr><th>Campo</th><th>Finalidade</th><th>MZN</th><th>Valor exacto</th></tr></thead><tbody>${p.fields.map((f) => `<tr><td>${escape(f.field)}</td><td>${escape(f.label)}</td><td>${escape(f.value)}</td><td>${escape(f.exact)}</td></tr>`).join('')}</tbody></table>${p.notes.map((n) => `<p>${escape(n)}</p>`).join('')}<h2>Formalidades ainda por completar</h2>${l3FormalitiesDependencies
|
||
.filter(
|
||
(dep) =>
|
||
dep.branch === p.branch ||
|
||
(p.branch === 'A/B' && dep.branch === 'D'),
|
||
)
|
||
.map(
|
||
(dep) =>
|
||
`<p>${escape(dep.missing)}<small>Condição de retoma:${escape(dep.trigger)}</small></p>`,
|
||
)
|
||
.join(
|
||
'',
|
||
)}<p>Fracções exactas conservadas;decimais são apresentação técnica,sem arredondamento fiscal final ou alteração de histórico.</p></html>`;
|
||
}
|
||
export function l3FormPreparationReportRows(
|
||
d: Record<string, unknown>,
|
||
): Array<[string, string, string, string]> {
|
||
try {
|
||
return l3FormPreparations(d).flatMap((p) => [
|
||
[
|
||
'IVA mensal',
|
||
`Preparação formal L3${p.branch} — ${p.period}`,
|
||
`${p.current ? 'Entradas correspondentes' : 'Histórico;rever origem'};${l3PreparationStatus(p.status)};${p.fields.map((f) => `${f.field}/${f.label}:${f.value}MZN;exacto${f.exact}`).join(' | ')}`,
|
||
`${p.reference};${p.notes.join(' | ')}`,
|
||
] as [string, string, string, string],
|
||
]);
|
||
} catch (error) {
|
||
return [
|
||
[
|
||
'IVA mensal',
|
||
'Preparação formalL3',
|
||
'Dados não recuperáveis;originais preservados',
|
||
String(error),
|
||
],
|
||
];
|
||
}
|
||
}
|