Files
MixedAssets/server/services/zones.js
2026-06-05 04:15:24 -05:00

115 lines
4.1 KiB
JavaScript

const { all, audit, now, one, run } = require('../db');
let cache = null;
function clearCache() {
cache = null;
}
function lookup() {
if (!cache) {
cache = {};
for (const row of all('SELECT * FROM depreciation_zones')) cache[row.code] = row;
}
return cache;
}
// Raw row (incl. dates) for the engine; null when unknown or disabled.
function getZone(code) {
if (!code) return null;
const zone = lookup()[code];
return zone && zone.enabled ? zone : null;
}
function slugify(value) {
return String(value || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 40);
}
function fromRow(row) {
if (!row) return null;
return {
id: row.id,
code: row.code,
name: row.name,
allowance_percent: row.allowance_percent,
pis_start: row.pis_start,
pis_end: row.pis_end,
real_property_end: row.real_property_end,
max_recovery_years: row.max_recovery_years,
leasehold_life_years: row.leasehold_life_years,
notes: row.notes,
enabled: Boolean(row.enabled),
asset_count: one('SELECT COUNT(*) AS c FROM assets WHERE special_zone = ?', [row.code]).c
};
}
function list() {
return all('SELECT * FROM depreciation_zones ORDER BY name').map(fromRow);
}
function normalize(body) {
const num = (v) => (v === '' || v === null || v === undefined ? null : Number(v));
return {
name: String(body.name || '').trim(),
allowance_percent: Number(body.allowance_percent) || 0,
pis_start: body.pis_start || null,
pis_end: body.pis_end || null,
real_property_end: body.real_property_end || null,
max_recovery_years: num(body.max_recovery_years),
leasehold_life_years: num(body.leasehold_life_years),
notes: body.notes || null,
enabled: body.enabled === false ? 0 : 1
};
}
function createZone(body, userId) {
const input = normalize(body);
if (!input.name) throw Object.assign(new Error('A zone name is required'), { status: 400 });
const code = slugify(body.code || input.name);
if (!code) throw Object.assign(new Error('A valid zone code is required'), { status: 400 });
if (one('SELECT id FROM depreciation_zones WHERE code = ?', [code])) {
throw Object.assign(new Error('A zone with that code already exists'), { status: 400 });
}
const ts = now();
run(
`INSERT INTO depreciation_zones (code, name, allowance_percent, pis_start, pis_end, real_property_end, max_recovery_years, leasehold_life_years, notes, enabled, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[code, input.name, input.allowance_percent, input.pis_start, input.pis_end, input.real_property_end, input.max_recovery_years, input.leasehold_life_years, input.notes, input.enabled, ts, ts]
);
clearCache();
audit(userId, 'create', 'depreciation_zone', code, null, input);
return fromRow(one('SELECT * FROM depreciation_zones WHERE code = ?', [code]));
}
function updateZone(code, body, userId) {
const before = one('SELECT * FROM depreciation_zones WHERE code = ?', [code]);
if (!before) return null;
const input = normalize({ ...before, ...body, enabled: body.enabled === undefined ? Boolean(before.enabled) : body.enabled });
run(
`UPDATE depreciation_zones SET name = ?, allowance_percent = ?, pis_start = ?, pis_end = ?, real_property_end = ?,
max_recovery_years = ?, leasehold_life_years = ?, notes = ?, enabled = ?, updated_at = ? WHERE code = ?`,
[input.name, input.allowance_percent, input.pis_start, input.pis_end, input.real_property_end, input.max_recovery_years, input.leasehold_life_years, input.notes, input.enabled, now(), code]
);
clearCache();
audit(userId, 'update', 'depreciation_zone', code, before, input);
return fromRow(one('SELECT * FROM depreciation_zones WHERE code = ?', [code]));
}
function deleteZone(code, userId) {
const before = one('SELECT * FROM depreciation_zones WHERE code = ?', [code]);
if (!before) return false;
run('DELETE FROM depreciation_zones WHERE code = ?', [code]);
clearCache();
audit(userId, 'delete', 'depreciation_zone', code, before, null);
return true;
}
module.exports = {
clearCache,
createZone,
deleteZone,
getZone,
list,
updateZone
};