Files
Investidor-V2/resources/js/components/simulation-offer-preparations.tsx
T

739 lines
36 KiB
TypeScript

import { inputText } from '../lib/input-text.ts';
import { SimulationOfferWorkflow } from './simulation-offer-workflow';
import { useState } from 'react';
import {
newOfferPreparation,
offerPreparationBook,
offerPreparationSource,
offerReusableData,
recordOfferPreparations,
reuseOfferData,
saveOfferPreparationBook,
emptyOfferRequest,
recordOfferRequest,
offerRequestLabels,
offerScenarios,
type OfferRequest,
type OfferPreparation,
type OfferPreparationBook,
} from '@/lib/iva-offer-preparations';
export function SimulationOfferPreparations({
draft,
onChange,
}: {
draft: Record<string, unknown>;
onChange: (field: string, value: string) => void;
}) {
const [error, setError] = useState('');
let book: OfferPreparationBook;
let reusable: ReturnType<typeof offerReusableData>;
try {
book = offerPreparationBook(draft);
reusable = offerReusableData(draft);
} catch (cause) {
return <p role="alert">{String(cause)}</p>;
}
const run = (action: () => void) => {
try {
action();
setError('');
} catch (cause) {
setError(String(cause));
}
};
const save = (next: OfferPreparationBook) =>
onChange('modelo_a_snapshot', saveOfferPreparationBook(draft, next));
const change = (id: string, patch: Partial<OfferPreparation>) =>
run(() =>
save({
...book,
rows: book.rows.map((row) =>
row.id === id ? { ...row, ...patch } : row,
),
}),
);
const question = (
row: OfferPreparation,
key:
| 'nationalInstitution'
| 'publicSocialInstitution'
| 'goodsSuitable'
| 'publicInterestUse'
| 'mitigation',
label: string,
) => (
<label className="block" key={key}>
{label}
<select
className="block rounded border p-2"
value={row[key]}
onChange={(e) => change(row.id, { [key]: e.target.value })}
>
<option value="">Por indicar</option>
<option value="yes">Sim</option>
<option value="no">Não</option>
</select>
</label>
);
return (
<details className="space-y-3 rounded border p-3">
<summary>
Ofertas a instituições e para calamidades — preparar rascunhos
</summary>
<p>
Prepare apenas as ofertas relevantes para a oportunidade{' '}
{inputText(draft.context_opportunity || 'entrada geral')}. Pode
guardar dados incompletos e reutilizar referências existentes. A
reutilização preenche apenas campos vazios; as suas edições
mantêm-se.
</p>
<p>
Procedimento operacional indicado pela Tempo:prepare o pedido
escrito,registe a apresentação externa,a decisão com despacho e
o formulário aplicável. Antes do deferimento,compare o cenário
normal com uma hipótese condicionada. Apresentar o pedido não
concede isenção. Estes cenários não alteram a dedução no
campo11; os restantes enquadramentos continuam disponíveis.
</p>
{error && <p role="alert">{error}</p>}
<button
type="button"
className="underline"
onClick={() =>
run(() =>
save({
...book,
rows: [
...book.rows,
newOfferPreparation(
crypto.randomUUID(),
'P084',
inputText(draft.currency ?? ''),
),
],
}),
)
}
>
Preparar oferta a instituição
</button>{' '}
<button
type="button"
className="underline"
onClick={() =>
run(() =>
save({
...book,
rows: [
...book.rows,
newOfferPreparation(
crypto.randomUUID(),
'P085',
inputText(draft.currency ?? ''),
),
],
}),
)
}
>
Preparar oferta para calamidade
</button>
{book.rows.map((row) => (
<details key={row.id} className="rounded border p-3">
<summary>
{row.branch} —{' '}
{row.description || 'Bens por identificar'}
</summary>
<p>
{row.branch === 'P084'
? 'Artigo14(2)(a): aquisição ou importação de bens para ofertas a instituições nacionais de interesse público e relevantes fins sociais.'
: 'Artigo14(2)(b): aquisição de bens para ofertas destinadas a atenuar efeitos de calamidades naturais.'}{' '}
{offerPreparationSource}
</p>
{row.branch === 'P084' && (
<label>
Operação
<select
className="block rounded border p-2"
value={row.operation}
onChange={(e) =>
change(row.id, {
operation: e.target
.value as OfferPreparation['operation'],
})
}
>
<option value="">Por indicar</option>
<option value="acquisition">Aquisição</option>
<option value="import">Importação</option>
</select>
</label>
)}
<label>
Reutilizar dados desta oportunidade
<select
className="block rounded border p-2"
value=""
onChange={(e) =>
run(() => {
const source = reusable.find(
(item) => item.key === e.target.value,
);
if (source)
save({
...book,
rows: book.rows.map((item) =>
item.id === row.id
? reuseOfferData(
item,
source,
)
: item,
),
});
})
}
>
<option value="">
Escolher referência existente
</option>
{reusable
.filter(
(item) =>
(row.branch === 'P084' ||
item.operation === 'acquisition') &&
(!row.operation ||
item.operation === row.operation),
)
.map((item) => (
<option key={item.key} value={item.key}>
{item.label}
</option>
))}
</select>
</label>
{row.reusedFrom && (
<p>
Referência reutilizada: {row.reusedFrom.key}. A
origem foi conservada; a aquisição/importação
original não foi alterada.
</p>
)}
<div className="grid gap-3 md:grid-cols-2">
{(
[
'description',
'period',
'currency',
'amount',
'counterparty',
'documentRef',
'documentDate',
'beneficiary',
'purpose',
'evidence',
] as const
).map((key, index) => (
<label key={key}>
{
[
'Bens destinados à oferta',
'Período previsto / efectivo',
'Moeda',
'Valor de referência, sem cálculo fiscal',
'Fornecedor / importador',
'Documento / referência da previsão',
'Data do documento / prevista',
'Instituição / destinatário da oferta',
'Finalidade concreta da oferta',
'Fundamento factual / pressupostos',
][index]
}
<input
className="block rounded border p-2"
type={
key === 'period'
? 'month'
: key === 'documentDate'
? 'date'
: 'text'
}
value={row[key]}
onChange={(e) =>
change(row.id, {
[key]: e.target.value,
})
}
/>
</label>
))}
</div>
{row.branch === 'P084' ? (
<fieldset>
<legend>
Condições próprias da oferta à instituição
</legend>
{question(
row,
'nationalInstitution',
'A instituição beneficiária é nacional',
)}
{question(
row,
'publicSocialInstitution',
'A instituição tem interesse público e relevantes fins sociais',
)}
{question(
row,
'goodsSuitable',
'Os bens são inteiramente adequados à natureza da instituição',
)}
{question(
row,
'publicInterestUse',
'A instituição utilizará os bens em actividades de evidente interesse público',
)}
</fieldset>
) : (
<fieldset>
<legend>
Finalidade própria da oferta para calamidade
</legend>
<label>
Calamidade e contexto afectados
<input
className="block rounded border p-2"
value={row.disaster}
onChange={(e) =>
change(row.id, {
disaster: e.target.value,
})
}
/>
</label>
<label>
Natureza da calamidade
<input
className="block rounded border p-2"
value={row.disasterKind}
placeholder="Cheias, tempestades, secas, ciclones, sismos ou outra de idêntica natureza"
onChange={(e) =>
change(row.id, {
disasterKind: e.target.value,
})
}
/>
</label>
{question(
row,
'mitigation',
'Os bens destinam-se a ofertas para atenuar os efeitos da calamidade identificada',
)}
</fieldset>
)}
{row.branch === 'P085' && (
<label>
Finalidade humanitária da resposta à emergência
<textarea
className="block w-full rounded border p-2"
value={row.humanitarianPurpose ?? ''}
onChange={(e) =>
change(row.id, {
humanitarianPurpose: e.target.value,
})
}
/>
</label>
)}
<label>
Documentos disponíveis e respectivas referências
<textarea
className="block w-full rounded border p-2"
value={row.availableDocuments ?? ''}
onChange={(e) =>
change(row.id, {
availableDocuments: e.target.value,
})
}
/>
</label>
{!!row.documentRef && (
<button
type="button"
className="underline"
onClick={() => {
const reference = [
row.documentRef,
row.documentDate,
]
.filter(Boolean)
.join(', ');
if (
!(row.availableDocuments ?? '').includes(
reference,
)
)
change(row.id, {
availableDocuments: [
row.availableDocuments,
reference,
]
.filter(Boolean)
.join('\n'),
});
}}
>
Juntar referência do documento já indicado
</button>
)}
{(() => {
const request = row.request ?? emptyOfferRequest();
const edit = (patch: Partial<OfferRequest>) =>
change(row.id, {
request: { ...request, ...patch },
});
return (
<fieldset className="rounded border p-3">
<legend>Pedido e âmbito da decisão</legend>
<p>
Registe o estado declarado e os elementos
disponíveis. Este registo não apresenta um
pedido à autoridade nem concede o benefício.
Conserve o âmbito exacto de cada decisão,
mesmo que cubra apenas parte dos bens ou
períodos.
</p>
<label>
Estado declarado do pedido
<select
className="block rounded border p-2"
value={request.state}
onChange={(e) =>
edit({
state: e.target
.value as OfferRequest['state'],
})
}
>
{Object.entries(offerRequestLabels).map(
([value, label]) => (
<option
key={value}
value={value}
>
{label}
</option>
),
)}
</select>
</label>
{(
[
'reference',
'presentedOn',
'decisionRef',
'decisionOn',
'goods',
'periodFrom',
'periodTo',
'conditions',
] as const
).map((key, index) => (
<label className="block" key={key}>
{
[
'Referência do pedido',
'Data de apresentação, se disponível',
'Documento / referência da decisão, se disponível',
'Data da decisão, se disponível',
'Bens abrangidos pela decisão',
'Início do período abrangido',
'Fim do período abrangido',
'Condições, limites ou fundamento da decisão',
][index]
}
<input
className="block rounded border p-2"
type={
key === 'presentedOn' ||
key === 'decisionOn'
? 'date'
: 'text'
}
placeholder={
key === 'periodFrom' ||
key === 'periodTo'
? 'AAAA-MM ou AAAA-MM-DD, conforme a decisão'
: undefined
}
value={request[key]}
onChange={(e) =>
edit({ [key]: e.target.value })
}
/>
</label>
))}
<label>
Operação abrangida pela decisão
<select
className="block rounded border p-2"
value={request.operation}
onChange={(e) =>
edit({
operation: e.target
.value as OfferRequest['operation'],
})
}
>
<option value="">Por indicar</option>
<option value="acquisition">
Aquisição
</option>
<option value="import">
Importação
</option>
</select>
</label>
<label>
Âmbito de operações literal, se a decisão
abranger mais do que a opção indicada
<input
className="block rounded border p-2"
value={request.operationScope ?? ''}
onChange={(e) =>
edit({
operationScope: e.target.value,
})
}
/>
</label>
<label>
Âmbito temporal literal, se não estiver
limitado pelas datas indicadas
<input
className="block rounded border p-2"
placeholder="Conservar, por exemplo, a indicação sem termo ou até revogação do documento"
value={request.periodScope ?? ''}
onChange={(e) =>
edit({
periodScope: e.target.value,
})
}
/>
</label>
<button
type="button"
className="underline"
onClick={() =>
edit({
goods:
request.goods ||
row.description,
operation:
request.operation ||
row.operation,
periodFrom:
request.periodFrom ||
row.period,
periodTo:
request.periodTo || row.period,
})
}
>
Reutilizar contexto nos campos de âmbito
vazios
</button>{' '}
<button
type="button"
className="rounded border p-2"
onClick={() =>
run(() =>
save({
...book,
rows: book.rows.map((item) =>
item.id === row.id
? recordOfferRequest(
item,
)
: item,
),
}),
)
}
>
Registar estado declarado e âmbito
</button>
{(row.requestRecords ?? []).map(
(record, index) => (
<p key={index}>
{
offerRequestLabels[
record.request.state
]
}{' '}
— {record.recordedAt}; bens:{' '}
{record.request.goods ||
'por indicar'}
; operação:{' '}
{record.request.operationScope ||
record.request.operation ||
'por indicar'}
; período:{' '}
{record.request.periodScope ||
`${record.request.periodFrom || 'por indicar'} a ${record.request.periodTo || 'por indicar'}`}
; condições:{' '}
{record.request.conditions ||
'por indicar'}
; decisão:{' '}
{record.request.decisionRef ||
'por indicar'}
. Este âmbito não se estende
automaticamente à restante operação
ou oportunidade.
</p>
),
)}
</fieldset>
);
})()}
<SimulationOfferWorkflow
row={row}
draft={draft}
onChange={(patch) => change(row.id, patch)}
/>
<fieldset className="rounded border p-3">
<legend>
Cenário normal e cenário condicionado à isenção
</legend>
<label>
IVA no cenário normal, indicado em documento ou
pressuposto
<input
className="block rounded border p-2"
inputMode="decimal"
value={row.normalVat ?? ''}
onChange={(e) =>
change(row.id, {
normalVat: e.target.value,
})
}
/>
</label>
<label>
Documento ou pressuposto do IVA normal
<input
className="block rounded border p-2"
value={row.normalVatSource ?? ''}
onChange={(e) =>
change(row.id, {
normalVatSource: e.target.value,
})
}
/>
</label>
<label>
Assumir, apenas para comparar, a hipótese
condicionada de isenção
<select
className="block rounded border p-2"
value={row.conditionalAssumption ?? ''}
onChange={(e) =>
change(row.id, {
conditionalAssumption: e.target
.value as '' | 'yes' | 'no',
})
}
>
<option value="">Por indicar</option>
<option value="yes">
Sim, como hipótese condicionada
</option>
<option value="no">Não</option>
</select>
</label>
{(() => {
const scenarios = offerScenarios(row);
return (
<>
<table>
<thead>
<tr>
<th>Cenário</th>
<th>IVA ({row.currency})</th>
</tr>
</thead>
<tbody>
<tr>
<td>
Normal — valor indicado, sem
taxa presumida
</td>
<td>
{scenarios.normalVat ??
'Indeterminado'}
</td>
</tr>
<tr>
<td>
Condicionado à isenção —
hipótese, não concessão
</td>
<td>
{scenarios.conditionalVat ??
'Indeterminado'}
</td>
</tr>
{scenarios.applied && (
<tr>
<td>
Tratamento deferido
apenas no âmbito do
despacho
</td>
<td>
{scenarios.appliedVat}
</td>
</tr>
)}
</tbody>
</table>
<p>{scenarios.conditionalReason}</p>
<p>
O cenário condicionado não altera
imposto,dedução,totais ou o estado do
pedido. O tratamento deferido exige
despacho registado e correspondência
expressa desta linha ao âmbito;alterar
os dados exige nova conferência.
</p>
</>
);
})()}
</fieldset>
</details>
))}
{!!book.rows.length && (
<>
<button
type="button"
className="rounded border p-2"
onClick={() =>
run(() => save(recordOfferPreparations(book)))
}
>
Guardar versão dos rascunhos de ofertas
</button>
<p role="status">
{book.saved
? 'Versão de preparação conservada; alterações seguintes não substituem essa versão.'
: 'Preparação em curso; pode guardar mesmo com informação em falta.'}{' '}
Estado e âmbito do tratamento indicados em cada linha.{' '}
{book.history?.length ?? 0} versões anteriores.
</p>
</>
)}
</details>
);
}