569 lines
27 KiB
TypeScript
569 lines
27 KiB
TypeScript
import { FiscalProofParcelFields } from './fiscal-proof-parcel';
|
|
import CountrySelect, { type Country } from '@/components/country-select';
|
|
import { countryIsoCodes } from '@/lib/country-iso-codes';
|
|
import {
|
|
newInternationalProof,
|
|
prepareInternationalBook,
|
|
type AnnualBook,
|
|
} from '@/lib/irpc-annual-settlement';
|
|
import {
|
|
newInternationalPosition,
|
|
internationalCreditSource,
|
|
type CreditEvidence,
|
|
type InternationalCredits,
|
|
type InternationalPosition,
|
|
type InternationalTaxProof,
|
|
} from '@/lib/irpc-international-credit';
|
|
|
|
const pt = new Intl.DisplayNames(['pt'], { type: 'region' }),
|
|
en = new Intl.DisplayNames(['en'], { type: 'region' });
|
|
const countries: Country[] = countryIsoCodes
|
|
.filter((code) => code !== 'MZ')
|
|
.map((code) => ({
|
|
code,
|
|
name: pt.of(code) ?? code,
|
|
name_en: en.of(code) ?? code,
|
|
}))
|
|
.sort((a, b) => a.name.localeCompare(b.name, 'pt'));
|
|
function Evidence({
|
|
value,
|
|
label,
|
|
change,
|
|
}: {
|
|
value: CreditEvidence;
|
|
label: string;
|
|
change: (value: CreditEvidence) => void;
|
|
}) {
|
|
return (
|
|
<fieldset className="rounded border p-2">
|
|
<legend>{label}</legend>
|
|
<label className="block">
|
|
Estado
|
|
<select
|
|
aria-label={label + ' — Estado'}
|
|
value={value.state}
|
|
onChange={(e) =>
|
|
change({
|
|
...value,
|
|
state: e.target.value as CreditEvidence['state'],
|
|
})
|
|
}
|
|
>
|
|
<option value="">Informação em falta</option>
|
|
<option value="known">Valor conhecido</option>
|
|
<option value="absent">Ausência declarada</option>
|
|
</select>
|
|
</label>
|
|
{(value.state === 'known' || value.amount) && (
|
|
<label className="block">
|
|
Montante MZN
|
|
<input
|
|
aria-label={label + ' — Montante'}
|
|
value={value.amount}
|
|
onChange={(e) =>
|
|
change({ ...value, amount: e.target.value })
|
|
}
|
|
/>
|
|
</label>
|
|
)}
|
|
<label className="block">
|
|
Suporte
|
|
<input
|
|
aria-label={label + ' — Suporte'}
|
|
value={value.reference}
|
|
onChange={(e) =>
|
|
change({ ...value, reference: e.target.value })
|
|
}
|
|
/>
|
|
</label>
|
|
</fieldset>
|
|
);
|
|
}
|
|
export function SimulationIrpcInternational({
|
|
book,
|
|
save,
|
|
}: {
|
|
book: AnnualBook;
|
|
save: (book: AnnualBook) => void;
|
|
}) {
|
|
const c = book.internationalCredits;
|
|
if (!c)
|
|
return (
|
|
<fieldset className="rounded border p-3">
|
|
<legend>Crédito internacional — próprio exercício</legend>
|
|
<p>
|
|
Prepare a inclusão do imposto estrangeiro comprovado e dos
|
|
limites documentados, antes do PEC. O resumo anterior de
|
|
créditos e os históricos ficam conservados. A escolha do
|
|
país não confirma uma convenção ou benefício.
|
|
</p>
|
|
<button
|
|
type="button"
|
|
onClick={() => save(prepareInternationalBook(book))}
|
|
>
|
|
Preparar crédito internacional
|
|
</button>
|
|
</fieldset>
|
|
);
|
|
const update = (next: InternationalCredits) =>
|
|
save({ ...book, internationalCredits: next });
|
|
const positionChange = (p: InternationalPosition) =>
|
|
update({
|
|
...c,
|
|
positions: c.positions.map((x) => (x.id === p.id ? p : x)),
|
|
});
|
|
return (
|
|
<fieldset className="space-y-3 rounded border p-3">
|
|
<legend>Crédito por dupla tributação internacional</legend>
|
|
<p>{internationalCreditSource}</p>
|
|
<p>
|
|
Reúna os comprovativos de cada país numa posição por exercício.
|
|
Indique a fracção nacional e a convenção conforme o suporte do
|
|
caso. Limites admitem decimal ou fracção exacta n/d; a
|
|
apresentação não constitui arredondamento fiscal. Valores MZN
|
|
exigem correspondência documental; uma previsão de câmbio ou de
|
|
pagamento não comprova o crédito.
|
|
</p>
|
|
<label className="block">
|
|
Estado internacional
|
|
<select
|
|
aria-label="Estado do crédito internacional L8"
|
|
value={c.state}
|
|
onChange={(e) =>
|
|
update({
|
|
...c,
|
|
state: e.target
|
|
.value as InternationalCredits['state'],
|
|
})
|
|
}
|
|
>
|
|
<option value="">Informação em falta</option>
|
|
<option value="known">Posições conhecidas</option>
|
|
<option value="absent">Ausência declarada</option>
|
|
</select>
|
|
</label>
|
|
<label className="block">
|
|
Suporte do inventário
|
|
<input
|
|
aria-label="Suporte do crédito internacional L8"
|
|
value={c.reference}
|
|
onChange={(e) =>
|
|
update({ ...c, reference: e.target.value })
|
|
}
|
|
/>
|
|
</label>
|
|
{book.economicCredits ? (
|
|
<p>
|
|
Resumo económico anterior conservado:{' '}
|
|
{c.economic.state || 'Informação em falta'};{' '}
|
|
{c.economic.amount || 'Montante não preenchido'}. Nesta
|
|
execução é usado o detalhe económico acima.
|
|
</p>
|
|
) : (
|
|
<Evidence
|
|
label="Crédito económico — regra própria"
|
|
value={c.economic}
|
|
change={(economic) => update({ ...c, economic })}
|
|
/>
|
|
)}
|
|
<Evidence
|
|
label="Benefícios/créditos próprios — regra própria"
|
|
value={c.benefits}
|
|
change={(benefits) => update({ ...c, benefits })}
|
|
/>
|
|
<p>
|
|
{book.economicCredits
|
|
? 'Benefícios positivos exigem regras próprias; o crédito económico tem o seu detalhe separado. Informação em falta mantém o saldo dependente indeterminado.'
|
|
: 'Créditos económicos e benefícios positivos exigem regras próprias; informação em falta mantém o saldo dependente indeterminado.'}
|
|
</p>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
update({
|
|
...c,
|
|
state: 'known',
|
|
positions: [
|
|
...c.positions,
|
|
{
|
|
...newInternationalPosition(
|
|
crypto.randomUUID(),
|
|
),
|
|
subjectNuit: book.subjectNuit,
|
|
year: book.year,
|
|
assessmentKey: book.assessmentKey,
|
|
},
|
|
],
|
|
})
|
|
}
|
|
>
|
|
Registar posição internacional
|
|
</button>
|
|
{c.positions.map((p, index) => {
|
|
const patch = (v: Partial<InternationalPosition>) =>
|
|
positionChange({ ...p, ...v });
|
|
const paymentChange = (r: InternationalTaxProof) =>
|
|
patch({
|
|
payments: p.payments.map((x) =>
|
|
x.id === r.id ? r : x,
|
|
),
|
|
});
|
|
return (
|
|
<fieldset
|
|
key={p.id}
|
|
className="space-y-2 rounded border p-3"
|
|
>
|
|
<legend>Posição internacional {index + 1}</legend>
|
|
<CountrySelect
|
|
countries={countries}
|
|
label={'País do imposto estrangeiro ' + (index + 1)}
|
|
value={p.country}
|
|
onChange={(country) => patch({ country })}
|
|
allowLegacyValue
|
|
/>
|
|
{(
|
|
[
|
|
['subjectNuit', 'NUIT titular'],
|
|
['year', 'Exercício'],
|
|
['incomeAmount', 'Rendimento incluído — MZN'],
|
|
[
|
|
'incomeReference',
|
|
'Suporte da inclusão na matéria colectável',
|
|
],
|
|
[
|
|
'nationalLimit',
|
|
'Fracção nacional correspondente — MZN',
|
|
],
|
|
[
|
|
'nationalLimitReference',
|
|
'Suporte da fracção nacional e exercício',
|
|
],
|
|
] as const
|
|
).map(([key, label]) => (
|
|
<label key={key} className="block">
|
|
{label}
|
|
<input
|
|
aria-label={
|
|
'Internacional ' +
|
|
(index + 1) +
|
|
' — ' +
|
|
label
|
|
}
|
|
value={p[key]}
|
|
onChange={(e) =>
|
|
patch({ [key]: e.target.value })
|
|
}
|
|
/>
|
|
</label>
|
|
))}
|
|
<label className="block">
|
|
Rendimento incluído na matéria colectável
|
|
<select
|
|
aria-label={
|
|
'Internacional ' +
|
|
(index + 1) +
|
|
' — Inclusão'
|
|
}
|
|
value={p.incomeIncluded}
|
|
onChange={(e) =>
|
|
patch({
|
|
incomeIncluded: e.target
|
|
.value as InternationalPosition['incomeIncluded'],
|
|
})
|
|
}
|
|
>
|
|
<option value="">Informação em falta</option>
|
|
<option value="yes">Sim — documentado</option>
|
|
<option value="no">
|
|
Não — crédito não aplicável nesta base
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
patch({
|
|
assessmentKey: book.assessmentKey,
|
|
qualificationCountry: p.country,
|
|
})
|
|
}
|
|
>
|
|
Vincular inclusão e limites ao país e à colecta
|
|
seleccionados
|
|
</button>
|
|
<label className="block">
|
|
Convenção aplicável
|
|
<select
|
|
aria-label={
|
|
'Internacional ' +
|
|
(index + 1) +
|
|
' — Convenção'
|
|
}
|
|
value={p.treaty}
|
|
onChange={(e) =>
|
|
patch({
|
|
treaty: e.target
|
|
.value as InternationalPosition['treaty'],
|
|
})
|
|
}
|
|
>
|
|
<option value="">Informação em falta</option>
|
|
<option value="yes">
|
|
Sim — artigo, período e limite documentados
|
|
</option>
|
|
<option value="no">
|
|
Não — ausência de aplicação documentada
|
|
</option>
|
|
</select>
|
|
</label>
|
|
{(p.treaty === 'yes' || p.treatyLimit) && (
|
|
<label className="block">
|
|
Imposto pago elegível nos termos da convenção —
|
|
limite MZN
|
|
<input
|
|
aria-label={
|
|
'Internacional ' +
|
|
(index + 1) +
|
|
' — Limite CDT'
|
|
}
|
|
value={p.treatyLimit}
|
|
onChange={(e) =>
|
|
patch({ treatyLimit: e.target.value })
|
|
}
|
|
/>
|
|
</label>
|
|
)}
|
|
<label className="block">
|
|
Suporte da convenção ou da não aplicação
|
|
<input
|
|
aria-label={
|
|
'Internacional ' +
|
|
(index + 1) +
|
|
' — Suporte CDT'
|
|
}
|
|
value={p.treatyReference}
|
|
onChange={(e) =>
|
|
patch({ treatyReference: e.target.value })
|
|
}
|
|
/>
|
|
</label>
|
|
<Evidence
|
|
label={
|
|
'Utilização anterior do crédito — país ' +
|
|
(index + 1)
|
|
}
|
|
value={p.previousUse}
|
|
change={(previousUse) => patch({ previousUse })}
|
|
/>
|
|
<p>
|
|
Utilização anterior é crédito já deduzido e
|
|
documentado. Não é a soma de simulações históricas
|
|
nem o imposto pago acima dos limites.
|
|
</p>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
patch({
|
|
payments: [
|
|
...p.payments,
|
|
{
|
|
...newInternationalProof(
|
|
crypto.randomUUID(),
|
|
),
|
|
subjectNuit: p.subjectNuit,
|
|
year: p.year,
|
|
},
|
|
],
|
|
})
|
|
}
|
|
>
|
|
Registar imposto estrangeiro na posição {index + 1}
|
|
</button>
|
|
{p.payments.map((r, ri) => {
|
|
const change = (
|
|
v: Partial<InternationalTaxProof>,
|
|
) => paymentChange({ ...r, ...v });
|
|
return (
|
|
<fieldset
|
|
key={r.id}
|
|
className="rounded border p-2"
|
|
>
|
|
<legend>
|
|
Imposto estrangeiro {index + 1}.{ri + 1}
|
|
</legend>
|
|
{(
|
|
[
|
|
['document', 'Comprovativo'],
|
|
['issuer', 'Emitente'],
|
|
['date', 'Data (AAAA-MM-DD)'],
|
|
['subjectNuit', 'NUIT titular'],
|
|
['year', 'Exercício do pagamento'],
|
|
['currency', 'Moeda'],
|
|
[
|
|
'proofAmount',
|
|
'Total comprovado — MZN',
|
|
],
|
|
[
|
|
'amount',
|
|
'Parcela de imposto seleccionado — MZN',
|
|
],
|
|
[
|
|
'reference',
|
|
'Suporte do pagamento e afectação',
|
|
],
|
|
[
|
|
'taxReference',
|
|
'Suporte da natureza de imposto sobre o rendimento',
|
|
],
|
|
] as const
|
|
).map(([key, label]) => (
|
|
<label key={key} className="block">
|
|
{label}
|
|
<input
|
|
aria-label={
|
|
'Imposto estrangeiro ' +
|
|
(index + 1) +
|
|
'.' +
|
|
(ri + 1) +
|
|
' — ' +
|
|
label
|
|
}
|
|
value={r[key]}
|
|
onChange={(e) =>
|
|
change({
|
|
[key]: e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
))}
|
|
{(r.exerciseReference ||
|
|
(r.date &&
|
|
!r.date.startsWith(
|
|
p.year + '-',
|
|
))) && (
|
|
<label className="block">
|
|
Suporte da correspondência da data
|
|
ao exercício
|
|
<input
|
|
aria-label={
|
|
'Imposto estrangeiro ' +
|
|
(index + 1) +
|
|
'.' +
|
|
(ri + 1) +
|
|
' — Correspondência temporal'
|
|
}
|
|
value={r.exerciseReference}
|
|
onChange={(e) =>
|
|
change({
|
|
exerciseReference:
|
|
e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
)}
|
|
<label className="block">
|
|
Pagamento efectivo
|
|
<select
|
|
aria-label={
|
|
'Imposto estrangeiro ' +
|
|
(index + 1) +
|
|
'.' +
|
|
(ri + 1) +
|
|
' — Efectivo'
|
|
}
|
|
value={r.effective}
|
|
onChange={(e) =>
|
|
change({
|
|
effective: e.target
|
|
.value as InternationalTaxProof['effective'],
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Informação em falta
|
|
</option>
|
|
<option value="yes">
|
|
Sim — comprovado
|
|
</option>
|
|
<option value="no">
|
|
Não — previsão/outra situação
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<label className="block">
|
|
Imposto sobre o rendimento elegível
|
|
<select
|
|
aria-label={
|
|
'Imposto estrangeiro ' +
|
|
(index + 1) +
|
|
'.' +
|
|
(ri + 1) +
|
|
' — Natureza'
|
|
}
|
|
value={r.incomeTax}
|
|
onChange={(e) =>
|
|
change({
|
|
incomeTax: e.target
|
|
.value as InternationalTaxProof['incomeTax'],
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Informação em falta
|
|
</option>
|
|
<option value="yes">
|
|
Sim — natureza documentada
|
|
</option>
|
|
<option value="no">
|
|
Não — outra natureza, sem
|
|
crédito presumido
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<FiscalProofParcelFields
|
|
value={r}
|
|
change={change}
|
|
label={
|
|
'Imposto estrangeiro ' +
|
|
(index + 1) +
|
|
'.' +
|
|
(ri + 1)
|
|
}
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
patch({
|
|
payments: p.payments.filter(
|
|
(x) => x.id !== r.id,
|
|
),
|
|
})
|
|
}
|
|
>
|
|
Retirar imposto estrangeiro {index + 1}.
|
|
{ri + 1}
|
|
</button>
|
|
</fieldset>
|
|
);
|
|
})}
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
update({
|
|
...c,
|
|
positions: c.positions.filter(
|
|
(x) => x.id !== p.id,
|
|
),
|
|
})
|
|
}
|
|
>
|
|
Retirar posição internacional {index + 1}
|
|
</button>
|
|
</fieldset>
|
|
);
|
|
})}
|
|
</fieldset>
|
|
);
|
|
}
|