Assets
The Assets module keeps the fixed asset register, posts straight-line depreciation every month and records asset sales with their gain or loss. It replaces the legacy assets entry, depreciation schedule and asset sale (BOOK-24 to BOOK-26).
| Projects | CrestApps.Almohaseb.Assets (module), CrestApps.Almohaseb.Assets.Core (part, schedule, posting sources, rules, sync service) |
| Feature | CrestApps.Almohaseb.Assets (Fixed assets), enabled by both business recipes; depends on Accounting, Jobs and Customers & vendors |
FixedAsset
AssetsMigrations.CreateAsync (version 1) defines the AlmohasebFixedAsset content type (creatable, listable, securable) with AlmohasebFixedAssetPart, so a business can add its own fields. AlmohasebFixedAssetPart holds Description, VendorId, PurchaseDate, Cost, SalvageValue, DepreciationYears (0 = not depreciated), PaymentMethodId, AssetAccountId (default: the system FixedAssets account, 1700, added to the ledger by its migration version 8), InvoiceNumber, SoldDate, SoldAmount and SoldPaymentMethodId; IsSold is computed. FixedAssetPartIndex (ContentItemId, PurchaseDate, DepreciationYears, IsSold) serves the depreciation job.
Depreciation schedule
DepreciationSchedule.Build(purchase, cost, salvage, years, sold) is the legacy formula as a pure rule: annual = floor2((cost − salvage) / years), monthly = floor2(annual / 12); the first entry on the purchase date is floor2((30 − day + 1) / 30 × monthly); 12 × years − 1 monthly entries on month ends; a final remainder so the total is cost − salvage. A sale keeps the months before it, re-dates the sale month on the sale date with floor2(min(day + 1, 30) / 30 × monthly) (never more than that month had), and drops the rest. GetBookValue is cost less the entries through a date.
Posting
Three posting sources, each with its rule:
| Source (source type) | Source id | Entry |
|---|---|---|
AssetPurchase (Assets.Purchase) | the asset id | Dr the asset account; Cr the payment method's account (Accounts Payable on account) |
AssetDepreciation (Assets.Depreciation) | {asset id}:{yyyyMM} | Dr Depreciation Expenses; Cr Accumulated Depreciation |
AssetSale (Assets.Sale) | the asset id | Dr the money received, Dr Accumulated Depreciation (all taken through the sale), Cr the asset account (cost); the difference Cr Profit From Asset Sale or Dr Loss From Asset Sale, found by system key (not by title, as the legacy app did) |
IFixedAssetPostingService.SyncAsync(asset, part, today) posts the purchase, every month due through today and the sale; it unposts the months posted before that are no longer due (a shorter schedule, a sale), found with IJournalEntryStore.ListCurrentSourceIdsAsync. Each revision is the values it posts (date, amount, cost, accounts), so a save that changes nothing posted leaves every entry as it is — fixing the legacy save that regenerated the schedule of every asset, sold ones included (BOOK-25). RemoveAsync unposts everything.
FixedAssetPartDisplayDriver.UpdateAsync validates the asset (description, purchase date, cost with cents, salvage between 0 and the cost, 0 to 50 years, a sale on or after the purchase) and runs SyncAsync in the same unit of work; a posting error (a closed period) becomes a model error, so the save is refused. FixedAssetPartHandler.RemovingAsync unposts a deleted asset and cancels the deletion when that fails.
Monthly depreciation
DepreciationBackgroundTask runs every day at 04:30 (30 4 * * *) and, when an unsold depreciable asset exists, enqueues the CrestApps.Almohaseb.Assets.Depreciation job (idempotency key due). DepreciationJobHandler syncs 50 assets per batch, with the last index row as its checkpoint; assets whose posting fails count as warnings.
Admin
Books > Fixed assets (id almohasebFixedAssets) is the content list of the type, shown with ListContent_FixedAsset. The summary shows the purchase, the cost and the book value or Sold; the editor shows the schedule.
Permissions
The securable type has the Orchard Core content permissions; the module declares none of its own.