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

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>
);
}