374 lines
16 KiB
TypeScript
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>
|
|
);
|
|
}
|