Files
Investidor-V2/resources/js/components/acquisition-beverage-restitution.tsx
T

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