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

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>
);
}