feat(assets): atomic asset disposal workflow (avyttring, utrangering, verksamhetsoverlatelse) (#1391)

* feat(assets): atomic asset disposal workflow (avyttring, utrangering, verksamhetsoverlatelse)

Disposal books depreciation to the disposal date, clears cost and
accumulated depreciation, books gain (3973) or loss (7973), applies
output VAT on third-party sales, honors the ML 5 kap. 38 §
verksamhetsoverlatelse exemption, and recalculates ML 15 kap. jamkning
server-side from tax years and original input VAT. The voucher, the
disposal-date depreciation schedule and the immutable register state
commit in one dedicated commit_asset_disposal RPC transaction that
delegates voucher numbering to commit_journal_entry.

Fixes #325

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(assets): harden disposal per review and pg-real findings

- commit_asset_disposal now uses the NULL-safe caller_is_company_member()
  guard (tenant-guard ratchet) and passes the allowed 'user_accept'
  commit_method instead of the unlisted 'asset_disposal' value
- disposal metadata invariants validated in the RPC (non-negative
  proceeds/VAT, VAT requires a treatment, VAT <= gross, scrap carries no
  proceeds) since the RPC is independently callable
- new FK and CHECK constraints added NOT VALID + VALIDATE CONSTRAINT so
  the migration never blocks writes on the hot journal_entries table
- disposeAsset paginates fiscal periods and depreciation schedules with
  fetchAllRows; jamkning_remaining_years keeps a valid 0 (?? not ||)
- engine imports shared AssetDisposalType/AssetJamkningDirection/
  VatTreatment unions; post-commit reload retries once and logs before
  surfacing, so a transient read cannot masquerade as a failed disposal
- dispose page parses Swedish-formatted amounts (125 000,50) and blocks
  submission on unparseable proceeds
- assets pg tests write disposal attributes in the disposal transition
  itself and gain a regression test that the register is frozen after

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-08-04 09:59:52 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 1c9d378df8
commit cb3ef45f14
22 changed files with 19644 additions and 1805 deletions
+14 -3
View File
@@ -3334,6 +3334,9 @@ export type DepreciationMethod =
| 'declining_balance_20'
| 'restvardesavskrivning_25'
export type AssetDisposalType = 'sale' | 'scrap' | 'business_transfer'
export type AssetJamkningDirection = 'increase' | 'decrease' | 'none' | 'transferred'
/**
* K3 component (BFNAR 2012:1 ch 17.4: komponentavskrivning). When a
* substantial asset (typically real estate) has significant components with
@@ -3378,6 +3381,10 @@ export interface Asset {
restvarde_target: number | null
disposed_at: string | null
disposed_proceeds: number | null
/** How the asset left the register. Null for legacy disposal records. */
disposal_type?: AssetDisposalType | null
/** Posted voucher that atomically completed the disposal. */
disposal_journal_entry_id?: string | null
/** Output VAT on disposal proceeds (ML 3 kap 3 § / 7 kap 3 §). Defaults to
* 0: only nonzero when the sale was momspliktig. The VAT account
* (2611/2621/2631) is derived from disposed_vat_treatment. */
@@ -3386,9 +3393,7 @@ export interface Asset {
* without VAT data. Constrained by DB CHECK to the same enum as
* VatTreatment. */
disposed_vat_treatment: VatTreatment | null
/** Jämkning amount per ML 8a kap 7 §: input VAT paid back on disposal
* inside the correction period. Defaults to 0; positive number = debt
* to the state booked on 2641 credit. */
/** Absolute input VAT adjustment under ML (2023:200), chapter 15. */
jamkning_amount: number
/** Remaining months in the korrigeringstid at disposal date. Audit
* metadata only: the booking sits on the journal entry. */
@@ -3399,6 +3404,12 @@ export interface Asset {
/** Original input VAT that was deducted at acquisition. Audit metadata
* the user supplies (or the system derives from the supplier invoice). */
jamkning_original_input_vat: number | null
/** Current-law adjustment metadata. Old month fields remain for legacy rows. */
jamkning_direction?: AssetJamkningDirection | null
jamkning_remaining_years?: number | null
jamkning_total_years?: number | null
jamkning_original_deduction_percent?: number | null
jamkning_new_deduction_percent?: number | null
/** K3 component depreciation (BFNAR 2012:1 ch.17.4). When non-null, the
* depreciation engine sums per-component linear depreciation instead of
* applying `depreciation_method` to the asset as a whole. Null for K2