// 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 };