758 lines
33 KiB
TypeScript
758 lines
33 KiB
TypeScript
import { useState } from 'react';
|
|
import {
|
|
costRules,
|
|
newNormalCosts,
|
|
newCostRow,
|
|
reusableCreditBalance,
|
|
type CostRow,
|
|
type NormalCostsInput,
|
|
} from '@/lib/irpc-normal-costs';
|
|
import { normalFinancialSource, type NormalBook } from '@/lib/irpc-normal';
|
|
import { fiscalMoney } from '@/lib/irpc-normal-schedules';
|
|
import {
|
|
addFractions,
|
|
subtractFractions,
|
|
fractionPresentation,
|
|
} from '@/lib/fiscal-rational';
|
|
import { type ProfitTopic } from '@/lib/irpc-normal-profit';
|
|
type Field = [
|
|
string,
|
|
string,
|
|
'money' | 'text' | 'choice' | 'method' | 'source',
|
|
];
|
|
const fields: Record<string, Field[]> = {
|
|
allowance: [
|
|
['billed', 'Parcela facturada a clientes', 'money'],
|
|
[
|
|
'taxedNotBilled',
|
|
'Parcela tributada emIRPS e não facturada a clientes',
|
|
'money',
|
|
],
|
|
],
|
|
advertising: [
|
|
[
|
|
'campaign',
|
|
'Campanha de lançamento com horizonte superior a um ano',
|
|
'choice',
|
|
],
|
|
],
|
|
vehicle: [
|
|
['passenger', 'Viatura ligeira de passageiros', 'choice'],
|
|
[
|
|
'publicOrRental',
|
|
'Afecta a serviço público de transportes ou aluguer na actividade normal',
|
|
'choice',
|
|
],
|
|
],
|
|
fuel: [
|
|
[
|
|
'supportedNormal',
|
|
'Parcela com activo/locação e consumo normal comprovados',
|
|
'money',
|
|
],
|
|
['passenger', 'Viatura ligeira de passageiros', 'choice'],
|
|
[
|
|
'publicOrRental',
|
|
'Afecta a serviço público de transportes ou aluguer na actividade normal',
|
|
'choice',
|
|
],
|
|
],
|
|
rental: [
|
|
[
|
|
'nonAcceptedQuota',
|
|
'Parcela documentada da quota não aceite (36h)',
|
|
'money',
|
|
],
|
|
['quotaReference', 'Documento e regra da quota não aceite', 'text'],
|
|
['passenger', 'Aplicação36(4) — ligeira de passageiros', 'choice'],
|
|
[
|
|
'publicOrRental',
|
|
'Afecta a serviço público de transportes ou aluguer na actividade normal',
|
|
'choice',
|
|
],
|
|
],
|
|
stock: [
|
|
['balanceKey', 'Identificação única do inventário/objecto', 'text'],
|
|
['lossObserved', 'Perda de valor efectivamente observada', 'choice'],
|
|
['observedLoss', 'Perda total observada documentada', 'money'],
|
|
['previous', 'Provisão fiscal anterior que subsiste', 'money'],
|
|
],
|
|
litigation: [
|
|
['balanceKey', 'Identificação única do processo/objecto', 'text'],
|
|
['caseOngoing', 'Processo judicial em curso', 'choice'],
|
|
[
|
|
'chargeDeductible',
|
|
'Obrigação daria lugar a custo fiscal dedutível',
|
|
'choice',
|
|
],
|
|
['obligation', 'Obrigação coberta documentada', 'money'],
|
|
['previous', 'Provisão fiscal anterior que subsiste', 'money'],
|
|
],
|
|
provision_reversal: [
|
|
['balanceKey', 'Identificação única da provisão/objecto', 'text'],
|
|
['previous', 'Parcela antes fiscalmente deduzida', 'money'],
|
|
[
|
|
'eventEnded',
|
|
'Evento não subsiste ou utilização para fim diferente',
|
|
'choice',
|
|
],
|
|
[
|
|
'inResult',
|
|
'Reversão já incluída em proveitos do resultado actual',
|
|
'choice',
|
|
],
|
|
],
|
|
taxed_reversal: [
|
|
['balanceKey', 'Identificação única da provisão/objecto', 'text'],
|
|
['previous', 'Parcela antes tributada e não deduzida', 'money'],
|
|
[
|
|
'eventEnded',
|
|
'Evento não subsiste ou utilização para fim diferente',
|
|
'choice',
|
|
],
|
|
[
|
|
'inResult',
|
|
'Reversão já incluída em proveitos do resultado actual',
|
|
'choice',
|
|
],
|
|
],
|
|
prior_cost: [
|
|
['originalYear', 'Exercício de origem', 'text'],
|
|
[
|
|
'unknownAtClose',
|
|
'Imprevisível ou manifestamente desconhecido no encerramento anterior',
|
|
'choice',
|
|
],
|
|
[
|
|
'closingEvidence',
|
|
'Suporte do conhecimento no encerramento anterior',
|
|
'text',
|
|
],
|
|
],
|
|
prior_income: [
|
|
['originalYear', 'Exercício de origem', 'text'],
|
|
[
|
|
'unknownAtClose',
|
|
'Imprevisível ou manifestamente desconhecido no encerramento anterior',
|
|
'choice',
|
|
],
|
|
[
|
|
'closingEvidence',
|
|
'Suporte do conhecimento no encerramento anterior',
|
|
'text',
|
|
],
|
|
],
|
|
work: [
|
|
['projectKey', 'Identificação única da obra', 'text'],
|
|
['method', 'Critério fiscal da obra', 'method'],
|
|
['price', 'Preço contratual estabelecido', 'money'],
|
|
['totalCost', 'Custo total estimado para concluir', 'money'],
|
|
['incurred', 'Custos efectivamente incorporados acumulados', 'money'],
|
|
[
|
|
'priorRevenue',
|
|
'Proveitos fiscais reconhecidos em exercícios anteriores',
|
|
'money',
|
|
],
|
|
[
|
|
'priorCost',
|
|
'Custos fiscais reconhecidos em exercícios anteriores',
|
|
'money',
|
|
],
|
|
['costSource', 'Linha de custos da obra no resultado actual', 'source'],
|
|
[
|
|
'bookCost',
|
|
'Parcela de custos da obra já contabilizada no exercício',
|
|
'money',
|
|
],
|
|
[
|
|
'partialBilling',
|
|
'Facturação parcial com grau de acabamento correspondente',
|
|
'choice',
|
|
],
|
|
[
|
|
'ownFractionalDelivery',
|
|
'Obra por conta própria vendida e entregue fraccionadamente',
|
|
'choice',
|
|
],
|
|
[
|
|
'consistentMethod',
|
|
'Critério uniforme para obras idênticas e mantido até ao final',
|
|
'choice',
|
|
],
|
|
['publicReception', 'Recepção provisória de obra pública', 'choice'],
|
|
[
|
|
'deferredReceipt',
|
|
'Receita antecipada19(5) proposta — zero conhecido se não aplicada',
|
|
'money',
|
|
],
|
|
],
|
|
};
|
|
export function SimulationIrpcCosts({
|
|
draft,
|
|
book,
|
|
save,
|
|
}: {
|
|
draft: Record<string, unknown>;
|
|
book: NormalBook;
|
|
save: (b: NormalBook) => void;
|
|
}) {
|
|
const [review, setReview] = useState(false),
|
|
[error, setError] = useState(''),
|
|
i = book.costs,
|
|
source = normalFinancialSource(draft);
|
|
if (!i)
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={() => save({ ...book, costs: newNormalCosts() })}
|
|
>
|
|
Iniciar custos com limites, provisões e periodização
|
|
</button>
|
|
);
|
|
const patch = (p: Partial<NormalCostsInput>) => {
|
|
setReview(false);
|
|
save({ ...book, costs: { ...i, ...p } });
|
|
};
|
|
const row = (id: string, p: Partial<CostRow>) =>
|
|
patch({ rows: i.rows.map((r) => (r.id === id ? { ...r, ...p } : r)) });
|
|
const fact = (r: CostRow, k: string, v: string) =>
|
|
row(r.id, { facts: { ...r.facts, [k]: v } });
|
|
const summaries = () => {
|
|
const groups = new Map<ProfitTopic, string[]>();
|
|
for (const r of book.schedules?.rows ?? []) {
|
|
if (!r.rule) continue;
|
|
const k = (
|
|
{
|
|
tax_own: 'expenseCorrections',
|
|
tax_other: 'expenseCorrections',
|
|
undocumented: 'expenseCorrections',
|
|
capital_in: 'positivePatrimonial',
|
|
patrimonial_gain: 'positivePatrimonial',
|
|
capital_out: 'negativePatrimonial',
|
|
patrimonial_cost: 'negativePatrimonial',
|
|
future_cost: 'timing',
|
|
future_income: 'timing',
|
|
current_cost: 'timing',
|
|
current_income: 'timing',
|
|
} as Record<string, ProfitTopic>
|
|
)[r.rule];
|
|
groups.set(k, [...(groups.get(k) ?? []), r.amount]);
|
|
}
|
|
for (const r of i.rows)
|
|
if (r.rule) {
|
|
const k = costRules[r.rule][1];
|
|
groups.set(k, [...(groups.get(k) ?? []), r.amount]);
|
|
}
|
|
return [...groups].map(([key, values]) => ({
|
|
key,
|
|
amount: fractionPresentation(addFractions(values.map(fiscalMoney)))
|
|
.decimal,
|
|
}));
|
|
};
|
|
const reuse = (r: CostRow) => {
|
|
try {
|
|
const code = r.sourceCode,
|
|
l = source?.accountingLines.find((l) => l.code === code);
|
|
if (!l) throw Error('Seleccione uma origem.');
|
|
const claims = [
|
|
...book.rows,
|
|
...(book.schedules?.rows ?? []),
|
|
...i.rows.filter((q) => q.id !== r.id),
|
|
...i.rows
|
|
.filter((q) => q.rule === 'work')
|
|
.map((q) => ({
|
|
sourceCode: q.facts.costSource,
|
|
amount: q.facts.bookCost,
|
|
})),
|
|
].filter((q) => q.sourceCode === code);
|
|
const available = subtractFractions(
|
|
fiscalMoney(l.total),
|
|
addFractions(claims.map((q) => fiscalMoney(q.amount))),
|
|
);
|
|
if (BigInt(available.numerator) <= 0n)
|
|
throw Error('Não há parcela livre conhecida nesta origem.');
|
|
row(r.id, { amount: fractionPresentation(available).decimal });
|
|
setError('');
|
|
} catch (e) {
|
|
setError(String(e));
|
|
}
|
|
};
|
|
return (
|
|
<fieldset
|
|
aria-label="Custos com limites provisões periodização"
|
|
className="space-y-3 rounded border p-3"
|
|
>
|
|
<legend>
|
|
Custos com limites, provisões e periodização — exercício2026
|
|
</legend>
|
|
<p>
|
|
Reutiliza o resultado e as linhas já declaradas. Identifique só
|
|
a parcela de cada categoria e os parâmetros em falta. Os limites
|
|
anuais de publicidade e créditos são partilhados por todas as
|
|
parcelas; os saldos de provisões não são consumidos. A
|
|
divergência M20/M22 não bloqueia estes cálculos.
|
|
Benefícios/casos especiaisL7 e saldo anualL8 mantêm os seus
|
|
âmbitos.
|
|
</p>
|
|
{error && <p role="alert">{error}</p>}
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
patch({
|
|
rows: [...i.rows, newCostRow(crypto.randomUUID())],
|
|
})
|
|
}
|
|
>
|
|
Acrescentar custo, provisão ou periodização
|
|
</button>
|
|
{i.rows.some((r) => r.rule === 'advertising') && (
|
|
<fieldset>
|
|
<legend>Publicidade — limite único anual</legend>
|
|
<p>
|
|
Volume de vendas da origem:{' '}
|
|
{source?.accountingLines.find(
|
|
(l) => l.code === 'revenue',
|
|
)?.total ?? 'Em falta'}{' '}
|
|
MZN. O limite não se repete por factura.
|
|
</p>
|
|
<label>
|
|
Correspondência às vendas normais do exercício
|
|
<select
|
|
aria-label="Vendas normais para publicidade"
|
|
value={i.salesOrdinary}
|
|
onChange={(e) =>
|
|
patch({
|
|
salesOrdinary: e.target.value as '' | 'yes',
|
|
})
|
|
}
|
|
>
|
|
<option value="">Informação em falta</option>
|
|
<option value="yes">
|
|
Toda a receita da origem corresponde a
|
|
vendas/serviços normais
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<label className="block">
|
|
Suporte do volume de vendas e completude da publicidade
|
|
<input
|
|
value={i.advertisingReference}
|
|
onChange={(e) =>
|
|
patch({ advertisingReference: e.target.value })
|
|
}
|
|
/>
|
|
</label>
|
|
<p>
|
|
Campanhas de lançamento com efeito superior a um ano
|
|
exigem36-A e articulação própria; permanecem
|
|
discriminadas, sem dedução ordinária presumida.
|
|
</p>
|
|
</fieldset>
|
|
)}
|
|
{i.rows.some((r) => r.rule === 'credit') && (
|
|
<fieldset>
|
|
<legend>Créditos — limites anual e acumulado</legend>
|
|
<label>
|
|
Regime da provisão de créditos
|
|
<select
|
|
aria-label="Regime da provisão de créditos"
|
|
value={i.creditRegime}
|
|
onChange={(e) =>
|
|
patch({
|
|
creditRegime: e.target
|
|
.value as NormalCostsInput['creditRegime'],
|
|
})
|
|
}
|
|
>
|
|
<option value="">Por identificar</option>
|
|
<option value="ordinary">
|
|
Ordinário — fora de supervisão
|
|
financeira/seguradora
|
|
</option>
|
|
<option value="regulated">
|
|
Provisão sujeita a supervisão — correspondência
|
|
própria
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<label className="block">
|
|
Créditos da actividade normal existentes no fim do
|
|
exercício
|
|
<input
|
|
value={i.creditBase}
|
|
onChange={(e) =>
|
|
patch({ creditBase: e.target.value })
|
|
}
|
|
/>
|
|
</label>
|
|
{reusableCreditBalance(source) && (
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
patch({
|
|
creditBase: fractionPresentation(
|
|
reusableCreditBalance(source)!,
|
|
).decimal,
|
|
})
|
|
}
|
|
>
|
|
Reutilizar saldo financeiro de créditos:{' '}
|
|
{
|
|
fractionPresentation(
|
|
reusableCreditBalance(source)!,
|
|
).decimal
|
|
}{' '}
|
|
MZN — conferir âmbito
|
|
</button>
|
|
)}
|
|
<label className="block">
|
|
Provisão fiscal anterior que subsiste no termo do
|
|
exercício
|
|
<input
|
|
value={i.creditPrevious}
|
|
onChange={(e) =>
|
|
patch({ creditPrevious: e.target.value })
|
|
}
|
|
/>
|
|
</label>
|
|
<label className="block">
|
|
Suporte do saldo de créditos e provisão anterior
|
|
<input
|
|
value={i.creditReference}
|
|
onChange={(e) =>
|
|
patch({ creditReference: e.target.value })
|
|
}
|
|
/>
|
|
</label>
|
|
<p>
|
|
1,5% anual e6% acumulado; saldo anterior em falta não
|
|
significa zero. Não aplica regras bancárias/seguradoras
|
|
nem percentagens de antiguidade presumidas.
|
|
</p>
|
|
</fieldset>
|
|
)}
|
|
{i.rows.map((r, n) => {
|
|
const income = [
|
|
'provision_reversal',
|
|
'taxed_reversal',
|
|
'prior_income',
|
|
'work',
|
|
].includes(r.rule),
|
|
omitted =
|
|
r.rule === 'provision_reversal' &&
|
|
r.facts.inResult === 'no';
|
|
return (
|
|
<fieldset key={r.id} className="rounded border p-3">
|
|
<legend>
|
|
Parcela {n + 1} — {r.document || 'Por identificar'}
|
|
</legend>
|
|
<label>
|
|
Categoria
|
|
<select
|
|
aria-label={'Categoria custo ' + (n + 1)}
|
|
value={r.rule}
|
|
onChange={(e) =>
|
|
row(r.id, {
|
|
rule: e.target.value as CostRow['rule'],
|
|
})
|
|
}
|
|
>
|
|
<option value="">Por identificar</option>
|
|
{Object.entries(costRules).map(([k, v]) => (
|
|
<option key={k} value={k}>
|
|
{v[0]} — {v[2]}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
{(
|
|
['document', 'line', 'amount', 'reference'] as const
|
|
).map((k) => (
|
|
<label key={k} className="block">
|
|
{
|
|
{
|
|
document:
|
|
'Documento ou mapa discriminado',
|
|
line: 'Linha distinta',
|
|
amount:
|
|
r.rule === 'work'
|
|
? 'Proveitos da obra já contabilizados no exercício — zero se conhecido'
|
|
: 'Parcela contabilística MZN',
|
|
reference:
|
|
'Suporte da classificação, condições e período',
|
|
}[k]
|
|
}
|
|
<input
|
|
aria-label={'Custo ' + (n + 1) + ' ' + k}
|
|
value={r[k]}
|
|
onChange={(e) =>
|
|
row(r.id, { [k]: e.target.value })
|
|
}
|
|
/>
|
|
</label>
|
|
))}
|
|
{!omitted && (
|
|
<>
|
|
<label>
|
|
Origem contabilística
|
|
<select
|
|
aria-label={'Origem custo ' + (n + 1)}
|
|
value={r.sourceCode}
|
|
onChange={(e) =>
|
|
row(r.id, {
|
|
sourceCode: e.target.value,
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Por corresponder
|
|
</option>
|
|
{source?.accountingLines
|
|
.filter(
|
|
(l) =>
|
|
l.accountingEffect ===
|
|
(income
|
|
? 'income'
|
|
: 'expense'),
|
|
)
|
|
.map((l) => (
|
|
<option
|
|
key={l.code}
|
|
value={l.code}
|
|
>
|
|
{l.origin}: {l.total} MZN
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
<button type="button" onClick={() => reuse(r)}>
|
|
Reutilizar parcela livre da origem {n + 1}
|
|
</button>
|
|
</>
|
|
)}
|
|
{(fields[r.rule] ?? [])
|
|
.filter(
|
|
([k]) =>
|
|
!(
|
|
k === 'publicOrRental' &&
|
|
r.facts.passenger !== 'yes'
|
|
) &&
|
|
!(
|
|
k === 'publicReception' &&
|
|
r.facts.method !== 'closing'
|
|
) &&
|
|
!(
|
|
r.rule === 'stock' &&
|
|
r.facts.lossObserved === 'no' &&
|
|
['observedLoss', 'previous'].includes(k)
|
|
) &&
|
|
!(
|
|
r.rule === 'litigation' &&
|
|
r.facts.caseOngoing === 'no' &&
|
|
[
|
|
'obligation',
|
|
'previous',
|
|
'chargeDeductible',
|
|
].includes(k)
|
|
),
|
|
)
|
|
.map(([k, label, type]) => (
|
|
<label key={k} className="block">
|
|
{label}
|
|
{type === 'choice' ? (
|
|
<select
|
|
aria-label={
|
|
'Custo ' + (n + 1) + ' ' + k
|
|
}
|
|
value={r.facts[k] ?? ''}
|
|
onChange={(e) =>
|
|
fact(r, k, e.target.value)
|
|
}
|
|
>
|
|
<option value="">
|
|
Informação em falta
|
|
</option>
|
|
<option value="yes">
|
|
Sim — documentado
|
|
</option>
|
|
<option value="no">
|
|
Não — documentado
|
|
</option>
|
|
</select>
|
|
) : type === 'method' ? (
|
|
<select
|
|
aria-label={
|
|
'Custo ' + (n + 1) + ' ' + k
|
|
}
|
|
value={r.facts[k] ?? ''}
|
|
onChange={(e) =>
|
|
fact(r, k, e.target.value)
|
|
}
|
|
>
|
|
<option value="">
|
|
Por identificar
|
|
</option>
|
|
<option value="percentage">
|
|
Percentagem de acabamento
|
|
</option>
|
|
<option value="closing">
|
|
Encerramento da obra
|
|
</option>
|
|
</select>
|
|
) : type === 'source' ? (
|
|
<select
|
|
aria-label={
|
|
'Custo ' + (n + 1) + ' ' + k
|
|
}
|
|
value={r.facts[k] ?? ''}
|
|
onChange={(e) =>
|
|
fact(r, k, e.target.value)
|
|
}
|
|
>
|
|
<option value="">
|
|
Por corresponder/zero fora de
|
|
linha
|
|
</option>
|
|
{source?.accountingLines
|
|
.filter(
|
|
(l) =>
|
|
l.accountingEffect ===
|
|
'expense',
|
|
)
|
|
.map((l) => (
|
|
<option
|
|
key={l.code}
|
|
value={l.code}
|
|
>
|
|
{l.origin}: {l.total}{' '}
|
|
MZN
|
|
</option>
|
|
))}
|
|
</select>
|
|
) : (
|
|
<input
|
|
aria-label={
|
|
'Custo ' + (n + 1) + ' ' + k
|
|
}
|
|
value={r.facts[k] ?? ''}
|
|
onChange={(e) =>
|
|
fact(r, k, e.target.value)
|
|
}
|
|
/>
|
|
)}
|
|
</label>
|
|
))}
|
|
<label>
|
|
Condições ordinárias e inclusão na origem
|
|
demonstradas
|
|
<select
|
|
aria-label={'Condições custo ' + (n + 1)}
|
|
value={r.conditions}
|
|
onChange={(e) =>
|
|
row(r.id, {
|
|
conditions: e.target.value as
|
|
| ''
|
|
| 'yes',
|
|
})
|
|
}
|
|
>
|
|
<option value="">Informação em falta</option>
|
|
<option value="yes">
|
|
Sim, nos documentos identificados
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
patch({
|
|
rows: i.rows.filter((q) => q.id !== r.id),
|
|
})
|
|
}
|
|
>
|
|
Retirar parcela {n + 1} das entradas actuais
|
|
</button>
|
|
</fieldset>
|
|
);
|
|
})}
|
|
{book.profit && (
|
|
<>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
try {
|
|
summaries();
|
|
setReview(true);
|
|
setError('');
|
|
} catch (e) {
|
|
setError(String(e));
|
|
}
|
|
}}
|
|
>
|
|
Conferir correspondência dos temas com os mapas
|
|
</button>
|
|
{review && (
|
|
<div>
|
|
<p>
|
|
Reveja as somas brutas antes de aplicar; não
|
|
alteram os factos, o resultado financeiro ou os
|
|
históricos.
|
|
</p>
|
|
{summaries().map((t) => (
|
|
<p key={t.key}>
|
|
{
|
|
{
|
|
expenseCorrections:
|
|
'Correcções aos custos',
|
|
incomeCorrections:
|
|
'Correcções aos proveitos',
|
|
positivePatrimonial:
|
|
'Variações positivas',
|
|
negativePatrimonial:
|
|
'Variações negativas',
|
|
timing: 'Periodização',
|
|
special: 'Casos especiais',
|
|
}[t.key]
|
|
}
|
|
: actual{' '}
|
|
{book.profit!.topics[t.key].amount ||
|
|
book.profit!.topics[t.key].state ||
|
|
'Em falta'}{' '}
|
|
→ mapas {t.amount} MZN
|
|
</p>
|
|
))}
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
const topics = structuredClone(
|
|
book.profit!.topics,
|
|
);
|
|
for (const t of summaries())
|
|
topics[t.key] = {
|
|
state: 'known',
|
|
amount: t.amount,
|
|
reference:
|
|
'Correspondência aritmética aos documentos/linhas dos mapas de ajustamentos e custos do exercício.',
|
|
};
|
|
save({
|
|
...book,
|
|
profit: { ...book.profit!, topics },
|
|
});
|
|
setReview(false);
|
|
}}
|
|
>
|
|
Aplicar correspondências conferidas dos mapas
|
|
</button>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
<p>
|
|
Ao calcular e guardar, os novos resultados entram numa versão
|
|
própria; o resultado anterior permanece no histórico. Informação
|
|
em falta impede apenas os totais que dependem dessa parcela.
|
|
</p>
|
|
</fieldset>
|
|
);
|
|
}
|