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

620 lines
30 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 {
prepareEconomicBook,
type AnnualBook,
} from '@/lib/irpc-annual-settlement';
import {
economicSource,
newEconomicPosition,
type EconomicPosition,
} from '@/lib/irpc-economic-credit';
export function SimulationIrpcEconomic({
book,
save,
residentConfirmed,
}: {
book: AnnualBook;
save: (book: AnnualBook) => void;
residentConfirmed: boolean;
}) {
const c = book.economicCredits;
if (!c)
return (
<fieldset className="rounded border p-3">
<legend>Crédito por dupla tributação económica</legend>
<p>
Para lucros distribuídos por entidade moçambicana sujeita e
não isenta de IRPC, distinga o imposto correspondente aos
lucros da retenção sofrida e do imposto estrangeiro.
Reutiliza a colecta e conserva o resumo de créditos
anterior.
</p>
<button
type="button"
onClick={() => save(prepareEconomicBook(book))}
>
Preparar crédito económico
</button>
</fieldset>
);
const update = (next: typeof c) => save({ ...book, economicCredits: next });
const rowChange = (r: EconomicPosition) =>
update({ ...c, rows: c.rows.map((x) => (x.id === r.id ? r : x)) });
return (
<fieldset className="space-y-3 rounded border p-3">
<legend>Crédito económico — lucros distribuídos</legend>
<p>{economicSource}</p>
<p>
O detalhe substitui nesta execução o resumo económico anterior,
que permanece conservado:{' '}
{book.internationalCredits?.economic.state ||
'informação em falta'}
; montante{' '}
{book.internationalCredits?.economic.amount || 'não preenchido'}
. Os benefícios e o crédito internacional mantêm os seus estados
próprios.
</p>
<label className="block">
Estado do crédito económico
<select
aria-label="Estado do crédito económico L8"
value={c.state}
onChange={(e) =>
update({
...c,
state: e.target.value as typeof c.state,
})
}
>
<option value="">Informação em falta</option>
<option value="known">Lucros e IRPC conhecidos</option>
<option value="absent">Ausência documentada</option>
</select>
</label>
<label className="block">
Suporte do inventário económico
<input
aria-label="Suporte económico L8"
value={c.reference}
onChange={(e) =>
update({ ...c, reference: e.target.value })
}
/>
</label>
{(c.state === 'known' || c.rows.length > 0) && (
<button
type="button"
onClick={() =>
update({
...c,
state: 'known',
rows: [
...c.rows,
{
...newEconomicPosition(crypto.randomUUID()),
subjectNuit: book.subjectNuit,
year: book.year,
recipientResident: residentConfirmed
? 'yes'
: '',
},
],
})
}
>
Registar lucros distribuídos
</button>
)}
{c.rows.map((r, i) => {
const patch = (v: Partial<EconomicPosition>) =>
rowChange({ ...r, ...v });
const prefix = 'Crédito económico ' + (i + 1) + ' — ';
const field = (
key: Exclude<
keyof EconomicPosition,
'previousUse' | 'liquidation' | 'association'
>,
label: string,
) => (
<label key={key} className="block">
{label}
<input
aria-label={prefix + label}
value={r[key]}
onChange={(e) => patch({ [key]: e.target.value })}
/>
</label>
);
const choice = (
key:
| 'recipientResident'
| 'payerResident'
| 'payerIrpc'
| 'payerExempt'
| 'article40'
| 'incomeIncluded',
label: string,
) => (
<label key={key} className="block">
{label}
<select
aria-label={prefix + label}
value={r[key]}
onChange={(e) => patch({ [key]: e.target.value })}
>
<option value="">Informação em falta</option>
<option value="yes">Sim — documentado</option>
<option value="no">Não — documentado</option>
</select>
</label>
);
const outside = r.article40 === 'yes';
const detail = (
<>
{(
[
[
'distributionDocument',
'Documento da distribuição',
],
[
'distributionParcel',
'Parcela dos lucros no documento',
],
[
'distributionDate',
'Data da distribuição (AAAA-MM-DD)',
],
[
'income',
'Lucros incluídos antes do crédito — MZN, ilíquidos de retenção',
],
[
'incomeReference',
'Suporte da inclusão e do valor ilíquido na base corrente',
],
[
'taxIssuer',
'Emitente do comprovativo de IRPC correspondente',
],
[
'taxDocument',
'Documento do IRPC correspondente aos lucros',
],
[
'taxParcel',
'Parcela desse IRPC no documento',
],
[
'parcelReference',
'Suporte da identificação da parcela de imposto',
],
['currency', 'Moeda comprovada'],
[
'proofAmount',
'IRPC comprovado nesta parcela — MZN',
],
[
'taxAmount',
'IRPC correspondente aos lucros seleccionados — MZN',
],
[
'attributionReference',
'Suporte da correspondência entre imposto e lucros',
],
[
'previouslyAttributed',
'IRPC desta parcela já atribuído a outros lucros — MZN',
],
[
'priorAttributionReference',
'Suporte das atribuições anteriores, incluindo zero conhecido',
],
[
'grossUpIncluded',
'Crédito nominal já acrescido à base (art.53) — MZN',
],
[
'grossUpReference',
'Suporte do acréscimo art.53 já incluído em L6',
],
[
'nationalLimit',
'Quota proporcional da colecta após acréscimo — MZN',
],
[
'nationalLimitReference',
'Suporte da quota proporcional da colecta',
],
] as const
)
.filter(
([key]) =>
r.nationalMethod !== 'proportional' ||
(key !== 'nationalLimit' &&
key !== 'nationalLimitReference'),
)
.map(([key, label]) => field(key, label))}
<label className="block">
Quota proporcional
<select
aria-label={prefix + 'Método da quota'}
value={r.nationalMethod ?? 'documented'}
onChange={(e) =>
patch({
nationalMethod: e.target
.value as EconomicPosition['nationalMethod'],
})
}
>
<option value="documented">
Limite documentado
</option>
<option value="proportional">
Derivar da colecta L6 ordinária
</option>
</select>
</label>
{r.nationalMethod === 'proportional' && (
<label className="block">
Correspondência dos lucros mais acréscimo53 na
base corrente
<textarea
aria-label={
prefix + 'Correspondência da quota'
}
value={r.nationalBasisReference ?? ''}
onChange={(e) =>
patch({
nationalBasisReference:
e.target.value,
})
}
/>
<span className="block">
Quota = colecta L6 × (lucros incluídos +
crédito nominal acrescido) / matéria
colectável L6. Não altera a base nem
acrescenta o crédito novamente. O limite
manual anterior fica conservado, mas não é
usado neste modo.
</span>
</label>
)}
{choice(
'incomeIncluded',
'Lucros ilíquidos e acréscimo incluídos na base corrente',
)}
<p>
O cálculo confere 60% do IRPC correspondente com o
acréscimo já incluído em L6 e a quota proporcional
documentada. Se a base estiver incompleta,
reconcilie-a no percurso existente. Esta acção
conserva os valores e não altera a colecta
automaticamente.
</p>
<button
type="button"
onClick={() =>
patch({ assessmentKey: book.assessmentKey })
}
>
Confirmar inclusão e quota na colecta seleccionada
</button>
<label className="block">
Utilização anterior efectiva do crédito
<select
aria-label={prefix + 'Estado do uso anterior'}
value={r.previousUse.state}
onChange={(e) =>
patch({
previousUse: {
...r.previousUse,
state: e.target
.value as EconomicPosition['previousUse']['state'],
},
})
}
>
<option value="">Informação em falta</option>
<option value="known">
Montante conhecido
</option>
<option value="absent">
Ausência documentada
</option>
</select>
</label>
{(r.previousUse.state === 'known' ||
r.previousUse.amount) && (
<label className="block">
Crédito já utilizado — MZN
<input
aria-label={prefix + 'Uso anterior MZN'}
value={r.previousUse.amount}
onChange={(e) =>
patch({
previousUse: {
...r.previousUse,
amount: e.target.value,
},
})
}
/>
</label>
)}
<label className="block">
Suporte do uso anterior ou da sua ausência
<input
aria-label={prefix + 'Suporte do uso anterior'}
value={r.previousUse.reference}
onChange={(e) =>
patch({
previousUse: {
...r.previousUse,
reference: e.target.value,
},
})
}
/>
</label>
</>
);
return (
<fieldset
key={r.id}
className="space-y-2 rounded border p-3"
>
<legend>Lucros {i + 1}</legend>
{field('subjectNuit', 'NUIT beneficiário')}
{field('payerNuit', 'NUIT distribuidor')}
{field('year', 'Exercício')}
<label className="block">
Natureza
<select
aria-label={prefix + 'Natureza'}
value={r.kind}
onChange={(e) =>
patch({
kind: e.target
.value as EconomicPosition['kind'],
})
}
>
<option value="">Por identificar</option>
<option value="ordinary_profit">
Lucros ordinários — art.64(1–2)
</option>
<option value="liquidation">
Partilha em liquidação — art.64(3)
</option>
<option value="association">
Associação — art.64(4)
</option>
</select>
</label>
{r.kind === 'association' && (
<fieldset className="space-y-2 rounded border p-3">
<legend>Associação — art.64(4)</legend>
<label className="block">
Modalidade
<select
aria-label={
prefix + 'Modalidade da associação'
}
value={r.association?.kind ?? ''}
onChange={(e) =>
patch({
association: {
kind: e.target.value as
| ''
| 'participation'
| 'quota',
effectivelyTaxed:
r.association
?.effectivelyTaxed ??
'',
reference:
r.association
?.reference ?? '',
},
})
}
>
<option value="">
Escolha a modalidade
</option>
<option value="participation">
Em participação
</option>
<option value="quota">À quota</option>
</select>
</label>
{r.association?.kind === 'participation' && (
<label className="block">
Rendimentos distribuídos efectivamente
tributados
<select
aria-label={
prefix +
'Tributação efectiva da participação'
}
value={
r.association.effectivelyTaxed
}
onChange={(e) =>
patch({
association: {
...r.association!,
effectivelyTaxed: e
.target.value as
| ''
| 'yes'
| 'no',
},
})
}
>
<option value="">
Informação em falta
</option>
<option value="yes">
Sim — documentado
</option>
<option value="no">
Não — documentado
</option>
</select>
</label>
)}
<label className="block">
Suporte do contrato, rendimentos e adaptação
do crédito
<input
aria-label={
prefix + 'Suporte da associação'
}
value={r.association?.reference ?? ''}
onChange={(e) =>
patch({
association: {
kind: '',
effectivelyTaxed: '',
...r.association,
reference: e.target.value,
},
})
}
/>
</label>
</fieldset>
)}
{r.kind === 'liquidation' && (
<fieldset className="space-y-2 rounded border p-3">
<legend>
Partilha em liquidação — art.64(3)
</legend>
<p>
No rendimento abaixo indique a diferença
positiva classificada documentalmente como
aplicação de capitais. O crédito limita-se
também ao valor atribuído menos entradas
efectivas para capital. O excesso mantém
tratamento próprio de mais-valia; não
integra a quota económica. Confirme a
inclusão L6 e o IRPC correspondente apenas à
parcela elegível.
</p>
{(
[
[
'attributed',
'Valor atribuído na partilha — MZN',
],
[
'paidInCapital',
'Entradas efectivas para capital na contabilidade da liquidada — MZN',
],
[
'reference',
'Suporte da partilha e classificação do rendimento',
],
] as const
).map(([key, label]) => (
<label key={key} className="block">
{label}
<input
aria-label={prefix + label}
value={r.liquidation?.[key] ?? ''}
onChange={(e) =>
patch({
liquidation: {
attributed: '',
paidInCapital: '',
reference: '',
...r.liquidation,
[key]: e.target.value,
},
})
}
/>
</label>
))}
</fieldset>
)}
{residentConfirmed && r.recipientResident === 'yes' ? (
<p>
Residência do beneficiário reutilizada da
colecta corrente no regime geral. NUIT e
exercício conservados.
</p>
) : (
choice(
'recipientResident',
'Beneficiário com sede/direcção efectiva em Moçambique',
)
)}
{choice(
'payerResident',
'Distribuidor com sede/direcção efectiva em Moçambique',
)}
{choice('payerIrpc', 'Distribuidor sujeito a IRPC')}
{choice('payerExempt', 'Distribuidor isento de IRPC')}
{field(
'qualificationReference',
'Suporte da qualificação das entidades e dos lucros',
)}
{choice(
'article40',
'Esta parcela está abrangida pela exclusão do art.40(1)',
)}
{field(
'article40Reference',
'Suporte da correspondência do art.40(1) e seu período',
)}
<p>
O art.40(1), na redacção de 2009, contempla as
entidades e condições próprias, incluindo
participação directa de pelo menos20% e prazo de
dois anos, nos termos da fonte. Uma parcela excluída
da base não recebe também o crédito64. A simples
percentagem de participação não decide todas as
condições.
</p>
{outside ? (
<>
<p>
Esta parcela exige o tratamento específico
identificado. O crédito ordinário não é
aplicado; os valores introduzidos ficam
conservados.
</p>
<details>
<summary>
Consultar ou reconciliar dados
conservados
</summary>
{detail}
</details>
</>
) : (
detail
)}
<button
type="button"
onClick={() =>
update({
...c,
rows: c.rows.filter((x) => x.id !== r.id),
})
}
>
Retirar lucros {i + 1}
</button>
</fieldset>
);
})}
</fieldset>
);
}