salvage_percentage new column added
This commit is contained in:
parent
b4141b9b5b
commit
36b5170c53
@ -747,7 +747,7 @@ flowchart TB
|
|||||||
V2[plant / dept / warehouse / user refs]
|
V2[plant / dept / warehouse / user refs]
|
||||||
V3[vendor / PO / GRN / grn_item refs]
|
V3[vendor / PO / GRN / grn_item refs]
|
||||||
V4[disposal_date required if DISPOSED/SCRAPPED]
|
V4[disposal_date required if DISPOSED/SCRAPPED]
|
||||||
V5[depreciation_rate required if method=OTHER]
|
V5[depreciation_rate required if method=CUSTOM]
|
||||||
V6[resolve rate from category defaults]
|
V6[resolve rate from category defaults]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -888,7 +888,7 @@ flowchart TD
|
|||||||
subgraph Methods["depreciation_method"]
|
subgraph Methods["depreciation_method"]
|
||||||
SLM[SLM Straight Line]
|
SLM[SLM Straight Line]
|
||||||
WDV[WDV Written Down Value]
|
WDV[WDV Written Down Value]
|
||||||
OTHER[OTHER manual rate required]
|
CUSTOM[CUSTOM manual rate required]
|
||||||
end
|
end
|
||||||
|
|
||||||
PREVIEW --> CALC[calculateDepreciation]
|
PREVIEW --> CALC[calculateDepreciation]
|
||||||
@ -900,7 +900,7 @@ flowchart TD
|
|||||||
|
|
||||||
SLM --> CALC
|
SLM --> CALC
|
||||||
WDV --> CALC
|
WDV --> CALC
|
||||||
OTHER --> CALC
|
CUSTOM --> CALC
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -909,12 +909,13 @@ flowchart TD
|
|||||||
<thead><tr><th>Field</th><th>Meaning</th><th>Role in formula</th></tr></thead>
|
<thead><tr><th>Field</th><th>Meaning</th><th>Role in formula</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td><code>purchase_cost</code></td><td>Asset cost / amount</td><td>Starting book value; base for rate & annual dep</td></tr>
|
<tr><td><code>purchase_cost</code></td><td>Asset cost / amount</td><td>Starting book value; base for rate & annual dep</td></tr>
|
||||||
<tr><td><code>salvage_value</code></td><td>Residual value at end of life</td><td>Floor for book value; used in rate auto-calc</td></tr>
|
<tr><td><code>salvage_value</code></td><td>Residual value at end of life (amount)</td><td>Floor for book value; used in rate auto-calc</td></tr>
|
||||||
|
<tr><td><code>salvage_percentage</code></td><td>Residual as % of purchase_cost (0–100)</td><td>Synced with amount; FE may send either</td></tr>
|
||||||
<tr><td><code>useful_life_years</code></td><td>Expected life in years</td><td>Rate auto-calc + cap on years elapsed</td></tr>
|
<tr><td><code>useful_life_years</code></td><td>Expected life in years</td><td>Rate auto-calc + cap on years elapsed</td></tr>
|
||||||
<tr><td><code>commencement_date</code></td><td>Usage / put-to-use date</td><td><strong>Primary</strong> start date for years elapsed</td></tr>
|
<tr><td><code>commencement_date</code></td><td>Usage / put-to-use date</td><td><strong>Primary</strong> start date for years elapsed</td></tr>
|
||||||
<tr><td><code>purchase_date</code></td><td>Purchase date</td><td>Fallback start date if commencement is null</td></tr>
|
<tr><td><code>purchase_date</code></td><td>Purchase date</td><td>Fallback start date if commencement is null</td></tr>
|
||||||
<tr><td><code>depreciation_method</code></td><td><code>SLM</code> / <code>WDV</code> / <code>OTHER</code></td><td>Which formula path to run</td></tr>
|
<tr><td><code>depreciation_method</code></td><td><code>SLM</code> / <code>WDV</code> / <code>CUSTOM</code></td><td>Which formula path to run</td></tr>
|
||||||
<tr><td><code>depreciation_rate</code></td><td>% per year</td><td>Optional for SLM/WDV (auto); required for OTHER</td></tr>
|
<tr><td><code>depreciation_rate</code></td><td>% per year</td><td>Optional for SLM/WDV (auto); required for CUSTOM</td></tr>
|
||||||
<tr><td><code>as_of_date</code></td><td>Calculate as of (preview only)</td><td>Defaults to today</td></tr>
|
<tr><td><code>as_of_date</code></td><td>Calculate as of (preview only)</td><td>Defaults to today</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -971,7 +972,7 @@ annual_depreciation = book_value × rate / 100 // next year's dep on curr
|
|||||||
<li>Next annual (on WDV) ≈ 79430 × 20.57% ≈ <strong>₹16,340</strong></li>
|
<li>Next annual (on WDV) ≈ 79430 × 20.57% ≈ <strong>₹16,340</strong></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>OTHER method</h3>
|
<h3>CUSTOM method</h3>
|
||||||
<p>Same accumulation style as SLM, but <code>depreciation_rate</code> is <strong>mandatory</strong> (no auto-calc).</p>
|
<p>Same accumulation style as SLM, but <code>depreciation_rate</code> is <strong>mandatory</strong> (no auto-calc).</p>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
@ -979,7 +980,7 @@ annual_depreciation = book_value × rate / 100 // next year's dep on curr
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr><td><strong>SLM</strong></td><td>Yes (if rate omitted)</td><td>Original <code>purchase_cost</code> every year</td><td>In rate formula + book-value floor</td></tr>
|
<tr><td><strong>SLM</strong></td><td>Yes (if rate omitted)</td><td>Original <code>purchase_cost</code> every year</td><td>In rate formula + book-value floor</td></tr>
|
||||||
<tr><td><strong>WDV</strong></td><td>Yes (if rate omitted)</td><td>Reducing book value each year</td><td>In rate formula + book-value floor</td></tr>
|
<tr><td><strong>WDV</strong></td><td>Yes (if rate omitted)</td><td>Reducing book value each year</td><td>In rate formula + book-value floor</td></tr>
|
||||||
<tr><td><strong>OTHER</strong></td><td>No — send <code>depreciation_rate</code></td><td>Original cost × rate (like SLM)</td><td>Book-value floor only</td></tr>
|
<tr><td><strong>CUSTOM</strong></td><td>No — send <code>depreciation_rate</code></td><td>Original cost × rate (like SLM)</td><td>Book-value floor only</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
@ -353,7 +353,7 @@ flowchart TB
|
|||||||
V2[location / dept / user refs]
|
V2[location / dept / user refs]
|
||||||
V3[vendor / PO / GRN / grn_item refs]
|
V3[vendor / PO / GRN / grn_item refs]
|
||||||
V4[disposal_date required if DISPOSED/SCRAPPED]
|
V4[disposal_date required if DISPOSED/SCRAPPED]
|
||||||
V5[depreciation_rate required if method=OTHER]
|
V5[depreciation_rate required if method=CUSTOM]
|
||||||
V6[resolve rate from category defaults]
|
V6[resolve rate from category defaults]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -497,7 +497,7 @@ flowchart TD
|
|||||||
subgraph Methods["depreciation_method"]
|
subgraph Methods["depreciation_method"]
|
||||||
SLM[SLM Straight Line]
|
SLM[SLM Straight Line]
|
||||||
WDV[WDV Written Down Value]
|
WDV[WDV Written Down Value]
|
||||||
OTHER[OTHER manual rate required]
|
CUSTOM[CUSTOM manual rate required]
|
||||||
end
|
end
|
||||||
|
|
||||||
PREVIEW --> CALC[calculateDepreciation]
|
PREVIEW --> CALC[calculateDepreciation]
|
||||||
@ -509,7 +509,7 @@ flowchart TD
|
|||||||
|
|
||||||
SLM --> CALC
|
SLM --> CALC
|
||||||
WDV --> CALC
|
WDV --> CALC
|
||||||
OTHER --> CALC
|
CUSTOM --> CALC
|
||||||
```
|
```
|
||||||
|
|
||||||
### Input fields used in calculation
|
### Input fields used in calculation
|
||||||
@ -517,12 +517,13 @@ flowchart TD
|
|||||||
| Field | Meaning | Role in formula |
|
| Field | Meaning | Role in formula |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `purchase_cost` | Asset cost / amount | Starting book value; base for rate & annual dep |
|
| `purchase_cost` | Asset cost / amount | Starting book value; base for rate & annual dep |
|
||||||
| `salvage_value` | Residual value at end of life | Floor for book value; used in rate auto-calc |
|
| `salvage_value` | Residual value at end of life (amount) | Floor for book value; used in rate auto-calc |
|
||||||
|
| `salvage_percentage` | Residual as % of purchase_cost (0–100) | Synced with amount; FE may send either |
|
||||||
| `useful_life_years` | Expected life in years | Rate auto-calc + cap on years elapsed |
|
| `useful_life_years` | Expected life in years | Rate auto-calc + cap on years elapsed |
|
||||||
| `commencement_date` | Usage / put-to-use date | **Primary** start date for years elapsed |
|
| `commencement_date` | Usage / put-to-use date | **Primary** start date for years elapsed |
|
||||||
| `purchase_date` | Purchase date | Fallback start date if commencement is null |
|
| `purchase_date` | Purchase date | Fallback start date if commencement is null |
|
||||||
| `depreciation_method` | `SLM` / `WDV` / `OTHER` | Which formula path to run |
|
| `depreciation_method` | `SLM` / `WDV` / `CUSTOM` | Which formula path to run |
|
||||||
| `depreciation_rate` | % per year | Optional for SLM/WDV (auto); required for OTHER |
|
| `depreciation_rate` | % per year | Optional for SLM/WDV (auto); required for CUSTOM |
|
||||||
| `as_of_date` | Calculate as of (preview only) | Defaults to today |
|
| `as_of_date` | Calculate as of (preview only) | Defaults to today |
|
||||||
|
|
||||||
### Date → years elapsed
|
### Date → years elapsed
|
||||||
@ -594,7 +595,7 @@ annual_depreciation = book_value × rate / 100 // next year's dep on curr
|
|||||||
- Accumulated ≈ **₹20,570**
|
- Accumulated ≈ **₹20,570**
|
||||||
- Next annual (on WDV) ≈ 79430 × 20.57% ≈ **₹16,340**
|
- Next annual (on WDV) ≈ 79430 × 20.57% ≈ **₹16,340**
|
||||||
|
|
||||||
### OTHER method
|
### CUSTOM method
|
||||||
|
|
||||||
Same accumulation style as SLM, but `depreciation_rate` is **mandatory** (no auto-calc).
|
Same accumulation style as SLM, but `depreciation_rate` is **mandatory** (no auto-calc).
|
||||||
|
|
||||||
@ -602,7 +603,7 @@ Same accumulation style as SLM, but `depreciation_rate` is **mandatory** (no aut
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **SLM** | Yes (if rate omitted) | Original `purchase_cost` every year | In rate formula + book-value floor |
|
| **SLM** | Yes (if rate omitted) | Original `purchase_cost` every year | In rate formula + book-value floor |
|
||||||
| **WDV** | Yes (if rate omitted) | Reducing book value each year | In rate formula + book-value floor |
|
| **WDV** | Yes (if rate omitted) | Reducing book value each year | In rate formula + book-value floor |
|
||||||
| **OTHER** | No — send `depreciation_rate` | Original cost × rate (like SLM) | Book-value floor only |
|
| **CUSTOM** | No — send `depreciation_rate` | Original cost × rate (like SLM) | Book-value floor only |
|
||||||
|
|
||||||
**FE usage:**
|
**FE usage:**
|
||||||
|
|
||||||
|
|||||||
@ -189,6 +189,7 @@ model assets {
|
|||||||
depreciation_method String? @db.VarChar(20)
|
depreciation_method String? @db.VarChar(20)
|
||||||
depreciation_rate Decimal? @db.Decimal(8, 4)
|
depreciation_rate Decimal? @db.Decimal(8, 4)
|
||||||
salvage_value Decimal? @default(0) @db.Decimal(15, 4)
|
salvage_value Decimal? @default(0) @db.Decimal(15, 4)
|
||||||
|
salvage_percentage Decimal? @default(0) @db.Decimal(8, 4)
|
||||||
warranty_expiry_date DateTime? @db.Date
|
warranty_expiry_date DateTime? @db.Date
|
||||||
condition String @default("NEW") @db.VarChar(20)
|
condition String @default("NEW") @db.VarChar(20)
|
||||||
status String @default("IN_USE") @db.VarChar(30)
|
status String @default("IN_USE") @db.VarChar(30)
|
||||||
|
|||||||
27
scripts/patch-assets-salvage-percentage.sql
Normal file
27
scripts/patch-assets-salvage-percentage.sql
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
-- Rename depreciation method OTHER → CUSTOM; add salvage_percentage
|
||||||
|
-- Run on each environment after deploy.
|
||||||
|
|
||||||
|
ALTER TABLE assets
|
||||||
|
ADD COLUMN IF NOT EXISTS salvage_percentage DECIMAL(8, 4) DEFAULT 0;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN assets.salvage_percentage IS 'Salvage as % of purchase_cost (0–100). Synced with salvage_value.';
|
||||||
|
COMMENT ON COLUMN assets.depreciation_method IS 'SLM, WDV, CUSTOM';
|
||||||
|
|
||||||
|
UPDATE assets
|
||||||
|
SET depreciation_method = 'CUSTOM'
|
||||||
|
WHERE depreciation_method = 'OTHER'
|
||||||
|
AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
UPDATE assets
|
||||||
|
SET salvage_percentage = ROUND(
|
||||||
|
(COALESCE(salvage_value, 0) / purchase_cost) * 100,
|
||||||
|
4
|
||||||
|
)
|
||||||
|
WHERE purchase_cost > 0
|
||||||
|
AND (salvage_percentage IS NULL OR salvage_percentage = 0)
|
||||||
|
AND COALESCE(salvage_value, 0) > 0
|
||||||
|
AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
UPDATE item_categories
|
||||||
|
SET default_depreciation_method = 'CUSTOM'
|
||||||
|
WHERE default_depreciation_method = 'OTHER';
|
||||||
@ -38,9 +38,10 @@ components:
|
|||||||
purchase_date: { type: string, format: date, nullable: true }
|
purchase_date: { type: string, format: date, nullable: true }
|
||||||
purchase_cost: { type: number, example: 850000 }
|
purchase_cost: { type: number, example: 850000 }
|
||||||
useful_life_years: { type: integer, example: 10 }
|
useful_life_years: { type: integer, example: 10 }
|
||||||
depreciation_method: { type: string, enum: [SLM, WDV, OTHER], example: SLM }
|
depreciation_method: { type: string, enum: [SLM, WDV, CUSTOM], example: SLM }
|
||||||
depreciation_rate: { type: number, nullable: true, example: 10, description: 'Required for OTHER; auto-calculated for SLM/WDV when omitted' }
|
depreciation_rate: { type: number, nullable: true, example: 10, description: 'Required for CUSTOM; auto-calculated for SLM/WDV when omitted' }
|
||||||
salvage_value: { type: number, example: 50000 }
|
salvage_value: { type: number, example: 50000, description: 'Salvage amount. Send this OR salvage_percentage (not both required).' }
|
||||||
|
salvage_percentage: { type: number, example: 5, description: 'Salvage as % of purchase_cost (0–100). If both sent, percentage wins and amount is derived.' }
|
||||||
warranty_expiry_date: { type: string, format: date, nullable: true }
|
warranty_expiry_date: { type: string, format: date, nullable: true }
|
||||||
condition: { type: string, enum: [NEW, GOOD, FAIR, POOR] }
|
condition: { type: string, enum: [NEW, GOOD, FAIR, POOR] }
|
||||||
status: { type: string, enum: [IN_USE, IDLE, UNDER_MAINTENANCE, DISPOSED, SCRAPPED] }
|
status: { type: string, enum: [IN_USE, IDLE, UNDER_MAINTENANCE, DISPOSED, SCRAPPED] }
|
||||||
@ -84,9 +85,10 @@ components:
|
|||||||
purchase_date: { type: string, format: date, nullable: true }
|
purchase_date: { type: string, format: date, nullable: true }
|
||||||
purchase_cost: { type: number }
|
purchase_cost: { type: number }
|
||||||
useful_life_years: { type: integer }
|
useful_life_years: { type: integer }
|
||||||
depreciation_method: { type: string, enum: [SLM, WDV, OTHER] }
|
depreciation_method: { type: string, enum: [SLM, WDV, CUSTOM] }
|
||||||
depreciation_rate: { type: number, nullable: true }
|
depreciation_rate: { type: number, nullable: true }
|
||||||
salvage_value: { type: number }
|
salvage_value: { type: number }
|
||||||
|
salvage_percentage: { type: number, description: 'Salvage as % of purchase_cost (0–100)' }
|
||||||
warranty_expiry_date: { type: string, format: date, nullable: true }
|
warranty_expiry_date: { type: string, format: date, nullable: true }
|
||||||
condition: { type: string, enum: [NEW, GOOD, FAIR, POOR] }
|
condition: { type: string, enum: [NEW, GOOD, FAIR, POOR] }
|
||||||
status: { type: string, enum: [IN_USE, IDLE, UNDER_MAINTENANCE, DISPOSED, SCRAPPED] }
|
status: { type: string, enum: [IN_USE, IDLE, UNDER_MAINTENANCE, DISPOSED, SCRAPPED] }
|
||||||
@ -100,10 +102,11 @@ components:
|
|||||||
type: object
|
type: object
|
||||||
required: [depreciation_method]
|
required: [depreciation_method]
|
||||||
properties:
|
properties:
|
||||||
depreciation_method: { type: string, enum: [SLM, WDV, OTHER] }
|
depreciation_method: { type: string, enum: [SLM, WDV, CUSTOM] }
|
||||||
depreciation_rate: { type: number, nullable: true }
|
depreciation_rate: { type: number, nullable: true }
|
||||||
purchase_cost: { type: number, example: 850000 }
|
purchase_cost: { type: number, example: 850000 }
|
||||||
salvage_value: { type: number, example: 50000 }
|
salvage_value: { type: number, example: 50000 }
|
||||||
|
salvage_percentage: { type: number, example: 5, description: 'Optional; if sent with/without salvage_value, % wins when both present' }
|
||||||
useful_life_years: { type: integer, example: 10 }
|
useful_life_years: { type: integer, example: 10 }
|
||||||
commencement_date: { type: string, format: date, description: 'Preferred depreciation start date' }
|
commencement_date: { type: string, format: date, description: 'Preferred depreciation start date' }
|
||||||
purchase_date: { type: string, format: date }
|
purchase_date: { type: string, format: date }
|
||||||
@ -418,7 +421,7 @@ paths:
|
|||||||
/assets/depreciation-methods:
|
/assets/depreciation-methods:
|
||||||
get:
|
get:
|
||||||
tags: [Assets]
|
tags: [Assets]
|
||||||
summary: List depreciation method dropdown options (SLM, WDV, OTHER)
|
summary: List depreciation method dropdown options (SLM, WDV, CUSTOM)
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Depreciation methods fetched
|
description: Depreciation methods fetched
|
||||||
|
|||||||
@ -116,7 +116,7 @@ components:
|
|||||||
category_type: { type: string, enum: [STOCK, ASSET], example: STOCK, description: 'STOCK categories are shown in the Items module; ASSET categories in the Assets module' }
|
category_type: { type: string, enum: [STOCK, ASSET], example: STOCK, description: 'STOCK categories are shown in the Items module; ASSET categories in the Assets module' }
|
||||||
code_prefix: { type: string, nullable: true, example: 'MCH', description: Optional; used for asset code series when category is used for assets }
|
code_prefix: { type: string, nullable: true, example: 'MCH', description: Optional; used for asset code series when category is used for assets }
|
||||||
default_useful_life_years: { type: integer, nullable: true, example: 15 }
|
default_useful_life_years: { type: integer, nullable: true, example: 15 }
|
||||||
default_depreciation_method: { type: string, nullable: true, enum: [SLM, WDV, OTHER], example: SLM }
|
default_depreciation_method: { type: string, nullable: true, enum: [SLM, WDV, CUSTOM], example: SLM }
|
||||||
is_active: { type: boolean, example: true }
|
is_active: { type: boolean, example: true }
|
||||||
ItemCategoriesUpdateBody:
|
ItemCategoriesUpdateBody:
|
||||||
type: object
|
type: object
|
||||||
@ -127,7 +127,7 @@ components:
|
|||||||
category_type: { type: string, enum: [STOCK, ASSET], example: STOCK }
|
category_type: { type: string, enum: [STOCK, ASSET], example: STOCK }
|
||||||
code_prefix: { type: string, nullable: true, example: 'MCH' }
|
code_prefix: { type: string, nullable: true, example: 'MCH' }
|
||||||
default_useful_life_years: { type: integer, nullable: true, example: 15 }
|
default_useful_life_years: { type: integer, nullable: true, example: 15 }
|
||||||
default_depreciation_method: { type: string, nullable: true, enum: [SLM, WDV, OTHER], example: SLM }
|
default_depreciation_method: { type: string, nullable: true, enum: [SLM, WDV, CUSTOM], example: SLM }
|
||||||
is_active: { type: boolean, example: true }
|
is_active: { type: boolean, example: true }
|
||||||
ItemSubcategoriesCreateBody:
|
ItemSubcategoriesCreateBody:
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
@ -142,7 +142,7 @@ paths:
|
|||||||
schema: { type: string }
|
schema: { type: string }
|
||||||
- in: query
|
- in: query
|
||||||
name: depreciation_method
|
name: depreciation_method
|
||||||
schema: { type: string, enum: [SLM, WDV, OTHER] }
|
schema: { type: string, enum: [SLM, WDV, CUSTOM] }
|
||||||
- in: query
|
- in: query
|
||||||
name: item_category_id
|
name: item_category_id
|
||||||
schema: { type: integer }
|
schema: { type: integer }
|
||||||
@ -197,7 +197,7 @@ paths:
|
|||||||
schema: { type: string }
|
schema: { type: string }
|
||||||
- in: query
|
- in: query
|
||||||
name: depreciation_method
|
name: depreciation_method
|
||||||
schema: { type: string, enum: [SLM, WDV, OTHER] }
|
schema: { type: string, enum: [SLM, WDV, CUSTOM] }
|
||||||
- in: query
|
- in: query
|
||||||
name: item_category_id
|
name: item_category_id
|
||||||
schema: { type: integer }
|
schema: { type: integer }
|
||||||
|
|||||||
@ -16,7 +16,7 @@ const ASSET_STATUS_OPTIONS = toOptions([
|
|||||||
['SCRAPPED', 'Scrapped'],
|
['SCRAPPED', 'Scrapped'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const DEPRECIATION_METHOD_VALUES = ['SLM', 'WDV', 'OTHER'];
|
const DEPRECIATION_METHOD_VALUES = ['SLM', 'WDV', 'CUSTOM'];
|
||||||
|
|
||||||
const AMC_CONTRACT_TYPE_OPTIONS = toOptions([
|
const AMC_CONTRACT_TYPE_OPTIONS = toOptions([
|
||||||
['COMPREHENSIVE', 'Comprehensive'],
|
['COMPREHENSIVE', 'Comprehensive'],
|
||||||
|
|||||||
@ -14,12 +14,57 @@ const DEPRECIATION_METHOD_OPTIONS = [
|
|||||||
'Depreciation on reducing book value. Rate auto-calculated from cost, salvage and useful life when not provided.',
|
'Depreciation on reducing book value. Rate auto-calculated from cost, salvage and useful life when not provided.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'OTHER',
|
value: 'CUSTOM',
|
||||||
label: 'Other',
|
label: 'Custom',
|
||||||
description: 'Custom method. Depreciation rate must be entered manually.',
|
description: 'Custom method. Depreciation rate must be entered manually.',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** Map legacy OTHER → CUSTOM for reads/writes. */
|
||||||
|
const normalizeDepreciationMethod = (method) => {
|
||||||
|
if (method === 'OTHER') return 'CUSTOM';
|
||||||
|
return method || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve salvage amount and % from either input.
|
||||||
|
* - If only salvage_percentage → derive salvage_value from purchase_cost
|
||||||
|
* - If only salvage_value → derive salvage_percentage
|
||||||
|
* - If both → prefer salvage_percentage and recompute salvage_value
|
||||||
|
* - If neither → both 0
|
||||||
|
*/
|
||||||
|
const resolveSalvageFields = ({
|
||||||
|
purchase_cost: cost,
|
||||||
|
salvage_value: salvageAmount,
|
||||||
|
salvage_percentage: salvagePct,
|
||||||
|
} = {}) => {
|
||||||
|
const purchaseCost = Number(cost || 0);
|
||||||
|
const hasPct = salvagePct !== undefined && salvagePct !== null && salvagePct !== '';
|
||||||
|
const hasAmount =
|
||||||
|
salvageAmount !== undefined && salvageAmount !== null && salvageAmount !== '';
|
||||||
|
|
||||||
|
let salvagePercentage = 0;
|
||||||
|
let salvageValue = 0;
|
||||||
|
|
||||||
|
if (hasPct && !hasAmount) {
|
||||||
|
salvagePercentage = round4(Math.min(Math.max(Number(salvagePct), 0), 100));
|
||||||
|
salvageValue = purchaseCost > 0 ? round4((purchaseCost * salvagePercentage) / 100) : 0;
|
||||||
|
} else if (hasAmount && !hasPct) {
|
||||||
|
salvageValue = round4(Math.max(Number(salvageAmount), 0));
|
||||||
|
salvagePercentage =
|
||||||
|
purchaseCost > 0 ? round4(Math.min((salvageValue / purchaseCost) * 100, 100)) : 0;
|
||||||
|
} else if (hasPct && hasAmount) {
|
||||||
|
// Both sent: percentage is source of truth (FE amount/% toggle)
|
||||||
|
salvagePercentage = round4(Math.min(Math.max(Number(salvagePct), 0), 100));
|
||||||
|
salvageValue = purchaseCost > 0 ? round4((purchaseCost * salvagePercentage) / 100) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
salvage_value: salvageValue,
|
||||||
|
salvage_percentage: salvagePercentage,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const yearsElapsed = (purchaseDate, asOfDate = new Date()) => {
|
const yearsElapsed = (purchaseDate, asOfDate = new Date()) => {
|
||||||
if (!purchaseDate) return 0;
|
if (!purchaseDate) return 0;
|
||||||
const start = new Date(purchaseDate);
|
const start = new Date(purchaseDate);
|
||||||
@ -43,16 +88,17 @@ const resolveDepreciationRate = ({
|
|||||||
const purchaseCost = Number(cost || 0);
|
const purchaseCost = Number(cost || 0);
|
||||||
const salvageValue = Number(salvage || 0);
|
const salvageValue = Number(salvage || 0);
|
||||||
const years = Number(lifeYears || 0);
|
const years = Number(lifeYears || 0);
|
||||||
|
const normalizedMethod = normalizeDepreciationMethod(method);
|
||||||
|
|
||||||
if (!method || method === 'OTHER' || years <= 0) return null;
|
if (!normalizedMethod || normalizedMethod === 'CUSTOM' || years <= 0) return null;
|
||||||
|
|
||||||
if (method === 'SLM') {
|
if (normalizedMethod === 'SLM') {
|
||||||
if (purchaseCost <= 0) return round4(100 / years);
|
if (purchaseCost <= 0) return round4(100 / years);
|
||||||
const depreciable = Math.max(purchaseCost - salvageValue, 0);
|
const depreciable = Math.max(purchaseCost - salvageValue, 0);
|
||||||
return round4((depreciable / purchaseCost / years) * 100);
|
return round4((depreciable / purchaseCost / years) * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method === 'WDV') {
|
if (normalizedMethod === 'WDV') {
|
||||||
if (purchaseCost <= 0 || salvageValue <= 0 || salvageValue >= purchaseCost) return null;
|
if (purchaseCost <= 0 || salvageValue <= 0 || salvageValue >= purchaseCost) return null;
|
||||||
return round4((1 - (salvageValue / purchaseCost) ** (1 / years)) * 100);
|
return round4((1 - (salvageValue / purchaseCost) ** (1 / years)) * 100);
|
||||||
}
|
}
|
||||||
@ -65,15 +111,23 @@ const calculateDepreciation = ({
|
|||||||
depreciation_rate: rateInput,
|
depreciation_rate: rateInput,
|
||||||
purchase_cost: cost,
|
purchase_cost: cost,
|
||||||
salvage_value: salvage,
|
salvage_value: salvage,
|
||||||
|
salvage_percentage: salvagePct,
|
||||||
useful_life_years: lifeYears,
|
useful_life_years: lifeYears,
|
||||||
commencement_date: commencementDate,
|
commencement_date: commencementDate,
|
||||||
purchase_date: purchaseDate,
|
purchase_date: purchaseDate,
|
||||||
as_of_date: asOfDate,
|
as_of_date: asOfDate,
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
const purchaseCost = Number(cost || 0);
|
const purchaseCost = Number(cost || 0);
|
||||||
const salvageValue = Number(salvage || 0);
|
const { salvage_value: salvageValue, salvage_percentage: salvagePercentage } =
|
||||||
|
resolveSalvageFields({
|
||||||
|
purchase_cost: purchaseCost,
|
||||||
|
salvage_value: salvage,
|
||||||
|
salvage_percentage: salvagePct,
|
||||||
|
});
|
||||||
|
|
||||||
|
const normalizedMethod = normalizeDepreciationMethod(method);
|
||||||
const rate = resolveDepreciationRate({
|
const rate = resolveDepreciationRate({
|
||||||
method,
|
method: normalizedMethod,
|
||||||
depreciation_rate: rateInput,
|
depreciation_rate: rateInput,
|
||||||
purchase_cost: purchaseCost,
|
purchase_cost: purchaseCost,
|
||||||
salvage_value: salvageValue,
|
salvage_value: salvageValue,
|
||||||
@ -91,12 +145,12 @@ const calculateDepreciation = ({
|
|||||||
let accumulatedDepreciation = 0;
|
let accumulatedDepreciation = 0;
|
||||||
let bookValue = purchaseCost;
|
let bookValue = purchaseCost;
|
||||||
|
|
||||||
if (method === 'SLM' && rate !== null) {
|
if (normalizedMethod === 'SLM' && rate !== null) {
|
||||||
annualDepreciation = round4((purchaseCost * rate) / 100);
|
annualDepreciation = round4((purchaseCost * rate) / 100);
|
||||||
const maxDepreciable = Math.max(purchaseCost - salvageValue, 0);
|
const maxDepreciable = Math.max(purchaseCost - salvageValue, 0);
|
||||||
accumulatedDepreciation = round4(Math.min(annualDepreciation * cappedYears, maxDepreciable));
|
accumulatedDepreciation = round4(Math.min(annualDepreciation * cappedYears, maxDepreciable));
|
||||||
bookValue = round4(Math.max(purchaseCost - accumulatedDepreciation, salvageValue));
|
bookValue = round4(Math.max(purchaseCost - accumulatedDepreciation, salvageValue));
|
||||||
} else if (method === 'WDV' && rate !== null) {
|
} else if (normalizedMethod === 'WDV' && rate !== null) {
|
||||||
annualDepreciation = round4((purchaseCost * rate) / 100);
|
annualDepreciation = round4((purchaseCost * rate) / 100);
|
||||||
bookValue = purchaseCost;
|
bookValue = purchaseCost;
|
||||||
const fullYears = Math.floor(cappedYears);
|
const fullYears = Math.floor(cappedYears);
|
||||||
@ -116,7 +170,7 @@ const calculateDepreciation = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
annualDepreciation = round4((bookValue * rate) / 100);
|
annualDepreciation = round4((bookValue * rate) / 100);
|
||||||
} else if (method === 'OTHER' && rate !== null) {
|
} else if (normalizedMethod === 'CUSTOM' && rate !== null) {
|
||||||
annualDepreciation = round4((purchaseCost * rate) / 100);
|
annualDepreciation = round4((purchaseCost * rate) / 100);
|
||||||
const maxDepreciable = Math.max(purchaseCost - salvageValue, 0);
|
const maxDepreciable = Math.max(purchaseCost - salvageValue, 0);
|
||||||
accumulatedDepreciation = round4(Math.min(annualDepreciation * cappedYears, maxDepreciable));
|
accumulatedDepreciation = round4(Math.min(annualDepreciation * cappedYears, maxDepreciable));
|
||||||
@ -124,7 +178,7 @@ const calculateDepreciation = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
depreciation_method: method || null,
|
depreciation_method: normalizedMethod,
|
||||||
depreciation_rate: rate,
|
depreciation_rate: rate,
|
||||||
annual_depreciation: annualDepreciation,
|
annual_depreciation: annualDepreciation,
|
||||||
accumulated_depreciation: accumulatedDepreciation,
|
accumulated_depreciation: accumulatedDepreciation,
|
||||||
@ -132,6 +186,7 @@ const calculateDepreciation = ({
|
|||||||
years_elapsed: elapsed,
|
years_elapsed: elapsed,
|
||||||
purchase_cost: purchaseCost,
|
purchase_cost: purchaseCost,
|
||||||
salvage_value: salvageValue,
|
salvage_value: salvageValue,
|
||||||
|
salvage_percentage: salvagePercentage,
|
||||||
useful_life_years: lifeYears ?? null,
|
useful_life_years: lifeYears ?? null,
|
||||||
depreciation_start_date: depreciationStartDate || null,
|
depreciation_start_date: depreciationStartDate || null,
|
||||||
};
|
};
|
||||||
@ -140,6 +195,8 @@ const calculateDepreciation = ({
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
DEPRECIATION_METHOD_OPTIONS,
|
DEPRECIATION_METHOD_OPTIONS,
|
||||||
resolveDepreciationRate,
|
resolveDepreciationRate,
|
||||||
|
resolveSalvageFields,
|
||||||
|
normalizeDepreciationMethod,
|
||||||
calculateDepreciation,
|
calculateDepreciation,
|
||||||
yearsElapsed,
|
yearsElapsed,
|
||||||
round4,
|
round4,
|
||||||
|
|||||||
@ -10,6 +10,8 @@ const repository = require('./assets.repository');
|
|||||||
const {
|
const {
|
||||||
DEPRECIATION_METHOD_OPTIONS,
|
DEPRECIATION_METHOD_OPTIONS,
|
||||||
resolveDepreciationRate,
|
resolveDepreciationRate,
|
||||||
|
resolveSalvageFields,
|
||||||
|
normalizeDepreciationMethod,
|
||||||
calculateDepreciation,
|
calculateDepreciation,
|
||||||
} = require('./assets.depreciation');
|
} = require('./assets.depreciation');
|
||||||
const { assertReference, toDateOnly, normalizeChecklistTemplate, presentChecklistTemplate } = require('./assets.helpers');
|
const { assertReference, toDateOnly, normalizeChecklistTemplate, presentChecklistTemplate } = require('./assets.helpers');
|
||||||
@ -143,13 +145,24 @@ const sanitizeAsset = (asset) => {
|
|||||||
rest.salvage_value !== null && rest.salvage_value !== undefined
|
rest.salvage_value !== null && rest.salvage_value !== undefined
|
||||||
? Number(rest.salvage_value)
|
? Number(rest.salvage_value)
|
||||||
: 0;
|
: 0;
|
||||||
|
let salvagePercentage =
|
||||||
|
rest.salvage_percentage !== null && rest.salvage_percentage !== undefined
|
||||||
|
? Number(rest.salvage_percentage)
|
||||||
|
: null;
|
||||||
|
if (salvagePercentage === null) {
|
||||||
|
salvagePercentage =
|
||||||
|
purchaseCost > 0 ? Math.round((salvageValue / purchaseCost) * 10000) / 10000 : 0;
|
||||||
|
}
|
||||||
const depreciationRate =
|
const depreciationRate =
|
||||||
rest.depreciation_rate !== null && rest.depreciation_rate !== undefined
|
rest.depreciation_rate !== null && rest.depreciation_rate !== undefined
|
||||||
? Number(rest.depreciation_rate)
|
? Number(rest.depreciation_rate)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const depreciationMethod = normalizeDepreciationMethod(rest.depreciation_method);
|
||||||
|
|
||||||
|
// Use stored salvage_value for calc (do not re-derive from % on read)
|
||||||
const depreciation = calculateDepreciation({
|
const depreciation = calculateDepreciation({
|
||||||
depreciation_method: rest.depreciation_method,
|
depreciation_method: depreciationMethod,
|
||||||
depreciation_rate: depreciationRate,
|
depreciation_rate: depreciationRate,
|
||||||
purchase_cost: purchaseCost,
|
purchase_cost: purchaseCost,
|
||||||
salvage_value: salvageValue,
|
salvage_value: salvageValue,
|
||||||
@ -163,6 +176,8 @@ const sanitizeAsset = (asset) => {
|
|||||||
maintenance_checklist_json: presentChecklistTemplate(rest.maintenance_checklist_json),
|
maintenance_checklist_json: presentChecklistTemplate(rest.maintenance_checklist_json),
|
||||||
purchase_cost: purchaseCost,
|
purchase_cost: purchaseCost,
|
||||||
salvage_value: salvageValue,
|
salvage_value: salvageValue,
|
||||||
|
salvage_percentage: salvagePercentage,
|
||||||
|
depreciation_method: depreciationMethod,
|
||||||
depreciation_rate: depreciationRate,
|
depreciation_rate: depreciationRate,
|
||||||
// Current written-down / book value after depreciation (same as depreciation.book_value)
|
// Current written-down / book value after depreciation (same as depreciation.book_value)
|
||||||
current_value: depreciation.book_value,
|
current_value: depreciation.book_value,
|
||||||
@ -286,16 +301,27 @@ const normalizeAssetPayload = async (payload, { isCreate = false } = {}) => {
|
|||||||
assertDisposalFields(status, disposalDate);
|
assertDisposalFields(status, disposalDate);
|
||||||
|
|
||||||
const purchaseCost = payload.purchase_cost ?? 0;
|
const purchaseCost = payload.purchase_cost ?? 0;
|
||||||
const salvageValue = payload.salvage_value ?? 0;
|
const { salvage_value: salvageValue, salvage_percentage: salvagePercentage } =
|
||||||
|
resolveSalvageFields({
|
||||||
|
purchase_cost: purchaseCost,
|
||||||
|
salvage_value: payload.salvage_value,
|
||||||
|
salvage_percentage: payload.salvage_percentage,
|
||||||
|
});
|
||||||
const usefulLifeYears =
|
const usefulLifeYears =
|
||||||
payload.useful_life_years !== undefined && payload.useful_life_years !== null
|
payload.useful_life_years !== undefined && payload.useful_life_years !== null
|
||||||
? payload.useful_life_years
|
? payload.useful_life_years
|
||||||
: category.default_useful_life_years;
|
: category.default_useful_life_years;
|
||||||
const depreciationMethod =
|
const depreciationMethod = normalizeDepreciationMethod(
|
||||||
payload.depreciation_method || category.default_depreciation_method || null;
|
payload.depreciation_method || category.default_depreciation_method || null
|
||||||
|
);
|
||||||
|
|
||||||
if (depreciationMethod === 'OTHER' && (payload.depreciation_rate === undefined || payload.depreciation_rate === null || payload.depreciation_rate === '')) {
|
if (
|
||||||
throw new ApiError(422, 'depreciation_rate is required when depreciation_method is OTHER');
|
depreciationMethod === 'CUSTOM' &&
|
||||||
|
(payload.depreciation_rate === undefined ||
|
||||||
|
payload.depreciation_rate === null ||
|
||||||
|
payload.depreciation_rate === '')
|
||||||
|
) {
|
||||||
|
throw new ApiError(422, 'depreciation_rate is required when depreciation_method is CUSTOM');
|
||||||
}
|
}
|
||||||
|
|
||||||
const depreciationRate = resolveDepreciationRate({
|
const depreciationRate = resolveDepreciationRate({
|
||||||
@ -342,6 +368,7 @@ const normalizeAssetPayload = async (payload, { isCreate = false } = {}) => {
|
|||||||
depreciation_method: depreciationMethod,
|
depreciation_method: depreciationMethod,
|
||||||
depreciation_rate: depreciationRate,
|
depreciation_rate: depreciationRate,
|
||||||
salvage_value: salvageValue,
|
salvage_value: salvageValue,
|
||||||
|
salvage_percentage: salvagePercentage,
|
||||||
warranty_expiry_date: payload.warranty_expiry_date
|
warranty_expiry_date: payload.warranty_expiry_date
|
||||||
? toDateOnly(payload.warranty_expiry_date)
|
? toDateOnly(payload.warranty_expiry_date)
|
||||||
: null,
|
: null,
|
||||||
@ -498,6 +525,8 @@ const exportAssets = async (query) => {
|
|||||||
{ key: (row) => row.depreciation?.annual_depreciation ?? '', header: 'Annual Depreciation' },
|
{ key: (row) => row.depreciation?.annual_depreciation ?? '', header: 'Annual Depreciation' },
|
||||||
{ key: (row) => row.depreciation?.depreciation_method || '', header: 'Depreciation Method' },
|
{ key: (row) => row.depreciation?.depreciation_method || '', header: 'Depreciation Method' },
|
||||||
{ key: (row) => row.depreciation?.depreciation_rate ?? '', header: 'Depreciation Rate %' },
|
{ key: (row) => row.depreciation?.depreciation_rate ?? '', header: 'Depreciation Rate %' },
|
||||||
|
{ key: 'salvage_value', header: 'Salvage Value' },
|
||||||
|
{ key: 'salvage_percentage', header: 'Salvage %' },
|
||||||
{ key: 'warranty_expiry_date', header: 'Warranty Expiry', type: 'date' },
|
{ key: 'warranty_expiry_date', header: 'Warranty Expiry', type: 'date' },
|
||||||
{
|
{
|
||||||
key: (row) => row.maintenance_incharge_user?.full_name || '',
|
key: (row) => row.maintenance_incharge_user?.full_name || '',
|
||||||
@ -574,7 +603,20 @@ const updateAsset = async (id, payload, userId, requestId) => {
|
|||||||
: existing.depreciation_rate !== null && existing.depreciation_rate !== undefined
|
: existing.depreciation_rate !== null && existing.depreciation_rate !== undefined
|
||||||
? Number(existing.depreciation_rate)
|
? Number(existing.depreciation_rate)
|
||||||
: null,
|
: null,
|
||||||
salvage_value: payload.salvage_value ?? Number(existing.salvage_value ?? 0),
|
salvage_value:
|
||||||
|
payload.salvage_value !== undefined
|
||||||
|
? payload.salvage_value
|
||||||
|
: payload.salvage_percentage !== undefined
|
||||||
|
? undefined
|
||||||
|
: Number(existing.salvage_value ?? 0),
|
||||||
|
salvage_percentage:
|
||||||
|
payload.salvage_percentage !== undefined
|
||||||
|
? payload.salvage_percentage
|
||||||
|
: payload.salvage_value !== undefined
|
||||||
|
? undefined
|
||||||
|
: existing.salvage_percentage !== null && existing.salvage_percentage !== undefined
|
||||||
|
? Number(existing.salvage_percentage)
|
||||||
|
: undefined,
|
||||||
warranty_expiry_date:
|
warranty_expiry_date:
|
||||||
payload.warranty_expiry_date !== undefined
|
payload.warranty_expiry_date !== undefined
|
||||||
? payload.warranty_expiry_date
|
? payload.warranty_expiry_date
|
||||||
@ -732,10 +774,14 @@ const listVisitConditionsAfter = () => getAssetDropdownOptions().visit_condition
|
|||||||
const listAssetOptions = () => getAssetDropdownOptions();
|
const listAssetOptions = () => getAssetDropdownOptions();
|
||||||
|
|
||||||
const previewDepreciation = (payload) => {
|
const previewDepreciation = (payload) => {
|
||||||
if (payload.depreciation_method === 'OTHER' && (payload.depreciation_rate === undefined || payload.depreciation_rate === null)) {
|
const method = normalizeDepreciationMethod(payload.depreciation_method);
|
||||||
throw new ApiError(422, 'depreciation_rate is required when depreciation_method is OTHER');
|
if (
|
||||||
|
method === 'CUSTOM' &&
|
||||||
|
(payload.depreciation_rate === undefined || payload.depreciation_rate === null)
|
||||||
|
) {
|
||||||
|
throw new ApiError(422, 'depreciation_rate is required when depreciation_method is CUSTOM');
|
||||||
}
|
}
|
||||||
return calculateDepreciation(payload);
|
return calculateDepreciation({ ...payload, depreciation_method: method });
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@ -57,11 +57,12 @@ const assetFields = {
|
|||||||
purchase_cost: Joi.number().min(0).default(0),
|
purchase_cost: Joi.number().min(0).default(0),
|
||||||
useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
|
useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
|
||||||
depreciation_method: Joi.string()
|
depreciation_method: Joi.string()
|
||||||
.valid(...DEPRECIATION_METHODS)
|
.valid(...DEPRECIATION_METHODS, 'OTHER') // OTHER accepted as alias → stored as CUSTOM
|
||||||
.allow(null)
|
.allow(null)
|
||||||
.optional(),
|
.optional(),
|
||||||
depreciation_rate: Joi.number().min(0).max(100).allow(null).optional(),
|
depreciation_rate: Joi.number().min(0).max(100).allow(null).optional(),
|
||||||
salvage_value: Joi.number().min(0).allow(null).optional(),
|
salvage_value: Joi.number().min(0).allow(null).optional(),
|
||||||
|
salvage_percentage: Joi.number().min(0).max(100).allow(null).optional(),
|
||||||
warranty_expiry_date: Joi.date().iso().allow(null).optional(),
|
warranty_expiry_date: Joi.date().iso().allow(null).optional(),
|
||||||
condition: Joi.string()
|
condition: Joi.string()
|
||||||
.valid(...ASSET_CONDITIONS)
|
.valid(...ASSET_CONDITIONS)
|
||||||
@ -105,6 +106,7 @@ const updateAssetSchema = Joi.object({
|
|||||||
depreciation_method: assetFields.depreciation_method,
|
depreciation_method: assetFields.depreciation_method,
|
||||||
depreciation_rate: assetFields.depreciation_rate,
|
depreciation_rate: assetFields.depreciation_rate,
|
||||||
salvage_value: assetFields.salvage_value,
|
salvage_value: assetFields.salvage_value,
|
||||||
|
salvage_percentage: assetFields.salvage_percentage,
|
||||||
warranty_expiry_date: assetFields.warranty_expiry_date,
|
warranty_expiry_date: assetFields.warranty_expiry_date,
|
||||||
condition: assetFields.condition.optional(),
|
condition: assetFields.condition.optional(),
|
||||||
status: assetFields.status.optional(),
|
status: assetFields.status.optional(),
|
||||||
@ -304,11 +306,12 @@ const renewInsurancePolicySchema = Joi.object({
|
|||||||
|
|
||||||
const depreciationCalculateSchema = Joi.object({
|
const depreciationCalculateSchema = Joi.object({
|
||||||
depreciation_method: Joi.string()
|
depreciation_method: Joi.string()
|
||||||
.valid(...DEPRECIATION_METHODS)
|
.valid(...DEPRECIATION_METHODS, 'OTHER')
|
||||||
.required(),
|
.required(),
|
||||||
depreciation_rate: Joi.number().min(0).max(100).allow(null).optional(),
|
depreciation_rate: Joi.number().min(0).max(100).allow(null).optional(),
|
||||||
purchase_cost: Joi.number().min(0).default(0),
|
purchase_cost: Joi.number().min(0).default(0),
|
||||||
salvage_value: Joi.number().min(0).default(0),
|
salvage_value: Joi.number().min(0).allow(null).optional(),
|
||||||
|
salvage_percentage: Joi.number().min(0).max(100).allow(null).optional(),
|
||||||
useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
|
useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
|
||||||
commencement_date: Joi.date().iso().allow(null).optional(),
|
commencement_date: Joi.date().iso().allow(null).optional(),
|
||||||
purchase_date: Joi.date().iso().allow(null).optional(),
|
purchase_date: Joi.date().iso().allow(null).optional(),
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
const { buildMasterService } = require('../_shared/master.factory');
|
const { buildMasterService } = require('../_shared/master.factory');
|
||||||
|
const { normalizeDepreciationMethod } = require('../../assets/assets.depreciation');
|
||||||
|
|
||||||
const emptyToNull = (value) => (value === '' ? null : value);
|
const emptyToNull = (value) => (value === '' ? null : value);
|
||||||
|
|
||||||
@ -75,7 +76,9 @@ const normalizeAssetDefaults = (payload) => {
|
|||||||
out.code_prefix = emptyToNull(out.code_prefix);
|
out.code_prefix = emptyToNull(out.code_prefix);
|
||||||
}
|
}
|
||||||
if (Object.prototype.hasOwnProperty.call(out, 'default_depreciation_method')) {
|
if (Object.prototype.hasOwnProperty.call(out, 'default_depreciation_method')) {
|
||||||
out.default_depreciation_method = emptyToNull(out.default_depreciation_method);
|
out.default_depreciation_method = normalizeDepreciationMethod(
|
||||||
|
emptyToNull(out.default_depreciation_method)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const createSchema = Joi.object({
|
|||||||
code_prefix: masterCode({ max: 10 }).allow(null, ''),
|
code_prefix: masterCode({ max: 10 }).allow(null, ''),
|
||||||
default_useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
|
default_useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
|
||||||
default_depreciation_method: Joi.string()
|
default_depreciation_method: Joi.string()
|
||||||
.valid(...DEPRECIATION_METHODS)
|
.valid(...DEPRECIATION_METHODS, 'OTHER')
|
||||||
.allow(null, '')
|
.allow(null, '')
|
||||||
.optional(),
|
.optional(),
|
||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
@ -29,7 +29,7 @@ const updateSchema = Joi.object({
|
|||||||
code_prefix: masterCode({ max: 10 }).allow(null, ''),
|
code_prefix: masterCode({ max: 10 }).allow(null, ''),
|
||||||
default_useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
|
default_useful_life_years: Joi.number().integer().min(0).allow(null).optional(),
|
||||||
default_depreciation_method: Joi.string()
|
default_depreciation_method: Joi.string()
|
||||||
.valid(...DEPRECIATION_METHODS)
|
.valid(...DEPRECIATION_METHODS, 'OTHER')
|
||||||
.allow(null, '')
|
.allow(null, '')
|
||||||
.optional(),
|
.optional(),
|
||||||
is_active: Joi.boolean().optional(),
|
is_active: Joi.boolean().optional(),
|
||||||
|
|||||||
@ -11,10 +11,16 @@ const depreciationInclude = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const buildDepreciationWhere = (query) => {
|
const buildDepreciationWhere = (query) => {
|
||||||
|
const methodFilter = query.depreciation_method
|
||||||
|
? query.depreciation_method === 'CUSTOM' || query.depreciation_method === 'OTHER'
|
||||||
|
? { depreciation_method: { in: ['CUSTOM', 'OTHER'] } }
|
||||||
|
: { depreciation_method: query.depreciation_method }
|
||||||
|
: {};
|
||||||
|
|
||||||
const where = {
|
const where = {
|
||||||
deleted_at: null,
|
deleted_at: null,
|
||||||
...(query.status ? { status: query.status } : {}),
|
...(query.status ? { status: query.status } : {}),
|
||||||
...(query.depreciation_method ? { depreciation_method: query.depreciation_method } : {}),
|
...methodFilter,
|
||||||
...(query.item_category_id ? { item_category_id: BigInt(query.item_category_id) } : {}),
|
...(query.item_category_id ? { item_category_id: BigInt(query.item_category_id) } : {}),
|
||||||
...(query.item_subcategory_id
|
...(query.item_subcategory_id
|
||||||
? { item_subcategory_id: BigInt(query.item_subcategory_id) }
|
? { item_subcategory_id: BigInt(query.item_subcategory_id) }
|
||||||
@ -70,8 +76,14 @@ const sanitizeDepreciationRow = (asset, asOfDate) => {
|
|||||||
purchase_date: asset.purchase_date,
|
purchase_date: asset.purchase_date,
|
||||||
purchase_cost: purchaseCost,
|
purchase_cost: purchaseCost,
|
||||||
salvage_value: salvageValue,
|
salvage_value: salvageValue,
|
||||||
|
salvage_percentage:
|
||||||
|
asset.salvage_percentage !== null && asset.salvage_percentage !== undefined
|
||||||
|
? toNumber(asset.salvage_percentage)
|
||||||
|
: purchaseCost > 0
|
||||||
|
? round4((salvageValue / purchaseCost) * 100)
|
||||||
|
: 0,
|
||||||
useful_life_years: asset.useful_life_years,
|
useful_life_years: asset.useful_life_years,
|
||||||
depreciation_method: asset.depreciation_method,
|
depreciation_method: asset.depreciation_method === 'OTHER' ? 'CUSTOM' : asset.depreciation_method,
|
||||||
depreciation_rate: depreciationRate,
|
depreciation_rate: depreciationRate,
|
||||||
item_category: asset.item_categories || null,
|
item_category: asset.item_categories || null,
|
||||||
item_subcategory: asset.item_subcategories || null,
|
item_subcategory: asset.item_subcategories || null,
|
||||||
@ -174,7 +186,7 @@ const getDepreciationFilterOptions = async () => {
|
|||||||
depreciation_methods: [
|
depreciation_methods: [
|
||||||
{ value: 'SLM', label: 'Straight Line Method (SLM)' },
|
{ value: 'SLM', label: 'Straight Line Method (SLM)' },
|
||||||
{ value: 'WDV', label: 'Written Down Value (WDV)' },
|
{ value: 'WDV', label: 'Written Down Value (WDV)' },
|
||||||
{ value: 'OTHER', label: 'Other' },
|
{ value: 'CUSTOM', label: 'Custom' },
|
||||||
],
|
],
|
||||||
statuses: [
|
statuses: [
|
||||||
{ value: 'IN_USE', label: 'In Use' },
|
{ value: 'IN_USE', label: 'In Use' },
|
||||||
@ -209,6 +221,7 @@ const exportAssetDepreciation = async (query) => {
|
|||||||
{ key: 'purchase_date', header: 'Purchase Date', type: 'date' },
|
{ key: 'purchase_date', header: 'Purchase Date', type: 'date' },
|
||||||
{ key: 'purchase_cost', header: 'Purchase Cost' },
|
{ key: 'purchase_cost', header: 'Purchase Cost' },
|
||||||
{ key: 'salvage_value', header: 'Salvage Value' },
|
{ key: 'salvage_value', header: 'Salvage Value' },
|
||||||
|
{ key: 'salvage_percentage', header: 'Salvage %' },
|
||||||
{ key: (row) => row.useful_life_years ?? '', header: 'Useful Life (Years)' },
|
{ key: (row) => row.useful_life_years ?? '', header: 'Useful Life (Years)' },
|
||||||
{ key: (row) => row.depreciation_method || '', header: 'Depreciation Method' },
|
{ key: (row) => row.depreciation_method || '', header: 'Depreciation Method' },
|
||||||
{ key: (row) => row.depreciation_rate ?? '', header: 'Depreciation Rate (%)' },
|
{ key: (row) => row.depreciation_rate ?? '', header: 'Depreciation Rate (%)' },
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const depreciationReportQuerySchema = Joi.object({
|
|||||||
status: Joi.string()
|
status: Joi.string()
|
||||||
.valid(...ASSET_STATUSES)
|
.valid(...ASSET_STATUSES)
|
||||||
.optional(),
|
.optional(),
|
||||||
depreciation_method: Joi.string().valid('SLM', 'WDV', 'OTHER').optional(),
|
depreciation_method: Joi.string().valid('SLM', 'WDV', 'CUSTOM', 'OTHER').optional(),
|
||||||
item_category_id: Joi.number().integer().positive().optional(),
|
item_category_id: Joi.number().integer().positive().optional(),
|
||||||
item_subcategory_id: Joi.number().integer().positive().optional(),
|
item_subcategory_id: Joi.number().integer().positive().optional(),
|
||||||
location_id: Joi.number().integer().positive().optional(),
|
location_id: Joi.number().integer().positive().optional(),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user