Files
Investidor-V2/resources/js/components/simulation-irpc-normal.tsx
T
2026-11-10 13:23:11 +02:00

517 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { SimulationIrpcSpecial } from '@/components/simulation-irpc-special';
import { SimulationIrpcFormPreparation } from '@/components/simulation-irpc-form-preparation';
import { SimulationIrpcPeriodFx } from '@/components/simulation-irpc-period-fx';
import { SimulationIrpcDepreciation } from '@/components/simulation-irpc-depreciation';
import { SimulationIrpcCosts } from '@/components/simulation-irpc-costs';
import { SimulationIrpcSchedules } from '@/components/simulation-irpc-schedules';
import { SimulationIrpcEconomicInclusion } from '@/components/simulation-irpc-economic-inclusion';
import { SimulationIrpcAssessment } from '@/components/simulation-irpc-assessment';
import { useState } from 'react';
import { SimulationIrpcTaxableBase } from '@/components/simulation-irpc-taxable-base';
import {
newNormalProfitInput,
profitTopics,
type NormalProfitInput,
type ProfitEvidence,
} from '@/lib/irpc-normal-profit';
import {
normalBook,
saveNormalBook,
normalFinancialSource,
newNormalAdjustment,
recordNormal,
normalHtml,
normalReportRows,
type NormalBook,
type NormalAdjustment,
} from '@/lib/irpc-normal';
export function SimulationIrpcNormal({
draft,
onChange,
}: {
draft: Record<string, unknown>;
onChange: (field: string, value: string) => void;
}) {
const [error, setError] = useState('');
if (draft.fiscal_component !== 'irpc') return null;
try {
const b = normalBook(draft),
source = normalFinancialSource(draft);
const save = (next: NormalBook) => {
try {
onChange('modelo_a_snapshot', saveNormalBook(draft, next));
setError('');
} catch (e) {
setError(String(e));
}
};
const patch = (id: string, p: Partial<NormalAdjustment>) =>
save({
...b,
rows: b.rows.map((r) => (r.id === id ? { ...r, ...p } : r)),
});
const profit = (patch: Partial<NormalProfitInput>) =>
save({
...b,
profit: { ...(b.profit ?? newNormalProfitInput()), ...patch },
});
const field = (
r: NormalAdjustment,
k: 'document' | 'line' | 'amount' | 'reference',
label: string,
) => (
<label className="block">
{label}
<input
value={r[k]}
onChange={(e) => patch(r.id, { [k]: e.target.value })}
/>
</label>
);
return (
<section
aria-label="IRPC normal — ajustamentos"
className="space-y-3 rounded border p-4"
>
<h4>L6 — IRPC normal: ajustamentos ao resultado financeiro</h4>
<p>
Acréscimos de multas sem origem contratual e juros
compensatórios incluídos nos custos do plano, artigo 36(1)c.
A conferência anual pode determinar o lucro tributável
simulado no recorte ordinário documentado. A matéria
colectável exige conferência específica de deduções e
perdas; a colecta tem cálculo próprio e o saldo final a
pagar ou recuperar pertence aL8. Não reutiliza
automaticamente juros simulados de L5.
</p>
{error && <p role="alert">{error}</p>}
{source ? (
<p>
Origem: plano financeiro desta oportunidade;{' '}
{source.periods} meses; {source.currency}; resultado
antes de imposto: {source.accountingProfitBeforeTax}.
Cobranças, pagamentos e reembolso de capital ficam fora
do resultado.
</p>
) : (
<p>
{b.periodsFx
? 'Complete a correspondência do período e moeda abaixo para reutilizar o plano existente emMZN.'
: 'Prepare o plano financeiro para reutilizar o resultado e os custos reconciliados.'}
</p>
)}
<label className="block">
Exercício fiscal
<input
value={b.year}
onChange={(e) => save({ ...b, year: e.target.value })}
/>
</label>
<label className="block">
{b.periodsFx
? 'Correspondência do plano ao período documentado'
: b.schedules
? 'Correspondência dos12 meses do plano ao período anual documentado'
: 'Correspondência dos 12 meses do plano a Janeiro–Dezembro de 2026'}
<input
value={b.mappingReference}
onChange={(e) =>
save({ ...b, mappingReference: e.target.value })
}
/>
</label>
<label className="block">
Âmbito
<select
value={b.scope}
onChange={(e) =>
save({
...b,
scope: e.target.value as NormalBook['scope'],
})
}
>
<option value="">Por identificar</option>
<option value="resident_general">
Sujeito residente — regime geral, fora de
transparência e regimes especiais
</option>
<option value="ep_general">
Não residente com estabelecimento estável — regime
geral, só resultado imputável ao EP (mapas
complementares)
</option>
<option value="other">
Outro âmbito — por corresponder
</option>
</select>
</label>
<label className="block">
Suporte do âmbito e da imputação integral do resultado ao
sujeito/EP
<input
value={b.scopeReference}
onChange={(e) =>
save({ ...b, scopeReference: e.target.value })
}
/>
</label>
<button
type="button"
onClick={() =>
save({
...b,
rows: [
...b.rows,
newNormalAdjustment(crypto.randomUUID()),
],
})
}
>
Acrescentar ajustamento ao resultado
</button>
{b.rows.map((r) => (
<fieldset
key={r.id}
className="space-y-2 rounded border p-3"
>
<legend>
{r.document || 'Ajustamento por identificar'}
</legend>
<label className="block">
Custo de origem na ponte
<select
value={r.sourceCode}
onChange={(e) =>
patch(r.id, { sourceCode: e.target.value })
}
>
<option value="">Seleccione</option>
{source?.accountingLines
.filter(
(l) => l.accountingEffect === 'expense',
)
.map((l) => (
<option key={l.code} value={l.code}>
{l.origin}: {l.total}{' '}
{source.currency}
</option>
))}
</select>
</label>
{field(
r,
'document',
'Documento ou pressuposto identificado',
)}
{field(r, 'line', 'Linha distinta do documento')}
{field(r, 'amount', 'Parcela do custo em MZN')}
<label className="block">
Natureza documentada
<select
value={r.rule}
onChange={(e) =>
patch(r.id, {
rule: e.target
.value as NormalAdjustment['rule'],
})
}
>
<option value="">Por classificar</option>
<option value="noncontractual_fine">
Multa/encargo por infracção sem origem
contratual — 36(1)c
</option>
<option value="compensatory_interest">
Juros compensatórios — 36(1)c
</option>
<option value="other">
Outro ajustamento — por corresponder
</option>
</select>
</label>
<label className="block">
Parcela incluída nos custos do resultado de partida
<select
value={r.included}
onChange={(e) =>
patch(r.id, {
included: e.target
.value as NormalAdjustment['included'],
})
}
>
<option value="">Por demonstrar</option>
<option value="yes">
Sim, reconciliada com a linha de origem
</option>
<option value="no">
Não — não acrescentar novamente
</option>
</select>
</label>
{field(
r,
'reference',
'Suporte da qualificação, ocorrência e inclusão contabilística',
)}
<p>
Juros bancários e penalidades contratuais não são
classificados automaticamente como infracções.
</p>
</fieldset>
))}
{!b.profit ? (
<button type="button" onClick={() => profit({})}>
Iniciar conferência até ao lucro tributável
</button>
) : (
<fieldset
aria-label="Conferência do lucro tributável"
className="space-y-3 rounded border p-3"
>
<legend>
Resultado financeiro → ajustamentos → lucro
tributável simulado
</legend>
<p>
Conferência do sujeito passivo/exercício completo.
Cada receita remanescente deve ser ordinária do
exercício;cada custo remanescente deve estar
documentado e ser indispensável à fonte
produtora,sem outras exclusões ou limites. Outras
situações conhecidas permanecem discriminadas.
Ausência declarada exige suporte e não é informação
em falta.
</p>
<label className="block">
Suporte da completude do sujeito passivo no
exercício
<input
value={b.profit.completenessReference}
onChange={(e) =>
profit({
completenessReference: e.target.value,
})
}
/>
</label>
{source?.accountingLines.map((l) => {
const review = b.profit!.lines.find(
(r) => r.code === l.code,
) ?? {
code: l.code,
state: '' as const,
reference: '',
};
const change = (p: Partial<typeof review>) =>
profit({
lines: [
...b.profit!.lines.filter(
(r) => r.code !== l.code,
),
{ ...review, ...p },
],
});
return (
<fieldset
key={l.code}
className="rounded border p-2"
>
<legend>
{l.origin} — {l.total} {source.currency}
</legend>
<label className="block">
Estado fiscal — {l.origin}
<select
aria-label={
'Estado fiscal — ' + l.origin
}
value={review.state}
onChange={(e) =>
change({
state: e.target
.value as ProfitEvidence['state'],
})
}
>
<option value="">
Informação em falta
</option>
<option value="known">
Valor conhecido e qualificação
ordinária documentada
</option>
<option value="absent">
Ausência declarada — conferir
origem zero
</option>
</select>
</label>
<label className="block">
Suporte fiscal — {l.origin}
<input
value={review.reference}
onChange={(e) =>
change({
reference: e.target.value,
})
}
/>
</label>
</fieldset>
);
})}
<p>
Depreciação positiva e juros financeiros não
integralmente acrescidos exigem correspondência
própria;uma declaração de ausência não apaga os
valores do plano.
</p>
{profitTopics.map(([k, label]) => {
const t = b.profit!.topics[k];
const change = (p: Partial<ProfitEvidence>) =>
profit({
topics: {
...b.profit!.topics,
[k]: { ...t, ...p },
},
});
return (
<fieldset
key={k}
className="rounded border p-2"
>
<legend>{label}</legend>
<label className="block">
Estado — {label}
<select
aria-label={'Estado — ' + label}
value={t.state}
onChange={(e) =>
change({
state: e.target
.value as ProfitEvidence['state'],
})
}
>
<option value="">
Informação em falta
</option>
<option value="known">
Valor conhecido
</option>
<option value="absent">
Ausência declarada com suporte
</option>
</select>
</label>
{(t.state === 'known' ||
t.amount !== '') && (
<label className="block">
Montante conhecido — {label}
<input
value={t.amount}
onChange={(e) =>
change({
amount: e.target.value,
})
}
/>
</label>
)}
<label className="block">
Suporte — {label}
<input
value={t.reference}
onChange={(e) =>
change({
reference: e.target.value,
})
}
/>
</label>
</fieldset>
);
})}
</fieldset>
)}
<SimulationIrpcPeriodFx draft={draft} book={b} save={save} />
<SimulationIrpcSchedules book={b} save={save} />
<SimulationIrpcEconomicInclusion
key={JSON.stringify([
draft.context_opportunity,
b.base?.subjectNuit,
b.year,
])}
draft={draft}
book={b}
save={save}
/>
<SimulationIrpcDepreciation
draft={draft}
book={b}
save={save}
/>
<SimulationIrpcCosts draft={draft} book={b} save={save} />
<SimulationIrpcTaxableBase draft={draft} book={b} save={save} />
<SimulationIrpcAssessment book={b} save={save} />
<SimulationIrpcSpecial draft={draft} book={b} save={save} />
<SimulationIrpcFormPreparation
draft={draft}
book={b}
save={save}
/>
<button
type="button"
onClick={() => {
try {
save(recordNormal(draft, b));
} catch (e) {
setError(String(e));
}
}}
>
Calcular e guardar L6 e tratamentos L7 preparados
</button>
<button
type="button"
onClick={() => {
try {
const html = normalHtml(draft),
url = URL.createObjectURL(
new Blob([html], {
type: 'text/html;charset=utf-8',
}),
),
a = document.createElement('a');
a.href = url;
a.download = 'IRPC-normal-ajustamentos.html';
a.click();
URL.revokeObjectURL(url);
setError('');
} catch (e) {
setError(String(e));
}
}}
>
Exportar mapa parcial L6
</button>
{normalReportRows(draft).map((r, n) => (
<p key={n}>
{r[1]} — {r[2]} — {r[3]}
</p>
))}
</section>
);
} catch (e) {
return (
<p role="alert">
Preparação L6 não recuperável: {String(e)}. Originais e
histórico conservados.
</p>
);
}
}