460 lines
21 KiB
TypeScript
460 lines
21 KiB
TypeScript
import {
|
||
newNormalSchedules,
|
||
newScheduleRow,
|
||
newNormalAsset,
|
||
scheduleRules,
|
||
type NormalSchedules,
|
||
type ScheduleRow,
|
||
type NormalAsset,
|
||
} from '@/lib/irpc-normal-schedules';
|
||
import type { NormalBook } from '@/lib/irpc-normal';
|
||
export function SimulationIrpcSchedules({
|
||
book,
|
||
save,
|
||
}: {
|
||
book: NormalBook;
|
||
save: (b: NormalBook) => void;
|
||
}) {
|
||
const s = book.schedules;
|
||
if (!s)
|
||
return (
|
||
<button
|
||
type="button"
|
||
onClick={() =>
|
||
save({ ...book, schedules: newNormalSchedules() })
|
||
}
|
||
>
|
||
Iniciar mapas complementares L6
|
||
</button>
|
||
);
|
||
const patch = (p: Partial<NormalSchedules>) =>
|
||
save({ ...book, schedules: { ...s, ...p } });
|
||
const row = (id: string, p: Partial<ScheduleRow>) =>
|
||
patch({ rows: s.rows.map((r) => (r.id === id ? { ...r, ...p } : r)) });
|
||
const asset = (id: string, p: Partial<NormalAsset>) =>
|
||
patch({
|
||
assets: s.assets.map((a) => (a.id === id ? { ...a, ...p } : a)),
|
||
});
|
||
return (
|
||
<fieldset
|
||
aria-label="Mapas complementares L6"
|
||
className="space-y-3 rounded border p-3"
|
||
>
|
||
<legend>
|
||
Mapas complementares — regras ordinárias documentadas
|
||
</legend>
|
||
<p>
|
||
Reutiliza o resultado e as linhas do plano. As somas brutas de
|
||
cada tema devem corresponder à conferência do lucro tributável.
|
||
Os efeitos com sinal ficam discriminados; uma dúvida não apaga
|
||
as parcelas independentes. L7 conserva benefícios e casos
|
||
especiais; L8 conserva o fecho anual.
|
||
</p>
|
||
{book.periodsFx && book.periodsFx.kind !== 'annual' ? (
|
||
<p>
|
||
Período actual no mapa «Período fiscal e conversão do
|
||
plano». O período anual anterior permanece conservado no
|
||
histórico.
|
||
</p>
|
||
) : (
|
||
<fieldset>
|
||
<legend>Período anual e correspondência dos meses</legend>
|
||
<label>
|
||
Tipo de período
|
||
<select
|
||
aria-label="Tipo de período L6"
|
||
value={s.period.kind}
|
||
onChange={(e) =>
|
||
patch({
|
||
period: {
|
||
...s.period,
|
||
kind: e.target
|
||
.value as NormalSchedules['period']['kind'],
|
||
},
|
||
})
|
||
}
|
||
>
|
||
<option value="">Por identificar</option>
|
||
<option value="calendar">
|
||
Ano civil — artigo7(1)
|
||
</option>
|
||
<option value="authorised_annual">
|
||
Anual diferente — autorização artigo7(2)
|
||
</option>
|
||
<option value="ep_annual">
|
||
Anual diferente de EP — comunicação artigo7(3)
|
||
</option>
|
||
</select>
|
||
</label>
|
||
{(['from', 'to', 'reference'] as const).map((k) => (
|
||
<label key={k} className="block">
|
||
{
|
||
{
|
||
from: 'Início do período AAAA-MM-DD',
|
||
to: 'Termo do período AAAA-MM-DD',
|
||
reference:
|
||
'Suporte do período e da correspondência mensal',
|
||
}[k]
|
||
}
|
||
<input
|
||
aria-label={'Período L6 ' + k}
|
||
value={s.period[k]}
|
||
onChange={(e) =>
|
||
patch({
|
||
period: {
|
||
...s.period,
|
||
[k]: e.target.value,
|
||
},
|
||
})
|
||
}
|
||
/>
|
||
</label>
|
||
))}
|
||
<p>
|
||
Período anual diferente: documente autorização do
|
||
artigo7(2) ou comunicação do EP do artigo7(3), início de
|
||
aplicação e manutenção por cinco exercícios. O plano
|
||
representa o período completo com termo em2026, emMZN.
|
||
Períodos curtos e conversão fiscal anual de outra moeda
|
||
ficam por corresponder.
|
||
</p>
|
||
</fieldset>
|
||
)}
|
||
<button
|
||
type="button"
|
||
onClick={() =>
|
||
patch({
|
||
rows: [...s.rows, newScheduleRow(crypto.randomUUID())],
|
||
})
|
||
}
|
||
>
|
||
Acrescentar regra complementar
|
||
</button>
|
||
{s.rows.map((r, n) => (
|
||
<fieldset key={r.id} className="rounded border p-2">
|
||
<legend>Regra complementar {n + 1}</legend>
|
||
<label>
|
||
Tratamento
|
||
<select
|
||
aria-label={'Tratamento complementar ' + (n + 1)}
|
||
value={r.rule}
|
||
onChange={(e) =>
|
||
row(r.id, {
|
||
rule: e.target.value as ScheduleRow['rule'],
|
||
})
|
||
}
|
||
>
|
||
<option value="">Por identificar</option>
|
||
{Object.entries(scheduleRules).map(([k, v]) => (
|
||
<option key={k} value={k}>
|
||
{v[0]} — {v[2]}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
{(
|
||
[
|
||
['amount', 'Montante MZN'],
|
||
[
|
||
'sourceCode',
|
||
'Código da linha de origem (custos/proveitos já incluídos)',
|
||
],
|
||
['document', 'Documento'],
|
||
['line', 'Linha distinta'],
|
||
['reference', 'Suporte da regra e dos factos'],
|
||
[
|
||
'fiscalYear',
|
||
'Exercício de imputação (periodização)',
|
||
],
|
||
] as const
|
||
).map(([k, label]) => (
|
||
<label key={k} className="block">
|
||
{label}
|
||
<input
|
||
aria-label={label + ' complementar ' + (n + 1)}
|
||
value={r[k]}
|
||
onChange={(e) =>
|
||
row(r.id, { [k]: e.target.value })
|
||
}
|
||
/>
|
||
</label>
|
||
))}
|
||
<p>
|
||
Entradas/distribuições de capital e variações
|
||
patrimoniais são externas ao resultado. Variação
|
||
negativa ordinária: indispensável à actividade sujeita,
|
||
sem liberalidade, menos-valia latente ou distribuição;
|
||
sem gratificações especiais. Periodização: operação
|
||
ordinária e exercício de realização documentados; sem
|
||
obra plurianual ou correcção de exercício anterior.
|
||
</p>
|
||
<label>
|
||
Condições demonstradas
|
||
<select
|
||
aria-label={'Condições complementar ' + (n + 1)}
|
||
value={r.conditions}
|
||
onChange={(e) =>
|
||
row(r.id, {
|
||
conditions: e.target.value as '' | 'yes',
|
||
})
|
||
}
|
||
>
|
||
<option value="">Informação em falta</option>
|
||
<option value="yes">
|
||
Sim, nos documentos identificados
|
||
</option>
|
||
</select>
|
||
</label>
|
||
<button
|
||
type="button"
|
||
onClick={() =>
|
||
patch({ rows: s.rows.filter((q) => q.id !== r.id) })
|
||
}
|
||
>
|
||
Retirar regra {n + 1} das entradas actuais
|
||
</button>
|
||
</fieldset>
|
||
))}
|
||
<fieldset>
|
||
<legend>Juros financeiros ordinários — artigo22c</legend>
|
||
<p>
|
||
Somente capitais alheios aplicados na exploração, encargos
|
||
documentados, não capitalizáveis, sem partes relacionadas,
|
||
subcapitalização ou outra limitação especial. Identifique o
|
||
remanescente após os juros compensatórios acrescidos no mapa
|
||
anterior. Outros juros permanecem pendentes neste caso.
|
||
</p>
|
||
<label className="block">
|
||
Juros ordinários remanescentes MZN
|
||
<input
|
||
value={s.interest.amount}
|
||
onChange={(e) =>
|
||
patch({
|
||
interest: {
|
||
...s.interest,
|
||
amount: e.target.value,
|
||
},
|
||
})
|
||
}
|
||
/>
|
||
</label>
|
||
<label className="block">
|
||
Suporte dos juros e condições
|
||
<input
|
||
value={s.interest.reference}
|
||
onChange={(e) =>
|
||
patch({
|
||
interest: {
|
||
...s.interest,
|
||
reference: e.target.value,
|
||
},
|
||
})
|
||
}
|
||
/>
|
||
</label>
|
||
<label>
|
||
Condições dos juros
|
||
<select
|
||
aria-label="Condições dos juros ordinários"
|
||
value={s.interest.conditions}
|
||
onChange={(e) =>
|
||
patch({
|
||
interest: {
|
||
...s.interest,
|
||
conditions: e.target.value as '' | 'yes',
|
||
},
|
||
})
|
||
}
|
||
>
|
||
<option value="">Informação em falta</option>
|
||
<option value="yes">
|
||
Demonstradas no recorte ordinário
|
||
</option>
|
||
</select>
|
||
</label>
|
||
</fieldset>
|
||
{!book.depreciation ? (
|
||
<fieldset>
|
||
<legend>
|
||
Amortizações fiscais — quotas constantes anuais
|
||
</legend>
|
||
<p>
|
||
Decreto72/2013, artigos3–7. Activo próprio sujeito a
|
||
deperecimento, custo histórico, uso no início de2026,
|
||
método uniforme, vida útil fiscal não excedida.
|
||
Documente que não há regra sectorial específica
|
||
prevalecente ou limitação especial. A categoria exige
|
||
qualificação documentada; os itens genéricos9/9.1 só se
|
||
aplicam quando não há taxa específica da tabelaI.
|
||
Duodécimos, reavaliação, bens usados e recuperação de
|
||
quotas antes tributadas ficam separados.
|
||
</p>
|
||
<label className="block">
|
||
Reconciliação de todos os activos com a depreciação do
|
||
plano
|
||
<input
|
||
value={s.assetsReference}
|
||
onChange={(e) =>
|
||
patch({ assetsReference: e.target.value })
|
||
}
|
||
/>
|
||
</label>
|
||
<button
|
||
type="button"
|
||
onClick={() =>
|
||
patch({
|
||
assets: [
|
||
...s.assets,
|
||
newNormalAsset(crypto.randomUUID()),
|
||
],
|
||
})
|
||
}
|
||
>
|
||
Acrescentar activo fiscal
|
||
</button>
|
||
{s.assets.map((a, n) => (
|
||
<fieldset key={a.id} className="rounded border p-2">
|
||
<legend>Activo {n + 1}</legend>
|
||
<label>
|
||
Categoria fiscal
|
||
<select
|
||
aria-label={'Categoria activo ' + (n + 1)}
|
||
value={a.category}
|
||
onChange={(e) =>
|
||
asset(a.id, {
|
||
category: e.target
|
||
.value as NormalAsset['category'],
|
||
rate:
|
||
e.target.value === 'computer'
|
||
? '25'
|
||
: e.target.value ===
|
||
'server'
|
||
? '20'
|
||
: '',
|
||
tableReference:
|
||
e.target.value === 'computer'
|
||
? 'Decreto72/2013 tabelaII grupo3 item9'
|
||
: e.target.value ===
|
||
'server'
|
||
? 'Decreto72/2013 tabelaII grupo3 item9.1'
|
||
: '',
|
||
})
|
||
}
|
||
>
|
||
<option value="">Por identificar</option>
|
||
<option value="computer">
|
||
Computador, impressora, UPS — II/3/9 —
|
||
25%
|
||
</option>
|
||
<option value="server">
|
||
Servidor — II/3/9.1 — 20%
|
||
</option>
|
||
<option value="other">
|
||
Outra categoria — correspondência
|
||
pendente
|
||
</option>
|
||
</select>
|
||
</label>
|
||
{(
|
||
[
|
||
['description', 'Descrição'],
|
||
['cost', 'Custo fiscal documentado MZN'],
|
||
['residual', 'Valor residual MZN'],
|
||
['rate', 'Taxa anual documentada %'],
|
||
[
|
||
'accounting',
|
||
'Depreciação contabilística de2026 MZN',
|
||
],
|
||
[
|
||
'previousFiscal',
|
||
'Amortização fiscal acumulada anterior MZN',
|
||
],
|
||
[
|
||
'inUseFrom',
|
||
'Data de entrada em uso AAAA-MM-DD',
|
||
],
|
||
[
|
||
'tableReference',
|
||
'Diploma, tabela, código e taxa aplicáveis',
|
||
],
|
||
[
|
||
'reference',
|
||
'Documentos e condições do activo',
|
||
],
|
||
] as const
|
||
).map(([k, label]) => (
|
||
<label key={k} className="block">
|
||
{label}
|
||
<input
|
||
aria-label={
|
||
label + ' activo ' + (n + 1)
|
||
}
|
||
value={a[k]}
|
||
onChange={(e) =>
|
||
asset(a.id, { [k]: e.target.value })
|
||
}
|
||
/>
|
||
</label>
|
||
))}
|
||
<label>
|
||
Condições do activo
|
||
<select
|
||
aria-label={'Condições activo ' + (n + 1)}
|
||
value={a.conditions}
|
||
onChange={(e) =>
|
||
asset(a.id, {
|
||
conditions: e.target.value as
|
||
| ''
|
||
| 'yes',
|
||
})
|
||
}
|
||
>
|
||
<option value="">
|
||
Informação em falta
|
||
</option>
|
||
<option value="yes">
|
||
Demonstradas no recorte anual constante
|
||
</option>
|
||
</select>
|
||
</label>
|
||
<button
|
||
type="button"
|
||
onClick={() =>
|
||
patch({
|
||
assets: s.assets.filter(
|
||
(q) => q.id !== a.id,
|
||
),
|
||
})
|
||
}
|
||
>
|
||
Retirar activo {n + 1} das entradas actuais
|
||
</button>
|
||
</fieldset>
|
||
))}
|
||
</fieldset>
|
||
) : (
|
||
<p>
|
||
Activos do mapa inicial conservados; a depreciação é agora
|
||
reconciliada exclusivamente pelo mapa de tabelas, métodos,
|
||
duodécimos e quotas anteriores abaixo.
|
||
</p>
|
||
)}
|
||
<label className="block">
|
||
Suporte da perda fiscal do próprio exercício, se apurada
|
||
<input
|
||
value={s.currentLossReference}
|
||
onChange={(e) =>
|
||
patch({ currentLossReference: e.target.value })
|
||
}
|
||
/>
|
||
</label>
|
||
<p>
|
||
Perda actual conhecida: matéria colectável e colecta zero apenas
|
||
depois de conferidas as restantes condições. Não é dedução a si
|
||
própria nem saldo oficial de reporte. Informação insuficiente
|
||
mantém os totais dependentes indeterminados.
|
||
</p>
|
||
</fieldset>
|
||
);
|
||
}
|