449 lines
22 KiB
TypeScript
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>
|
|
);
|
|
}
|