Files
Investidor-V2/resources/js/components/acquisition-closure.tsx
T

599 lines
27 KiB
TypeScript

import { inputText } from '../lib/input-text.ts';
import { useState } from 'react';
import {
closureBook,
closureSources,
newClosure,
newClosureCredit,
saveClosureBook,
recordClosure,
adoptPreviousCredit,
closureReportRows,
type ClosureInput,
type ClosureCredit,
type ClosureBook,
} from '@/lib/iva-closure';
export function AcquisitionClosure({
draft,
onChange,
}: {
draft: Record<string, unknown>;
onChange: (field: string, value: string) => void;
}) {
const [error, setError] = useState('');
let b: ClosureBook;
try {
b = closureBook(draft);
} catch (e) {
return <p role="alert">{String(e)}</p>;
}
const save = (next: ClosureBook) => {
try {
onChange('modelo_a_snapshot', saveClosureBook(draft, next));
setError('');
} catch (e) {
setError(String(e));
}
};
const change = (id: string, p: Partial<ClosureInput>) =>
save({
...b,
rows: b.rows.map((r) => (r.id === id ? { ...r, ...p } : r)),
});
const sources = closureSources(draft);
const entities = [
...new Set(b.rows.map((r) => r.entityRef.trim()).filter(Boolean)),
];
const creditChange = (
r: ClosureInput,
id: string,
p: Partial<ClosureCredit>,
) =>
change(r.id, {
credits: r.credits.map((c) => (c.id === id ? { ...c, ...p } : c)),
});
const choice = (
r: ClosureInput,
key: 'firstDeclaration' | 'creditsComplete',
label: string,
) => (
<label className="block">
{label}
<select
value={r[key]}
onChange={(e) => change(r.id, { [key]: e.target.value })}
>
<option value="">Por confirmar</option>
<option value="yes">Sim</option>
<option value="no">Não</option>
</select>
</label>
);
return (
<details className="rounded border p-3">
<summary>L3-A/B — crédito,fecho e pagamentos por período</summary>
<p>
Reutiliza resultados congelados de L2. Crédito anterior,saldo
calculado,pagamento registado e transporte são distintos.
Identifique o âmbito fiscal da entidade,sem consolidar
oportunidades implicitamente.
</p>
<p>
Primeira declaração em prazo,2026/MZN e reporte integral ou
afectação de parcela ao pedidoD. SubstituiçõesC,acompanhamento
de reembolsoD e restituição de bebidasE continuam próprios. A
parcela preparada para pedido é separada do reporte,sem
concessão automática. Prazo/data são declarados,não calculados
pelo portal. Uma simulação não submete declarações nem efectua
pagamentos.
</p>
{error && <p role="alert">{error}</p>}
<button
type="button"
onClick={() =>
save({
...b,
rows: [
...b.rows,
newClosure(
crypto.randomUUID(),
sources.length === 1 ? sources[0] : null,
entities.length === 1
? entities[0]
: inputText(
draft.fiscal_entity_ref ??
draft.organization_name ??
'',
),
),
],
})
}
>
Preparar fecho de outro período
</button>
{b.rows.map((r) => (
<fieldset className="rounded border p-3" key={r.id}>
<legend>
Fecho — {r.period || 'período por escolher'}
</legend>
<label>
Resultado L2 congelado
<select
value={sources.findIndex(
(s) =>
JSON.stringify(s) ===
JSON.stringify(r.source),
)}
onChange={(e) => {
const s = sources[Number(e.target.value)];
if (s)
change(r.id, {
source: structuredClone(s),
period: s.period,
});
}}
>
<option value={-1}>Escolha versão e período</option>
{sources.map((s, i) => (
<option key={i} value={i}>
{s.period} — revisão{s.revision} —{' '}
{s.calculatedAt}
</option>
))}
</select>
</label>
<label>
Âmbito fiscal da entidade
<input
value={r.entityRef}
onChange={(e) =>
change(r.id, { entityRef: e.target.value })
}
/>
</label>
<label>
Natureza do fecho
<select
value={r.origin}
onChange={(e) =>
change(r.id, {
origin: e.target
.value as ClosureInput['origin'],
})
}
>
<option value="projected">
Pressuposto de simulação
</option>
<option value="effective">
Dados efectivos declarados
</option>
</select>
</label>
{choice(
r,
'firstDeclaration',
'É a primeira declaração deste período (ou pressuposto explícito)',
)}
<label>
Data de entrega ou prevista
<input
type="date"
value={r.submissionDate}
onChange={(e) =>
change(r.id, { submissionDate: e.target.value })
}
/>
</label>
<label>
Situação do prazo
<select
value={r.filing}
onChange={(e) =>
change(r.id, {
filing: e.target
.value as ClosureInput['filing'],
})
}
>
<option value="">Por identificar</option>
<option value="timely">
Dentro do prazo declarado
</option>
<option value="late">
Fora do prazo — procedimento próprio
</option>
</select>
</label>
<label>
Fundamento/referência do prazo declarado
<input
value={r.filingRef}
onChange={(e) =>
change(r.id, { filingRef: e.target.value })
}
/>
</label>
<h5>Créditos de origem identificada</h5>
{r.credits.map((c) => (
<fieldset className="rounded border p-2" key={c.id}>
<legend>
{c.kind === 'opening'
? '19 — saldo de abertura'
: c.kind === 'previous'
? '19 — transporte anterior'
: '20 — crédito comunicado'}
</legend>
{c.kind === 'previous' ? (
<label>
Adoptar expressamente o reporte anterior
<select
value={c.previousId}
onChange={(e) => {
try {
const p = b.result?.rows.find(
(p) =>
p.id === e.target.value,
);
if (p)
creditChange(
r,
c.id,
adoptPreviousCredit(
c,
p,
),
);
} catch (err) {
setError(String(err));
}
}}
>
<option value="">
Escolha o fecho anterior
</option>
{b.result?.rows
.filter(
(p) =>
p.id !== r.id &&
p.period < r.period,
)
.map((p) => (
<option key={p.id} value={p.id}>
{p.period} — {p.entityRef} —{' '}
{p.status === 'determined'
? 'calculado'
: 'indeterminado'}
</option>
))}
</select>
</label>
) : (
<>
<label>
Período de origem do crédito/saldo
<input
type="month"
value={c.period}
onChange={(e) =>
creditChange(r, c.id, {
period: e.target.value,
})
}
/>
</label>
<label>
Documento/referência do crédito
<input
value={c.reference}
onChange={(e) =>
creditChange(r, c.id, {
reference: e.target.value,
})
}
/>
</label>
<label>
Montante disponível documentado (MZN)
<input
value={c.amount}
onChange={(e) =>
creditChange(r, c.id, {
amount: e.target.value,
})
}
/>
</label>
{c.kind === 'communication' && (
<label>
Data da comunicação recebida
<input
type="date"
value={c.date}
onChange={(e) =>
creditChange(r, c.id, {
date: e.target.value,
})
}
/>
</label>
)}
</>
)}
{c.kind === 'previous' && (
<>
<p>
Origem:{c.reference};{c.period}
;apresentação{c.amount}. Cálculo pela
fracção exacta;origem alterada exige
adopção expressa.
</p>
<button
type="button"
onClick={() => {
try {
const p = b.result?.rows.find(
(p) =>
p.id === c.previousId,
);
if (!p)
throw Error(
'Fecho anterior não recuperável.',
);
creditChange(
r,
c.id,
adoptPreviousCredit(c, p),
);
} catch (e) {
setError(String(e));
}
}}
>
Adoptar versão actual deste reporte
</button>
</>
)}
<button
type="button"
onClick={() =>
change(r.id, {
credits: r.credits.filter(
(v) => v.id !== c.id,
),
})
}
>
Remover vínculo de crédito desta preparação
</button>
</fieldset>
))}
{(['opening', 'communication', 'previous'] as const).map(
(kind) => (
<button
type="button"
key={kind}
onClick={() =>
change(r.id, {
credits: [
...r.credits,
newClosureCredit(
crypto.randomUUID(),
kind,
),
],
})
}
>
{kind === 'opening'
? 'Identificar saldo inicial'
: kind === 'communication'
? 'Identificar crédito comunicado'
: 'Reutilizar reporte anterior'}
</button>
),
)}
{choice(
r,
'creditsComplete',
'Reconciliei todos os créditos;lista vazia confirma expressamente ausência',
)}
<label>
Juros23
<select
value={r.interest}
onChange={(e) =>
change(r.id, {
interest: e.target
.value as ClosureInput['interest'],
})
}
>
<option value="">Indeterminados</option>
<option value="none">
Sem juros aplicáveis — motivo identificado
</option>
<option value="documented">
Montante identificado,sem cálculo de taxa pelo
portal
</option>
</select>
</label>
{r.interest === 'documented' && (
<label>
Montante de juros (MZN)
<input
value={r.interestAmount}
onChange={(e) =>
change(r.id, {
interestAmount: e.target.value,
})
}
/>
</label>
)}
{r.interest && (
<label>
Fonte/motivo dos juros ou da sua inexistência
<input
value={r.interestRef}
onChange={(e) =>
change(r.id, {
interestRef: e.target.value,
})
}
/>
</label>
)}
<label>
Destino do crédito25
<select
value={r.destination}
onChange={(e) =>
change(r.id, {
destination: e.target
.value as ClosureInput['destination'],
})
}
>
<option value="">Por escolher</option>
<option value="report">
Reportar integralmente26;sem pedido27 neste
percurso
</option>
<option value="refund">
Destinar parcela ao pedido27 — acompanhar emD
</option>
</select>
</label>
{r.destination === 'refund' && (
<label>
Parcela do cr?dito destinada ao pedido27 (MZN)
<input
value={r.refundAmount ?? ''}
onChange={(e) =>
change(r.id, {
refundAmount: e.target.value,
})
}
/>
</label>
)}
<h5>Pagamentos registados,distintos do saldo calculado</h5>
{r.destination === 'refund' && (
<label>
Parcela do crédito destinada ao pedido27 (MZN)
<input
value={r.refundAmount ?? ''}
onChange={(e) =>
change(r.id, {
refundAmount: e.target.value,
})
}
/>
</label>
)}
{r.payments.map((p) => (
<fieldset className="rounded border p-2" key={p.id}>
{(['reference', 'date', 'amount'] as const).map(
(k, i) => (
<label key={k}>
{
[
'Referência do pagamento',
'Data do pagamento',
'Montante do pagamento (MZN)',
][i]
}
<input
type={
k === 'date' ? 'date' : 'text'
}
value={p[k]}
onChange={(e) =>
change(r.id, {
payments: r.payments.map(
(v) =>
v.id === p.id
? {
...v,
[k]: e
.target
.value,
}
: v,
),
})
}
/>
</label>
),
)}
<button
type="button"
onClick={() =>
change(r.id, {
payments: r.payments.filter(
(v) => v.id !== p.id,
),
})
}
>
Remover pagamento desta preparação
</button>
</fieldset>
))}
<button
type="button"
onClick={() =>
change(r.id, {
payments: [
...r.payments,
{
id: crypto.randomUUID(),
reference: '',
date: '',
amount: '',
},
],
})
}
>
Registar referência de pagamento
</button>
<button
type="button"
onClick={() =>
save({
...b,
rows: b.rows.filter((v) => v.id !== r.id),
})
}
>
Remover fecho em preparação (histórico conservado)
</button>
</fieldset>
))}
<button
type="button"
onClick={() => {
try {
save(recordClosure(draft, b));
} catch (e) {
setError(String(e));
}
}}
>
Calcular e guardar fecho A/B
</button>
{closureReportRows(draft).map(([, label, value, source], i) => (
<p key={i}>
<strong>{label}</strong>: {value}
<small className="block">{source}</small>
</p>
))}
</details>
);
}