Files
Investidor-V2/resources/js/components/simulation-travel-events.tsx
T

524 lines
25 KiB
TypeScript

import { inputText } from '../lib/input-text.ts';
import { useState } from 'react';
import {
travelEventBook,
travelEventCurrent,
travelEventReportRows,
travelEventSource,
travelEventSources,
newTravelEvent,
recordTravelEvents,
saveTravelEventBook,
switchTravelEvent,
type TravelEventBook,
type TravelEventRow,
} from '@/lib/iva-travel-events';
export function SimulationTravelEvents({
draft,
onChange,
}: {
draft: Record<string, unknown>;
onChange: (field: string, value: string) => void;
}) {
const [error, setError] = useState('');
let book: TravelEventBook;
try {
book = travelEventBook(draft);
} catch (e) {
return <p role="alert">{String(e)}</p>;
}
const save = (next: TravelEventBook) => {
try {
onChange('modelo_a_snapshot', saveTravelEventBook(draft, next));
setError('');
} catch (e) {
setError(String(e));
}
};
const change = (id: string, patch: Partial<TravelEventRow>) =>
save({
...book,
rows: book.rows.map((r) => (r.id === id ? { ...r, ...patch } : r)),
});
const fields = (
row: TravelEventRow,
list: ReadonlyArray<
readonly [Exclude<keyof TravelEventRow, 'fxSelection'>, string]
>,
) =>
list.map(([key, label]) => (
<label className="block" key={key}>
{label}
<input
type={
key === 'period'
? 'month'
: String(key).endsWith('Date') ||
key === 'legalDeadline'
? 'date'
: 'text'
}
value={row[key]}
onChange={(e) => change(row.id, { [key]: e.target.value })}
/>
</label>
));
const select = (
row: TravelEventRow,
key: Exclude<keyof TravelEventRow, 'fxSelection'>,
label: string,
options: ReadonlyArray<readonly [string, string]>,
) => (
<label>
{label}
<select
value={row[key]}
onChange={(e) => change(row.id, { [key]: e.target.value })}
>
<option value="">Por esclarecer</option>
{options.map(([value, text]) => (
<option key={value} value={value}>
{text}
</option>
))}
</select>
</label>
);
const yn = [
['yes', 'Sim'],
['no', 'Não'],
] as const;
let sources: ReturnType<typeof travelEventSources> = [];
try {
sources = travelEventSources(draft);
} catch {
/* Origin reader preserves originals. */
}
return (
<details className="rounded border p-3">
<summary>
Eventos de viagens e cessação de regimes suspensivos
</summary>
<p>
Conserve separados facto gerador e exigibilidade. Não inferir
pagamento integral por um sinal, início pela emissão do bilhete
ou cessação do regime por mudança de armazém. Estes eventos não
calculam imposto nem concedem benefício.
</p>
<p>{travelEventSource}</p>
{error && <p role="alert">{error}</p>}
<button
type="button"
onClick={() =>
save({
...book,
rows: [
...book.rows,
newTravelEvent(
crypto.randomUUID(),
inputText(draft.currency ?? ''),
),
],
})
}
>
Acrescentar evento especial
</button>
{book.rows.map((row) => {
const source = sources.find((s) => s.id === row.travelRowId);
return (
<details className="rounded border p-3" key={row.id}>
<summary>
{row.description || 'Evento por identificar'} —{' '}
{row.period || 'período por indicar'}
</summary>
<label>
Percurso do evento especial
<select
value={row.treatment}
onChange={(e) =>
change(
row.id,
switchTravelEvent(row, e.target.value),
)
}
>
<option value="">Por esclarecer</option>
<option value="travel_special">
Viagens — pagamento integral/início7(9)
</option>
<option value="suspensive_exit">
Cessação de regime/procedimento7(8)
</option>
</select>
</label>
{fields(row, [
['description', 'Evento ou operação identificada'],
[
'operationRef',
'Referência da operação existente',
],
['period', 'Mês de contexto do evento'],
[
'documentRef',
'Documento disponível / referência da previsão',
],
['documentDate', 'Data do documento / prevista'],
[
'reviewDate',
'Data até à qual os eventos são conferidos',
],
[
'scope',
'Âmbito e parcela efectivamente abrangidos',
],
['evidence', 'Factos ou pressupostos expressos'],
])}
{select(row, 'origin', 'Natureza dos dados', [
['projected', 'Projectados / pressupostos'],
['effective', 'Efectivos declarados'],
])}
{select(
row,
'factsConfirmed',
'Factos correspondem ao percurso e âmbito identificados',
yn,
)}
{row.treatment === 'suspensive_exit' && (
<>
{select(
row,
'regimeClause',
'Regime/procedimento identificado no artigo14(1)',
[
[
'a',
'a — Importação/colocação em regimes ou depósitos indicados',
],
[
'b',
'b — Expedição/transporte para as zonas ou depósitos e conexos',
],
[
'c',
'c — Transmissão e serviços conexos sob o regime',
],
[
'd',
'd — Trânsito, transbordo, draubaque ou importação temporária e conexos',
],
],
)}
{fields(row, [
[
'entryDate',
'Data de colocação sob o regime/procedimento',
],
[
'entryRef',
'Referência do regime, bens e operação de entrada',
],
])}
{select(
row,
'regimeStatus',
'Situação do regime na conferência',
[
[
'still',
'Continua abrangido pelo regime',
],
[
'ceased',
'Cessou efectivamente o regime',
],
],
)}
{row.regimeStatus === 'ceased' && (
<>
{fields(row, [
[
'exitDate',
'Data da cessação efectiva',
],
[
'exitRef',
'Referência e âmbito da cessação',
],
])}
{select(
row,
'cessationConfirmed',
'Confirmada cessação fiscal, distinguida de transferência entre regimes ou mudança física',
yn,
)}
</>
)}
<p>
A referência demonstra o facto e o âmbito.
Não se exige certificado ou autorização
inventados; não há desembaraço, valor
aduaneiro, direitos ou ICE automáticos.
</p>
</>
)}
{row.treatment === 'travel_special' && (
<>
<label>
Operação de viagens já classificada nesta
oportunidade
<select
value={row.travelRowId}
onChange={(e) =>
change(row.id, {
travelRowId: e.target.value,
})
}
>
<option value="">
Escolher operação
</option>
{sources.map((s) => (
<option key={s.id} value={s.id}>
{s.description} — {s.period} —{' '}
{s.classification.regime}
{s.current
? ''
: ' — histórico'}
</option>
))}
</select>
</label>
{source && (
<>
<p>
Origem: {source.operationRef};{' '}
{source.scope}. Período do registo:{' '}
{source.period}.{' '}
{source.classification.reason}
</p>
<button
type="button"
onClick={() =>
change(row.id, {
operationRef:
row.operationRef ||
source.operationRef,
description:
row.description ||
source.description,
documentRef:
row.documentRef ||
source.input
.documentRef,
documentDate:
row.documentDate ||
source.input
.documentDate,
currency:
row.currency ||
source.input.currency,
scope:
row.scope ||
source.scope,
})
}
>
Preencher apenas referências em
falta a partir da origem
</button>
<p>
Referências já preenchidas são
conservadas; divergências precisam
de conferência. O período do evento
pode diferir do período da margem.
</p>
</>
)}
{select(
row,
'fullPaymentState',
'Estado do pagamento da contraprestação',
[
[
'full',
'Pagamento integral confirmado',
],
['partial', 'Apenas pagamento parcial'],
['none', 'Sem pagamentos'],
],
)}
{row.fullPaymentState === 'full' &&
fields(row, [
[
'fullPaymentDate',
'Data do pagamento integral',
],
[
'fullPaymentRef',
'Referência da liquidação integral / conjunto de recebimentos',
],
])}
{select(
row,
'startState',
'Início da viagem/alojamento e primeiro serviço',
[
[
'started',
'Primeiro serviço/início ocorrido no cenário conferido',
],
['not_started', 'Ainda não iniciado'],
],
)}
{row.startState === 'started' &&
fields(row, [
[
'startDate',
'Data do primeiro serviço ao cliente',
],
[
'startRef',
'Referência do primeiro serviço e âmbito',
],
])}
<p>
«Imediatamente antes» identifica o momento;
não se subtrai um dia à data. Pagamento e
início na mesma data não demonstram ordem
intradiária.
</p>
{fields(row, [
[
'amount',
'Contraprestação total da parcela analisada',
],
['currency', 'Moeda da contraprestação'],
])}
{select(
row,
'invoiceRequired',
'Operação com obrigação de emitir factura25',
yn,
)}
{row.invoiceRequired === 'yes' && (
<>
{fields(row, [
[
'legalDeadline',
'Termo do prazo legal de emissão aplicável',
],
[
'deadlineRef',
'Disposição, calendário e âmbito que fundamentam o prazo',
],
])}
{select(
row,
'invoiceState',
'Factura até à conferência',
[
['issued', 'Emitida'],
['not_issued', 'Não emitida'],
],
)}
{row.invoiceState === 'issued' &&
fields(row, [
[
'invoiceDate',
'Data de emissão da factura',
],
[
'invoiceRef',
'Referência da factura',
],
])}
</>
)}
{select(
row,
'paymentState',
'Recebimento ou parcela anterior à liquidação integral',
[
['none', 'Sem recebimento'],
[
'received',
'Recebimento identificado',
],
],
)}
{row.paymentState === 'received' &&
fields(row, [
[
'paymentDate',
'Data do recebimento identificado',
],
[
'paymentAmount',
'Montante recebido nessa parcela',
],
[
'paymentRef',
'Referência e âmbito da parcela recebida',
],
])}
<p>
Se existe sinal anterior e posterior
liquidação integral, identifique ambos: a
parcela e o saldo não são somados duas
vezes. A menção «IVA incluído» não é factura
emitida por este simulador.
</p>
</>
)}
<p>
Mudar percurso conserva campos ocultos e
referências; reveja as confirmações próprias.
</p>
</details>
);
})}
{!!book.rows.length && (
<button
type="button"
onClick={() => {
try {
save(recordTravelEvents(draft, book));
} catch (e) {
setError(String(e));
}
}}
>
Conferir eventos especiais e guardar versão
</button>
)}
{book.result && (
<>
<p role="status">
{travelEventCurrent(draft, book)
? 'Resultados correspondem às entradas e origens guardadas.'
: 'Entradas/origens alteradas; resultado histórico conservado. Reveja o delta.'}{' '}
{book.history?.length ?? 0} versões anteriores. Sem
base/IVA/dedução calculados.
</p>
<dl>
{travelEventReportRows(draft).map(
([, label, value, origin], i) => (
<div key={i}>
<dt>{label}</dt>
<dd>
{value}
<small className="block">
{origin}
</small>
</dd>
</div>
),
)}
</dl>
</>
)}
</details>
);
}