Files
Investidor-V2/resources/js/components/simulation-ispc-quarter.tsx
T

264 lines
11 KiB
TypeScript

import { useState } from 'react';
import {
ispcEligibilityBook,
ispcEligibilityCurrent,
} from '@/lib/ispc-eligibility';
import {
ispcQuarterBook,
saveIspcQuarterBook,
recordIspcQuarter,
ispcQuarterReportRows,
type IspcQuarterInput,
type IspcQuarterBook,
} from '@/lib/ispc-quarter';
export function SimulationIspcQuarter({
draft,
onChange,
}: {
draft: Record<string, unknown>;
onChange: (field: string, value: string) => void;
}) {
const [error, setError] = useState('');
if (draft.fiscal_component !== 'ispc') return null;
try {
if (ispcEligibilityBook(draft).input.mode === 'occasional')
return (
<p>
Actividade habitual:campos trimestrais conservados no
histórico. Use o percurso de operações ocasionais próprias
abaixo.
</p>
);
} catch {
/* Preserve the existing explicit recovery error below. */
}
let book: IspcQuarterBook;
try {
book = ispcQuarterBook(draft);
} catch (e) {
return <p role="alert">{String(e)}</p>;
}
const i = book.input,
save = (b: IspcQuarterBook) => {
try {
onChange('modelo_a_snapshot', saveIspcQuarterBook(draft, b));
setError('');
} catch (e) {
setError(String(e));
}
},
change = (patch: Partial<IspcQuarterInput>) =>
save({ ...book, input: { ...i, ...patch } });
let service = false,
ready = false;
try {
const e = ispcEligibilityBook(draft);
ready =
ispcEligibilityCurrent(draft, e) &&
e.result?.status === 'compatible';
service = e.input.activities.some((a) =>
['services12', 'liberal15'].includes(a),
);
} catch {
/* Show explicit preparation issue below, never fabricate eligibility. */
}
return (
<section
aria-label="Apuramento trimestral ISPC"
className="space-y-3 rounded border p-4"
>
<h4>ISPC — apuramento trimestral</h4>
<p>
Reutiliza o enquadramento e a opção guardados. Declare o volume
de todas as actividades do sujeito passivo em MZN; custos e IVA
de aquisições não reduzem esta base. Dados em falta permanecem
indeterminados.
</p>
{!ready && (
<p>
Reveja e guarde primeiro a avaliação de enquadramento acima.
As entradas trimestrais e o histórico ficam conservados.
</p>
)}
{error && <p role="alert">{error}</p>}
<label className="block">
Ano
<input
className="block rounded border p-2"
value={i.year}
onChange={(e) => change({ year: e.target.value })}
/>
</label>
<label className="block">
Trimestre
<select
className="block rounded border p-2"
value={i.quarter}
onChange={(e) => change({ quarter: e.target.value })}
>
{['1', '2', '3', '4'].map((q) => (
<option key={q} value={q}>
{q} —{' '}
{
[
'Janeiro a Março',
'Abril a Junho',
'Julho a Setembro',
'Outubro a Dezembro',
][Number(q) - 1]
}
</option>
))}
</select>
</label>
<label className="block">
Âmbito do volume
<select
className="block rounded border p-2"
value={i.coverage}
onChange={(e) =>
change({
coverage: e.target
.value as IspcQuarterInput['coverage'],
})
}
>
<option value="">Por confirmar</option>
<option value="all_taxpayer_activity">
Todas as actividades do sujeito passivo, sem repetir
oportunidades
</option>
</select>
</label>
<div className="grid gap-3 md:grid-cols-2">
{i.sales.map(
(v, n) =>
(n < Number(i.quarter) ||
(i.annualBandOrigin === 'closed_year' &&
!service)) && (
<fieldset
key={n}
className="space-y-2 rounded border p-2"
>
<legend>
Trimestre {n + 1}
{n + 1 === Number(i.quarter)
? ' — a apurar'
: ' — acumulado anterior'}
</legend>
<label className="block">
Volume T{n + 1} em MZN
<input
className="block rounded border p-2"
inputMode="decimal"
value={v}
onChange={(e) => {
const a = [
...i.sales,
] as IspcQuarterInput['sales'];
a[n] = e.target.value;
change({ sales: a });
}}
/>
</label>
<label className="block">
Suporte ou pressuposto T{n + 1}
<input
className="block rounded border p-2"
value={i.salesRefs[n]}
onChange={(e) => {
const a = [
...i.salesRefs,
] as IspcQuarterInput['salesRefs'];
a[n] = e.target.value;
change({ salesRefs: a });
}}
/>
</label>
</fieldset>
),
)}
</div>
{!service && (
<fieldset className="space-y-2 rounded border p-3">
<legend>
Escalão anual da actividade — distinto do volume de
acesso
</legend>
<p>
Os limites de 1.000.000 / 2.500.000 / 4.000.000 MZN são
anuais. A taxa do escalão é aplicada à base do
trimestre; não dividir os limites por quatro. O excesso
anual tem taxa própria de 20%.
</p>
<label className="block">
Volume anual 2026 para o escalão
<input
className="block rounded border p-2"
value={i.annualBandVolume}
onChange={(e) =>
change({ annualBandVolume: e.target.value })
}
/>
</label>
<label className="block">
Origem da referência anual
<select
className="block rounded border p-2"
value={i.annualBandOrigin}
onChange={(e) =>
change({
annualBandOrigin: e.target
.value as IspcQuarterInput['annualBandOrigin'],
})
}
>
<option value="">Por identificar</option>
<option value="forecast">
Previsão expressa — simulação
</option>
<option value="closed_year">
Ano encerrado — reconciliar quatro trimestres
</option>
</select>
</label>
<label className="block">
Suporte da referência anual
<input
className="block rounded border p-2"
value={i.annualBandRef}
onChange={(e) =>
change({ annualBandRef: e.target.value })
}
/>
</label>
</fieldset>
)}
<button
type="button"
className="rounded bg-emerald-800 p-2 text-white"
onClick={() => {
try {
save(recordIspcQuarter(draft, book));
} catch (e) {
setError(String(e));
}
}}
>
Calcular e guardar apuramento trimestral ISPC
</button>
{ispcQuarterReportRows(draft).map((r, n) => (
<p key={n}>
<strong>{r[1]}:</strong> {r[2]}
<small className="block">{r[3]}</small>
</p>
))}
<p>
Preparação para a declaração periódica dos artigos 10/11. Este
cálculo não submete formulários nem efectua pagamentos. Retenção
na fonte em aquisições ocasionais tem registo e base próprios.
</p>
</section>
);
}