341 lines
16 KiB
TypeScript
341 lines
16 KiB
TypeScript
import { inputText } from '../lib/input-text.ts';
|
|
import { useState } from 'react';
|
|
import {
|
|
beverageRestitutionBook,
|
|
beverageRestitutionSources,
|
|
newBeverageRestitution,
|
|
saveBeverageRestitutionBook,
|
|
recordBeverageRestitution,
|
|
beverageRestitutionReportRows,
|
|
type BeverageRestitutionBook,
|
|
type BeverageRestitutionInput,
|
|
} from '@/lib/iva-beverage-restitution';
|
|
import { beverageDeductionEvidence } from '@/lib/iva-beverage-allocation';
|
|
import { fractionPresentation } from '@/lib/fiscal-rational';
|
|
import { FiscalClaimEventsFields } from './fiscal-claim-events';
|
|
export function AcquisitionBeverageRestitution({
|
|
draft,
|
|
onChange,
|
|
}: {
|
|
draft: Record<string, unknown>;
|
|
onChange: (field: string, value: string) => void;
|
|
}) {
|
|
const [error, setError] = useState('');
|
|
let b: BeverageRestitutionBook;
|
|
try {
|
|
b = beverageRestitutionBook(draft);
|
|
} catch (e) {
|
|
return <p role="alert">{String(e)}</p>;
|
|
}
|
|
const save = (next: BeverageRestitutionBook) => {
|
|
try {
|
|
onChange(
|
|
'modelo_a_snapshot',
|
|
saveBeverageRestitutionBook(draft, next),
|
|
);
|
|
setError('');
|
|
} catch (e) {
|
|
setError(String(e));
|
|
}
|
|
},
|
|
sources = beverageRestitutionSources(draft);
|
|
const change = (id: string, p: Partial<BeverageRestitutionInput>) =>
|
|
save({
|
|
...b,
|
|
rows: b.rows.map((r) => (r.id === id ? { ...r, ...p } : r)),
|
|
});
|
|
return (
|
|
<details className="rounded border p-3">
|
|
<summary>L3-E — restituição de bebidas13(2)</summary>
|
|
<p>
|
|
Reutiliza bebidas de abastecimento de bordo13(1)(c/d/h),com
|
|
documento e vínculoL1/L2. Escolha restituição na aquisição e
|
|
recalculeL2:esta linha não gera dedução nem crédito para
|
|
reembolso geral. Não se aplica a qualquer despesa com
|
|
bebidas,tabaco ou recepção.
|
|
</p>
|
|
<p>
|
|
Âmbito2026/MZN:linha integral ou parcelas de IVA
|
|
documentadas,sem sobreposição. A parte não pedida fica sem
|
|
destino presumido. Uma dedução efectiva anterior conserva
|
|
montante e histórico;identifique a parcela já deduzida. A
|
|
mudança ou anulação dessa dedução requer regularização com
|
|
fundamento próprio,sem a libertar por simples edição.
|
|
Pedido,decisão e recebimento são registos distintos.
|
|
Procedimento/formulário específico e prazos não
|
|
automatizados;submissão fora doInvesteMoz,sem condições
|
|
importadas do reembolso geralD.
|
|
</p>
|
|
{error && <p role="alert">{error}</p>}
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
save({
|
|
...b,
|
|
rows: [
|
|
...b.rows,
|
|
newBeverageRestitution(
|
|
crypto.randomUUID(),
|
|
sources.length === 1 ? sources[0] : null,
|
|
inputText(
|
|
draft.fiscal_entity_ref ??
|
|
draft.organization_name ??
|
|
'',
|
|
),
|
|
),
|
|
],
|
|
})
|
|
}
|
|
>
|
|
Preparar restituição de bebidas
|
|
</button>
|
|
{b.rows.map((r) => (
|
|
<fieldset key={r.id} className="rounded border p-3">
|
|
<legend>
|
|
Restituição —{' '}
|
|
{r.source?.period || 'origem por escolher'}
|
|
</legend>
|
|
<label>
|
|
Linha documental destinada à restituição
|
|
<select
|
|
value={sources.findIndex(
|
|
(s) =>
|
|
s.parcel === r.source?.parcel &&
|
|
s.fingerprint === r.source?.fingerprint,
|
|
)}
|
|
onChange={(e) => {
|
|
const s = sources[Number(e.target.value)];
|
|
if (s)
|
|
change(r.id, {
|
|
source: structuredClone(s),
|
|
});
|
|
}}
|
|
>
|
|
<option value={-1}>
|
|
Escolha origem actualL1/L2
|
|
</option>
|
|
{sources.map((s, i) => (
|
|
<option key={s.parcel} value={i}>
|
|
{s.documentRef} — linha
|
|
{s.documentLine || 'única'} — {s.period}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
{r.source && (
|
|
<p>
|
|
Documento:{r.source.documentRef};
|
|
{r.source.documentDate};fornecedor
|
|
{r.source.supplier};operaçãoL1{r.source.operationId}
|
|
;{r.source.operationRef}. O total documental é
|
|
reutilizado sem taxa presumida; repartição e IVA do
|
|
fornecimento mantêm cálculos próprios.
|
|
</p>
|
|
)}
|
|
<label>
|
|
Âmbito fiscal da entidade
|
|
<input
|
|
value={r.entityRef}
|
|
onChange={(e) =>
|
|
change(r.id, { entityRef: e.target.value })
|
|
}
|
|
/>
|
|
</label>
|
|
<label>
|
|
Parcela exclusivamente para restituição,sem dedução da
|
|
mesma parcela
|
|
<select
|
|
value={r.exclusive}
|
|
onChange={(e) =>
|
|
change(r.id, {
|
|
exclusive: e.target
|
|
.value as BeverageRestitutionInput['exclusive'],
|
|
})
|
|
}
|
|
>
|
|
<option value="">Por confirmar</option>
|
|
<option value="yes">Sim</option>
|
|
<option value="no">
|
|
Não — reconciliar parcela
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
Âmbito da restituição
|
|
<select
|
|
value={r.allocation ? 'partial' : 'full'}
|
|
onChange={(e) =>
|
|
change(r.id, {
|
|
allocation:
|
|
e.target.value === 'partial'
|
|
? {
|
|
from: '',
|
|
to: '',
|
|
reference: '',
|
|
deductionFrom: '',
|
|
deductionTo: '',
|
|
deductionReference: '',
|
|
}
|
|
: undefined,
|
|
})
|
|
}
|
|
>
|
|
<option value="full">Linha integral</option>
|
|
<option value="partial">
|
|
Parcela documentada da mesma linha
|
|
</option>
|
|
</select>
|
|
</label>
|
|
{r.allocation && (
|
|
<fieldset>
|
|
<legend>Repartição do IVA documental em MZN</legend>
|
|
<p>
|
|
Os limites identificam partes do IVA da linha:
|
|
por exemplo, de 0 a 6 e de 6 a 16 são parcelas
|
|
distintas de um total de 16. A parte restante
|
|
não gera dedução automática. Pode indicar
|
|
valores decimais ou fracções
|
|
exactas,como16/3;sem arredondar a dedução
|
|
anterior.
|
|
</p>
|
|
{(
|
|
[
|
|
['from', 'Início da parcela'],
|
|
['to', 'Fim da parcela'],
|
|
['reference', 'Suporte da repartição'],
|
|
] as const
|
|
).map(([field, label]) => (
|
|
<label key={field}>
|
|
{label}
|
|
<input
|
|
value={r.allocation![field]}
|
|
onChange={(e) =>
|
|
change(r.id, {
|
|
allocation: {
|
|
...r.allocation!,
|
|
[field]: e.target.value,
|
|
},
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
))}
|
|
</fieldset>
|
|
)}
|
|
{r.source &&
|
|
(() => {
|
|
const evidence = beverageDeductionEvidence(
|
|
draft,
|
|
r.source!,
|
|
);
|
|
return (
|
|
<fieldset>
|
|
<legend>
|
|
Reconciliação da dedução anterior
|
|
</legend>
|
|
<p>
|
|
IVA documental:{' '}
|
|
{
|
|
fractionPresentation(r.source!.vat)
|
|
.decimal
|
|
}{' '}
|
|
MZN. Dedução efectiva positiva
|
|
identificada:{' '}
|
|
{evidence.amount
|
|
? fractionPresentation(
|
|
evidence.amount,
|
|
).decimal
|
|
: 'Indeterminada'}{' '}
|
|
MZN. {evidence.reason}
|
|
</p>
|
|
{evidence.records.map((v, i) => (
|
|
<p key={i}>
|
|
{v.context};revisão{v.revision};
|
|
{v.calculatedAt};
|
|
{
|
|
fractionPresentation(v.amount)
|
|
.decimal
|
|
}{' '}
|
|
MZN;{v.documents.join(' | ')}
|
|
</p>
|
|
))}
|
|
{r.allocation &&
|
|
evidence.records.length > 0 &&
|
|
(
|
|
[
|
|
[
|
|
'deductionFrom',
|
|
'Início da parcela já deduzida',
|
|
],
|
|
[
|
|
'deductionTo',
|
|
'Fim da parcela já deduzida',
|
|
],
|
|
[
|
|
'deductionReference',
|
|
'Suporte da afectação da dedução anterior',
|
|
],
|
|
] as const
|
|
).map(([field, label]) => (
|
|
<label key={field}>
|
|
{label}
|
|
<input
|
|
value={r.allocation![field]}
|
|
onChange={(e) =>
|
|
change(r.id, {
|
|
allocation: {
|
|
...r.allocation!,
|
|
[field]:
|
|
e.target
|
|
.value,
|
|
},
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
))}
|
|
</fieldset>
|
|
);
|
|
})()}
|
|
<FiscalClaimEventsFields
|
|
value={r}
|
|
onChange={(p) => change(r.id, p)}
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
save({
|
|
...b,
|
|
rows: b.rows.filter((q) => q.id !== r.id),
|
|
})
|
|
}
|
|
>
|
|
Remover restituição desta preparação
|
|
</button>
|
|
</fieldset>
|
|
))}
|
|
{b.rows.length > 0 && (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
try {
|
|
save(recordBeverageRestitution(draft, b));
|
|
} catch (e) {
|
|
setError(String(e));
|
|
}
|
|
}}
|
|
>
|
|
Calcular e guardar restituição E
|
|
</button>
|
|
)}
|
|
{beverageRestitutionReportRows(draft).map((r, i) => (
|
|
<p key={i}>
|
|
<strong>{r[1]}:</strong> {r[2]}
|
|
<br />
|
|
{r[3]}
|
|
</p>
|
|
))}
|
|
</details>
|
|
);
|
|
}
|