Files
Investidor-V2/resources/js/components/simulation-irpc-taxable-base.tsx
T

374 lines
16 KiB
TypeScript

import { irpcBook } from '@/lib/irpc-withholdings';
import {
newNormalBaseInput,
newNormalLoss,
type NormalBaseInput,
type NormalLoss,
} from '@/lib/irpc-normal-base';
import type { NormalBook } from '@/lib/irpc-normal';
export function SimulationIrpcTaxableBase({
draft,
book,
save,
}: {
draft: Record<string, unknown>;
book: NormalBook;
save: (b: NormalBook) => void;
}) {
if (!book.profit) return null;
const b = book.base;
const change = (p: Partial<NormalBaseInput>) =>
save({ ...book, base: { ...(b ?? newNormalBaseInput()), ...p } });
let identity: { nuit: string; name: string } | null = null;
try {
const m = irpcBook(draft).mrfta;
if (m && /^\d{9}$/.test(m.nuit))
identity = { nuit: m.nuit, name: m.name };
} catch {
/* A missing withholding book does not block this independent path. */
}
if (!b)
return (
<button type="button" onClick={() => change({})}>
Iniciar determinação da matéria colectável
</button>
);
const loss = (id: string, p: Partial<NormalLoss>) =>
change({
losses: b.losses.map((l) => (l.id === id ? { ...l, ...p } : l)),
});
const choices: Array<[string, string]> = [
['', 'Por esclarecer'],
['yes', 'Sim, documentado'],
['no', 'Não, documentado'],
];
return (
<fieldset
aria-label="Matéria colectável — perdas e deduções"
className="space-y-3 rounded border p-3"
>
<legend>Lucro tributável → matéria colectável simulada</legend>
<p>
Reutiliza o lucro tributável do cálculo actual, sem introduzir
novamente os montantes. Esse lucro e a matéria colectável não
são imposto a pagar. Recorte: regime geral, método declarativo,
perdas fiscais próprias em exercícios anuais ordinários, com
saldo e utilização propostos expressos. Não aplica benefícios
positivos sem regra específica nem créditos de retenção.
</p>
{identity &&
(!b.subjectNuit || b.subjectNuit === identity.nuit) && (
<button
type="button"
onClick={() => {
if (
!b.subjectNuit ||
b.subjectNuit === identity!.nuit
)
change({
subjectNuit: identity!.nuit,
identityReference:
b.identityReference ||
'Identidade reutilizada da preparação MRFTA desta oportunidade: ' +
identity!.name,
});
}}
>
Reutilizar NUIT da preparação existente {identity.nuit}
</button>
)}
<label className="block">
NUIT do sujeito passivo da matéria colectável
<input
value={b.subjectNuit}
onChange={(e) => change({ subjectNuit: e.target.value })}
/>
</label>
<label className="block">
Suporte da identidade e correspondência com o lucro
<input
value={b.identityReference}
onChange={(e) =>
change({ identityReference: e.target.value })
}
/>
</label>
<label className="block">
Método de determinação
<select
aria-label="Método de determinação"
value={b.method}
onChange={(e) =>
change({
method: e.target.value as NormalBaseInput['method'],
})
}
>
<option value="">Por esclarecer</option>
<option value="direct">
Declarativo/directo, documentado
</option>
<option value="indirect">
Métodos indirectos — fora deste recorte
</option>
</select>
</label>
<label className="block">
Suporte do método de determinação
<input
value={b.methodReference}
onChange={(e) =>
change({ methodReference: e.target.value })
}
/>
</label>
<label className="block">
Situação das perdas fiscais anteriores
<select
aria-label="Situação das perdas fiscais anteriores"
value={b.lossesState}
onChange={(e) =>
change({
lossesState: e.target
.value as NormalBaseInput['lossesState'],
})
}
>
<option value="">Informação em falta</option>
<option value="known">
Perdas/saldos conhecidos — discriminar
</option>
<option value="absent">
Ausência declarada com suporte
</option>
</select>
</label>
<label className="block">
Suporte e completude dos saldos de perdas
<input
value={b.lossesReference}
onChange={(e) =>
change({ lossesReference: e.target.value })
}
/>
</label>
{(b.lossesState === 'known' || b.losses.length > 0) && (
<>
<button
type="button"
onClick={() =>
change({
losses: [
...b.losses,
{
...newNormalLoss(crypto.randomUUID()),
subjectNuit: b.subjectNuit,
},
],
})
}
>
Acrescentar perda fiscal por exercício
</button>
<label className="block">
Objecto social e natureza da actividade mantidos no
termo do exercício
<select
aria-label="Objecto social e natureza da actividade mantidos no termo do exercício"
value={b.unchangedObject}
onChange={(e) =>
change({
unchangedObject: e.target
.value as NormalBaseInput['unchangedObject'],
})
}
>
{choices.map(([v, l]) => (
<option key={v} value={v}>
{l}
</option>
))}
</select>
</label>
<label className="block">
Suporte da manutenção ou alteração da actividade
<input
value={b.objectReference}
onChange={(e) =>
change({ objectReference: e.target.value })
}
/>
</label>
{b.losses.map((l, n) => (
<fieldset
key={l.id}
className="space-y-2 rounded border p-2"
>
<legend>
Perda {n + 1} —{' '}
{l.year || 'exercício por identificar'}
</legend>
{(
[
'year',
'subjectNuit',
'assessed',
'previouslyUsed',
'requested',
'reference',
] as const
).map((k) => (
<label className="block" key={k}>
{
{
year: 'Exercício da perda',
subjectNuit: 'NUIT da perda',
assessed:
'Perda fiscal apurada ou corrigida em MZN',
previouslyUsed:
'Utilizações anteriores em MZN, incluindo zero conhecido',
requested:
'Utilização proposta neste exercício em MZN',
reference:
'Documento do apuramento fiscal, correcções e saldo',
}[k]
}
<input
value={l[k]}
onChange={(e) =>
loss(l.id, { [k]: e.target.value })
}
/>
</label>
))}
<label className="block">
Origem da perda
<select
aria-label={'Origem da perda ' + (n + 1)}
value={l.kind}
onChange={(e) =>
loss(l.id, {
kind: e.target
.value as NormalLoss['kind'],
})
}
>
<option value="">Por esclarecer</option>
<option value="ordinary">
Perda própria ordinária do mesmo sujeito
</option>
<option value="transfer">
Transmitida/fusão — tratamento próprio
</option>
<option value="other">
Outra origem — por corresponder
</option>
</select>
</label>
{(
[
'annualPeriods',
'generalActivity',
'balanceConfirmed',
] as const
).map((k) => (
<label className="block" key={k}>
{
{
annualPeriods:
'Exercícios anuais ordinários, sem períodos curtos especiais',
generalActivity:
'Perda do regime geral, fora de transparência/isenção parcial/actividade reduzida',
balanceConfirmed:
'Saldo fiscal, correcções e utilizações anteriores reconciliados',
}[k]
}
<select
aria-label={k + ' ' + (n + 1)}
value={l[k]}
onChange={(e) =>
loss(l.id, {
[k]: e.target
.value as NormalLoss['annualPeriods'],
})
}
>
{choices.map(([v, label]) => (
<option key={v} value={v}>
{label}
</option>
))}
</select>
</label>
))}
</fieldset>
))}
<p>
A utilização é escolhida explicitamente. O portal não
presume ordem FIFO, não ultrapassa saldo ou lucro e não
consome o saldo oficial ao simular ou guardar.
</p>
</>
)}
<label className="block">
Benefícios/deduções à matéria colectável
<select
aria-label="Benefícios/deduções à matéria colectável"
value={b.benefits.state}
onChange={(e) =>
change({
benefits: {
...b.benefits,
state: e.target
.value as NormalBaseInput['benefits']['state'],
},
})
}
>
<option value="">Informação em falta</option>
<option value="known">Valor conhecido — identificar</option>
<option value="absent">
Ausência declarada com suporte
</option>
</select>
</label>
{(b.benefits.state === 'known' || b.benefits.amount !== '') && (
<label className="block">
Montante conhecido de benefícios/deduções em MZN
<input
value={b.benefits.amount}
onChange={(e) =>
change({
benefits: {
...b.benefits,
amount: e.target.value,
},
})
}
/>
</label>
)}
<label className="block">
Suporte dos benefícios/deduções, regra e exercício
<input
value={b.benefits.reference}
onChange={(e) =>
change({
benefits: {
...b.benefits,
reference: e.target.value,
},
})
}
/>
</label>
<p>
Um benefício positivo conhecido permanece por corresponder; a
referência não autoriza subtracção livre. Créditos à colecta,
retenções sofridas e imposto a pagar seguem etapa própria.
</p>
</fieldset>
);
}