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

603 lines
29 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { inputText } from '../lib/input-text.ts';
import { useState } from 'react';
import {
newSecondhand,
switchSecondhandTreatment,
switchSecondhandCategory,
secondhandBook,
secondhandConditions,
secondhandCategoryConditions,
secondhandCurrent,
secondhandReportRows,
secondhandSource,
secondhandTreatments,
secondhandCategories,
recordSecondhands,
saveSecondhandBook,
type SecondhandBook,
type SecondhandRow,
} from '@/lib/iva-secondhand';
export function SimulationSecondhand({
draft,
onChange,
}: {
draft: Record<string, unknown>;
onChange: (field: string, value: string) => void;
}) {
const [error, setError] = useState('');
let book: SecondhandBook;
try {
book = secondhandBook(draft);
} catch (cause) {
return <p role="alert">{String(cause)}</p>;
}
const save = (next: SecondhandBook) => {
try {
onChange('modelo_a_snapshot', saveSecondhandBook(draft, next));
setError('');
} catch (cause) {
setError(String(cause));
}
};
const change = (id: string, patch: Partial<SecondhandRow>) =>
save({
...book,
rows: book.rows.map((row) =>
row.id === id ? { ...row, ...patch } : row,
),
});
return (
<details className="space-y-3 rounded border p-3">
<summary>Bens emsegunda mão — revenda pela margem eleilão</summary>
<p>
Oportunidade:{' '}
{inputText(draft.context_opportunity || 'entrada geral')}. Uma
linha porbem.P125identifica
categoria/estatuto;P092ouP093determina a base aplicável.Sem
compensação de perdas entrebens,nova dedução ou IVAcalculado.
</p>
<p>
Revendedor:contraprestação total comIVA−preço compra,parcela
positiva÷1.16 ebase arredondada à unidade48(6)b.16%é parâmetro
do divisor,não IVAcalculado neste
percurso.Leilão:facturado−líquidocomitente−IVApróprio
identificado;líquido=adjudicação−comissão.Não substituir IVA
desconhecido porzero.
</p>
<p>{secondhandSource}</p>
{error && <p role="alert">{error}</p>}
<button
type="button"
className="underline"
onClick={() =>
save({
...book,
rows: [
...book.rows,
newSecondhand(
crypto.randomUUID(),
inputText(draft.currency ?? ''),
),
],
})
}
>
Acrescentar bem individual
</button>
{book.rows.map((row) => (
<details key={row.id} className="rounded border p-3">
<summary>
{row.description || 'Bem por identificar'} —{' '}
{row.period || 'mês por indicar'}
</summary>
<div className="grid gap-3 md:grid-cols-2">
<label>
Percurso da margem
<select
className="block rounded border p-2"
value={row.treatment}
onChange={(e) =>
change(
row.id,
switchSecondhandTreatment(
row,
e.target.value,
),
)
}
>
<option value="">Por indicar</option>
{secondhandTreatments.map((t) => (
<option key={t.id} value={t.id}>
{t.label}
</option>
))}
</select>
</label>
<label>
Categoria do bem
<select
className="block rounded border p-2"
value={row.category}
onChange={(e) =>
change(
row.id,
switchSecondhandCategory(
row,
e.target.value,
),
)
}
>
<option value="">Por indicar</option>
{secondhandCategories.map((c) => (
<option key={c.id} value={c.id}>
{c.label}
</option>
))}
</select>
</label>
<label>
Natureza dos dados
<select
className="block rounded border p-2"
value={row.origin}
onChange={(e) =>
change(row.id, {
origin: e.target
.value as SecondhandRow['origin'],
})
}
>
<option value="projected">
Projectados / pressupostos
</option>
<option value="effective">
Efectivos declarados
</option>
</select>
</label>
{(
[
['description', 'Bem/operação identificados'],
['operationRef', 'Referência da operação'],
[
'operationDate',
'Data da operação / prevista',
],
['period', 'Mês da operação'],
['currency', 'Moeda dos montantes'],
[
'documentRef',
'Documento / referência da previsão',
],
[
'documentDate',
'Data do documento / prevista',
],
['goodsRef', 'Identidade do bem individual'],
[
'operatorRef',
'Operador e estatuto revendedor/organizador',
],
[
'categoryRef',
'Características e referência da classificação',
],
[
'sourceRef',
'Fornecedor/comitente,origem e referência',
],
[
'scope',
'Âmbito:bem,partes,operação eperíodo',
],
[
'evidence',
'Fundamento factual / pressupostos',
],
] as const
).map(([field, label]) => (
<label key={field}>
{label}
<input
className="block rounded border p-2"
type={
[
'operationDate',
'documentDate',
].includes(field)
? 'date'
: field === 'period'
? 'month'
: 'text'
}
value={row[field]}
onChange={(e) =>
change(row.id, {
[field]: e.target.value,
})
}
/>
</label>
))}
<label>
Qualidade do fornecedor/comitente
<select
className="block rounded border p-2"
value={row.sourceType}
onChange={(e) =>
change(row.id, {
sourceType: e.target.value,
})
}
>
<option value="">Por esclarecer</option>
<option value="private">Particular</option>
<option value="margin_reseller">
Revendedor sujeito à margem
</option>
<option value="exempt_9_12_e">
Transmissão no âmbito9(12)e
</option>
<option value="legacy_35_42">
Referência histórica a35/42 — conferir
situação2026
</option>
<option value="other">
Outro enquadramento por esclarecer
</option>
</select>
</label>
{(
[
[
'domesticPurchase',
'Aquisição dos bens no território nacional',
],
[
'domesticTaxable',
'Transmissão interna tributável,não exportação isenta13',
],
] as const
).map(([field, label]) => (
<label key={field}>
{label}
<select
className="block rounded border p-2"
value={row[field]}
onChange={(e) =>
change(row.id, {
[field]: e.target.value,
})
}
>
<option value="">Por esclarecer</option>
<option value="yes">Sim</option>
<option value="no">Não</option>
</select>
</label>
))}
{row.category === 'art' && (
<>
<label>
Tipo de objecto de arte
<select
className="block rounded border p-2"
value={row.artSubtype}
onChange={(e) =>
change(row.id, {
artSubtype: e.target.value,
})
}
>
<option value="">Por indicar</option>
<option value="original">
Quadro,pintura ou desenho original
</option>
<option value="print">
Gravura,estampa ou litografia
</option>
<option value="sculpture">
Escultura ou estatuária
</option>
<option value="ceramic">
Cerâmica
</option>
</select>
</label>
{row.artSubtype === 'print' && (
<label>
Tiragem total
<input
className="block rounded border p-2"
value={row.editionCount}
onChange={(e) =>
change(row.id, {
editionCount:
e.target.value,
})
}
/>
</label>
)}
{row.artSubtype === 'ceramic' && (
<label>
Cerâmica única,executada eassinada pelo
artista
<select
className="block rounded border p-2"
value={row.ceramicUniqueSigned}
onChange={(e) =>
change(row.id, {
ceramicUniqueSigned:
e.target.value,
})
}
>
<option value="">
Por esclarecer
</option>
<option value="yes">Sim</option>
<option value="no">Não</option>
</select>
</label>
)}
</>
)}
{row.category === 'collection' && (
<>
<label>
Tipo de colecção
<select
className="block rounded border p-2"
value={row.collectionSubtype}
onChange={(e) =>
change(row.id, {
collectionSubtype:
e.target.value,
})
}
>
<option value="">Por indicar</option>
<option value="postal">
Postal/filatélica
</option>
<option value="scientific">
Colecções/espécimes científicos3(6)c
</option>
<option value="historical">
Interesse
histórico,arqueológico,paleontológico,etnográfico
ou numismático
</option>
</select>
</label>
{row.collectionSubtype === 'postal' && (
<label>
Fora decirculação esem destino aser
posto emcirculação
<select
className="block rounded border p-2"
value={row.postalNotCirculating}
onChange={(e) =>
change(row.id, {
postalNotCirculating:
e.target.value,
})
}
>
<option value="">
Por esclarecer
</option>
<option value="yes">Sim</option>
<option value="no">Não</option>
</select>
</label>
)}
</>
)}
{row.category === 'antique' && (
<label>
Idade emanos
<input
className="block rounded border p-2"
value={row.ageYears}
onChange={(e) =>
change(row.id, {
ageYears: e.target.value,
})
}
/>
</label>
)}
{row.treatment === 'reseller_margin' && (
<>
<label>
Opção do revendedor
<select
className="block rounded border p-2"
value={row.regimeChoice}
onChange={(e) =>
change(row.id, {
regimeChoice: e.target.value,
})
}
>
<option value="">Por esclarecer</option>
<option value="margin">
Regime da margem
</option>
<option value="general">
Regime geral — outra fórmula
</option>
</select>
</label>
{(
[
[
'grossSale',
'Contraprestação total devida pelocliente,comIVA incluído',
],
[
'purchasePrice',
'Preço compra do mesmo bem',
],
[
'purchaseRef',
'Justificação/referência do preço compra',
],
] as const
).map(([field, label]) => (
<label key={field}>
{label}
<input
className="block rounded border p-2"
value={row[field]}
onChange={(e) =>
change(row.id, {
[field]: e.target.value,
})
}
/>
</label>
))}
</>
)}
{row.treatment === 'auction_margin' &&
(
[
[
'grossInvoice',
'Montante facturado ao comprador49(4)',
],
['hammerPrice', 'Preço de adjudicação'],
[
'principalCommission',
'Comissão obtida/aobter do comitente',
],
[
'auctionRef',
'Contrato de comissão de venda emnome próprio eleilão',
],
[
'principalRef',
'Comitente e referência do líquido',
],
[
'ownVat',
'IVApróprio relativo àtransmissão,identificado',
],
[
'ownVatRef',
'Referência/composição do IVApróprio identificado',
],
] as const
).map(([field, label]) => (
<label key={field}>
{label}
<input
className="block rounded border p-2"
value={row[field]}
onChange={(e) =>
change(row.id, {
[field]: e.target.value,
})
}
/>
</label>
))}
</div>
<p>
Os montantes ocultos de outro percurso são conservados
esó entram na fórmula escolhida.Situação antiga35/42não
activa margem automaticamente em2026;sem novo
certificado/aprovador.
</p>
<fieldset>
<legend>Condições da classificação eregime</legend>
{[
...secondhandConditions,
...secondhandCategoryConditions(row.category),
[
'regimeConfirmed',
'Condições47/48(1)ou49(1),papéis/contrato equadro temporal conferidos',
],
[
'compositionConfirmed',
'Composição monetária corresponde ao percurso:revenda comIVA no total,ou leilão comfacturado,líquidocomitente eIVApróprio separados,sem dupla subtracção',
],
].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 as
| ''
| 'yes'
| 'no',
},
})
}
>
<option value="">Por indicar</option>
<option value="yes">Sim</option>
<option value="no">Não</option>
</select>
</label>
))}
</fieldset>
</details>
))}
{!!book.rows.length && (
<button
type="button"
className="rounded bg-emerald-800 p-2 text-white"
onClick={() => {
try {
save(recordSecondhands(draft, book));
} catch (cause) {
setError(String(cause));
}
}}
>
Classificar bens,calcular bases eguardar versão
</button>
)}
{book.result && (
<>
<p role="status">
{secondhandCurrent(draft, book)
? 'Classificações/bases correspondentes às entradas guardadas.'
: 'Entradas alteradas;resultados históricos conservados.'}{' '}
{book.history?.length ?? 0} versões anteriores.Sem
compensar margens entrebens,calcular IVA ou
dedução/campo11.
</p>
<dl>
{secondhandReportRows(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>
);
}