Added Multi-Company Support
This commit is contained in:
@@ -17,8 +17,12 @@ const userSelect = `
|
||||
LEFT JOIN teams t ON t.id = u.team_id
|
||||
`;
|
||||
|
||||
const companies = require('../services/companies');
|
||||
|
||||
function publicAdminUser(id) {
|
||||
return one(`${userSelect} WHERE u.id = ?`, [id]);
|
||||
const user = one(`${userSelect} WHERE u.id = ?`, [id]);
|
||||
if (user) user.company_ids = companies.assignedCompanyIds(user.id);
|
||||
return user;
|
||||
}
|
||||
|
||||
function validateRole(role) {
|
||||
@@ -45,7 +49,8 @@ router.put('/settings', requireCapability('admin.settings'), (req, res) => {
|
||||
});
|
||||
|
||||
router.get('/users', requireCapability('admin.users'), (req, res) => {
|
||||
res.json({ users: all(`${userSelect} ORDER BY u.name`) });
|
||||
const users = all(`${userSelect} ORDER BY u.name`).map((u) => ({ ...u, company_ids: companies.assignedCompanyIds(u.id) }));
|
||||
res.json({ users });
|
||||
});
|
||||
|
||||
router.post('/users', requireCapability('admin.users'), (req, res) => {
|
||||
@@ -77,6 +82,9 @@ router.post('/users', requireCapability('admin.users'), (req, res) => {
|
||||
]
|
||||
);
|
||||
passwordPolicy.recordPassword(result.lastInsertRowid, hash);
|
||||
// Assign company access: the supplied list, else default to the creating admin's active company.
|
||||
const companyIds = Array.isArray(req.body.company_ids) && req.body.company_ids.length ? req.body.company_ids : (req.companyId ? [req.companyId] : []);
|
||||
companies.setUserCompanies(result.lastInsertRowid, companyIds, req.user.id);
|
||||
audit(req.user.id, 'create', 'user', result.lastInsertRowid, null, { ...req.body, password: '[redacted]' });
|
||||
res.status(201).json({ user: publicAdminUser(result.lastInsertRowid) });
|
||||
});
|
||||
@@ -145,6 +153,10 @@ router.put('/users/:id', requireCapability('admin.users'), (req, res) => {
|
||||
passwordPolicy.recordPassword(req.params.id, hash);
|
||||
}
|
||||
|
||||
if (req.body.company_ids !== undefined) {
|
||||
companies.setUserCompanies(req.params.id, req.body.company_ids, req.user.id);
|
||||
}
|
||||
|
||||
const user = publicAdminUser(req.params.id);
|
||||
audit(req.user.id, 'update', 'user', req.params.id, before, { ...user, password: req.body.password ? '[changed]' : undefined });
|
||||
return res.json({ user });
|
||||
|
||||
@@ -8,12 +8,12 @@ const { submitTicket } = require('../services/servicenow');
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/alerts', (req, res) => {
|
||||
res.json({ alerts: listAlerts(req.query), summary: summary() });
|
||||
res.json({ alerts: listAlerts({ ...req.query, entity_id: req.companyId }), summary: summary(req.companyId) });
|
||||
});
|
||||
|
||||
router.post('/alerts/scan', requireCapability('alerts.manage'), (req, res) => {
|
||||
const result = scanAlerts(req.user.id);
|
||||
res.json({ created: result.created.length, openCount: result.openCount, alerts: listAlerts({}), summary: summary() });
|
||||
res.json({ created: result.created.length, openCount: result.openCount, alerts: listAlerts({ entity_id: req.companyId }), summary: summary(req.companyId) });
|
||||
});
|
||||
|
||||
router.post('/alerts/run', requireCapability('admin.integrations'), async (req, res, next) => {
|
||||
|
||||
@@ -21,48 +21,48 @@ const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 16
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/assets', (req, res) => {
|
||||
res.json({ assets: listAssets(req.query) });
|
||||
res.json({ assets: listAssets({ ...req.query, entity_id: req.companyId }) });
|
||||
});
|
||||
|
||||
router.post('/assets', requireCapability('assets.edit'), (req, res) => {
|
||||
res.status(201).json({ asset: createAsset(req.body, req.user.id) });
|
||||
res.status(201).json({ asset: createAsset(req.body, req.user.id, req.companyId) });
|
||||
});
|
||||
|
||||
router.get('/assets/lookup', (req, res) => {
|
||||
const code = String(req.query.code || '').trim();
|
||||
if (!code) return res.status(400).json({ error: 'Scan code is required' });
|
||||
const asset = lookupAssetByCode(code);
|
||||
const asset = lookupAssetByCode(code, req.companyId);
|
||||
if (!asset) return res.status(404).json({ error: `No asset matches "${code}"` });
|
||||
return res.json({ asset });
|
||||
});
|
||||
|
||||
router.get('/assets/:id', (req, res) => {
|
||||
const asset = hydrateAsset(req.params.id);
|
||||
if (!asset) return res.status(404).json({ error: 'Asset not found' });
|
||||
if (!asset || (req.companyId && asset.entity_id !== req.companyId)) return res.status(404).json({ error: 'Asset not found' });
|
||||
return res.json({ asset });
|
||||
});
|
||||
|
||||
router.put('/assets/:id', requireCapability('assets.edit'), (req, res) => {
|
||||
const asset = updateAsset(req.params.id, req.body, req.user.id);
|
||||
const asset = updateAsset(req.params.id, req.body, req.user.id, req.companyId);
|
||||
if (!asset) return res.status(404).json({ error: 'Asset not found' });
|
||||
return res.json({ asset });
|
||||
});
|
||||
|
||||
router.delete('/assets/:id', requireCapability('assets.delete'), (req, res) => {
|
||||
if (!deleteAsset(req.params.id, req.user.id)) return res.status(404).json({ error: 'Asset not found' });
|
||||
if (!deleteAsset(req.params.id, req.user.id, req.companyId)) return res.status(404).json({ error: 'Asset not found' });
|
||||
return res.status(204).end();
|
||||
});
|
||||
|
||||
router.post('/assets/mass-update', requireCapability('assets.bulk'), (req, res) => {
|
||||
const ids = Array.isArray(req.body.ids) ? req.body.ids : [];
|
||||
const updated = massUpdateAssets(ids, req.body.changes || {}, req.user.id);
|
||||
const updated = massUpdateAssets(ids, req.body.changes || {}, req.user.id, req.companyId);
|
||||
if (!updated) return res.status(400).json({ error: 'Choose assets and editable fields' });
|
||||
return res.json({ updated });
|
||||
});
|
||||
|
||||
router.get('/assets/:id/depreciation', (req, res) => {
|
||||
const asset = hydrateAsset(req.params.id);
|
||||
if (!asset) return res.status(404).json({ error: 'Asset not found' });
|
||||
if (!asset || (req.companyId && asset.entity_id !== req.companyId)) return res.status(404).json({ error: 'Asset not found' });
|
||||
const startYear = Number(req.query.startYear || new Date().getFullYear());
|
||||
const endYear = Number(req.query.endYear || startYear + 5);
|
||||
const schedules = asset.books
|
||||
|
||||
@@ -21,25 +21,25 @@ router.post('/employees', requireCapability('assets.assign'), (req, res) => {
|
||||
|
||||
router.get('/assignments', (req, res) => {
|
||||
res.json({
|
||||
summary: assignmentSummary(),
|
||||
summary: assignmentSummary(req.companyId),
|
||||
assignments: assignmentRows({
|
||||
asset_id: req.query.asset_id || null,
|
||||
employee_id: req.query.employee_id || null,
|
||||
active: req.query.active === 'true'
|
||||
})
|
||||
}, req.companyId)
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/assignments', requireCapability('assets.assign'), (req, res, next) => {
|
||||
try {
|
||||
res.status(201).json({ assignment: assignAsset(req.body, req.user.id) });
|
||||
res.status(201).json({ assignment: assignAsset(req.body, req.user.id, req.companyId) });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/assignments/:id/release', requireCapability('assets.assign'), (req, res) => {
|
||||
const assignment = releaseAssignment(req.params.id, req.body, req.user.id);
|
||||
const assignment = releaseAssignment(req.params.id, req.body, req.user.id, req.companyId);
|
||||
if (!assignment) return res.status(404).json({ error: 'Assignment not found' });
|
||||
return res.json({ assignment });
|
||||
});
|
||||
|
||||
@@ -6,34 +6,34 @@ const { exportReport } = require('../services/reportExport');
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/books', (req, res) => {
|
||||
res.json(listBooks());
|
||||
res.json(listBooks(req.companyId));
|
||||
});
|
||||
|
||||
router.post('/books', requireCapability('finance.manage'), (req, res) => {
|
||||
res.status(201).json({ book: createBook(req.body, req.user.id) });
|
||||
res.status(201).json({ book: createBook(req.body, req.user.id, req.companyId) });
|
||||
});
|
||||
|
||||
router.put('/books/:id', requireCapability('finance.manage'), (req, res) => {
|
||||
const book = updateBook(req.params.id, req.body, req.user.id);
|
||||
const book = updateBook(req.params.id, req.body, req.user.id, req.companyId);
|
||||
if (!book) return res.status(404).json({ error: 'Book not found' });
|
||||
return res.json({ book });
|
||||
});
|
||||
|
||||
router.delete('/books/:id', requireCapability('finance.manage'), (req, res) => {
|
||||
if (!deleteBook(req.params.id, req.user.id)) return res.status(404).json({ error: 'Book not found' });
|
||||
if (!deleteBook(req.params.id, req.user.id, req.companyId)) return res.status(404).json({ error: 'Book not found' });
|
||||
return res.status(204).end();
|
||||
});
|
||||
|
||||
router.get('/books/:code/ledger', (req, res) => {
|
||||
const year = Number(req.query.year) || new Date().getFullYear();
|
||||
const ledger = bookLedger(req.params.code, year);
|
||||
const ledger = bookLedger(req.params.code, year, req.companyId);
|
||||
if (!ledger) return res.status(404).json({ error: 'Book not found' });
|
||||
return res.json({ ledger });
|
||||
});
|
||||
|
||||
router.post('/books/:code/ledger/export', async (req, res) => {
|
||||
const year = Number(req.body.year) || new Date().getFullYear();
|
||||
const report = ledgerReport(req.params.code, year);
|
||||
const report = ledgerReport(req.params.code, year, req.companyId);
|
||||
if (!report) return res.status(404).json({ error: 'Book not found' });
|
||||
const format = String(req.body.format || 'csv').toLowerCase();
|
||||
const output = await exportReport(report, format);
|
||||
|
||||
@@ -14,39 +14,39 @@ const router = express.Router();
|
||||
const editor = requireCapability('contacts.manage');
|
||||
|
||||
router.get('/contacts', (req, res) => {
|
||||
res.json({ contacts: listContacts(req.query) });
|
||||
res.json({ contacts: listContacts(req.query, req.companyId) });
|
||||
});
|
||||
|
||||
router.get('/contacts/:id', (req, res) => {
|
||||
const contact = getContact(req.params.id);
|
||||
const contact = getContact(req.params.id, req.companyId);
|
||||
if (!contact) return res.status(404).json({ error: 'Contact not found' });
|
||||
return res.json({ contact });
|
||||
});
|
||||
|
||||
router.post('/contacts', editor, (req, res) => {
|
||||
res.status(201).json({ contact: createContact(req.body, req.user.id) });
|
||||
res.status(201).json({ contact: createContact(req.body, req.user.id, req.companyId) });
|
||||
});
|
||||
|
||||
router.put('/contacts/:id', editor, (req, res) => {
|
||||
const contact = updateContact(req.params.id, req.body, req.user.id);
|
||||
const contact = updateContact(req.params.id, req.body, req.user.id, req.companyId);
|
||||
if (!contact) return res.status(404).json({ error: 'Contact not found' });
|
||||
return res.json({ contact });
|
||||
});
|
||||
|
||||
router.delete('/contacts/:id', requireCapability('contacts.manage'), (req, res) => {
|
||||
if (!deleteContact(req.params.id, req.user.id)) return res.status(404).json({ error: 'Contact not found' });
|
||||
if (!deleteContact(req.params.id, req.user.id, req.companyId)) return res.status(404).json({ error: 'Contact not found' });
|
||||
return res.status(204).end();
|
||||
});
|
||||
|
||||
router.post('/contacts/:id/notes', editor, (req, res) => {
|
||||
if (!req.body.body) return res.status(400).json({ error: 'Note body is required' });
|
||||
const note = addNote(req.params.id, req.body.body, req.user.id);
|
||||
const note = addNote(req.params.id, req.body.body, req.user.id, req.companyId);
|
||||
if (!note) return res.status(404).json({ error: 'Contact not found' });
|
||||
return res.status(201).json({ note });
|
||||
});
|
||||
|
||||
router.delete('/contacts/:id/notes/:noteId', editor, (req, res) => {
|
||||
if (!deleteNote(req.params.id, req.params.noteId, req.user.id)) return res.status(404).json({ error: 'Note not found' });
|
||||
if (!deleteNote(req.params.id, req.params.noteId, req.user.id, req.companyId)) return res.status(404).json({ error: 'Note not found' });
|
||||
return res.status(204).end();
|
||||
});
|
||||
|
||||
|
||||
@@ -4,24 +4,25 @@ const { all, one } = require('../db');
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/dashboard', (req, res) => {
|
||||
const cid = req.companyId || null;
|
||||
const stats = one(`
|
||||
SELECT
|
||||
COUNT(*) AS asset_count,
|
||||
COALESCE(SUM(acquisition_cost), 0) AS total_cost,
|
||||
COALESCE(SUM(CASE WHEN status = 'disposed' THEN 1 ELSE 0 END), 0) AS disposed_count,
|
||||
COALESCE(SUM(CASE WHEN status = 'in_service' THEN 1 ELSE 0 END), 0) AS in_service_count
|
||||
FROM assets
|
||||
`);
|
||||
const byCategory = all('SELECT category, COUNT(*) AS count, COALESCE(SUM(acquisition_cost), 0) AS value FROM assets GROUP BY category ORDER BY value DESC');
|
||||
const byStatus = all('SELECT status, COUNT(*) AS count FROM assets GROUP BY status ORDER BY count DESC');
|
||||
FROM assets WHERE (? IS NULL OR entity_id = ?)
|
||||
`, [cid, cid]);
|
||||
const byCategory = all('SELECT category, COUNT(*) AS count, COALESCE(SUM(acquisition_cost), 0) AS value FROM assets WHERE (? IS NULL OR entity_id = ?) GROUP BY category ORDER BY value DESC', [cid, cid]);
|
||||
const byStatus = all('SELECT status, COUNT(*) AS count FROM assets WHERE (? IS NULL OR entity_id = ?) GROUP BY status ORDER BY count DESC', [cid, cid]);
|
||||
const upcomingTasks = all(`
|
||||
SELECT t.*, a.asset_id, a.description
|
||||
FROM tasks t
|
||||
LEFT JOIN assets a ON a.id = t.asset_id
|
||||
WHERE t.status != 'complete'
|
||||
WHERE t.status != 'complete' AND (? IS NULL OR a.entity_id = ?)
|
||||
ORDER BY t.due_date IS NULL, t.due_date
|
||||
LIMIT 8
|
||||
`);
|
||||
`, [cid, cid]);
|
||||
const auditTrail = all('SELECT al.*, u.name AS actor_name FROM audit_logs al LEFT JOIN users u ON u.id = al.actor_id ORDER BY al.id DESC LIMIT 10');
|
||||
res.json({ stats, byCategory, byStatus, upcomingTasks, auditTrail });
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ const router = express.Router();
|
||||
|
||||
router.get('/export/assets', async (req, res) => {
|
||||
const format = String(req.query.format || 'json').toLowerCase();
|
||||
const output = await assetExport(format);
|
||||
const output = await assetExport(format, req.companyId);
|
||||
res.setHeader('Content-Type', output.contentType);
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${output.filename}"`);
|
||||
return res.send(output.body);
|
||||
@@ -19,7 +19,7 @@ router.get('/export/assets', async (req, res) => {
|
||||
router.post('/import/assets', requireCapability('assets.bulk'), upload.single('file'), async (req, res) => {
|
||||
if (!req.file) return res.status(400).json({ error: 'File is required' });
|
||||
const rows = await rowsFromImport(extensionForUpload(req.file.originalname), req.file.buffer);
|
||||
const imported = upsertImportedAssets(rows, req.user.id);
|
||||
const imported = upsertImportedAssets(rows, req.user.id, req.companyId);
|
||||
audit(req.user.id, 'import', 'asset', null, null, { file: req.file.originalname, imported });
|
||||
res.json({ imported });
|
||||
});
|
||||
|
||||
@@ -17,59 +17,59 @@ const router = express.Router();
|
||||
const editor = requireCapability('parts.manage');
|
||||
|
||||
router.get('/parts', (req, res) => {
|
||||
res.json({ parts: listParts(req.query) });
|
||||
res.json({ parts: listParts(req.query, req.companyId) });
|
||||
});
|
||||
|
||||
router.get('/parts/:id', (req, res) => {
|
||||
const part = getPart(req.params.id);
|
||||
const part = getPart(req.params.id, req.companyId);
|
||||
if (!part) return res.status(404).json({ error: 'Part not found' });
|
||||
return res.json({ part });
|
||||
});
|
||||
|
||||
router.post('/parts', editor, (req, res) => {
|
||||
res.status(201).json({ part: createPart(req.body, req.user.id) });
|
||||
res.status(201).json({ part: createPart(req.body, req.user.id, req.companyId) });
|
||||
});
|
||||
|
||||
router.put('/parts/:id', editor, (req, res) => {
|
||||
const part = updatePart(req.params.id, req.body, req.user.id);
|
||||
const part = updatePart(req.params.id, req.body, req.user.id, req.companyId);
|
||||
if (!part) return res.status(404).json({ error: 'Part not found' });
|
||||
return res.json({ part });
|
||||
});
|
||||
|
||||
router.delete('/parts/:id', requireCapability('parts.manage'), (req, res) => {
|
||||
if (!deletePart(req.params.id, req.user.id)) return res.status(404).json({ error: 'Part not found' });
|
||||
if (!deletePart(req.params.id, req.user.id, req.companyId)) return res.status(404).json({ error: 'Part not found' });
|
||||
return res.status(204).end();
|
||||
});
|
||||
|
||||
// Stock locations (aisle/bin + on-hand/reserved per location)
|
||||
router.post('/parts/:id/stock', editor, (req, res) => {
|
||||
const part = saveStock(req.params.id, req.body, req.user.id);
|
||||
const part = saveStock(req.params.id, req.body, req.user.id, req.companyId);
|
||||
if (!part) return res.status(404).json({ error: 'Part or stock location not found' });
|
||||
return res.status(201).json({ part });
|
||||
});
|
||||
|
||||
router.delete('/parts/:id/stock/:stockId', editor, (req, res) => {
|
||||
const part = deleteStock(req.params.id, req.params.stockId, req.user.id);
|
||||
const part = deleteStock(req.params.id, req.params.stockId, req.user.id, req.companyId);
|
||||
if (!part) return res.status(404).json({ error: 'Stock location not found' });
|
||||
return res.json({ part });
|
||||
});
|
||||
|
||||
// Stock movements (receive/issue/adjust/reserve/unreserve)
|
||||
router.post('/parts/:id/transactions', editor, (req, res) => {
|
||||
const part = recordTransaction(req.params.id, req.body, req.user.id);
|
||||
const part = recordTransaction(req.params.id, req.body, req.user.id, req.companyId);
|
||||
if (!part) return res.status(404).json({ error: 'Part not found' });
|
||||
return res.status(201).json({ part });
|
||||
});
|
||||
|
||||
// Photos
|
||||
router.post('/parts/:id/photos', editor, (req, res) => {
|
||||
const part = addPhoto(req.params.id, req.body, req.user.id);
|
||||
const part = addPhoto(req.params.id, req.body, req.user.id, req.companyId);
|
||||
if (!part) return res.status(404).json({ error: 'Part not found' });
|
||||
return res.status(201).json({ part });
|
||||
});
|
||||
|
||||
router.delete('/parts/:id/photos/:photoId', editor, (req, res) => {
|
||||
const part = deletePhoto(req.params.id, req.params.photoId, req.user.id);
|
||||
const part = deletePhoto(req.params.id, req.params.photoId, req.user.id, req.companyId);
|
||||
if (!part) return res.status(404).json({ error: 'Photo not found' });
|
||||
return res.json({ part });
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ const assetPm = requireCapability('pm.complete'); // attach to assets & complete
|
||||
|
||||
// PM plan templates
|
||||
router.get('/pm-plans', (req, res) => {
|
||||
res.json({ plans: listPlans() });
|
||||
res.json({ plans: listPlans(req.companyId) });
|
||||
});
|
||||
|
||||
router.get('/pm-plans/:id', (req, res) => {
|
||||
@@ -35,7 +35,7 @@ router.get('/pm-plans/:id', (req, res) => {
|
||||
});
|
||||
|
||||
router.post('/pm-plans', planEditor, (req, res) => {
|
||||
res.status(201).json({ plan: createPlan(req.body, req.user.id) });
|
||||
res.status(201).json({ plan: createPlan(req.body, req.user.id, req.companyId) });
|
||||
});
|
||||
|
||||
router.put('/pm-plans/:id', planEditor, (req, res) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ const express = require('express');
|
||||
const { all, audit, json, now, one, parseJson, run } = require('../db');
|
||||
const { requireCapability } = require('../middleware/auth');
|
||||
const { formatAssetId } = require('../services/assets');
|
||||
const companies = require('../services/companies');
|
||||
const assetClasses = require('../services/assetClasses');
|
||||
const zones = require('../services/zones');
|
||||
const section179Limits = require('../services/section179Limits');
|
||||
@@ -95,30 +96,43 @@ router.delete('/asset-classes/:id', requireCapability('config.manage'), (req, re
|
||||
return res.status(204).end();
|
||||
});
|
||||
|
||||
// Companies (stored in `entities`). Returns the user's accessible active companies for the switcher;
|
||||
// company admins requesting ?includeDisposed=1 get the full management list (incl. disposed).
|
||||
router.get('/entities', (req, res) => {
|
||||
res.json({ entities: all('SELECT * FROM entities ORDER BY name') });
|
||||
if (req.query.includeDisposed === '1' && companies.isCompanyAdmin(req.user)) {
|
||||
return res.json({ entities: companies.listCompanies({ includeDisposed: true }) });
|
||||
}
|
||||
return res.json({ entities: companies.companiesForUser(req.user) });
|
||||
});
|
||||
|
||||
router.post('/entities', requireCapability('config.manage'), (req, res) => {
|
||||
const created = now();
|
||||
const result = run(
|
||||
'INSERT INTO entities (name, legal_name, tax_id, fiscal_year_end_month, base_currency, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[
|
||||
req.body.name,
|
||||
req.body.legal_name || null,
|
||||
req.body.tax_id || null,
|
||||
Number(req.body.fiscal_year_end_month || 12),
|
||||
req.body.base_currency || 'USD',
|
||||
created,
|
||||
created
|
||||
]
|
||||
);
|
||||
audit(req.user.id, 'create', 'entity', result.lastInsertRowid, null, req.body);
|
||||
res.status(201).json({ entity: one('SELECT * FROM entities WHERE id = ?', [result.lastInsertRowid]) });
|
||||
router.post('/entities', requireCapability('config.manage'), (req, res, next) => {
|
||||
try {
|
||||
res.status(201).json({ entity: companies.createCompany(req.body, req.user.id) });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/entities/:id', requireCapability('config.manage'), (req, res) => {
|
||||
const entity = companies.updateCompany(req.params.id, req.body, req.user.id);
|
||||
if (!entity) return res.status(404).json({ error: 'Company not found' });
|
||||
return res.json({ entity });
|
||||
});
|
||||
|
||||
router.post('/entities/:id/dispose', requireCapability('config.manage'), (req, res) => {
|
||||
const result = companies.disposeCompany(req.params.id, req.body || {}, req.user.id);
|
||||
if (!result.ok) return res.status(result.status).json({ error: result.error });
|
||||
return res.json({ entity: result.company });
|
||||
});
|
||||
|
||||
router.post('/entities/:id/restore', requireCapability('config.manage'), (req, res) => {
|
||||
const entity = companies.restoreCompany(req.params.id, req.user.id);
|
||||
if (!entity) return res.status(404).json({ error: 'Company not found' });
|
||||
return res.json({ entity });
|
||||
});
|
||||
|
||||
router.get('/references', (req, res) => {
|
||||
const rows = all('SELECT * FROM reference_values ORDER BY type, name');
|
||||
const rows = all('SELECT * FROM reference_values WHERE entity_id = ? ORDER BY type, name', [req.companyId]);
|
||||
res.json({ references: rows.map((row) => ({ ...row, metadata: parseJson(row.metadata, {}) })) });
|
||||
});
|
||||
|
||||
@@ -144,7 +158,7 @@ function categoryRow(row) {
|
||||
id_template_id: template ? templateId : null,
|
||||
id_template_name: template ? template.name : null,
|
||||
id_template_pattern: template ? template.pattern : null,
|
||||
asset_count: one('SELECT COUNT(*) AS c FROM assets WHERE category = ?', [row.name]).c
|
||||
asset_count: one('SELECT COUNT(*) AS c FROM assets WHERE category = ? AND entity_id = ?', [row.name, row.entity_id]).c
|
||||
};
|
||||
}
|
||||
|
||||
@@ -165,31 +179,31 @@ function resolveCategoryClass(body, prev = {}) {
|
||||
const categoryEditor = requireCapability('config.manage');
|
||||
|
||||
router.get('/asset-categories', (req, res) => {
|
||||
const rows = all("SELECT * FROM reference_values WHERE type = 'category' ORDER BY name");
|
||||
const rows = all("SELECT * FROM reference_values WHERE type = 'category' AND entity_id = ? ORDER BY name", [req.companyId]);
|
||||
res.json({ categories: rows.map(categoryRow) });
|
||||
});
|
||||
|
||||
router.post('/asset-categories', categoryEditor, (req, res) => {
|
||||
const name = String(req.body.name || '').trim();
|
||||
if (!name) return res.status(400).json({ error: 'Category name is required' });
|
||||
if (one("SELECT id FROM reference_values WHERE type = 'category' AND name = ?", [name])) {
|
||||
if (one("SELECT id FROM reference_values WHERE type = 'category' AND name = ? AND entity_id = ?", [name, req.companyId])) {
|
||||
return res.status(400).json({ error: 'That category already exists' });
|
||||
}
|
||||
const { classId, classNumber } = resolveCategoryClass(req.body);
|
||||
const result = run(
|
||||
"INSERT INTO reference_values (type, name, code, metadata) VALUES ('category', ?, ?, ?)",
|
||||
[name, req.body.code || null, json({ id_template_id: req.body.id_template_id || null, asset_class_id: classId, asset_class_number: classNumber })]
|
||||
"INSERT INTO reference_values (entity_id, type, name, code, metadata) VALUES (?, 'category', ?, ?, ?)",
|
||||
[req.companyId, name, req.body.code || null, json({ id_template_id: req.body.id_template_id || null, asset_class_id: classId, asset_class_number: classNumber })]
|
||||
);
|
||||
audit(req.user.id, 'create', 'asset_category', result.lastInsertRowid, null, { name });
|
||||
return res.status(201).json({ category: categoryRow(one('SELECT * FROM reference_values WHERE id = ?', [result.lastInsertRowid])) });
|
||||
});
|
||||
|
||||
router.put('/asset-categories/:id', categoryEditor, (req, res) => {
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'category'", [req.params.id]);
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'category' AND entity_id = ?", [req.params.id, req.companyId]);
|
||||
if (!before) return res.status(404).json({ error: 'Category not found' });
|
||||
const name = String(req.body.name ?? before.name).trim();
|
||||
if (!name) return res.status(400).json({ error: 'Category name is required' });
|
||||
if (name !== before.name && one("SELECT id FROM reference_values WHERE type = 'category' AND name = ?", [name])) {
|
||||
if (name !== before.name && one("SELECT id FROM reference_values WHERE type = 'category' AND name = ? AND entity_id = ?", [name, req.companyId])) {
|
||||
return res.status(400).json({ error: 'That category already exists' });
|
||||
}
|
||||
const meta = parseJson(before.metadata, {});
|
||||
@@ -203,20 +217,20 @@ router.put('/asset-categories/:id', categoryEditor, (req, res) => {
|
||||
run('UPDATE reference_values SET name = ?, code = ?, metadata = ? WHERE id = ?', [name, req.body.code ?? before.code, json(meta), req.params.id]);
|
||||
let renamed = 0;
|
||||
if (name !== before.name) {
|
||||
renamed = run('UPDATE assets SET category = ?, updated_at = ? WHERE category = ?', [name, now(), before.name]).changes || 0;
|
||||
renamed = run('UPDATE assets SET category = ?, updated_at = ? WHERE category = ? AND entity_id = ?', [name, now(), before.name, req.companyId]).changes || 0;
|
||||
}
|
||||
// The class number is shared by every asset in the category — cascade any change.
|
||||
// The class number is shared by every asset in the category — cascade any change (within this company).
|
||||
if (classChanged) {
|
||||
run('UPDATE assets SET asset_class_number = ?, updated_at = ? WHERE category = ?', [meta.asset_class_number, now(), name]);
|
||||
run('UPDATE assets SET asset_class_number = ?, updated_at = ? WHERE category = ? AND entity_id = ?', [meta.asset_class_number, now(), name, req.companyId]);
|
||||
}
|
||||
audit(req.user.id, 'update', 'asset_category', req.params.id, before, { name, renamed_assets: renamed });
|
||||
return res.json({ category: categoryRow(one('SELECT * FROM reference_values WHERE id = ?', [req.params.id])), renamed_assets: renamed });
|
||||
});
|
||||
|
||||
router.delete('/asset-categories/:id', categoryEditor, (req, res) => {
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'category'", [req.params.id]);
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'category' AND entity_id = ?", [req.params.id, req.companyId]);
|
||||
if (!before) return res.status(404).json({ error: 'Category not found' });
|
||||
const affected = one('SELECT COUNT(*) AS c FROM assets WHERE category = ?', [before.name]).c;
|
||||
const affected = one('SELECT COUNT(*) AS c FROM assets WHERE category = ? AND entity_id = ?', [before.name, req.companyId]).c;
|
||||
run('DELETE FROM reference_values WHERE id = ?', [req.params.id]);
|
||||
audit(req.user.id, 'delete', 'asset_category', req.params.id, before, { asset_count: affected });
|
||||
return res.json({ deleted: true, asset_count: affected });
|
||||
@@ -230,50 +244,50 @@ function departmentRow(row) {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
code: row.code,
|
||||
asset_count: one('SELECT COUNT(*) AS c FROM assets WHERE department = ?', [row.name]).c
|
||||
asset_count: one('SELECT COUNT(*) AS c FROM assets WHERE department = ? AND entity_id = ?', [row.name, row.entity_id]).c
|
||||
};
|
||||
}
|
||||
|
||||
router.get('/asset-departments', (req, res) => {
|
||||
const rows = all("SELECT * FROM reference_values WHERE type = 'department' ORDER BY name");
|
||||
const rows = all("SELECT * FROM reference_values WHERE type = 'department' AND entity_id = ? ORDER BY name", [req.companyId]);
|
||||
res.json({ departments: rows.map(departmentRow) });
|
||||
});
|
||||
|
||||
router.post('/asset-departments', categoryEditor, (req, res) => {
|
||||
const name = String(req.body.name || '').trim();
|
||||
if (!name) return res.status(400).json({ error: 'Department name is required' });
|
||||
if (one("SELECT id FROM reference_values WHERE type = 'department' AND name = ?", [name])) {
|
||||
if (one("SELECT id FROM reference_values WHERE type = 'department' AND name = ? AND entity_id = ?", [name, req.companyId])) {
|
||||
return res.status(400).json({ error: 'That department already exists' });
|
||||
}
|
||||
const result = run(
|
||||
"INSERT INTO reference_values (type, name, code, metadata) VALUES ('department', ?, ?, '{}')",
|
||||
[name, req.body.code || null]
|
||||
"INSERT INTO reference_values (entity_id, type, name, code, metadata) VALUES (?, 'department', ?, ?, '{}')",
|
||||
[req.companyId, name, req.body.code || null]
|
||||
);
|
||||
audit(req.user.id, 'create', 'asset_department', result.lastInsertRowid, null, { name });
|
||||
return res.status(201).json({ department: departmentRow(one('SELECT * FROM reference_values WHERE id = ?', [result.lastInsertRowid])) });
|
||||
});
|
||||
|
||||
router.put('/asset-departments/:id', categoryEditor, (req, res) => {
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'department'", [req.params.id]);
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'department' AND entity_id = ?", [req.params.id, req.companyId]);
|
||||
if (!before) return res.status(404).json({ error: 'Department not found' });
|
||||
const name = String(req.body.name ?? before.name).trim();
|
||||
if (!name) return res.status(400).json({ error: 'Department name is required' });
|
||||
if (name !== before.name && one("SELECT id FROM reference_values WHERE type = 'department' AND name = ?", [name])) {
|
||||
if (name !== before.name && one("SELECT id FROM reference_values WHERE type = 'department' AND name = ? AND entity_id = ?", [name, req.companyId])) {
|
||||
return res.status(400).json({ error: 'That department already exists' });
|
||||
}
|
||||
run('UPDATE reference_values SET name = ?, code = ? WHERE id = ?', [name, req.body.code ?? before.code, req.params.id]);
|
||||
let renamed = 0;
|
||||
if (name !== before.name) {
|
||||
renamed = run('UPDATE assets SET department = ?, updated_at = ? WHERE department = ?', [name, now(), before.name]).changes || 0;
|
||||
renamed = run('UPDATE assets SET department = ?, updated_at = ? WHERE department = ? AND entity_id = ?', [name, now(), before.name, req.companyId]).changes || 0;
|
||||
}
|
||||
audit(req.user.id, 'update', 'asset_department', req.params.id, before, { name, renamed_assets: renamed });
|
||||
return res.json({ department: departmentRow(one('SELECT * FROM reference_values WHERE id = ?', [req.params.id])), renamed_assets: renamed });
|
||||
});
|
||||
|
||||
router.delete('/asset-departments/:id', categoryEditor, (req, res) => {
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'department'", [req.params.id]);
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'department' AND entity_id = ?", [req.params.id, req.companyId]);
|
||||
if (!before) return res.status(404).json({ error: 'Department not found' });
|
||||
const affected = one('SELECT COUNT(*) AS c FROM assets WHERE department = ?', [before.name]).c;
|
||||
const affected = one('SELECT COUNT(*) AS c FROM assets WHERE department = ? AND entity_id = ?', [before.name, req.companyId]).c;
|
||||
run('DELETE FROM reference_values WHERE id = ?', [req.params.id]);
|
||||
audit(req.user.id, 'delete', 'asset_department', req.params.id, before, { asset_count: affected });
|
||||
return res.json({ deleted: true, asset_count: affected });
|
||||
@@ -287,50 +301,50 @@ function pmCategoryRow(row) {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
code: row.code,
|
||||
plan_count: one('SELECT COUNT(*) AS c FROM pm_plans WHERE category = ?', [row.name]).c
|
||||
plan_count: one('SELECT COUNT(*) AS c FROM pm_plans WHERE category = ? AND entity_id = ?', [row.name, row.entity_id]).c
|
||||
};
|
||||
}
|
||||
|
||||
router.get('/pm-categories', (req, res) => {
|
||||
const rows = all("SELECT * FROM reference_values WHERE type = 'pm_category' ORDER BY name");
|
||||
const rows = all("SELECT * FROM reference_values WHERE type = 'pm_category' AND entity_id = ? ORDER BY name", [req.companyId]);
|
||||
res.json({ categories: rows.map(pmCategoryRow) });
|
||||
});
|
||||
|
||||
router.post('/pm-categories', categoryEditor, (req, res) => {
|
||||
const name = String(req.body.name || '').trim();
|
||||
if (!name) return res.status(400).json({ error: 'Category name is required' });
|
||||
if (one("SELECT id FROM reference_values WHERE type = 'pm_category' AND name = ?", [name])) {
|
||||
if (one("SELECT id FROM reference_values WHERE type = 'pm_category' AND name = ? AND entity_id = ?", [name, req.companyId])) {
|
||||
return res.status(400).json({ error: 'That PM category already exists' });
|
||||
}
|
||||
const result = run(
|
||||
"INSERT INTO reference_values (type, name, code, metadata) VALUES ('pm_category', ?, ?, '{}')",
|
||||
[name, req.body.code || null]
|
||||
"INSERT INTO reference_values (entity_id, type, name, code, metadata) VALUES (?, 'pm_category', ?, ?, '{}')",
|
||||
[req.companyId, name, req.body.code || null]
|
||||
);
|
||||
audit(req.user.id, 'create', 'pm_category', result.lastInsertRowid, null, { name });
|
||||
return res.status(201).json({ category: pmCategoryRow(one('SELECT * FROM reference_values WHERE id = ?', [result.lastInsertRowid])) });
|
||||
});
|
||||
|
||||
router.put('/pm-categories/:id', categoryEditor, (req, res) => {
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'pm_category'", [req.params.id]);
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'pm_category' AND entity_id = ?", [req.params.id, req.companyId]);
|
||||
if (!before) return res.status(404).json({ error: 'PM category not found' });
|
||||
const name = String(req.body.name ?? before.name).trim();
|
||||
if (!name) return res.status(400).json({ error: 'Category name is required' });
|
||||
if (name !== before.name && one("SELECT id FROM reference_values WHERE type = 'pm_category' AND name = ?", [name])) {
|
||||
if (name !== before.name && one("SELECT id FROM reference_values WHERE type = 'pm_category' AND name = ? AND entity_id = ?", [name, req.companyId])) {
|
||||
return res.status(400).json({ error: 'That PM category already exists' });
|
||||
}
|
||||
run('UPDATE reference_values SET name = ?, code = ? WHERE id = ?', [name, req.body.code ?? before.code, req.params.id]);
|
||||
let renamed = 0;
|
||||
if (name !== before.name) {
|
||||
renamed = run('UPDATE pm_plans SET category = ?, updated_at = ? WHERE category = ?', [name, now(), before.name]).changes || 0;
|
||||
renamed = run('UPDATE pm_plans SET category = ?, updated_at = ? WHERE category = ? AND entity_id = ?', [name, now(), before.name, req.companyId]).changes || 0;
|
||||
}
|
||||
audit(req.user.id, 'update', 'pm_category', req.params.id, before, { name, renamed_plans: renamed });
|
||||
return res.json({ category: pmCategoryRow(one('SELECT * FROM reference_values WHERE id = ?', [req.params.id])), renamed_plans: renamed });
|
||||
});
|
||||
|
||||
router.delete('/pm-categories/:id', categoryEditor, (req, res) => {
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'pm_category'", [req.params.id]);
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'pm_category' AND entity_id = ?", [req.params.id, req.companyId]);
|
||||
if (!before) return res.status(404).json({ error: 'PM category not found' });
|
||||
const affected = one('SELECT COUNT(*) AS c FROM pm_plans WHERE category = ?', [before.name]).c;
|
||||
const affected = one('SELECT COUNT(*) AS c FROM pm_plans WHERE category = ? AND entity_id = ?', [before.name, req.companyId]).c;
|
||||
run('DELETE FROM reference_values WHERE id = ?', [req.params.id]);
|
||||
audit(req.user.id, 'delete', 'pm_category', req.params.id, before, { plan_count: affected });
|
||||
return res.json({ deleted: true, plan_count: affected });
|
||||
@@ -349,30 +363,30 @@ function partCategoryRow(row) {
|
||||
}
|
||||
|
||||
router.get('/part-categories', (req, res) => {
|
||||
const rows = all("SELECT * FROM reference_values WHERE type = 'part_category' ORDER BY name");
|
||||
const rows = all("SELECT * FROM reference_values WHERE type = 'part_category' AND entity_id = ? ORDER BY name", [req.companyId]);
|
||||
res.json({ categories: rows.map(partCategoryRow) });
|
||||
});
|
||||
|
||||
router.post('/part-categories', categoryEditor, (req, res) => {
|
||||
const name = String(req.body.name || '').trim();
|
||||
if (!name) return res.status(400).json({ error: 'Category name is required' });
|
||||
if (one("SELECT id FROM reference_values WHERE type = 'part_category' AND name = ?", [name])) {
|
||||
if (one("SELECT id FROM reference_values WHERE type = 'part_category' AND name = ? AND entity_id = ?", [name, req.companyId])) {
|
||||
return res.status(400).json({ error: 'That part category already exists' });
|
||||
}
|
||||
const result = run(
|
||||
"INSERT INTO reference_values (type, name, code, metadata) VALUES ('part_category', ?, ?, '{}')",
|
||||
[name, req.body.code || null]
|
||||
"INSERT INTO reference_values (entity_id, type, name, code, metadata) VALUES (?, 'part_category', ?, ?, '{}')",
|
||||
[req.companyId, name, req.body.code || null]
|
||||
);
|
||||
audit(req.user.id, 'create', 'part_category', result.lastInsertRowid, null, { name });
|
||||
return res.status(201).json({ category: partCategoryRow(one('SELECT * FROM reference_values WHERE id = ?', [result.lastInsertRowid])) });
|
||||
});
|
||||
|
||||
router.put('/part-categories/:id', categoryEditor, (req, res) => {
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'part_category'", [req.params.id]);
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'part_category' AND entity_id = ?", [req.params.id, req.companyId]);
|
||||
if (!before) return res.status(404).json({ error: 'Part category not found' });
|
||||
const name = String(req.body.name ?? before.name).trim();
|
||||
if (!name) return res.status(400).json({ error: 'Category name is required' });
|
||||
if (name !== before.name && one("SELECT id FROM reference_values WHERE type = 'part_category' AND name = ?", [name])) {
|
||||
if (name !== before.name && one("SELECT id FROM reference_values WHERE type = 'part_category' AND name = ? AND entity_id = ?", [name, req.companyId])) {
|
||||
return res.status(400).json({ error: 'That part category already exists' });
|
||||
}
|
||||
run('UPDATE reference_values SET name = ?, code = ? WHERE id = ?', [name, req.body.code ?? before.code, req.params.id]);
|
||||
@@ -385,7 +399,7 @@ router.put('/part-categories/:id', categoryEditor, (req, res) => {
|
||||
});
|
||||
|
||||
router.delete('/part-categories/:id', categoryEditor, (req, res) => {
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'part_category'", [req.params.id]);
|
||||
const before = one("SELECT * FROM reference_values WHERE id = ? AND type = 'part_category' AND entity_id = ?", [req.params.id, req.companyId]);
|
||||
if (!before) return res.status(404).json({ error: 'Part category not found' });
|
||||
const affected = one('SELECT COUNT(*) AS c FROM parts WHERE category = ?', [before.name]).c;
|
||||
run('DELETE FROM reference_values WHERE id = ?', [req.params.id]);
|
||||
|
||||
@@ -18,12 +18,12 @@ router.get('/reports/catalog', (req, res) => {
|
||||
});
|
||||
|
||||
router.post('/reports/run', (req, res) => {
|
||||
res.json({ report: runReport(req.body.type, req.body.options || {}) });
|
||||
res.json({ report: runReport(req.body.type, { ...(req.body.options || {}), entity_id: req.companyId }) });
|
||||
});
|
||||
|
||||
router.post('/reports/export', async (req, res) => {
|
||||
const format = String(req.body.format || 'csv').toLowerCase();
|
||||
const report = runReport(req.body.type, req.body.options || {});
|
||||
const report = runReport(req.body.type, { ...(req.body.options || {}), entity_id: req.companyId });
|
||||
const output = await exportReport(report, format);
|
||||
const extension = EXPORT_EXTENSIONS[format] || 'txt';
|
||||
res.setHeader('Content-Type', output.contentType);
|
||||
@@ -34,15 +34,16 @@ router.post('/reports/export', async (req, res) => {
|
||||
router.get('/saved-reports', (req, res) => {
|
||||
const reports = all(
|
||||
`SELECT s.*, u.name AS created_by_name FROM saved_reports s
|
||||
LEFT JOIN users u ON u.id = s.created_by ORDER BY s.name`
|
||||
LEFT JOIN users u ON u.id = s.created_by WHERE s.entity_id = ? ORDER BY s.name`,
|
||||
[req.companyId]
|
||||
).map((row) => ({ ...row, options_json: parseJson(row.options_json, {}) }));
|
||||
res.json({ reports });
|
||||
});
|
||||
|
||||
router.post('/saved-reports', requireCapability('reports.save'), (req, res) => {
|
||||
const result = run(
|
||||
'INSERT INTO saved_reports (name, report_type, options_json, created_by, created_at) VALUES (?, ?, ?, ?, ?)',
|
||||
[req.body.name || 'Saved report', req.body.report_type, json(req.body.options || {}), req.user.id, now()]
|
||||
'INSERT INTO saved_reports (entity_id, name, report_type, options_json, created_by, created_at) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[req.companyId, req.body.name || 'Saved report', req.body.report_type, json(req.body.options || {}), req.user.id, now()]
|
||||
);
|
||||
audit(req.user.id, 'create', 'saved_report', result.lastInsertRowid, null, req.body);
|
||||
const saved = one('SELECT * FROM saved_reports WHERE id = ?', [result.lastInsertRowid]);
|
||||
@@ -50,7 +51,7 @@ router.post('/saved-reports', requireCapability('reports.save'), (req, res) => {
|
||||
});
|
||||
|
||||
router.delete('/saved-reports/:id', requireCapability('reports.save'), (req, res) => {
|
||||
const result = run('DELETE FROM saved_reports WHERE id = ?', [req.params.id]);
|
||||
const result = run('DELETE FROM saved_reports WHERE id = ? AND entity_id = ?', [req.params.id, req.companyId]);
|
||||
if (!result.changes) return res.status(404).json({ error: 'Saved report not found' });
|
||||
audit(req.user.id, 'delete', 'saved_report', req.params.id, null, null);
|
||||
return res.status(204).end();
|
||||
@@ -59,19 +60,19 @@ router.delete('/saved-reports/:id', requireCapability('reports.save'), (req, res
|
||||
router.get('/reports/depreciation', (req, res) => {
|
||||
const year = Number(req.query.year || new Date().getFullYear());
|
||||
const bookType = req.query.book || 'GAAP';
|
||||
res.json(depreciationReport(year, bookType));
|
||||
res.json(depreciationReport(year, bookType, req.companyId));
|
||||
});
|
||||
|
||||
router.get('/reports/monthly-depreciation', (req, res) => {
|
||||
const year = Number(req.query.year || new Date().getFullYear());
|
||||
const bookType = req.query.book || 'GAAP';
|
||||
res.json(monthlyDepreciationReport(year, bookType));
|
||||
res.json(monthlyDepreciationReport(year, bookType, req.companyId));
|
||||
});
|
||||
|
||||
router.get('/reports/depreciation.pdf', (req, res) => {
|
||||
const year = Number(req.query.year || new Date().getFullYear());
|
||||
const book = req.query.book || 'GAAP';
|
||||
writeDepreciationPdf(res, year, book);
|
||||
writeDepreciationPdf(res, year, book, req.companyId);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -7,7 +7,8 @@ const router = express.Router();
|
||||
router.get('/templates', (req, res) => {
|
||||
const templates = all(
|
||||
`SELECT t.*, (SELECT COUNT(*) FROM assets a WHERE a.template_id = t.id) AS asset_count
|
||||
FROM asset_templates t ORDER BY t.name`
|
||||
FROM asset_templates t WHERE t.entity_id = ? ORDER BY t.name`,
|
||||
[req.companyId]
|
||||
).map((row) => ({
|
||||
...row,
|
||||
defaults: parseJson(row.defaults, {}),
|
||||
@@ -19,15 +20,15 @@ router.get('/templates', (req, res) => {
|
||||
router.post('/templates', requireCapability('config.manage'), (req, res) => {
|
||||
const created = now();
|
||||
const result = run(
|
||||
'INSERT INTO asset_templates (name, description, defaults, custom_fields, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[req.body.name, req.body.description || null, json(req.body.defaults || {}), json(req.body.custom_fields || []), created, created]
|
||||
'INSERT INTO asset_templates (entity_id, name, description, defaults, custom_fields, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[req.companyId, req.body.name, req.body.description || null, json(req.body.defaults || {}), json(req.body.custom_fields || []), created, created]
|
||||
);
|
||||
audit(req.user.id, 'create', 'asset_template', result.lastInsertRowid, null, req.body);
|
||||
res.status(201).json({ template: one('SELECT * FROM asset_templates WHERE id = ?', [result.lastInsertRowid]) });
|
||||
});
|
||||
|
||||
router.put('/templates/:id', requireCapability('config.manage'), (req, res) => {
|
||||
const before = one('SELECT * FROM asset_templates WHERE id = ?', [req.params.id]);
|
||||
const before = one('SELECT * FROM asset_templates WHERE id = ? AND entity_id = ?', [req.params.id, req.companyId]);
|
||||
if (!before) return res.status(404).json({ error: 'Template not found' });
|
||||
run(
|
||||
'UPDATE asset_templates SET name = ?, description = ?, defaults = ?, custom_fields = ?, updated_at = ? WHERE id = ?',
|
||||
@@ -47,7 +48,7 @@ router.put('/templates/:id', requireCapability('config.manage'), (req, res) => {
|
||||
// Deleting a template unlinks it from any assets (their stored field values are kept)
|
||||
// and removes it from the picker for future assets.
|
||||
router.delete('/templates/:id', requireCapability('config.manage'), (req, res) => {
|
||||
const before = one('SELECT * FROM asset_templates WHERE id = ?', [req.params.id]);
|
||||
const before = one('SELECT * FROM asset_templates WHERE id = ? AND entity_id = ?', [req.params.id, req.companyId]);
|
||||
if (!before) return res.status(404).json({ error: 'Template not found' });
|
||||
const affected = one('SELECT COUNT(*) AS c FROM assets WHERE template_id = ?', [req.params.id]).c;
|
||||
run('UPDATE assets SET template_id = NULL, updated_at = ? WHERE template_id = ?', [now(), req.params.id]);
|
||||
|
||||
Reference in New Issue
Block a user