Files
Investidor-V2/resources/js/components/simulation-exclusion-base.tsx
T

449 lines
22 KiB
TypeScript

import { inputText } from '../lib/input-text.ts';
import { useState } from 'react';
import {
newExclusionBase,
newExclusionPart,
switchExclusionPart,
exclusionBook,
exclusionConditions,
exclusionCurrent,
exclusionReportRows,
exclusionBaseSource,
exclusionTreatments,
partConditions,
recordExclusions,
saveExclusionBook,
type ExclusionBook,
type ExclusionRow,
type ExclusionPart,
} from '@/lib/iva-exclusion-base';
export function SimulationExclusionBase({
draft,
onChange,
}: {
draft: Record<string, unknown>;
onChange: (field: string, value: string) => void;
}) {
const [error, setError] = useState('');
let book: ExclusionBook;
try {
book = exclusionBook(draft);
} catch (cause) {
return <p role="alert">{String(cause)}</p>;
}
const save = (next: ExclusionBook) => {
try {
onChange('modelo_a_snapshot', saveExclusionBook(draft, next));
setError('');
} catch (cause) {
setError(String(cause));
}
};
const change = (id: string, patch: Partial<ExclusionRow>) =>
save({
...book,
rows: book.rows.map((row) =>
row.id === id ? { ...row, ...patch } : row,
),
});
return (
<details className="space-y-3 rounded border p-3">
<summary>Base tributável — exclusões da contraprestação</summary>
<p>
Oportunidade:{' '}
{inputText(draft.context_opportunity || 'entrada geral')}.
Prepare uma composição por operação,partindo do montante sem
próprio IVA e com as inclusões aplicáveis já consideradas.
Identifique as parcelas e se ainda estão incluídas;subtraia
apenas essas parcelas,sem segunda exclusão.
</p>
<p>
Cada tratamento tem condições próprias. Indemnização judicial
não é qualquer compensação;desconto deve estar
concedido;pagamento por conta alheia exige nome/conta do
adquirente e adequadas contas de terceiros. Nas embalagens,não
se exige devolução já realizada:confira a não transmissão,a
indicação separada e a menção de devolução acordada. Reutilize
documentos ou declare pressupostos de projecção. Base não é IVA
nem regularização.
</p>
<p>{exclusionBaseSource}</p>
{error && <p role="alert">{error}</p>}
<button
type="button"
className="underline"
onClick={() =>
save({
...book,
rows: [
...book.rows,
newExclusionBase(
crypto.randomUUID(),
inputText(draft.currency ?? ''),
),
],
})
}
>
Acrescentar exclusões de operação
</button>
{book.rows.map((row) => {
const partChange = (
id: string,
patch: Partial<ExclusionPart>,
) =>
change(row.id, {
parts: row.parts.map((p) =>
p.id === id ? { ...p, ...patch } : p,
),
});
return (
<details key={row.id} className="rounded border p-3">
<summary>
{row.description || 'Operação por identificar'} —{' '}
{row.period || 'mês por indicar'}
</summary>
<div className="grid gap-3 md:grid-cols-2">
<label>
Natureza dos dados
<select
className="block rounded border p-2"
value={row.origin}
onChange={(e) =>
change(row.id, {
origin: e.target
.value as ExclusionRow['origin'],
})
}
>
<option value="projected">
Projectados / pressupostos
</option>
<option value="effective">
Efectivos declarados
</option>
</select>
</label>
{(
[
['description', 'Operação identificada'],
[
'operationRef',
'Referência da operação / pressuposto',
],
[
'initialAmount',
'Montante inicial sem próprio IVA (moeda indicada)',
],
[
'initialRef',
'Referência do montante inicial e da sua composição',
],
[
'documentRef',
'Documento da operação / referência da previsão',
],
[
'documentDate',
'Data do documento / prevista',
],
['period', 'Mês da operação'],
['currency', 'Moeda de origem'],
[
'exclusionRef',
'Referência da composição e exclusões aplicáveis',
],
[
'evidence',
'Fundamento factual / pressupostos',
],
] as const
).map(([field, label]) => (
<label key={field}>
{label}
<input
className="block rounded border p-2"
type={
field === 'documentDate'
? 'date'
: field === 'period'
? 'month'
: 'text'
}
value={row[field]}
onChange={(e) =>
change(row.id, {
[field]: e.target.value,
})
}
/>
</label>
))}
</div>
<fieldset>
<legend>Condições das exclusões</legend>
{exclusionConditions.map(([key, label]) => (
<label className="block" key={key}>
{label}
<select
className="block rounded border p-2"
value={row.conditions[key] ?? ''}
onChange={(e) =>
change(row.id, {
conditions: {
...row.conditions,
[key]: e.target.value,
},
})
}
>
<option value="">Por indicar</option>
<option value="yes">Sim</option>
<option value="no">Não</option>
</select>
</label>
))}
</fieldset>
<button
type="button"
className="underline"
onClick={() =>
change(row.id, {
parts: [
...row.parts,
newExclusionPart(crypto.randomUUID()),
],
})
}
>
Acrescentar parcela desta operação
</button>
{row.parts.map((part) => (
<fieldset
key={part.id}
className="rounded border p-3"
>
<legend>
{part.description ||
'Parcela por identificar'}
</legend>
<label>
Natureza da parcela
<select
className="block rounded border p-2"
value={part.treatment}
onChange={(e) =>
partChange(
part.id,
switchExclusionPart(
part,
e.target.value,
),
)
}
>
<option value="">Por indicar</option>
{exclusionTreatments.map((t) => (
<option key={t.id} value={t.id}>
{t.label}
</option>
))}
</select>
</label>
{(
[
['description', 'Descrição da parcela'],
[
'amount',
'Montante da parcela sem próprio IVA (moeda indicada)',
],
[
'reference',
'Referência da parcela / pressuposto',
],
] as const
).map(([field, label]) => (
<label className="block" key={field}>
{label}
<input
className="block rounded border p-2"
value={part[field]}
onChange={(e) =>
partChange(part.id, {
[field]: e.target.value,
})
}
/>
</label>
))}
<label>
Esta parcela ainda está incluída no montante
inicial?
<select
className="block rounded border p-2"
value={part.included}
onChange={(e) =>
partChange(part.id, {
included: e.target
.value as ExclusionPart['included'],
})
}
>
<option value="">Por indicar</option>
<option value="yes">
Sim — excluir uma vez
</option>
<option value="no">
Não — já excluída,conservar sem nova
subtracção
</option>
</select>
</label>
<label>
Âmbito da exclusão: operação, montantes e
condições
<input
className="block rounded border p-2"
value={part.scope}
onChange={(e) =>
partChange(part.id, {
scope: e.target.value,
})
}
/>
</label>
{part.treatment === 'third_party_payment' && (
<>
<label>
Referência das adequadas contas de
terceiros
<input
className="block rounded border p-2"
value={part.accountRef}
onChange={(e) =>
partChange(part.id, {
accountRef:
e.target.value,
})
}
/>
</label>
<label>
Adquirente/destinatário em nome e
por conta de quem se pagou
<input
className="block rounded border p-2"
value={part.beneficiaryRef}
onChange={(e) =>
partChange(part.id, {
beneficiaryRef:
e.target.value,
})
}
/>
</label>
</>
)}
{part.treatment === 'returnable_packaging' && (
<label>
Indicação separada e menção de devolução
acordada na factura/documento
<input
className="block rounded border p-2"
value={part.documentExcerpt}
onChange={(e) =>
partChange(part.id, {
documentExcerpt:
e.target.value,
})
}
/>
</label>
)}
{partConditions(part.treatment).map(
([key, label]) => (
<label className="block" key={key}>
{label}
<select
className="block rounded border p-2"
value={
part.conditions[key] ?? ''
}
onChange={(e) =>
partChange(part.id, {
conditions: {
...part.conditions,
[key]: e.target
.value as ExclusionPart['included'],
},
})
}
>
<option value="">
Por indicar
</option>
<option value="yes">Sim</option>
<option value="no">Não</option>
</select>
</label>
),
)}
<p>
Mudar a natureza conserva os montantes e
referências,mas exige rever a
correspondência da parcela. Cada parcela
será excluída uma única vez nesta operação.
Os campos sem aplicação ficam ocultos e
conservados.
</p>
</fieldset>
))}
</details>
);
})}
{!!book.rows.length && (
<button
type="button"
className="rounded bg-emerald-800 p-2 text-white"
onClick={() => {
try {
save(recordExclusions(draft, book));
} catch (cause) {
setError(String(cause));
}
}}
>
Aplicar exclusões à base e guardar versão
</button>
)}
{book.result && (
<>
<p role="status">
{exclusionCurrent(draft, book)
? 'Exclusões correspondentes às entradas guardadas.'
: 'Entradas alteradas;exclusões históricas conservadas. Reveja e determine novamente.'}{' '}
{book.history?.length ?? 0} versões anteriores. IVA e
campo11 não são preenchidos por estas bases.
</p>
<dl>
{exclusionReportRows(draft).map(
([, label, value, origin], index) => (
<div key={index}>
<dt className="font-semibold">{label}</dt>
<dd>
{value}
<small className="block">
{origin}
</small>
</dd>
</div>
),
)}
</dl>
</>
)}
</details>
);
}