enterprise_assest_managemen.../node_api/jobs/depreciationDatabase.js

765 lines
22 KiB
JavaScript

// node_api/jobs/depreciationDatabase.js
const { Pool } = require('pg');
const { logger } = require('../utils/logger');
class DepreciationDatabase {
constructor() {
this.pool = new Pool({
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
database: process.env.DB_DATABASE || 'directus',
user: process.env.DB_USERNAME || 'directus',
password: process.env.DB_PASSWORD || 'directus',
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
}
/**
* Get all assets eligible for depreciation
*/
async getAssetsForDepreciation(calculationDate = new Date()) {
const client = await this.pool.connect();
try {
const query = `
SELECT
a.id,
a.name,
a.acquisition_cost,
a.acquisition_date,
a.depreciation_method,
a.depreciation_percentage_rate,
a.expected_useful_life_months,
a.salvage_value,
a.total_accumulated_depreciation,
a.net_book_value,
a.status,
a.expected_total_units,
a.current_month_units,
a.organization_id,
a.date_created,
a.date_updated
FROM assets a
WHERE
a.status = 'active'
AND a.acquisition_cost > 0
AND a.acquisition_date IS NOT NULL
AND a.acquisition_date <= $1
AND a.depreciation_method IS NOT NULL
AND (
a.total_accumulated_depreciation IS NULL
OR a.total_accumulated_depreciation < (a.acquisition_cost - COALESCE(a.salvage_value, 0))
)
ORDER BY a.acquisition_date ASC
`;
const result = await client.query(query, [calculationDate]);
logger.depreciation(`Found ${result.rows.length} assets eligible for depreciation`, {
calculationDate,
assetCount: result.rows.length
});
return result.rows;
} catch (error) {
logger.error('Error fetching assets for depreciation:', error);
throw error;
} finally {
client.release();
}
}
/**
* Get the last depreciation record for an asset
*/
async getLastDepreciationRecord(assetId) {
const client = await this.pool.connect();
try {
const query = `
SELECT *
FROM asset_depreciation_records
WHERE asset_id = $1
ORDER BY depreciation_date DESC
LIMIT 1
`;
const result = await client.query(query, [assetId]);
return result.rows[0] || null;
} catch (error) {
logger.error(`Error fetching last depreciation record for asset ${assetId}:`, error);
throw error;
} finally {
client.release();
}
}
/**
* Check if depreciation record exists for asset and date
*/
async depreciationRecordExists(assetId, depreciationDate) {
const client = await this.pool.connect();
try {
const query = `
SELECT COUNT(*) as count
FROM asset_depreciation_records
WHERE asset_id = $1
AND DATE_TRUNC('month', depreciation_date) = DATE_TRUNC('month', $2)
`;
const result = await client.query(query, [assetId, depreciationDate]);
return parseInt(result.rows[0].count) > 0;
} catch (error) {
logger.error(`Error checking depreciation record existence:`, error);
throw error;
} finally {
client.release();
}
}
/**
* Create a new depreciation record
*/
async createDepreciationRecord(depreciationData) {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
const {
asset_id,
depreciation_date,
depreciation_method,
monthly_depreciation,
accumulated_depreciation_before,
accumulated_depreciation_after,
net_book_value_before,
net_book_value_after,
calculation_details
} = depreciationData;
// Insert depreciation record
const insertQuery = `
INSERT INTO asset_depreciation_records (
id,
asset_id,
depreciation_date,
depreciation_method,
monthly_depreciation,
accumulated_depreciation_before,
accumulated_depreciation_after,
net_book_value_before,
net_book_value_after,
calculation_details,
date_created,
date_updated
) VALUES (
gen_random_uuid(),
$1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW()
)
RETURNING id
`;
const insertResult = await client.query(insertQuery, [
asset_id,
depreciation_date,
depreciation_method,
monthly_depreciation,
accumulated_depreciation_before,
accumulated_depreciation_after,
net_book_value_before,
net_book_value_after,
JSON.stringify(calculation_details)
]);
// Update asset's accumulated depreciation and net book value
const updateAssetQuery = `
UPDATE assets
SET
total_accumulated_depreciation = $1,
net_book_value = $2,
date_updated = NOW()
WHERE id = $3
`;
await client.query(updateAssetQuery, [
accumulated_depreciation_after,
net_book_value_after,
asset_id
]);
await client.query('COMMIT');
logger.depreciation(`Created depreciation record for asset ${asset_id}`, {
recordId: insertResult.rows[0].id,
assetId: asset_id,
monthlyDepreciation: monthly_depreciation,
accumulatedDepreciation: accumulated_depreciation_after
});
return insertResult.rows[0].id;
} catch (error) {
await client.query('ROLLBACK');
logger.error(`Error creating depreciation record:`, error);
throw error;
} finally {
client.release();
}
}
/**
* Get depreciation summary for a specific period
*/
async getDepreciationSummary(startDate, endDate) {
const client = await this.pool.connect();
try {
const query = `
SELECT
COUNT(*) as total_assets,
SUM(monthly_depreciation) as total_monthly_depreciation,
AVG(monthly_depreciation) as avg_monthly_depreciation,
MIN(monthly_depreciation) as min_monthly_depreciation,
MAX(monthly_depreciation) as max_monthly_depreciation,
depreciation_method,
COUNT(*) as method_count
FROM asset_depreciation_records
WHERE depreciation_date BETWEEN $1 AND $2
GROUP BY depreciation_method
ORDER BY method_count DESC
`;
const result = await client.query(query, [startDate, endDate]);
const overallQuery = `
SELECT
COUNT(*) as total_assets,
SUM(monthly_depreciation) as total_monthly_depreciation,
AVG(monthly_depreciation) as avg_monthly_depreciation
FROM asset_depreciation_records
WHERE depreciation_date BETWEEN $1 AND $2
`;
const overallResult = await client.query(overallQuery, [startDate, endDate]);
return {
overall: overallResult.rows[0],
byMethod: result.rows
};
} catch (error) {
logger.error('Error getting depreciation summary:', error);
throw error;
} finally {
client.release();
}
}
/**
* Get assets with depreciation issues
*/
async getDepreciationIssues() {
const client = await this.pool.connect();
try {
const query = `
SELECT
a.id,
a.name,
a.acquisition_cost,
a.total_accumulated_depreciation,
a.net_book_value,
a.depreciation_method,
CASE
WHEN a.total_accumulated_depreciation > a.acquisition_cost THEN 'over_depreciated'
WHEN a.net_book_value < 0 THEN 'negative_book_value'
WHEN a.net_book_value != (a.acquisition_cost - COALESCE(a.total_accumulated_depreciation, 0)) THEN 'calculation_mismatch'
ELSE 'unknown'
END as issue_type
FROM assets a
WHERE
a.status = 'active'
AND (
a.total_accumulated_depreciation > a.acquisition_cost
OR a.net_book_value < 0
OR a.net_book_value != (a.acquisition_cost - COALESCE(a.total_accumulated_depreciation, 0))
)
ORDER BY a.name
`;
const result = await client.query(query);
return result.rows;
} catch (error) {
logger.error('Error getting depreciation issues:', error);
throw error;
} finally {
client.release();
}
}
/**
* Fix calculation mismatches
*/
async fixCalculationMismatches() {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
const updateQuery = `
UPDATE assets
SET
net_book_value = acquisition_cost - COALESCE(total_accumulated_depreciation, 0),
date_updated = NOW()
WHERE
status = 'active'
AND net_book_value != (acquisition_cost - COALESCE(total_accumulated_depreciation, 0))
`;
const result = await client.query(updateQuery);
await client.query('COMMIT');
logger.depreciation(`Fixed calculation mismatches for ${result.rowCount} assets`);
return result.rowCount;
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error fixing calculation mismatches:', error);
throw error;
} finally {
client.release();
}
}
/**
* Get monthly depreciation report
*/
async getMonthlyDepreciationReport(year, month) {
const client = await this.pool.connect();
try {
const query = `
SELECT
a.id,
a.name,
a.acquisition_cost,
dr.monthly_depreciation,
dr.accumulated_depreciation_before,
dr.accumulated_depreciation_after,
dr.net_book_value_before,
dr.net_book_value_after,
dr.depreciation_method,
dr.depreciation_date,
c.name as category_name,
l.name as location_name
FROM asset_depreciation_records dr
JOIN assets a ON dr.asset_id = a.id
LEFT JOIN categories c ON a.category_id = c.id
LEFT JOIN locations l ON a.location_id = l.id
WHERE
EXTRACT(YEAR FROM dr.depreciation_date) = $1
AND EXTRACT(MONTH FROM dr.depreciation_date) = $2
ORDER BY dr.monthly_depreciation DESC
`;
const result = await client.query(query, [year, month]);
return result.rows;
} catch (error) {
logger.error('Error getting monthly depreciation report:', error);
throw error;
} finally {
client.release();
}
}
/**
* Get all asset components eligible for depreciation
*/
async getAssetComponentsForDepreciation(calculationDate = new Date()) {
const client = await this.pool.connect();
try {
const query = `
SELECT
ac.id,
ac.name,
ac.component_identifier,
ac.parent_asset_id,
ac.acquisition_cost,
ac.acquisition_date,
ac.depreciation_method,
ac.depreciation_percentage_rate,
ac.expected_useful_life_months,
ac.salvage_value,
ac.total_accumulated_depreciation,
ac.net_book_value,
ac.status,
ac.expected_total_units,
ac.current_month_units,
ac.organization_id,
ac.date_created,
ac.date_updated,
a.name as parent_asset_name,
a.asset_identifier as parent_asset_identifier
FROM asset_components ac
JOIN assets a ON ac.parent_asset_id = a.id
WHERE
ac.status = 'active'
AND ac.acquisition_cost > 0
AND ac.acquisition_date IS NOT NULL
AND ac.acquisition_date <= $1
AND ac.depreciation_method IS NOT NULL
AND (
ac.total_accumulated_depreciation IS NULL
OR ac.total_accumulated_depreciation < (ac.acquisition_cost - COALESCE(ac.salvage_value, 0))
)
ORDER BY ac.acquisition_date ASC
`;
const result = await client.query(query, [calculationDate]);
logger.depreciation(`Found ${result.rows.length} asset components eligible for depreciation`, {
calculationDate,
componentCount: result.rows.length
});
return result.rows;
} catch (error) {
logger.error('Error fetching asset components for depreciation:', error);
throw error;
} finally {
client.release();
}
}
/**
* Get the last depreciation record for an asset component
*/
async getLastComponentDepreciationRecord(componentId) {
const client = await this.pool.connect();
try {
const query = `
SELECT *
FROM asset_component_depreciation_records
WHERE component_id = $1
ORDER BY depreciation_date DESC
LIMIT 1
`;
const result = await client.query(query, [componentId]);
return result.rows[0] || null;
} catch (error) {
logger.error(`Error fetching last depreciation record for component ${componentId}:`, error);
throw error;
} finally {
client.release();
}
}
/**
* Check if depreciation record exists for component and date
*/
async componentDepreciationRecordExists(componentId, depreciationDate) {
const client = await this.pool.connect();
try {
const query = `
SELECT COUNT(*) as count
FROM asset_component_depreciation_records
WHERE component_id = $1
AND DATE_TRUNC('month', depreciation_date) = DATE_TRUNC('month', $2)
`;
const result = await client.query(query, [componentId, depreciationDate]);
return parseInt(result.rows[0].count) > 0;
} catch (error) {
logger.error(`Error checking component depreciation record existence:`, error);
throw error;
} finally {
client.release();
}
}
/**
* Create a new asset component depreciation record
*/
async createComponentDepreciationRecord(depreciationData) {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
const {
component_id,
asset_id,
depreciation_date,
depreciation_method,
monthly_depreciation,
accumulated_depreciation_before,
accumulated_depreciation_after,
net_book_value_before,
net_book_value_after,
calculation_details
} = depreciationData;
// Insert component depreciation record
const insertQuery = `
INSERT INTO asset_component_depreciation_records (
id,
component_id,
asset_id,
depreciation_date,
depreciation_method,
monthly_depreciation,
accumulated_depreciation_before,
accumulated_depreciation_after,
net_book_value_before,
net_book_value_after,
calculation_details,
date_created,
date_updated
) VALUES (
gen_random_uuid(),
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW(), NOW()
)
RETURNING id
`;
const insertResult = await client.query(insertQuery, [
component_id,
asset_id,
depreciation_date,
depreciation_method,
monthly_depreciation,
accumulated_depreciation_before,
accumulated_depreciation_after,
net_book_value_before,
net_book_value_after,
JSON.stringify(calculation_details)
]);
// Update component's accumulated depreciation and net book value
const updateComponentQuery = `
UPDATE asset_components
SET
total_accumulated_depreciation = $1,
net_book_value = $2,
date_updated = NOW()
WHERE id = $3
`;
await client.query(updateComponentQuery, [
accumulated_depreciation_after,
net_book_value_after,
component_id
]);
await client.query('COMMIT');
logger.depreciation(`Created component depreciation record for component ${component_id}`, {
recordId: insertResult.rows[0].id,
componentId: component_id,
assetId: asset_id,
monthlyDepreciation: monthly_depreciation,
accumulatedDepreciation: accumulated_depreciation_after
});
return insertResult.rows[0].id;
} catch (error) {
await client.query('ROLLBACK');
logger.error(`Error creating component depreciation record:`, error);
throw error;
} finally {
client.release();
}
}
/**
* Get asset components with depreciation issues
*/
async getComponentDepreciationIssues() {
const client = await this.pool.connect();
try {
const query = `
SELECT
ac.id,
ac.name,
ac.component_identifier,
ac.parent_asset_id,
a.name as parent_asset_name,
a.asset_identifier as parent_asset_identifier,
ac.acquisition_cost,
ac.total_accumulated_depreciation,
ac.net_book_value,
ac.depreciation_method,
CASE
WHEN ac.total_accumulated_depreciation > ac.acquisition_cost THEN 'over_depreciated'
WHEN ac.net_book_value < 0 THEN 'negative_book_value'
WHEN ac.net_book_value != (ac.acquisition_cost - COALESCE(ac.total_accumulated_depreciation, 0)) THEN 'calculation_mismatch'
ELSE 'unknown'
END as issue_type
FROM asset_components ac
JOIN assets a ON ac.parent_asset_id = a.id
WHERE
ac.status = 'active'
AND (
ac.total_accumulated_depreciation > ac.acquisition_cost
OR ac.net_book_value < 0
OR ac.net_book_value != (ac.acquisition_cost - COALESCE(ac.total_accumulated_depreciation, 0))
)
ORDER BY ac.name
`;
const result = await client.query(query);
return result.rows;
} catch (error) {
logger.error('Error getting component depreciation issues:', error);
throw error;
} finally {
client.release();
}
}
/**
* Fix component calculation mismatches
*/
async fixComponentCalculationMismatches() {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
const updateQuery = `
UPDATE asset_components
SET
net_book_value = acquisition_cost - COALESCE(total_accumulated_depreciation, 0),
date_updated = NOW()
WHERE
status = 'active'
AND net_book_value != (acquisition_cost - COALESCE(total_accumulated_depreciation, 0))
`;
const result = await client.query(updateQuery);
await client.query('COMMIT');
logger.depreciation(`Fixed component calculation mismatches for ${result.rowCount} components`);
return result.rowCount;
} catch (error) {
await client.query('ROLLBACK');
logger.error('Error fixing component calculation mismatches:', error);
throw error;
} finally {
client.release();
}
}
/**
* Get monthly component depreciation report
*/
async getMonthlyComponentDepreciationReport(year, month) {
const client = await this.pool.connect();
try {
const query = `
SELECT
ac.id,
ac.name,
ac.component_identifier,
ac.parent_asset_id,
a.name as parent_asset_name,
a.asset_identifier as parent_asset_identifier,
ac.acquisition_cost,
dr.monthly_depreciation,
dr.accumulated_depreciation_before,
dr.accumulated_depreciation_after,
dr.net_book_value_before,
dr.net_book_value_after,
dr.depreciation_method,
dr.depreciation_date,
c.name as category_name,
l.name as location_name
FROM asset_component_depreciation_records dr
JOIN asset_components ac ON dr.component_id = ac.id
JOIN assets a ON dr.asset_id = a.id
LEFT JOIN asset_categories c ON a.category_id = c.id
LEFT JOIN locations l ON a.location_id = l.id
WHERE
EXTRACT(YEAR FROM dr.depreciation_date) = $1
AND EXTRACT(MONTH FROM dr.depreciation_date) = $2
ORDER BY dr.monthly_depreciation DESC
`;
const result = await client.query(query, [year, month]);
return result.rows;
} catch (error) {
logger.error('Error getting monthly component depreciation report:', error);
throw error;
} finally {
client.release();
}
}
/**
* Get component depreciation summary for a period
*/
async getComponentDepreciationSummary(startDate, endDate) {
const client = await this.pool.connect();
try {
const query = `
SELECT
COUNT(*) as total_components,
SUM(monthly_depreciation) as total_monthly_depreciation,
AVG(monthly_depreciation) as avg_monthly_depreciation,
MIN(monthly_depreciation) as min_monthly_depreciation,
MAX(monthly_depreciation) as max_monthly_depreciation,
depreciation_method,
COUNT(*) as method_count
FROM asset_component_depreciation_records
WHERE depreciation_date BETWEEN $1 AND $2
GROUP BY depreciation_method
ORDER BY method_count DESC
`;
const result = await client.query(query, [startDate, endDate]);
const overallQuery = `
SELECT
COUNT(*) as total_components,
SUM(monthly_depreciation) as total_monthly_depreciation,
AVG(monthly_depreciation) as avg_monthly_depreciation
FROM asset_component_depreciation_records
WHERE depreciation_date BETWEEN $1 AND $2
`;
const overallResult = await client.query(overallQuery, [startDate, endDate]);
return {
overall: overallResult.rows[0],
byMethod: result.rows
};
} catch (error) {
logger.error('Error getting component depreciation summary:', error);
throw error;
} finally {
client.release();
}
}
/**
* Close database connection pool
*/
async close() {
await this.pool.end();
logger.info('Database connection pool closed');
}
}
module.exports = { DepreciationDatabase };