1073 lines
54 KiB
TypeScript
1073 lines
54 KiB
TypeScript
import { inputText } from '../lib/input-text.ts';
|
||
import { SimulationIrpcMrfta } from '@/components/simulation-irpc-mrfta';
|
||
import {
|
||
irpcReliefRules,
|
||
newIrpcReliefPreparation,
|
||
prepareIrpcRelief,
|
||
} from '@/lib/irpc-relief-preparation';
|
||
import { SimulationFxSelection } from '@/components/simulation-fx-selection';
|
||
import {
|
||
irpcFxSources,
|
||
irpcFxRow,
|
||
updateIrpcFx,
|
||
type IrpcFxAmount,
|
||
} from '@/lib/irpc-fx';
|
||
import { useState } from 'react';
|
||
import {
|
||
irpcBook,
|
||
saveIrpcBook,
|
||
reuseIrpcSources,
|
||
irpcSources,
|
||
newIrpcRow,
|
||
recordIrpc,
|
||
irpcKinds,
|
||
emptyIrpcOrdinaryFacts,
|
||
irpcReportRows,
|
||
irpcPreparationHtml,
|
||
type IrpcBook,
|
||
type IrpcRow,
|
||
type IrpcEvent,
|
||
type IrpcPayment,
|
||
type IrpcOrdinaryFacts,
|
||
} from '@/lib/irpc-withholdings';
|
||
export function SimulationIrpcWithholdings({
|
||
draft,
|
||
onChange,
|
||
}: {
|
||
draft: Record<string, unknown>;
|
||
onChange: (field: string, value: string) => void;
|
||
}) {
|
||
const [error, setError] = useState('');
|
||
if (
|
||
draft.fiscal_component !== 'irpc' &&
|
||
draft.fiscal_component !== 'withholding'
|
||
)
|
||
return null;
|
||
try {
|
||
const book = irpcBook(draft),
|
||
sources = irpcSources(draft),
|
||
save = (b: IrpcBook) => {
|
||
try {
|
||
onChange('modelo_a_snapshot', saveIrpcBook(draft, b));
|
||
setError('');
|
||
} catch (e) {
|
||
setError(String(e));
|
||
}
|
||
},
|
||
change = (id: string, p: Partial<IrpcRow>) =>
|
||
save({
|
||
...book,
|
||
rows: book.rows.map((r) =>
|
||
r.id === id ? { ...r, ...p } : r,
|
||
),
|
||
}),
|
||
uid = () => crypto.randomUUID();
|
||
const field = (
|
||
r: IrpcRow,
|
||
k: keyof IrpcRow,
|
||
label: string,
|
||
type = 'text',
|
||
) => (
|
||
<label className="block">
|
||
{label}
|
||
<input
|
||
className="block rounded border p-2"
|
||
type={type}
|
||
value={inputText(r[k] ?? '')}
|
||
onChange={(e) => change(r.id, { [k]: e.target.value })}
|
||
/>
|
||
</label>
|
||
);
|
||
const select = (
|
||
r: IrpcRow,
|
||
k: keyof IrpcRow,
|
||
label: string,
|
||
options: Array<[string, string]>,
|
||
) => (
|
||
<label className="block">
|
||
{label}
|
||
<select
|
||
className="block rounded border p-2"
|
||
value={inputText(r[k] ?? '')}
|
||
onChange={(e) => change(r.id, { [k]: e.target.value })}
|
||
>
|
||
{options.map(([v, l]) => (
|
||
<option key={v} value={v}>
|
||
{l}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
);
|
||
const yn: Array<[string, string]> = [
|
||
['', 'Por esclarecer'],
|
||
['yes', 'Sim'],
|
||
['no', 'Não'],
|
||
];
|
||
const eventLabels: Array<[IrpcEvent['kind'], string]> = [
|
||
['cost', 'Reconhecimento do custo'],
|
||
['payment', 'Pagamento do rendimento'],
|
||
['maturity', 'Vencimento, ainda que presumido'],
|
||
['availability', 'Colocação à disposição'],
|
||
['liquidation', 'Liquidação'],
|
||
['quantification', 'Apuramento do quantitativo'],
|
||
];
|
||
const fxSources = irpcFxSources(draft);
|
||
const renderFx = (
|
||
id: string,
|
||
input: IrpcFxAmount,
|
||
apply: (patch: Partial<IrpcFxAmount>) => void,
|
||
) => (
|
||
<div className="space-y-2">
|
||
<p>
|
||
A base/comprovativo conserva a moeda e o montante originais.
|
||
A conversão reutiliza o selector e a aritmética entregues; a
|
||
data cambial fica distinta do evento da retenção.
|
||
</p>
|
||
{fxSources.map((src) => (
|
||
<div key={src.id}>
|
||
<span>
|
||
{src.label};data:
|
||
{src.selection.applied?.date || 'por confirmar'}
|
||
;selecção actual:
|
||
{input.fxSelection?.applied?.rate || 'nenhuma'}.
|
||
</span>
|
||
<button
|
||
type="button"
|
||
className="rounded border p-2"
|
||
onClick={() => {
|
||
if (
|
||
window.confirm(
|
||
`Substituir a selecção cambial desta linha (${input.fxSelection?.applied?.rate || 'vazia'}) pela origem ${src.label},data ${src.selection.applied?.date || 'por confirmar'}? O montante e a moeda originais são conservados;resultado anterior permanece no histórico até novo cálculo.`,
|
||
)
|
||
)
|
||
apply({
|
||
fxSelection: structuredClone(
|
||
src.selection,
|
||
),
|
||
fxLink: {
|
||
id: src.id,
|
||
fingerprint: src.fingerprint,
|
||
},
|
||
});
|
||
}}
|
||
>
|
||
Reutilizar selecção cambial {src.id}
|
||
</button>
|
||
</div>
|
||
))}
|
||
<SimulationFxSelection
|
||
row={irpcFxRow(id, input)}
|
||
onChange={(patch) => {
|
||
if (patch.fxSelection)
|
||
apply(updateIrpcFx(input, patch.fxSelection));
|
||
}}
|
||
/>
|
||
</div>
|
||
);
|
||
const ordinarySelect = (
|
||
r: IrpcRow,
|
||
k: keyof IrpcOrdinaryFacts,
|
||
label: string,
|
||
options: Array<[string, string]>,
|
||
) => (
|
||
<label className="block">
|
||
{label}
|
||
<select
|
||
value={String(
|
||
(r.ordinary ?? emptyIrpcOrdinaryFacts())[k] ?? '',
|
||
)}
|
||
onChange={(e) =>
|
||
change(r.id, {
|
||
ordinary: {
|
||
...(r.ordinary ?? emptyIrpcOrdinaryFacts()),
|
||
[k]: e.target.value,
|
||
},
|
||
})
|
||
}
|
||
>
|
||
{options.map(([v, l]) => (
|
||
<option key={v} value={v}>
|
||
{l}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
);
|
||
const paymentChange = (id: string, p: Partial<IrpcPayment>) =>
|
||
save({
|
||
...book,
|
||
payments: book.payments.map((v) =>
|
||
v.id === id ? { ...v, ...p } : v,
|
||
),
|
||
});
|
||
return (
|
||
<section
|
||
aria-label="Retenções de IRPC"
|
||
className="space-y-4 rounded border p-4"
|
||
>
|
||
<h4>IRPC — retenções e preparação documental</h4>
|
||
<p>
|
||
Reutilize documentos e contrapartes existentes. Classifique
|
||
o rendimento e o evento aplicável:facturar,pagar e
|
||
reconhecer o custo são factos distintos. O conjunto calcula
|
||
os casos digitais de 10%, as taxas normais de 20% e as taxas
|
||
especiais do artigo 62(4) nos âmbitos identificados.
|
||
Dispensas dos artigos 68(d/f) e limite convencional de
|
||
royalties Portugal–Moçambique são calculados nos casos
|
||
documentados. Informação insuficiente condiciona apenas o
|
||
respectivo rendimento.
|
||
</p>
|
||
{error && <p role="alert">{error}</p>}
|
||
<button
|
||
type="button"
|
||
className="rounded border p-2"
|
||
onClick={() => {
|
||
try {
|
||
save(reuseIrpcSources(draft, book));
|
||
} catch (e) {
|
||
setError(String(e));
|
||
}
|
||
}}
|
||
>
|
||
Reutilizar contrapartes e documentos existentes
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className="rounded border p-2"
|
||
onClick={() =>
|
||
save({
|
||
...book,
|
||
rows: [...book.rows, newIrpcRow(uid())],
|
||
})
|
||
}
|
||
>
|
||
Acrescentar rendimento IRPC
|
||
</button>
|
||
{book.rows.map((r) => (
|
||
<fieldset
|
||
key={r.id}
|
||
className="space-y-3 rounded border p-3"
|
||
>
|
||
<legend>
|
||
{r.description ||
|
||
r.documentRef ||
|
||
'Rendimento por identificar'}
|
||
</legend>
|
||
{r.source && (
|
||
<p>
|
||
Origem:{r.source.id}. Entradas deliberadamente
|
||
editadas são conservadas.{' '}
|
||
{sources.find((s) => s.id === r.source!.id)
|
||
?.key !== r.source.key && (
|
||
<>
|
||
<strong>
|
||
Origem alterada ou indisponível.
|
||
</strong>
|
||
{sources.find(
|
||
(s) => s.id === r.source!.id,
|
||
) && (
|
||
<>
|
||
<span>
|
||
{' '}
|
||
Base actual:{r.base};base na
|
||
origem:
|
||
{sources.find(
|
||
(s) =>
|
||
s.id ===
|
||
r.source!.id,
|
||
)?.amount || 'desconhecida'}
|
||
;documento actual:
|
||
{r.documentRef};origem:
|
||
{sources.find(
|
||
(s) =>
|
||
s.id ===
|
||
r.source!.id,
|
||
)?.documentRef ||
|
||
'desconhecido'}
|
||
.
|
||
</span>
|
||
<button
|
||
type="button"
|
||
className="rounded border p-2"
|
||
onClick={() => {
|
||
const s = sources.find(
|
||
(s) =>
|
||
s.id ===
|
||
r.source!.id,
|
||
)!;
|
||
if (
|
||
window.confirm(
|
||
'Confirma a reconciliação da origem indicada, conservando as entradas deliberadas e os resultados anteriores no histórico?',
|
||
)
|
||
)
|
||
change(r.id, {
|
||
source: {
|
||
...r.source!,
|
||
key: s.key,
|
||
},
|
||
});
|
||
}}
|
||
>
|
||
Conservar edições e
|
||
reconciliar origem
|
||
</button>
|
||
</>
|
||
)}
|
||
</>
|
||
)}
|
||
</p>
|
||
)}
|
||
<div className="grid gap-3 md:grid-cols-2">
|
||
{select(r, 'direction', 'Perspectiva', [
|
||
[
|
||
'paid',
|
||
'Rendimento pago — retenção a entregar',
|
||
],
|
||
[
|
||
'received',
|
||
'Rendimento recebido — retenção sofrida',
|
||
],
|
||
])}
|
||
{field(r, 'counterparty', 'Contraparte')}
|
||
{field(
|
||
r,
|
||
'documentRef',
|
||
'Documento / operação / linha distinta',
|
||
)}
|
||
{field(r, 'description', 'Descrição')}
|
||
{select(r, 'origin', 'Origem', [
|
||
['projected', 'Projecção com pressupostos'],
|
||
['effective', 'Factos documentados'],
|
||
])}
|
||
{select(
|
||
r,
|
||
'kind',
|
||
'Natureza do rendimento',
|
||
irpcKinds,
|
||
)}
|
||
{field(
|
||
r,
|
||
'base',
|
||
'Base do rendimento, separada do IVA',
|
||
)}
|
||
{field(r, 'currency', 'Moeda da base')}
|
||
{select(
|
||
r,
|
||
'recipientIrpc',
|
||
'Titular sujeito a IRPC neste rendimento',
|
||
yn,
|
||
)}
|
||
{select(
|
||
r,
|
||
'payerIrpc',
|
||
'Devedor sujeito a IRPC',
|
||
yn,
|
||
)}
|
||
{select(r, 'residence', 'Residência do titular', [
|
||
['', 'Por esclarecer'],
|
||
['resident', 'Residente'],
|
||
['nonresident', 'Não residente'],
|
||
])}
|
||
{r.residence === 'nonresident' && (
|
||
<>
|
||
{select(
|
||
r,
|
||
'permanentEstablishment',
|
||
'Existe estabelecimento estável em Moçambique',
|
||
yn,
|
||
)}
|
||
{r.permanentEstablishment === 'yes' &&
|
||
select(
|
||
r,
|
||
'attributable',
|
||
'Rendimento imputável ao estabelecimento',
|
||
yn,
|
||
)}
|
||
</>
|
||
)}
|
||
{select(
|
||
r,
|
||
'mozambique',
|
||
'Rendimento obtido em Moçambique, conforme qualificação legal',
|
||
yn,
|
||
)}
|
||
{field(
|
||
r,
|
||
'qualificationRef',
|
||
'Suporte / pressuposto da qualificação, residência e incidência',
|
||
)}
|
||
{select(
|
||
r,
|
||
'specialRelief',
|
||
'Convenção, isenção ou dispensa aplicável',
|
||
[
|
||
['', 'Por esclarecer'],
|
||
[
|
||
'none',
|
||
'Não invocada neste caso qualificado',
|
||
],
|
||
[
|
||
'claimed',
|
||
'Invocada — identificar regra e condições',
|
||
],
|
||
],
|
||
)}
|
||
{r.specialRelief === 'claimed' &&
|
||
field(
|
||
r,
|
||
'reliefRef',
|
||
'Artigo, condições, período e prova invocados',
|
||
)}
|
||
</div>
|
||
{r.specialRelief === 'claimed' && (
|
||
<fieldset className="space-y-2 rounded border p-2">
|
||
<legend>
|
||
Preparação de dispensa ou convenção
|
||
</legend>
|
||
<p>
|
||
Reutiliza contraparte, residência,
|
||
estabelecimento estável e documento deste
|
||
rendimento.
|
||
</p>
|
||
<label>
|
||
Enquadramento
|
||
<select
|
||
value={
|
||
r.reliefPreparation?.kind ??
|
||
'dispensation'
|
||
}
|
||
onChange={(e) =>
|
||
change(r.id, {
|
||
reliefPreparation: {
|
||
...(r.reliefPreparation ??
|
||
newIrpcReliefPreparation()),
|
||
kind: e.target.value as
|
||
| 'dispensation'
|
||
| 'exemption'
|
||
| 'treaty',
|
||
},
|
||
})
|
||
}
|
||
>
|
||
<option value="dispensation">
|
||
Dispensa de retenção
|
||
</option>
|
||
<option value="exemption">
|
||
Isenção
|
||
</option>
|
||
<option value="treaty">
|
||
Convenção
|
||
</option>
|
||
</select>
|
||
</label>
|
||
{(
|
||
[
|
||
'instrument',
|
||
'article',
|
||
'country',
|
||
'residenceDocument',
|
||
'validFrom',
|
||
'validTo',
|
||
'conditions',
|
||
'evidence',
|
||
] as const
|
||
)
|
||
.filter(
|
||
(k) =>
|
||
![
|
||
'country',
|
||
'residenceDocument',
|
||
].includes(k) ||
|
||
r.reliefPreparation?.kind ===
|
||
'treaty',
|
||
)
|
||
.map((k) => (
|
||
<label key={k} className="block">
|
||
{
|
||
{
|
||
instrument:
|
||
'Diploma / convenção',
|
||
article: 'Artigo',
|
||
country:
|
||
'Estado contratante',
|
||
residenceDocument:
|
||
'Referência do documento de residência fiscal',
|
||
validFrom:
|
||
'Aplicável desde',
|
||
validTo: 'Aplicável até',
|
||
conditions:
|
||
'Condições do benefício',
|
||
evidence:
|
||
'Referências dos documentos existentes',
|
||
}[k]
|
||
}
|
||
<input
|
||
type={
|
||
k === 'validFrom' ||
|
||
k === 'validTo'
|
||
? 'date'
|
||
: 'text'
|
||
}
|
||
value={
|
||
r.reliefPreparation?.[k] ??
|
||
''
|
||
}
|
||
onChange={(e) =>
|
||
change(r.id, {
|
||
reliefPreparation: {
|
||
...(r.reliefPreparation ??
|
||
newIrpcReliefPreparation()),
|
||
[k]: e.target.value,
|
||
},
|
||
})
|
||
}
|
||
/>
|
||
</label>
|
||
))}
|
||
<label>
|
||
Regra aplicável
|
||
<select
|
||
value={r.reliefPreparation?.rule ?? ''}
|
||
onChange={(e) =>
|
||
change(r.id, {
|
||
reliefPreparation: {
|
||
...(r.reliefPreparation ??
|
||
newIrpcReliefPreparation()),
|
||
rule: e.target
|
||
.value as NonNullable<
|
||
IrpcRow['reliefPreparation']
|
||
>['rule'],
|
||
},
|
||
})
|
||
}
|
||
>
|
||
{irpcReliefRules.map(([v, l]) => (
|
||
<option key={v} value={v}>
|
||
{l}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
{(r.reliefPreparation?.rule === 'ptMz12'
|
||
? ([
|
||
'treatyResident',
|
||
'beneficialOwner',
|
||
'payerResidentMz',
|
||
'armLength',
|
||
'royaltyDefinition',
|
||
] as const)
|
||
: r.reliefPreparation?.rule === 'cirpc68f'
|
||
? ([
|
||
'subjectToIrpc',
|
||
'notTransparent',
|
||
] as const)
|
||
: r.reliefPreparation?.rule === 'cirpc68d'
|
||
? (['subjectToIrpc'] as const)
|
||
: []
|
||
).map((k) => (
|
||
<label key={k} className="block">
|
||
{
|
||
{
|
||
subjectToIrpc:
|
||
'Titular sujeito a IRPC relativamente a este rendimento (ainda que isento)',
|
||
notTransparent:
|
||
'Sociedade fora da transparência fiscal — 6(1)c',
|
||
treatyResident:
|
||
'Residência em Portugal nos termos do artigo 4 documentada',
|
||
beneficialOwner:
|
||
'Titular é beneficiário efectivo das royalties',
|
||
payerResidentMz:
|
||
'Devedor residente em Moçambique; origem do rendimento conforme 12(5)',
|
||
armLength:
|
||
'Montante sem excesso por relações especiais — 12(6)',
|
||
royaltyDefinition:
|
||
'Rendimento corresponde à definição de royalties — 12(3), versão 2009',
|
||
}[k]
|
||
}
|
||
<select
|
||
value={
|
||
r.reliefPreparation?.facts?.[
|
||
k
|
||
] ?? ''
|
||
}
|
||
onChange={(e) =>
|
||
change(r.id, {
|
||
reliefPreparation: {
|
||
...(r.reliefPreparation ??
|
||
newIrpcReliefPreparation()),
|
||
facts: {
|
||
...r
|
||
.reliefPreparation
|
||
?.facts,
|
||
[k]: e.target
|
||
.value as
|
||
| ''
|
||
| 'yes'
|
||
| 'no',
|
||
},
|
||
},
|
||
})
|
||
}
|
||
>
|
||
{yn.map(([v, l]) => (
|
||
<option key={v} value={v}>
|
||
{l}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
))}
|
||
<p>
|
||
{prepareIrpcRelief(
|
||
r,
|
||
r.reliefPreparation ??
|
||
newIrpcReliefPreparation(),
|
||
).missing.length
|
||
? 'Faltam elementos documentais; confira artigo, período e provas.'
|
||
: 'Documentos registados; calcule para conferir as condições da regra seleccionada.'}
|
||
</p>
|
||
</fieldset>
|
||
)}
|
||
{r.currency !== 'MZN' &&
|
||
renderFx(r.id, r, (patch) => change(r.id, patch))}
|
||
{r.kind &&
|
||
![
|
||
'digital_goods',
|
||
'digital_services',
|
||
'emoney_commission',
|
||
'other',
|
||
].includes(r.kind) && (
|
||
<fieldset className="space-y-2 rounded border p-2">
|
||
<legend>
|
||
Factos específicos do rendimento
|
||
</legend>
|
||
{ordinarySelect(
|
||
r,
|
||
'nonDigital',
|
||
'Rendimento fora do âmbito digital/moeda electrónica do artigo62(5)',
|
||
yn,
|
||
)}
|
||
{r.kind === 'capital' &&
|
||
ordinarySelect(
|
||
r,
|
||
'capitalType',
|
||
'Categoria dos capitais',
|
||
[
|
||
['', 'Por esclarecer'],
|
||
[
|
||
'unlisted_interest',
|
||
'Juros não cotados, fora das permutas de liquidez',
|
||
],
|
||
[
|
||
'unlisted_dividend',
|
||
'Dividendos não cotados',
|
||
],
|
||
[
|
||
'listed_debt_mz',
|
||
'Juros de bilhetes do Tesouro/títulos de dívida cotados em bolsa —62(3)a',
|
||
],
|
||
[
|
||
'listed_other_mz',
|
||
'Outros títulos cotados na Bolsa de Valores de Moçambique —62(4)d',
|
||
],
|
||
[
|
||
'liquidity_repo',
|
||
'Juros de permutas de liquidez em bancos,com ou sem garantia —62(3)b',
|
||
],
|
||
['other', 'Outra categoria'],
|
||
],
|
||
)}
|
||
{r.kind === 'capital' &&
|
||
r.residence === 'resident' &&
|
||
[
|
||
'listed_debt_mz',
|
||
'liquidity_repo',
|
||
].includes(
|
||
r.ordinary?.capitalType ?? '',
|
||
) &&
|
||
ordinarySelect(
|
||
r,
|
||
'capitalSeatAndManagement',
|
||
'Titular com sede e direcção efectiva nacionais —62(3)',
|
||
yn,
|
||
)}
|
||
{r.kind === 'rent' &&
|
||
ordinarySelect(
|
||
r,
|
||
'rentOwnPropertyManagement',
|
||
'Titular é sociedade com objecto de gestão de imóveis próprios',
|
||
yn,
|
||
)}
|
||
{([
|
||
'telecom',
|
||
'international_transport',
|
||
'connected_installation',
|
||
'aircraft_maintenance_charter',
|
||
'rural_public_electrification',
|
||
'maritime_fishing_cabotage',
|
||
].includes(r.kind) ||
|
||
(r.kind === 'capital' &&
|
||
r.ordinary?.capitalType ===
|
||
'listed_other_mz')) &&
|
||
ordinarySelect(
|
||
r,
|
||
'specialFacts',
|
||
'Factos exactos da categoria62(4) identificados no suporte — não apenas actividade semelhante',
|
||
yn,
|
||
)}
|
||
<label className="block">
|
||
Suporte ou pressuposto específico da
|
||
classificação
|
||
<input
|
||
value={r.ordinary?.reference ?? ''}
|
||
onChange={(e) =>
|
||
change(r.id, {
|
||
ordinary: {
|
||
...(r.ordinary ??
|
||
emptyIrpcOrdinaryFacts()),
|
||
reference:
|
||
e.target.value,
|
||
},
|
||
})
|
||
}
|
||
/>
|
||
</label>
|
||
</fieldset>
|
||
)}
|
||
<h5>Eventos aplicáveis — artigo67(5), versão2026</h5>
|
||
{r.events.map((e, n) => (
|
||
<div key={n} className="grid gap-2 md:grid-cols-3">
|
||
<label>
|
||
Evento
|
||
<select
|
||
value={e.kind}
|
||
onChange={(ev) =>
|
||
change(r.id, {
|
||
events: r.events.map((v, j) =>
|
||
j === n
|
||
? {
|
||
...v,
|
||
kind: ev.target
|
||
.value as IrpcEvent['kind'],
|
||
}
|
||
: v,
|
||
),
|
||
})
|
||
}
|
||
>
|
||
{eventLabels.map(([k, l]) => (
|
||
<option key={k} value={k}>
|
||
{l}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
<label>
|
||
Data
|
||
<input
|
||
type="date"
|
||
value={e.date}
|
||
onChange={(ev) =>
|
||
change(r.id, {
|
||
events: r.events.map((v, j) =>
|
||
j === n
|
||
? {
|
||
...v,
|
||
date: ev.target
|
||
.value,
|
||
}
|
||
: v,
|
||
),
|
||
})
|
||
}
|
||
/>
|
||
</label>
|
||
<label>
|
||
Suporte / pressuposto
|
||
<input
|
||
value={e.reference}
|
||
onChange={(ev) =>
|
||
change(r.id, {
|
||
events: r.events.map((v, j) =>
|
||
j === n
|
||
? {
|
||
...v,
|
||
reference:
|
||
ev.target
|
||
.value,
|
||
}
|
||
: v,
|
||
),
|
||
})
|
||
}
|
||
/>
|
||
</label>
|
||
</div>
|
||
))}
|
||
<button
|
||
type="button"
|
||
onClick={() =>
|
||
change(r.id, {
|
||
events: [
|
||
...r.events,
|
||
{
|
||
kind: 'cost',
|
||
date: '',
|
||
reference: '',
|
||
},
|
||
],
|
||
})
|
||
}
|
||
>
|
||
Acrescentar evento aplicável
|
||
</button>
|
||
{select(
|
||
r,
|
||
'eventsComplete',
|
||
'Todos os eventos aplicáveis identificados, incluindo custo quando aplicável',
|
||
yn,
|
||
)}
|
||
{select(
|
||
r,
|
||
'settlement',
|
||
'Reconciliação da entrega / prova de retenção sofrida',
|
||
[
|
||
['', 'Por esclarecer'],
|
||
['none', 'Ainda sem entrega / prova'],
|
||
[
|
||
'complete',
|
||
'Pagamentos / provas associados completos',
|
||
],
|
||
],
|
||
)}
|
||
{field(
|
||
r,
|
||
'settlementRef',
|
||
'Referência da situação declarada',
|
||
)}
|
||
{r.allocations.map((a, n) => (
|
||
<div key={n}>
|
||
<label>
|
||
Pagamento / prova existente
|
||
<select
|
||
value={a.paymentId}
|
||
onChange={(e) =>
|
||
change(r.id, {
|
||
allocations: r.allocations.map(
|
||
(v, j) =>
|
||
j === n
|
||
? {
|
||
...v,
|
||
paymentId:
|
||
e.target
|
||
.value,
|
||
}
|
||
: v,
|
||
),
|
||
})
|
||
}
|
||
>
|
||
<option value="">Seleccione</option>
|
||
{book.payments.map((p) => (
|
||
<option key={p.id} value={p.id}>
|
||
{p.reference} — {p.amount}{' '}
|
||
{p.currency} — {p.kind}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
<label>
|
||
Parcela usada em MZN
|
||
<input
|
||
value={a.amount}
|
||
onChange={(e) =>
|
||
change(r.id, {
|
||
allocations: r.allocations.map(
|
||
(v, j) =>
|
||
j === n
|
||
? {
|
||
...v,
|
||
amount: e
|
||
.target
|
||
.value,
|
||
}
|
||
: v,
|
||
),
|
||
})
|
||
}
|
||
/>
|
||
</label>
|
||
</div>
|
||
))}
|
||
{r.settlement === 'complete' && (
|
||
<button
|
||
type="button"
|
||
onClick={() =>
|
||
change(r.id, {
|
||
allocations: [
|
||
...r.allocations,
|
||
{ paymentId: '', amount: '' },
|
||
],
|
||
})
|
||
}
|
||
>
|
||
Associar pagamento/prova existente
|
||
</button>
|
||
)}
|
||
</fieldset>
|
||
))}
|
||
<h5>Pagamentos e comprovativos reutilizáveis neste livro</h5>
|
||
<p>
|
||
Uma referência é registada uma vez e repartida entre
|
||
rendimentos sem exceder o montante. Pagamento ao fornecedor
|
||
não é entrega à AT; pagamentos de IVA conservam o seu
|
||
vínculo anterior e não são automaticamente usados em IRPC.
|
||
</p>
|
||
{book.payments.map((p) => (
|
||
<fieldset
|
||
key={p.id}
|
||
className="grid gap-2 rounded border p-2 md:grid-cols-3"
|
||
>
|
||
<label>
|
||
Natureza
|
||
<select
|
||
value={p.kind}
|
||
onChange={(e) =>
|
||
paymentChange(p.id, {
|
||
kind: e.target
|
||
.value as IrpcPayment['kind'],
|
||
})
|
||
}
|
||
>
|
||
<option value="remittance">
|
||
Entrega IRPC à AT
|
||
</option>
|
||
<option value="withholding_proof">
|
||
Comprovativo de retenção sofrida
|
||
</option>
|
||
<option value="beneficiary">
|
||
Pagamento ao beneficiário
|
||
</option>
|
||
</select>
|
||
</label>
|
||
{(
|
||
['reference', 'date', 'amount', 'currency'] as const
|
||
).map((k) => (
|
||
<label key={k}>
|
||
{
|
||
{
|
||
reference: 'Referência',
|
||
date: 'Data',
|
||
amount: 'Montante',
|
||
currency: 'Moeda',
|
||
}[k]
|
||
}
|
||
<input
|
||
type={k === 'date' ? 'date' : 'text'}
|
||
value={p[k]}
|
||
onChange={(e) =>
|
||
paymentChange(p.id, {
|
||
[k]: e.target.value,
|
||
})
|
||
}
|
||
/>
|
||
</label>
|
||
))}
|
||
{p.currency !== 'MZN' &&
|
||
(p.kind === 'withholding_proof' ? (
|
||
renderFx(p.id, p, (patch) =>
|
||
paymentChange(p.id, patch),
|
||
)
|
||
) : (
|
||
<p>
|
||
A entrega IRPC à AT requer o montante MZN do
|
||
comprovativo; converter o pagamento ao
|
||
beneficiário não prova entrega do imposto.
|
||
</p>
|
||
))}
|
||
</fieldset>
|
||
))}
|
||
<button
|
||
type="button"
|
||
onClick={() =>
|
||
save({
|
||
...book,
|
||
payments: [
|
||
...book.payments,
|
||
{
|
||
id: uid(),
|
||
kind: 'remittance',
|
||
reference: '',
|
||
date: '',
|
||
amount: '',
|
||
currency: 'MZN',
|
||
},
|
||
],
|
||
})
|
||
}
|
||
>
|
||
Registar pagamento/prova para reutilização
|
||
</button>
|
||
<div className="space-x-3">
|
||
<button
|
||
type="button"
|
||
className="rounded border p-2"
|
||
onClick={() => {
|
||
try {
|
||
save(recordIrpc(draft, book));
|
||
} catch (e) {
|
||
setError(String(e));
|
||
}
|
||
}}
|
||
>
|
||
Calcular e guardar retenções IRPC
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className="rounded border p-2"
|
||
onClick={() => {
|
||
try {
|
||
const html = irpcPreparationHtml(draft),
|
||
u = URL.createObjectURL(
|
||
new Blob([html], {
|
||
type: 'text/html;charset=utf-8',
|
||
}),
|
||
),
|
||
a = document.createElement('a');
|
||
a.href = u;
|
||
a.download = 'IRPC-L5-preparacao.html';
|
||
a.click();
|
||
URL.revokeObjectURL(u);
|
||
setError('');
|
||
} catch (e) {
|
||
setError(String(e));
|
||
}
|
||
}}
|
||
>
|
||
Exportar preparação IRPC
|
||
</button>
|
||
</div>
|
||
<SimulationIrpcMrfta draft={draft} book={book} save={save} />
|
||
{irpcReportRows(draft)
|
||
.slice(0, book.result?.rows.length ?? 0)
|
||
.map((r, n) => (
|
||
<p key={n}>
|
||
<strong>{r[1]}</strong>: {r[2]}
|
||
<small className="block">{r[3]}</small>
|
||
</p>
|
||
))}
|
||
<p>
|
||
Formalidades externas:MRFTA19/39 e guia M/39 preservados.
|
||
CER/rubricas digitais e remissão67(7)→62(6) por
|
||
corresponder. Exportar não submete nem obtém aceitação AT.
|
||
L5 continua aberto;ISPC e IRPS conservam os seus limites
|
||
anteriores.
|
||
</p>
|
||
</section>
|
||
);
|
||
} catch (e) {
|
||
return <p role="alert">{String(e)}</p>;
|
||
}
|
||
}
|