634 lines
30 KiB
TypeScript
634 lines
30 KiB
TypeScript
import { useState } from 'react';
|
|
import {
|
|
depreciationCatalog,
|
|
newNormalDepreciation,
|
|
newDepreciationAsset,
|
|
reuseNormalAssets,
|
|
type DepreciationAsset,
|
|
} from '@/lib/irpc-normal-depreciation';
|
|
import { normalFinancialSource, type NormalBook } from '@/lib/irpc-normal';
|
|
import { fiscalMoney } from '@/lib/irpc-normal-schedules';
|
|
import {
|
|
addFractions,
|
|
subtractFractions,
|
|
fractionPresentation,
|
|
} from '@/lib/fiscal-rational';
|
|
export function SimulationIrpcDepreciation({
|
|
draft,
|
|
book,
|
|
save,
|
|
}: {
|
|
draft: Record<string, unknown>;
|
|
book: NormalBook;
|
|
save: (b: NormalBook) => void;
|
|
}) {
|
|
const [error, setError] = useState(''),
|
|
input = book.depreciation,
|
|
source = normalFinancialSource(draft);
|
|
if (!input)
|
|
return (
|
|
<div>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
save({ ...book, depreciation: newNormalDepreciation() })
|
|
}
|
|
>
|
|
Iniciar amortizações — tabelas, métodos e quotas anteriores
|
|
</button>
|
|
{!!book.schedules?.assets.length && (
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
save({
|
|
...book,
|
|
depreciation: {
|
|
...reuseNormalAssets(
|
|
book.schedules!.assets,
|
|
),
|
|
completenessReference:
|
|
book.schedules!.assetsReference,
|
|
},
|
|
})
|
|
}
|
|
>
|
|
Reutilizar activos e valores do mapa inicial
|
|
</button>
|
|
)}
|
|
</div>
|
|
);
|
|
const patch = (id: string, p: Partial<DepreciationAsset>) =>
|
|
save({
|
|
...book,
|
|
depreciation: {
|
|
...input,
|
|
assets: input.assets.map((a) =>
|
|
a.id === id ? { ...a, ...p } : a,
|
|
),
|
|
},
|
|
});
|
|
const field = (
|
|
a: DepreciationAsset,
|
|
n: number,
|
|
k: keyof DepreciationAsset,
|
|
label: string,
|
|
) => (
|
|
<label key={k} className="block">
|
|
{label}
|
|
<input
|
|
aria-label={'Activo ' + n + ' ' + k}
|
|
value={a[k]}
|
|
onChange={(e) => patch(a.id, { [k]: e.target.value })}
|
|
/>
|
|
</label>
|
|
);
|
|
const choice = (
|
|
a: DepreciationAsset,
|
|
n: number,
|
|
k:
|
|
| 'specificRate'
|
|
| 'consistentMethod'
|
|
| 'newTangible'
|
|
| 'switchToStraight'
|
|
| 'priorFullQuotas'
|
|
| 'vehicleException'
|
|
| 'limitedRights'
|
|
| 'individual',
|
|
label: string,
|
|
) => (
|
|
<label key={k} className="block">
|
|
{label}
|
|
<select
|
|
aria-label={'Activo ' + n + ' ' + k}
|
|
value={a[k]}
|
|
onChange={(e) => patch(a.id, { [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>
|
|
</label>
|
|
);
|
|
return (
|
|
<fieldset
|
|
aria-label="Amortizações ampliadas L6"
|
|
className="space-y-3 rounded border p-3"
|
|
>
|
|
<legend>
|
|
Amortizações — tabelas, métodos, duodécimos e quotas anteriores
|
|
</legend>
|
|
<p>
|
|
Decreto72/2013 eCIRPC26/27. Reutiliza a depreciação financeira e
|
|
os activos do mapa inicial; os originais/históricos ficam
|
|
conservados. O novo mapa substitui exclusivamente o cálculo de
|
|
amortizações, sem somar novamente o mapa inicial. Custo
|
|
histórico e condições ordinárias documentados; reavaliações,
|
|
turnos intensivos, concessões e outros casos especiais
|
|
permanecem separados.
|
|
</p>
|
|
<p>
|
|
Depreciação contabilística a repartir:{' '}
|
|
{source?.accountingLines.find((l) => l.code === 'depreciation')
|
|
?.total ?? 'Em falta'}{' '}
|
|
{source?.currency}. O catálogo apresenta apenas os códigos
|
|
correspondidos; outra categoria permanece identificada, sem taxa
|
|
livre.
|
|
</p>
|
|
{error && <p role="alert">{error}</p>}
|
|
<label className="block">
|
|
Suporte da reconciliação integral dos activos e quotas
|
|
anteriores
|
|
<input
|
|
value={input.completenessReference}
|
|
onChange={(e) =>
|
|
save({
|
|
...book,
|
|
depreciation: {
|
|
...input,
|
|
completenessReference: e.target.value,
|
|
},
|
|
})
|
|
}
|
|
/>
|
|
</label>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
save({
|
|
...book,
|
|
depreciation: {
|
|
...input,
|
|
assets: [
|
|
...input.assets,
|
|
newDepreciationAsset(crypto.randomUUID()),
|
|
],
|
|
},
|
|
})
|
|
}
|
|
>
|
|
Acrescentar activo ao mapa fiscal
|
|
</button>
|
|
{input.assets.map((a, index) => {
|
|
const n = index + 1,
|
|
entry = Object.hasOwn(depreciationCatalog, a.category)
|
|
? depreciationCatalog[
|
|
a.category as keyof typeof depreciationCatalog
|
|
]
|
|
: null;
|
|
return (
|
|
<fieldset key={a.id} className="rounded border p-3">
|
|
<legend>
|
|
Activo fiscal {n} —{' '}
|
|
{a.description || 'Por identificar'}
|
|
</legend>
|
|
{field(
|
|
a,
|
|
n,
|
|
'assetKey',
|
|
'Identificação única do activo',
|
|
)}
|
|
{field(a, n, 'description', 'Descrição')}
|
|
{field(
|
|
a,
|
|
n,
|
|
'document',
|
|
'Documento, pressuposto ou mapa de origem',
|
|
)}
|
|
{field(
|
|
a,
|
|
n,
|
|
'line',
|
|
'Linha distinta do documento/mapa',
|
|
)}
|
|
<label>
|
|
Categoria/tabela
|
|
<select
|
|
aria-label={'Activo ' + n + ' category'}
|
|
value={a.category}
|
|
onChange={(e) => {
|
|
const category = e.target
|
|
.value as DepreciationAsset['category'],
|
|
c = Object.hasOwn(
|
|
depreciationCatalog,
|
|
category,
|
|
)
|
|
? depreciationCatalog[
|
|
category as keyof typeof depreciationCatalog
|
|
]
|
|
: null;
|
|
patch(a.id, {
|
|
category,
|
|
rate: c?.rate ?? '',
|
|
});
|
|
}}
|
|
>
|
|
<option value="">Por identificar</option>
|
|
{Object.entries(depreciationCatalog).map(
|
|
([k, c]) => (
|
|
<option key={k} value={k}>
|
|
Tabela{c.table}/{c.code} — {c.label}{' '}
|
|
— {c.rate}%
|
|
</option>
|
|
),
|
|
)}
|
|
<option value="nondepreciable">
|
|
Elemento comprovadamente não depreciável —
|
|
exclusão
|
|
</option>
|
|
<option value="other">
|
|
Outra categoria — correspondência pendente
|
|
</option>
|
|
</select>
|
|
</label>
|
|
{entry && (
|
|
<p>
|
|
Taxa máxima: {entry.rate}%; taxas constantes
|
|
cobertas: {Number(entry.rate) / 2}% a
|
|
{entry.rate}%; vida útil mínima:{' '}
|
|
{100 / Number(entry.rate)}anos, máxima:{' '}
|
|
{200 / Number(entry.rate)}anos. Coeficiente
|
|
degressivo máximo:{' '}
|
|
{100 / Number(entry.rate) < 5
|
|
? '1,5'
|
|
: 100 / Number(entry.rate) <= 6
|
|
? '2'
|
|
: '2,5'}
|
|
, apenas nos activos elegíveis. Fonte:tabela
|
|
{entry.table}/{entry.code},PDF{entry.page}.
|
|
</p>
|
|
)}
|
|
{field(a, n, 'cost', 'Custo histórico documentado MZN')}
|
|
{field(
|
|
a,
|
|
n,
|
|
'accounting',
|
|
'Depreciação contabilística do exercício MZN',
|
|
)}
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
try {
|
|
const total = fiscalMoney(
|
|
source?.accountingLines.find(
|
|
(l) =>
|
|
l.code === 'depreciation',
|
|
)?.total ?? '',
|
|
),
|
|
used = addFractions(
|
|
input.assets
|
|
.filter((q) => q.id !== a.id)
|
|
.map((q) =>
|
|
fiscalMoney(q.accounting),
|
|
),
|
|
),
|
|
left = subtractFractions(total, used);
|
|
if (BigInt(left.numerator) < 0n)
|
|
throw Error(
|
|
'Valores repartidos excedem a origem.',
|
|
);
|
|
patch(a.id, {
|
|
accounting:
|
|
fractionPresentation(left).decimal,
|
|
});
|
|
setError('');
|
|
} catch (e) {
|
|
setError(String(e));
|
|
}
|
|
}}
|
|
>
|
|
Reutilizar depreciação financeira ainda não
|
|
repartida — activo {n}
|
|
</button>
|
|
{a.category !== 'nondepreciable' && (
|
|
<>
|
|
<label>
|
|
Tipo de aquisição/base
|
|
<select
|
|
aria-label={
|
|
'Activo ' + n + ' valuation'
|
|
}
|
|
value={a.valuation}
|
|
onChange={(e) =>
|
|
patch(a.id, {
|
|
valuation: e.target
|
|
.value as DepreciationAsset['valuation'],
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Por identificar
|
|
</option>
|
|
<option value="historical_new">
|
|
Bem adquirido novo/construído ou
|
|
produzido — custo histórico
|
|
</option>
|
|
<option value="used">
|
|
Bem adquirido usado — regra
|
|
própria7(2)
|
|
</option>
|
|
<option value="revalued">
|
|
Bem reavaliado — regra própria15
|
|
</option>
|
|
<option value="other">
|
|
Outra base — por corresponder
|
|
</option>
|
|
</select>
|
|
</label>
|
|
{entry &&
|
|
choice(
|
|
a,
|
|
n,
|
|
'specificRate',
|
|
'Existe taxa específica da tabelaI para o activo/actividade',
|
|
)}
|
|
{field(a, n, 'residual', 'Valor residual MZN')}
|
|
{entry?.kind === 'building' &&
|
|
field(
|
|
a,
|
|
n,
|
|
'land',
|
|
'Terreno/parte não depreciável incluídos no custo MZN',
|
|
)}
|
|
<label>
|
|
Método
|
|
<select
|
|
aria-label={'Activo ' + n + ' method'}
|
|
value={a.method}
|
|
onChange={(e) =>
|
|
patch(a.id, {
|
|
method: e.target
|
|
.value as DepreciationAsset['method'],
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Por identificar
|
|
</option>
|
|
<option value="constant">
|
|
Quotas constantes — artigos5/7
|
|
</option>
|
|
<option value="declining">
|
|
Quotas degressivas — artigos6/8
|
|
</option>
|
|
<option value="low_value">
|
|
Valor reduzido, opção integral —
|
|
artigo17
|
|
</option>
|
|
</select>
|
|
</label>
|
|
{a.method === 'constant' &&
|
|
field(
|
|
a,
|
|
n,
|
|
'rate',
|
|
'Taxa constante escolhida %',
|
|
)}
|
|
{a.method === 'declining' && (
|
|
<>
|
|
{choice(
|
|
a,
|
|
n,
|
|
'newTangible',
|
|
'Activo tangível novo adquirido a terceiros ou construído/produzido',
|
|
)}
|
|
{field(
|
|
a,
|
|
n,
|
|
'factor',
|
|
'Coeficiente degressivo escolhido (máximo1,5/2/2,5)',
|
|
)}
|
|
{choice(
|
|
a,
|
|
n,
|
|
'switchToStraight',
|
|
'Aplicar saldo/vida útil restante8(2) quando mais favorável',
|
|
)}
|
|
{a.switchToStraight === 'yes' &&
|
|
choice(
|
|
a,
|
|
n,
|
|
'priorFullQuotas',
|
|
'Condição de quotas anteriores do artigo8(2) integralmente documentada',
|
|
)}
|
|
</>
|
|
)}
|
|
{a.method === 'low_value' ? (
|
|
<>
|
|
{choice(
|
|
a,
|
|
n,
|
|
'individual',
|
|
'Activo individual, fora de conjunto depreciado como um todo',
|
|
)}
|
|
<p>
|
|
Art17:até20000MZN, sem amortização
|
|
fiscal anterior e entrada emuso
|
|
neste período; a dedução não excede
|
|
o contabilizado e o saldo fiscal.
|
|
</p>
|
|
</>
|
|
) : (
|
|
<label>
|
|
Período da quota
|
|
<select
|
|
aria-label={
|
|
'Activo ' + n + ' timing'
|
|
}
|
|
value={a.timing}
|
|
onChange={(e) =>
|
|
patch(a.id, {
|
|
timing: e.target
|
|
.value as DepreciationAsset['timing'],
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Por identificar
|
|
</option>
|
|
<option value="annual">
|
|
Quota anual
|
|
</option>
|
|
<option value="months">
|
|
Duodécimos em vigor desde o
|
|
início — artigo9
|
|
</option>
|
|
</select>
|
|
</label>
|
|
)}
|
|
{field(
|
|
a,
|
|
n,
|
|
'start',
|
|
'Entrada em funcionamento/utilização AAAA-MM-DD',
|
|
)}
|
|
<label>
|
|
Transmissão, inutilização ou termo da vida
|
|
útil
|
|
<select
|
|
aria-label={
|
|
'Activo ' + n + ' exitState'
|
|
}
|
|
value={a.exitState}
|
|
onChange={(e) =>
|
|
patch(a.id, {
|
|
exitState: e.target
|
|
.value as DepreciationAsset['exitState'],
|
|
})
|
|
}
|
|
>
|
|
<option value="">
|
|
Informação em falta
|
|
</option>
|
|
<option value="absent">
|
|
Ausência declarada de evento
|
|
</option>
|
|
<option value="known">
|
|
Evento/data conhecidos
|
|
</option>
|
|
</select>
|
|
</label>
|
|
{(a.exitState === 'known' || a.exitDate) &&
|
|
field(
|
|
a,
|
|
n,
|
|
'exitDate',
|
|
'Data do evento AAAA-MM-DD',
|
|
)}
|
|
{choice(
|
|
a,
|
|
n,
|
|
'consistentMethod',
|
|
'Método uniforme desde o início, sem mudança ou excepção por corresponder',
|
|
)}
|
|
{entry?.kind === 'vehicle' &&
|
|
choice(
|
|
a,
|
|
n,
|
|
'vehicleException',
|
|
'Afectação a serviço público de transportes/locação na actividade normal',
|
|
)}
|
|
{entry?.kind === 'software' &&
|
|
choice(
|
|
a,
|
|
n,
|
|
'limitedRights',
|
|
'Aquisição onerosa e utilização exclusiva por tempo limitado',
|
|
)}
|
|
{field(
|
|
a,
|
|
n,
|
|
'previousAccounting',
|
|
'Quotas contabilísticas anteriores desde a utilização MZN',
|
|
)}
|
|
{field(
|
|
a,
|
|
n,
|
|
'previousFiscal',
|
|
'Quotas fiscais anteriormente deduzidas MZN',
|
|
)}
|
|
{field(
|
|
a,
|
|
n,
|
|
'priorAvailable',
|
|
'Parcela anterior temporariamente tributada, recuperável MZN',
|
|
)}
|
|
{field(
|
|
a,
|
|
n,
|
|
'previousReference',
|
|
'Suporte das quotas anteriores e da recuperabilidade fiscal',
|
|
)}
|
|
{[
|
|
'previousAccounting',
|
|
'previousFiscal',
|
|
'priorAvailable',
|
|
].every(
|
|
(k) =>
|
|
!a[k as 'previousFiscal'] ||
|
|
/^0(?:\.0+)?$/.test(
|
|
a[k as 'previousFiscal'],
|
|
),
|
|
) && (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
if (!a.reference.trim()) {
|
|
setError(
|
|
'Identifique primeiro o suporte da ausência de quotas anteriores.',
|
|
);
|
|
return;
|
|
}
|
|
patch(a.id, {
|
|
previousAccounting: '0',
|
|
previousFiscal: '0',
|
|
priorAvailable: '0',
|
|
previousReference:
|
|
'Ausência de quotas anteriores expressamente declarada; ' +
|
|
a.reference,
|
|
});
|
|
setError('');
|
|
}}
|
|
>
|
|
Declarar ausência de quotas anteriores —
|
|
activo {n}
|
|
</button>
|
|
)}
|
|
<p>
|
|
Quota anterior recuperável exige
|
|
contabilização e tributação temporária
|
|
demonstradas. Não inclui encargos
|
|
permanentemente excluídos ou quota nunca
|
|
contabilizada. O cálculo apresenta o saldo
|
|
posterior aritmético; não consome saldos
|
|
oficiais.
|
|
</p>
|
|
</>
|
|
)}
|
|
{field(
|
|
a,
|
|
n,
|
|
'reference',
|
|
'Suporte do activo, categoria, condições e período',
|
|
)}
|
|
<label>
|
|
Condições ordinárias e documentos conferidos
|
|
<select
|
|
aria-label={'Activo ' + n + ' conditions'}
|
|
value={a.conditions}
|
|
onChange={(e) =>
|
|
patch(a.id, {
|
|
conditions: e.target.value as
|
|
| ''
|
|
| 'yes',
|
|
})
|
|
}
|
|
>
|
|
<option value="">Informação em falta</option>
|
|
<option value="yes">
|
|
Sim, no recorte documentado
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
save({
|
|
...book,
|
|
depreciation: {
|
|
...input,
|
|
assets: input.assets.filter(
|
|
(q) => q.id !== a.id,
|
|
),
|
|
},
|
|
})
|
|
}
|
|
>
|
|
Retirar activo {n} das entradas actuais
|
|
</button>
|
|
</fieldset>
|
|
);
|
|
})}
|
|
</fieldset>
|
|
);
|
|
}
|