Added Multi-Company Support
This commit is contained in:
@@ -100,7 +100,7 @@ function decorate(row, totals) {
|
||||
};
|
||||
}
|
||||
|
||||
function listParts(query = {}) {
|
||||
function listParts(query = {}, companyId) {
|
||||
const rows = all(
|
||||
`SELECT p.*,
|
||||
sup.id AS supplier_id, sup.organization AS supplier_org, sup.first_name AS supplier_first, sup.last_name AS supplier_last,
|
||||
@@ -111,7 +111,8 @@ function listParts(query = {}) {
|
||||
SELECT part_id, SUM(quantity) AS on_hand, SUM(reserved) AS reserved, COUNT(*) AS locations
|
||||
FROM part_stock GROUP BY part_id
|
||||
) st ON st.part_id = p.id
|
||||
WHERE (? IS NULL OR p.status = ?)
|
||||
WHERE (? IS NULL OR p.entity_id = ?)
|
||||
AND (? IS NULL OR p.status = ?)
|
||||
AND (? IS NULL OR p.category = ?)
|
||||
AND (? IS NULL OR (
|
||||
p.part_number LIKE '%' || ? || '%' OR p.name LIKE '%' || ? || '%' OR
|
||||
@@ -120,6 +121,7 @@ function listParts(query = {}) {
|
||||
))
|
||||
ORDER BY p.name`,
|
||||
[
|
||||
companyId || null, companyId || null,
|
||||
query.status || null, query.status || null,
|
||||
query.category || null, query.category || null,
|
||||
query.search || null, query.search || null, query.search || null,
|
||||
@@ -134,11 +136,11 @@ function listParts(query = {}) {
|
||||
return rows;
|
||||
}
|
||||
|
||||
function getPart(id) {
|
||||
function getPart(id, companyId) {
|
||||
const row = one(
|
||||
`SELECT p.*, sup.id AS supplier_id, sup.organization AS supplier_org, sup.first_name AS supplier_first, sup.last_name AS supplier_last
|
||||
FROM parts p LEFT JOIN contacts sup ON sup.id = p.supplier_contact_id WHERE p.id = ?`,
|
||||
[id]
|
||||
FROM parts p LEFT JOIN contacts sup ON sup.id = p.supplier_contact_id WHERE p.id = ? AND (? IS NULL OR p.entity_id = ?)`,
|
||||
[id, companyId || null, companyId || null]
|
||||
);
|
||||
if (!row) return null;
|
||||
const stock = stockRows(id);
|
||||
@@ -163,28 +165,28 @@ function normalize(body) {
|
||||
return input;
|
||||
}
|
||||
|
||||
function createPart(body, userId) {
|
||||
function createPart(body, userId, companyId) {
|
||||
const input = normalize(body);
|
||||
if (!input.part_number) throw Object.assign(new Error('A part number is required'), { status: 400 });
|
||||
if (one('SELECT id FROM parts WHERE part_number = ?', [input.part_number])) {
|
||||
if (one('SELECT id FROM parts WHERE part_number = ? AND entity_id = ?', [input.part_number, companyId])) {
|
||||
throw Object.assign(new Error('That part number already exists'), { status: 400 });
|
||||
}
|
||||
const ts = now();
|
||||
const cols = COLUMNS.filter((c) => input[c] !== undefined);
|
||||
const result = run(
|
||||
`INSERT INTO parts (${cols.join(', ')}, created_by, created_at, updated_at)
|
||||
VALUES (${cols.map(() => '?').join(', ')}, ?, ?, ?)`,
|
||||
[...cols.map((c) => input[c]), userId, ts, ts]
|
||||
`INSERT INTO parts (entity_id, ${cols.join(', ')}, created_by, created_at, updated_at)
|
||||
VALUES (?, ${cols.map(() => '?').join(', ')}, ?, ?, ?)`,
|
||||
[companyId || null, ...cols.map((c) => input[c]), userId, ts, ts]
|
||||
);
|
||||
audit(userId, 'create', 'part', result.lastInsertRowid, null, { part_number: input.part_number });
|
||||
return getPart(result.lastInsertRowid);
|
||||
return getPart(result.lastInsertRowid, companyId);
|
||||
}
|
||||
|
||||
function updatePart(id, body, userId) {
|
||||
function updatePart(id, body, userId, companyId) {
|
||||
const before = one('SELECT * FROM parts WHERE id = ?', [id]);
|
||||
if (!before) return null;
|
||||
if (!before || (companyId && before.entity_id !== companyId)) return null;
|
||||
const input = normalize({ ...before, ...body });
|
||||
if (input.part_number !== before.part_number && one('SELECT id FROM parts WHERE part_number = ? AND id <> ?', [input.part_number, id])) {
|
||||
if (input.part_number !== before.part_number && one('SELECT id FROM parts WHERE part_number = ? AND entity_id = ? AND id <> ?', [input.part_number, before.entity_id, id])) {
|
||||
throw Object.assign(new Error('That part number already exists'), { status: 400 });
|
||||
}
|
||||
run(
|
||||
@@ -192,12 +194,12 @@ function updatePart(id, body, userId) {
|
||||
[...COLUMNS.map((c) => input[c] ?? null), now(), id]
|
||||
);
|
||||
audit(userId, 'update', 'part', id, before, body);
|
||||
return getPart(id);
|
||||
return getPart(id, companyId);
|
||||
}
|
||||
|
||||
function deletePart(id, userId) {
|
||||
function deletePart(id, userId, companyId) {
|
||||
const before = one('SELECT * FROM parts WHERE id = ?', [id]);
|
||||
if (!before) return false;
|
||||
if (!before || (companyId && before.entity_id !== companyId)) return false;
|
||||
run('DELETE FROM parts WHERE id = ?', [id]);
|
||||
audit(userId, 'delete', 'part', id, before, null);
|
||||
return true;
|
||||
@@ -205,8 +207,12 @@ function deletePart(id, userId) {
|
||||
|
||||
// ---- Stock locations -------------------------------------------------------
|
||||
|
||||
function saveStock(partId, body, userId) {
|
||||
if (!one('SELECT id FROM parts WHERE id = ?', [partId])) return null;
|
||||
function partInCompany(partId, companyId) {
|
||||
return Boolean(one('SELECT id FROM parts WHERE id = ? AND (? IS NULL OR entity_id = ?)', [partId, companyId || null, companyId || null]));
|
||||
}
|
||||
|
||||
function saveStock(partId, body, userId, companyId) {
|
||||
if (!partInCompany(partId, companyId)) return null;
|
||||
const ts = now();
|
||||
const location = body.location_contact_id || null;
|
||||
const aisle = body.aisle || null;
|
||||
@@ -228,20 +234,21 @@ function saveStock(partId, body, userId) {
|
||||
);
|
||||
audit(userId, 'create', 'part_stock', result.lastInsertRowid, null, { part_id: partId });
|
||||
}
|
||||
return getPart(partId);
|
||||
return getPart(partId, companyId);
|
||||
}
|
||||
|
||||
function deleteStock(partId, stockId, userId) {
|
||||
function deleteStock(partId, stockId, userId, companyId) {
|
||||
if (!partInCompany(partId, companyId)) return null;
|
||||
const result = run('DELETE FROM part_stock WHERE id = ? AND part_id = ?', [stockId, partId]);
|
||||
if (!result.changes) return null;
|
||||
audit(userId, 'delete', 'part_stock', stockId, { part_id: partId }, null);
|
||||
return getPart(partId);
|
||||
return getPart(partId, companyId);
|
||||
}
|
||||
|
||||
// ---- Stock movements -------------------------------------------------------
|
||||
|
||||
function recordTransaction(partId, body, userId) {
|
||||
if (!one('SELECT id FROM parts WHERE id = ?', [partId])) return null;
|
||||
function recordTransaction(partId, body, userId, companyId) {
|
||||
if (!partInCompany(partId, companyId)) return null;
|
||||
const type = TRANSACTION_TYPES.includes(body.type) ? body.type : 'receive';
|
||||
const stock = one('SELECT * FROM part_stock WHERE id = ? AND part_id = ?', [body.stock_id, partId]);
|
||||
if (!stock) throw Object.assign(new Error('Select a stock location for this movement'), { status: 400 });
|
||||
@@ -272,28 +279,29 @@ function recordTransaction(partId, body, userId) {
|
||||
]
|
||||
);
|
||||
audit(userId, 'movement', 'part', partId, null, { type, quantity: qty, stock_id: stock.id });
|
||||
return getPart(partId);
|
||||
return getPart(partId, companyId);
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Photos ----------------------------------------------------------------
|
||||
|
||||
function addPhoto(partId, body, userId) {
|
||||
if (!one('SELECT id FROM parts WHERE id = ?', [partId])) return null;
|
||||
function addPhoto(partId, body, userId, companyId) {
|
||||
if (!partInCompany(partId, companyId)) return null;
|
||||
if (!body.data) throw Object.assign(new Error('Photo data is required'), { status: 400 });
|
||||
run(
|
||||
'INSERT INTO part_photos (part_id, name, mime_type, data_base64, created_at) VALUES (?, ?, ?, ?, ?)',
|
||||
[partId, body.name || null, body.mime || body.mime_type || 'image/jpeg', body.data, now()]
|
||||
);
|
||||
audit(userId, 'create', 'part_photo', partId, null, { name: body.name });
|
||||
return getPart(partId);
|
||||
return getPart(partId, companyId);
|
||||
}
|
||||
|
||||
function deletePhoto(partId, photoId, userId) {
|
||||
function deletePhoto(partId, photoId, userId, companyId) {
|
||||
if (!partInCompany(partId, companyId)) return null;
|
||||
const result = run('DELETE FROM part_photos WHERE id = ? AND part_id = ?', [photoId, partId]);
|
||||
if (!result.changes) return null;
|
||||
audit(userId, 'delete', 'part_photo', photoId, { part_id: partId }, null);
|
||||
return getPart(partId);
|
||||
return getPart(partId, companyId);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
Reference in New Issue
Block a user