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

1339 lines
74 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 { fundraisingActivities } from '@/lib/iva-fundraising';
import {
pharmaAnnexEntries,
pharmaAnnexEntry,
pharmaAnnexReference,
} from '@/lib/iva-pharma-annex';
import { annexIEntries, annexIEntry, annexIReference } from '@/lib/iva-annex-i';
import {
temporaryBenefit,
temporaryBenefitNotice,
} from '@/lib/iva-temporary-benefits';
import { isExportService } from '@/lib/iva-export-services';
import {
isSpecialRegime,
specialRegimesFor,
specialRegimeIssue,
type SpecialRegime,
} from '@/lib/iva-special-regimes';
import {
isOnboardSupply,
supplyCategories,
onboardSupplyIssue,
type SupplyCategory,
} from '@/lib/iva-onboard-supplies';
import {
emptyProductionTaxOption,
type ProductionTaxOption,
} from '@/lib/iva-production-option';
import {
referenceExportOperation,
continueTemporaryOperation,
continueAnnexIOperation,
modeloA06Correspondence,
newSalesOperation,
operationTreatments,
recordSales,
reuseSales,
salesBook,
salesCurrent,
salesReportRows,
saveSalesBook,
splitSales,
type SalesBook,
type SalesOperation,
} from '@/lib/iva-operations';
export function SimulationOperations({
draft,
onChange,
}: {
draft: Record<string, unknown>;
onChange: (field: string, value: string) => void;
}) {
const [error, setError] = useState('');
const [reuseOption, setReuseOption] = useState<{
rowId: string;
sourceId: string;
} | null>(null);
const [annexSearch, setAnnexSearch] = useState<Record<string, string>>({});
let book: SalesBook;
try {
book = salesBook(draft);
} catch (cause) {
return <p role="alert">{String(cause)}</p>;
}
const run = (action: () => void) => {
try {
action();
setError('');
} catch (cause) {
setError(cause instanceof Error ? cause.message : String(cause));
}
};
const save = (next: SalesBook) =>
onChange('modelo_a_snapshot', saveSalesBook(draft, next));
const change = (id: string, patch: Partial<SalesOperation>) =>
run(() =>
save({
...book,
rows: book.rows.map((r) =>
r.id === id ? { ...r, ...patch } : r,
),
}),
);
const continueTemporary = (id: string, target: string) =>
run(() =>
save({
...book,
rows: book.rows.map((row) =>
row.id === id
? continueTemporaryOperation(row, target)
: row,
),
}),
);
return (
<section
aria-label="Operações IVA por oportunidade"
className="space-y-3 rounded border p-3"
>
<h4 className="font-semibold">
Operações de IVA — enquadramento das vendas
</h4>
<p>
Oportunidade{' '}
{inputText(draft.context_opportunity || 'entrada geral')}; moeda
original {book.currency || 'por indicar'}. Cobertura2026/MZN.
Reutilize as vendas e complete apenas o enquadramento. Este
bloco classifica as operações; não substitui nem soma novamente
a execução mensal geral acima.
</p>
<p>
Uma empresa pode ter operações com tratamentos diferentes. Os
valores são projecções sob pressupostos declarados, sem
certificar facturas, reconhecer créditos ou submeter uma
declaração à AT.
</p>
<details>
<summary>
Correspondência dos campos05/06/07 e contagem única
</summary>
<p>{modeloA06Correspondence}</p>
</details>
{error && <p role="alert">{error}</p>}
<button
type="button"
className="underline"
onClick={() => run(() => save(reuseSales(draft, book)))}
>
Reutilizar vendas e períodos existentes
</button>
<button
type="button"
className="ml-3 underline"
disabled={!book.periods.length}
onClick={() =>
run(() =>
save({
...book,
rows: [
...book.rows,
newSalesOperation(
crypto.randomUUID(),
book.periods[0].period,
book.currency,
),
],
}),
)
}
>
Acrescentar operação em falta
</button>
{book.rows.map((row) => {
const rule = operationTreatments.find(
(r) => r.id === row.treatment,
),
annexI = annexIEntry(row.annexIItem);
return (
<details className="rounded border p-3" key={row.id}>
<summary>
{row.description || 'Operação adicional'} —{' '}
{row.period}
</summary>
<p>
Origem: {row.source};{' '}
{row.sourceAmount ?? 'sem valor de origem'}{' '}
{row.sourceCurrency}; {row.sourcePeriod}. O
estabelecimento não é inferido da localização do
projecto.
</p>
<div className="grid gap-3 md:grid-cols-2">
<label>
Descrição da operação
<input
className="block rounded border p-2"
value={row.description}
onChange={(e) =>
change(row.id, {
description: e.target.value,
})
}
/>
</label>
<label>
Estabelecimento
<input
className="block rounded border p-2"
value={row.establishment}
onChange={(e) =>
change(row.id, {
establishment: e.target.value,
})
}
/>
</label>
<label>
Período
<input
type="month"
className="block rounded border p-2"
min="2026-01"
max="2026-12"
value={row.period}
onChange={(e) =>
change(row.id, {
period: e.target.value,
})
}
/>
</label>
<label>
Valor declarado
<input
inputMode="decimal"
className="block rounded border p-2"
value={row.amount}
onChange={(e) =>
change(row.id, {
amount: e.target.value,
})
}
/>
</label>
<label>
Representação do preço
<select
className="block rounded border p-2"
value={row.basis}
onChange={(e) =>
change(row.id, {
basis: e.target
.value as SalesOperation['basis'],
})
}
>
<option value="">Ainda desconhecida</option>
<option value="net">Sem IVA</option>
<option value="included">
IVA incluído
</option>
</select>
</label>
<label>
Tratamento da operação
<select
className="block rounded border p-2"
value={row.treatment}
onChange={(e) =>
temporaryBenefit(row.treatment) &&
!temporaryBenefit(e.target.value)
? continueTemporary(
row.id,
e.target.value,
)
: change(row.id, {
treatment: e.target.value,
confirmed: [],
})
}
>
{operationTreatments.map((t) => (
<option value={t.id} key={t.id}>
{t.label}
</option>
))}
</select>
</label>
</div>
{isSpecialRegime(row.treatment) && (
<fieldset className="space-y-2 rounded border p-2">
<legend>
Regime aduaneiro ou fiscal da operação
</legend>
<label>
Regime do artigo14(1)
<select
value={
specialRegimesFor(
row.treatment,
).some(
([value]) =>
value === row.specialRegime,
)
? row.specialRegime
: ''
}
onChange={(e) =>
change(row.id, {
specialRegime: e.target
.value as SpecialRegime,
confirmed: [],
})
}
>
<option value="">
Por identificar
</option>
{specialRegimesFor(row.treatment).map(
([value, label]) => (
<option
value={value}
key={value}
>
{label}
</option>
),
)}
</select>
</label>
<p>
Identifique no fundamento os bens, a
operação e o documento ou pressuposto
expresso do regime. A escolha conserva
preço, moeda e período e pede a revisão das
condições; não comprova o enquadramento por
estar numa zona. Bens e serviço de
transporte ou serviço conexo têm preços
próprios, sem duplicar o valor.
</p>
{specialRegimeIssue(
row.treatment,
row.specialRegime,
) && (
<p role="status">
{specialRegimeIssue(
row.treatment,
row.specialRegime,
)}
</p>
)}
</fieldset>
)}
{isOnboardSupply(row.treatment) && (
<fieldset className="space-y-2 rounded border p-2">
<legend>
Natureza do abastecimento a bordo
</legend>
<label>
Categoria do artigo13(3)
<select
value={row.supplyCategory ?? ''}
onChange={(e) =>
change(row.id, {
supplyCategory: e.target
.value as SupplyCategory,
confirmed: [],
})
}
>
<option value="">
Por identificar
</option>
{supplyCategories.map(
([value, label]) => (
<option
key={value}
value={value}
>
{label}
</option>
),
)}
</select>
</label>
<p>
A escolha conserva o preço, a moeda e o
período; reveja as condições. Para
fornecimento misto, discrimine as parcelas
efectivas sem repetir o total em cada linha.
Desconhecido não equivale a «sem bebidas».
</p>
{onboardSupplyIssue(
row.treatment,
row.supplyCategory,
) && (
<p role="status">
{onboardSupplyIssue(
row.treatment,
row.supplyCategory,
)}
</p>
)}
</fieldset>
)}
{isExportService(row.treatment) && (
<fieldset className="space-y-2 rounded border p-2">
<legend>
Referência à operação relacionada
</legend>
<p>
Se a operação já consta desta
oportunidade,pode referenciá-la sem
preencher de novo a descrição. A escolha
conserva o preço,moeda,período e fundamento
deste serviço;limpa as confirmações para
revisão. Não copia o valor dos bens para o
preço do serviço nem comprova
automaticamente a isenção. Para operação
documentada fora deste livro,use o
fundamento abaixo.
</p>
<label>
Operação já preenchida
<select
value={row.relatedExport?.id ?? ''}
onChange={(e) =>
run(() => {
if (!e.target.value) {
save({
...book,
rows: book.rows.map(
(item) =>
item.id ===
row.id
? {
...item,
relatedExport:
undefined,
confirmed:
[],
}
: item,
),
});
return;
}
const related = book.rows.find(
(item) =>
item.id ===
e.target.value,
);
if (!related)
throw new Error(
'Operação relacionada já não disponível; dados conservados.',
);
save({
...book,
rows: book.rows.map(
(item) =>
item.id === row.id
? referenceExportOperation(
item,
related,
)
: item,
),
});
})
}
>
<option value="">
Sem referência interna — identificar
no fundamento
</option>
{book.rows
.filter(
(item) => item.id !== row.id,
)
.map((item) => (
<option
key={item.id}
value={item.id}
>
{item.description ||
item.id}{' '}
— {item.period}
</option>
))}
</select>
</label>
{row.relatedExport && (
<>
<p>
Referência guardada:{' '}
{row.relatedExport.description}; ID{' '}
{row.relatedExport.id};{' '}
{row.relatedExport.treatment}.
</p>
{book.rows.some(
(item) =>
item.id ===
row.relatedExport!.id,
) && (
<button
type="button"
onClick={() =>
run(() => {
const related =
book.rows.find(
(item) =>
item.id ===
row
.relatedExport!
.id,
)!;
save({
...book,
rows: book.rows.map(
(item) =>
item.id ===
row.id
? referenceExportOperation(
item,
related,
)
: item,
),
});
})
}
>
Actualizar referência escolhida
e rever condições
</button>
)}
</>
)}
</fieldset>
)}
{temporaryBenefit(row.treatment) && (
<fieldset className="space-y-2 rounded border p-2">
<legend>
Benefício temporário — prazo e continuidade
</legend>
<p>
{temporaryBenefitNotice(
row.treatment,
row.period,
)}
</p>
<p>
{
temporaryBenefit(row.treatment)!
.historicalScope
}
</p>
<p>
Não precisa de preencher documentos de uma
isenção já terminada para prosseguir em2026.
Antes de escolher outro tratamento, confirme
a natureza da operação: a escolha conserva
valor,moeda,período,estabelecimento eorigem;
limpa as confirmações para revisão e mantém
o resultado anterior no histórico.
</p>
<button
type="button"
onClick={() =>
continueTemporary(row.id, 'general')
}
>
Escolher operação interna geral e rever
condições
</button>
{row.treatment === 'temporary_sugar_cane' && (
<button
type="button"
onClick={() =>
continueTemporary(
row.id,
'production_agriculture',
)
}
>
Analisar produção agrícola própria —
artigo9(7)(a)
</button>
)}
<p>
Para outro enquadramento disponível,use a
escolha «Tratamento da operação». Nenhum
resultado é recalculado automaticamente.
</p>
</fieldset>
)}
{temporaryBenefit(row.temporaryBenefitOrigin) &&
!temporaryBenefit(row.treatment) && (
<p>
Origem conservada:{' '}
{
temporaryBenefit(
row.temporaryBenefitOrigin,
)!.label
}
,benefício com termo31/12/2025.
Enquadramento actual escolhido
expressamente;confirme as respectivas
condições antes de calcular.
</p>
)}
{row.treatment === 'annexI_goods' && (
<fieldset className="space-y-2 rounded border p-2">
<legend>
Bem do Anexo I — edição Lei22/2022
</legend>
<p>
Escolha código e designação e confira apenas
os qualificadores dessa linha. A pesquisa
conserva a escolha actual; mudar de posição
exige rever as condições específicas. O
anexo não dispensa o enquadramento de outras
isenções aplicáveis ao mesmo bem.
</p>
<label className="block">
Pesquisar código ou designação no Anexo I
<input
value={annexSearch[`I:${row.id}`] ?? ''}
onChange={(e) =>
setAnnexSearch({
...annexSearch,
[`I:${row.id}`]: e.target.value,
})
}
/>
</label>
<label className="block">
Posição do Anexo I
<select
className="block max-w-full rounded border p-2"
value={row.annexIItem ?? ''}
onChange={(e) =>
change(row.id, {
annexIItem: e.target.value,
annexIOnly: undefined,
confirmed: row.confirmed.filter(
(n) => n === 0,
),
})
}
>
<option value="">
Ainda por determinar
</option>
{annexIEntries
.filter(
(entry) =>
entry.id ===
row.annexIItem ||
`${entry.code} ${entry.context ?? ''} ${entry.label}`
.toLocaleLowerCase('pt')
.includes(
(
annexSearch[
`I:${row.id}`
] ?? ''
)
.trim()
.toLocaleLowerCase(
'pt',
),
),
)
.map((entry) => (
<option
key={entry.id}
value={entry.id}
>
{entry.code} —{' '}
{entry.context
? entry.context + ' — '
: ''}
{entry.label} (PDF
{entry.pdfPage})
</option>
))}
</select>
</label>
{row.annexIItem && (
<p>
{annexI
? annexIReference(annexI)
: `Posição guardada não encontrada nesta edição: ${row.annexIItem}. Original conservado; escolha explicitamente a correspondência.`}
</p>
)}
{!!annexI?.relatedTreatments?.length && (
<div>
<p>
Confira a natureza e o destino: há
um tratamento existente que pode
abranger este bem. Se for aplicável,
use-o para conservar também o
direito à dedução nas condições
legais. Ao confirmar a passagem,
conserva valores e origem e terá de
rever as condições do tratamento
escolhido; nenhum resultado anterior
é substituído automaticamente.
</p>
{annexI.relatedTreatments.map((id) => {
const target =
operationTreatments.find(
(t) => t.id === id,
);
return (
target && (
<button
className="block underline"
type="button"
key={id}
onClick={() =>
run(() =>
save({
...book,
rows: book.rows.map(
(r) =>
r.id ===
row.id
? continueAnnexIOperation(
r,
id,
)
: r,
),
}),
)
}
>
Usar {target.label} e
rever condições
</button>
)
);
})}
{!annexI.relatedRequired && (
<label className="block">
<input
type="checkbox"
checked={
row.annexIOnly === true
}
onChange={(e) =>
change(row.id, {
annexIOnly:
e.target
.checked,
})
}
/>
Conferi a designação e o
destino: esta operação não
corresponde ao tratamento
indicado acima; o fundamento
explica a aplicação apenas do
artigo9(12)(h).
</label>
)}
</div>
)}
</fieldset>
)}
{row.treatment === 'pharma_manufacturing_inputs' && (
<fieldset className="space-y-2 rounded border p-2">
<legend>
Bem do Anexo III — edição Lei22/2022
</legend>
<p>
Escolha a linha pelo código e pela
designação. O estabelecimento que transmite
deve estar integrado no SNS; o destino
hospitalar não substitui essa condição. A
selecção não classifica automaticamente uma
mercadoria nem confirma a sua utilização.
</p>
<label className="block">
Pesquisar código ou designação
<input
value={annexSearch[row.id] ?? ''}
onChange={(e) =>
setAnnexSearch({
...annexSearch,
[row.id]: e.target.value,
})
}
/>
</label>
<label className="block">
Linha do Anexo III
<select
className="block max-w-full rounded border p-2"
value={row.pharmaAnnexItem ?? ''}
onChange={(e) =>
change(row.id, {
pharmaAnnexItem: e.target.value,
})
}
>
<option value="">
Ainda por determinar
</option>
{pharmaAnnexEntries
.filter(
(entry) =>
entry.id ===
row.pharmaAnnexItem ||
`${entry.code} ${entry.label}`
.toLocaleLowerCase('pt')
.includes(
(
annexSearch[
row.id
] ?? ''
)
.trim()
.toLocaleLowerCase(
'pt',
),
),
)
.map((entry) => (
<option
key={entry.id}
value={entry.id}
>
{entry.code} — {entry.label}{' '}
(PDF{entry.pdfPage})
</option>
))}
</select>
</label>
{row.pharmaAnnexItem && (
<p>
{pharmaAnnexEntry(row.pharmaAnnexItem)
? pharmaAnnexReference(
pharmaAnnexEntry(
row.pharmaAnnexItem,
)!,
)
: `Escolha guardada não encontrada nesta edição: ${row.pharmaAnnexItem}. Original conservado; seleccione explicitamente a correspondência.`}
</p>
)}
<p>
O exemplar repete2912.19.00 em duas linhas
com designações diferentes e imprime2935.
sem completar a posição. As linhas mantêm
identidades próprias; não se inventam
dígitos nem se estende a isenção apenas pelo
código.
</p>
</fieldset>
)}
{row.treatment.startsWith('fundraising_') && (
<fieldset className="space-y-2 rounded border p-2">
<legend>
Manifestação ocasional — limite anual da
entidade
</legend>
<p>
O total é declarado para a entidade em todo
o ano, incluindo manifestações fora deste
dossier. Várias vendas da mesma manifestação
não são novas manifestações.
</p>
{(
[
[
'entityReference',
'Referência da entidade organizadora',
],
[
'annualEvents',
'Total anual de manifestações da entidade, incluindo esta',
],
[
'eventNumber',
'Número desta manifestação no ano',
],
] as const
).map(([field, label]) => (
<label className="block" key={field}>
{label}
<input
value={
row.fundraiser?.[field] ?? ''
}
onChange={(e) =>
change(row.id, {
fundraiser: {
entityReference: '',
annualEvents: '',
eventNumber: '',
activity: '',
...row.fundraiser,
[field]: e.target.value,
},
})
}
/>
</label>
))}
<label className="block">
Actividade habitual isenta
<select
value={row.fundraiser?.activity ?? ''}
onChange={(e) =>
change(row.id, {
fundraiser: {
entityReference: '',
annualEvents: '',
eventNumber: '',
...row.fundraiser,
activity: e.target.value,
},
})
}
>
<option value="">
Por identificar
</option>
{fundraisingActivities
.filter(([id]) =>
row.treatment ===
'fundraising_nonprofit'
? !id.endsWith('_public')
: id !==
'statutory_membership',
)
.map(([id, label]) => (
<option key={id} value={id}>
{label}
</option>
))}
</select>
</label>
</fieldset>
)}
{row.treatment.startsWith('production_') &&
row.treatment.endsWith('_opted') && (
<fieldset className="space-y-2 rounded border p-2">
<legend>
Opção de tributação da produção —
artigo11
</legend>
<p>
Complete apenas as datas em falta. A
opção não cessa automaticamente ao fim
de cinco anos. O prazo termina às 24
horas do dia correspondente; se faltar
esse dia, no último dia do mesmo mês. O
artigo11(3) exige declaração antes de
expirar o prazo e fixa os efeitos em 1
de Janeiro seguinte, respeitando a
permanência mínima.
</p>
{book.rows.some(
(source) =>
source.id !== row.id &&
source.taxOption,
) && (
<label className="block">
Reutilizar opção já preenchida neste
dossier
<select
value={
reuseOption?.rowId ===
row.id
? reuseOption.sourceId
: ''
}
onChange={(e) =>
setReuseOption(
e.target.value
? {
rowId: row.id,
sourceId:
e.target
.value,
}
: null,
)
}
>
<option value="">
Escolha a origem, se
aplicável ao mesmo sujeito
passivo
</option>
{book.rows
.filter(
(source) =>
source.id !==
row.id &&
source.taxOption,
)
.map((source) => (
<option
key={source.id}
value={source.id}
>
{source.description}{' '}
—{' '}
{
source.establishment
}
</option>
))}
</select>
</label>
)}
{reuseOption?.rowId === row.id && (
<div>
<p>
Ao confirmar, substituirá o
enquadramento e as datas da
opção desta linha pelos da
origem escolhida, conservando a
data desta operação. Confirme
que se aplicam ao mesmo sujeito
passivo; o resultado anterior
permanece no histórico.
</p>
<button
type="button"
onClick={() =>
run(() => {
const source =
book.rows.find(
(r) =>
r.id ===
reuseOption.sourceId,
);
if (!source?.taxOption)
throw new Error(
'Origem da opção já não disponível; dados conservados.',
);
change(row.id, {
taxOption: {
...structuredClone(
source.taxOption,
),
operationDate:
row
.taxOption
?.operationDate ??
'',
origin: {
rowId: source.id,
description:
source.description,
establishment:
source.establishment,
},
},
});
setReuseOption(null);
})
}
>
Confirmar reutilização da opção
escolhida
</button>
<button
type="button"
onClick={() =>
setReuseOption(null)
}
>
Conservar os dados desta linha
</button>
</div>
)}
<label className="block">
Tipo de declaração de opção
<select
value={row.taxOption?.mode ?? ''}
onChange={(e) =>
change(row.id, {
taxOption: {
...emptyProductionTaxOption(),
...row.taxOption,
mode: e.target
.value as ProductionTaxOption['mode'],
},
})
}
>
<option value="">
Por indicar
</option>
<option value="annual">
Opção com efeitos no ano
seguinte
</option>
<option value="startup">
Opção na declaração de início da
actividade
</option>
</select>
</label>
<label className="block">
Declaração de regresso à isenção
<select
value={
row.taxOption?.returnStatus ??
''
}
onChange={(e) =>
change(row.id, {
taxOption: {
...emptyProductionTaxOption(),
...row.taxOption,
returnStatus: e.target
.value as ProductionTaxOption['returnStatus'],
},
})
}
>
<option value="">
Ainda desconhecido
</option>
<option value="none">
Não existe declaração de
regresso
</option>
<option value="declared">
Existe declaração de regresso
</option>
</select>
</label>
{(
[
[
'declarationDate',
'Data da declaração de opção',
],
...(row.taxOption?.mode ===
'startup'
? [
[
'startupDate',
'Data de início da actividade',
],
]
: []),
[
'operationDate',
'Data da operação nesta linha',
],
...(row.taxOption?.returnStatus ===
'declared' ||
row.taxOption?.returnDeclarationDate
? [
[
'returnDeclarationDate',
'Data da declaração de regresso à isenção',
],
]
: []),
] as Array<
[
(
| 'declarationDate'
| 'startupDate'
| 'operationDate'
| 'returnDeclarationDate'
),
string,
]
>
).map(([field, label]) => (
<label className="block" key={field}>
{label}
<input
type="date"
value={
row.taxOption?.[field] ?? ''
}
onChange={(e) =>
change(row.id, {
taxOption: {
...emptyProductionTaxOption(),
...row.taxOption,
[field]:
e.target.value,
},
})
}
/>
</label>
))}
</fieldset>
)}
<p>
{rule?.article}; {rule?.source}. Aplicação nesta
versão: Janeiro–Dezembro2026, sujeita às condições
seguintes.
</p>
{rule?.conditions.map((condition, index) => (
<label className="block" key={index}>
<input
type="checkbox"
checked={row.confirmed.includes(index)}
onChange={(e) =>
change(row.id, {
confirmed: e.target.checked
? [...row.confirmed, index]
: row.confirmed.filter(
(i) => i !== index,
),
})
}
/>{' '}
{condition}
</label>
))}
<label className="block">
Documento ou pressuposto que sustenta estas
condições
<textarea
className="block w-full rounded border p-2"
value={row.evidence}
onChange={(e) =>
change(row.id, { evidence: e.target.value })
}
/>
</label>
<p>
Se precisar de repartir uma venda agregada, conserve
o valor de origem e prepare linhas discriminadas.
Não altere silenciosamente a sua representação
monetária.
</p>
<button
type="button"
className="mr-3 underline"
onClick={() =>
run(() =>
save(
splitSales(
book,
row.id,
crypto.randomUUID(),
),
),
)
}
>
Repartir explicitamente esta venda, conservando o
total de origem
</button>
<button
type="button"
className="underline"
onClick={() =>
run(() =>
save({
...book,
rows: book.rows.filter(
(r) => r.id !== row.id,
),
periods: book.periods.map((p) =>
p.period === row.period
? { ...p, complete: false }
: p,
),
}),
)
}
>
Remover esta linha do preenchimento (resultado
anterior conservado)
</button>
</details>
);
})}
{book.periods.map((p) => (
<label className="block" key={p.period}>
<input
type="checkbox"
checked={p.complete}
onChange={(e) =>
run(() =>
save({
...book,
periods: book.periods.map((v) =>
v.period === p.period
? {
...v,
complete: e.target.checked,
}
: v,
),
}),
)
}
/>{' '}
{p.period}: incluí todas as operações deste período; lista
vazia significa ausência expressamente confirmada.
</label>
))}
<p>
A correspondência05/06/07 está identificada no exemplar.
Tratamentos ainda não implementados conservam o seu fundamento
por determinar;localização estrangeira não determina06. IVA
incluído a5% conserva a fracção exacta e apresenta um decimal
a6casas; a aproximação de apresentação não constitui regra
declarativa. O desconhecido nunca é assumido como zero.
</p>
<button
type="button"
disabled={!book.periods.length}
className="rounded bg-emerald-800 p-2 text-white disabled:opacity-50"
onClick={() => run(() => save(recordSales(draft, book)))}
>
Calcular e guardar operações IVA
</button>
{book.result && (
<>
<p role="status">
{salesCurrent(draft, book)
? 'Resultado correspondente às entradas.'
: 'Resultado histórico conservado; reveja as alterações antes de recalcular.'}
</p>
<dl>
{salesReportRows(draft).map(
([, label, value, origin], index) => (
<div
className="break-inside-avoid border-t py-2"
key={index}
>
<dt className="font-semibold">{label}</dt>
<dd>
{value}
<small className="block">
{origin}
</small>
</dd>
</div>
),
)}
</dl>
</>
)}
</section>
);
}