517 lines
24 KiB
TypeScript
517 lines
24 KiB
TypeScript
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>
|
||
);
|
||
}
|
||
}
|