enterprise_assest_managemen.../node_api/jobs/depreciationCalculator.js

303 lines
9.4 KiB
JavaScript

// node_api/jobs/depreciationCalculator.js
const { logger } = require('../utils/logger');
class DepreciationCalculator {
constructor() {
this.methods = {
'straight_line': this.calculateStraightLine.bind(this),
'declining_balance': this.calculateDecliningBalance.bind(this),
'sum_of_years': this.calculateSumOfYears.bind(this),
'units_of_production': this.calculateUnitsOfProduction.bind(this)
};
}
/**
* Calculate monthly depreciation for an asset
* @param {Object} asset - Asset object with depreciation parameters
* @param {Date} calculationDate - Date for which to calculate depreciation
* @returns {Object} Depreciation calculation result
*/
calculateMonthlyDepreciation(asset, calculationDate = new Date()) {
try {
const method = asset.depreciation_method || 'straight_line';
const calculator = this.methods[method];
if (!calculator) {
throw new Error(`Unsupported depreciation method: ${method}`);
}
const result = calculator(asset, calculationDate);
logger.depreciation(`Calculated ${method} depreciation for asset ${asset.id}`, {
assetId: asset.id,
method,
monthlyDepreciation: result.monthlyDepreciation,
calculationDate
});
return result;
} catch (error) {
logger.error(`Error calculating depreciation for asset ${asset.id}:`, error);
throw error;
}
}
/**
* Straight-line depreciation calculation
* Formula: (Cost - Salvage Value) / Useful Life
*/
calculateStraightLine(asset, calculationDate) {
const {
acquisition_cost,
salvage_value = 0,
expected_useful_life_months,
depreciation_percentage_rate
} = asset;
if (!acquisition_cost || !expected_useful_life_months) {
throw new Error('Missing required parameters for straight-line depreciation');
}
const cost = parseFloat(acquisition_cost);
const salvage = parseFloat(salvage_value);
const usefulLifeMonths = parseInt(expected_useful_life_months);
let monthlyDepreciation;
// If depreciation rate is provided, use it
if (depreciation_percentage_rate) {
const rate = parseFloat(depreciation_percentage_rate);
monthlyDepreciation = (cost * rate) / 100 / 12;
} else {
// Standard straight-line calculation
monthlyDepreciation = (cost - salvage) / usefulLifeMonths;
}
return {
method: 'straight_line',
monthlyDepreciation,
calculation: {
acquisitionCost: cost,
salvageValue: salvage,
usefulLifeMonths,
formula: depreciation_percentage_rate ?
`(${cost} * ${depreciation_percentage_rate}%) / 12` :
`(${cost} - ${salvage}) / ${usefulLifeMonths}`
}
};
}
/**
* Declining balance depreciation calculation
* Formula: Book Value * (Depreciation Rate / 12)
*/
calculateDecliningBalance(asset, calculationDate) {
const {
acquisition_cost,
depreciation_percentage_rate,
total_accumulated_depreciation = 0
} = asset;
if (!acquisition_cost || !depreciation_percentage_rate) {
throw new Error('Missing required parameters for declining balance depreciation');
}
const cost = parseFloat(acquisition_cost);
const rate = parseFloat(depreciation_percentage_rate) / 100;
const accumulatedDepreciation = parseFloat(total_accumulated_depreciation);
const currentBookValue = cost - accumulatedDepreciation;
const monthlyDepreciation = currentBookValue * (rate / 12);
// Ensure we don't depreciate below zero
const adjustedDepreciation = Math.max(0, Math.min(monthlyDepreciation, currentBookValue));
return {
method: 'declining_balance',
monthlyDepreciation: adjustedDepreciation,
calculation: {
acquisitionCost: cost,
accumulatedDepreciation,
currentBookValue,
depreciationRate: rate,
formula: `${currentBookValue} * (${rate} / 12)`
}
};
}
/**
* Sum of years digits depreciation calculation
* Formula: (Cost - Salvage) * (Remaining Life / Sum of Years) / 12
*/
calculateSumOfYears(asset, calculationDate) {
const {
acquisition_cost,
salvage_value = 0,
expected_useful_life_months,
acquisition_date
} = asset;
if (!acquisition_cost || !expected_useful_life_months || !acquisition_date) {
throw new Error('Missing required parameters for sum of years depreciation');
}
const cost = parseFloat(acquisition_cost);
const salvage = parseFloat(salvage_value);
const usefulLifeMonths = parseInt(expected_useful_life_months);
const usefulLifeYears = Math.ceil(usefulLifeMonths / 12);
// Calculate months elapsed since acquisition
const startDate = new Date(acquisition_date);
const monthsElapsed = this.getMonthsDifference(startDate, calculationDate);
const remainingMonths = Math.max(0, usefulLifeMonths - monthsElapsed);
const remainingYears = Math.ceil(remainingMonths / 12);
// Sum of years digits
const sumOfYears = (usefulLifeYears * (usefulLifeYears + 1)) / 2;
// Annual depreciation for current year
const annualDepreciation = (cost - salvage) * (remainingYears / sumOfYears);
const monthlyDepreciation = annualDepreciation / 12;
return {
method: 'sum_of_years',
monthlyDepreciation,
calculation: {
acquisitionCost: cost,
salvageValue: salvage,
usefulLifeYears,
remainingYears,
sumOfYears,
annualDepreciation,
formula: `(${cost} - ${salvage}) * (${remainingYears} / ${sumOfYears}) / 12`
}
};
}
/**
* Units of production depreciation calculation
* Formula: (Cost - Salvage) * (Units Used / Total Expected Units)
*/
calculateUnitsOfProduction(asset, calculationDate) {
const {
acquisition_cost,
salvage_value = 0,
expected_total_units,
current_month_units = 0
} = asset;
if (!acquisition_cost || !expected_total_units) {
throw new Error('Missing required parameters for units of production depreciation');
}
const cost = parseFloat(acquisition_cost);
const salvage = parseFloat(salvage_value);
const totalUnits = parseFloat(expected_total_units);
const monthlyUnits = parseFloat(current_month_units);
const depreciationPerUnit = (cost - salvage) / totalUnits;
const monthlyDepreciation = depreciationPerUnit * monthlyUnits;
return {
method: 'units_of_production',
monthlyDepreciation,
calculation: {
acquisitionCost: cost,
salvageValue: salvage,
totalExpectedUnits: totalUnits,
currentMonthUnits: monthlyUnits,
depreciationPerUnit,
formula: `(${cost} - ${salvage}) / ${totalUnits} * ${monthlyUnits}`
}
};
}
/**
* Validate asset for depreciation calculation
*/
validateAsset(asset) {
const errors = [];
if (!asset.acquisition_cost || asset.acquisition_cost <= 0) {
errors.push('Acquisition cost must be greater than 0');
}
if (!asset.acquisition_date) {
errors.push('Acquisition date is required');
}
if (!asset.depreciation_method) {
errors.push('Depreciation method is required');
}
const method = asset.depreciation_method;
if (method === 'straight_line' && !asset.expected_useful_life_months) {
errors.push('Expected useful life is required for straight-line depreciation');
}
if (method === 'declining_balance' && !asset.depreciation_percentage_rate) {
errors.push('Depreciation rate is required for declining balance method');
}
if (method === 'sum_of_years' && !asset.expected_useful_life_months) {
errors.push('Expected useful life is required for sum of years method');
}
if (method === 'units_of_production' && !asset.expected_total_units) {
errors.push('Expected total units is required for units of production method');
}
return errors;
}
/**
* Check if asset should be depreciated
*/
shouldDepreciate(asset, calculationDate = new Date()) {
// Don't depreciate if asset is fully depreciated
const acquisitionCost = parseFloat(asset.acquisition_cost) || 0;
const accumulatedDepreciation = parseFloat(asset.total_accumulated_depreciation) || 0;
const salvageValue = parseFloat(asset.salvage_value) || 0;
if (accumulatedDepreciation >= (acquisitionCost - salvageValue)) {
return false;
}
// Don't depreciate if asset is not active
if (asset.status !== 'active') {
return false;
}
// Don't depreciate if acquisition date is in the future
const acquisitionDate = new Date(asset.acquisition_date);
if (acquisitionDate > calculationDate) {
return false;
}
// Don't depreciate if asset is beyond useful life
if (asset.expected_useful_life_months) {
const monthsElapsed = this.getMonthsDifference(acquisitionDate, calculationDate);
if (monthsElapsed >= asset.expected_useful_life_months) {
return false;
}
}
return true;
}
/**
* Helper method to calculate months difference between two dates
*/
getMonthsDifference(startDate, endDate) {
const start = new Date(startDate);
const end = new Date(endDate);
const yearsDiff = end.getFullYear() - start.getFullYear();
const monthsDiff = end.getMonth() - start.getMonth();
return yearsDiff * 12 + monthsDiff;
}
}
module.exports = { DepreciationCalculator };