303 lines
9.4 KiB
JavaScript
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 }; |