Aplikasi billing & manajemen ISP (fiber optic) — PWA pelanggan, portal teknisi & kolektor, admin panel lengkap, multi-vendor OLT, RADIUS, WhatsApp/Telegram, pembayaran QRIS. Kredit & lisensi (ISC): - Base kode & repo: alijayanet/billing-rtrw oleh Alijaya Net — terima kasih banyak atas base kode dan repo yang bermanfaat. - Add-on: GoWA, LibreSpeed/OpenSpeedTest, Uptime Kuma, notification-forwarder. - Dikembangkan menjadi OPEN-ISP oleh oxpoe — https://github.com/oxpoe/open-isp
172 lines
6.3 KiB
JavaScript
172 lines
6.3 KiB
JavaScript
/**
|
|
* Service: Manajemen Inventaris / Gudang (Warehouse)
|
|
*/
|
|
const db = require('../config/database');
|
|
const { logger } = require('../config/logger');
|
|
|
|
// ─── CATEGORIES ───────────────────────────────────────────────────────────
|
|
function getAllCategories() {
|
|
return db.prepare('SELECT * FROM inventory_categories ORDER BY name ASC').all();
|
|
}
|
|
|
|
function createCategory(data) {
|
|
const { name, description } = data;
|
|
return db.prepare('INSERT INTO inventory_categories (name, description) VALUES (?, ?)').run(name, description);
|
|
}
|
|
|
|
function updateCategory(id, data) {
|
|
const { name, description } = data;
|
|
return db.prepare('UPDATE inventory_categories SET name = ?, description = ? WHERE id = ?').run(name, description, id);
|
|
}
|
|
|
|
function deleteCategory(id) {
|
|
return db.prepare('DELETE FROM inventory_categories WHERE id = ?').run(id);
|
|
}
|
|
|
|
// ─── ITEMS ───────────────────────────────────────────────────────────────
|
|
function getAllItems(search = '') {
|
|
let query = `
|
|
SELECT i.*, c.name as category_name,
|
|
(SELECT SUM(quantity) FROM inventory_stock s WHERE s.item_id = i.id AND s.status = 'available') as stock_available,
|
|
(SELECT SUM(quantity) FROM inventory_stock s WHERE s.item_id = i.id AND s.status = 'assigned') as stock_assigned
|
|
FROM inventory_items i
|
|
LEFT JOIN inventory_categories c ON i.category_id = c.id
|
|
`;
|
|
const params = [];
|
|
if (search) {
|
|
query += ' WHERE i.name LIKE ? OR i.brand LIKE ? OR i.model LIKE ?';
|
|
const s = `%${search}%`;
|
|
params.push(s, s, s);
|
|
}
|
|
query += ' ORDER BY i.name ASC';
|
|
return db.prepare(query).all(...params);
|
|
}
|
|
|
|
function getItemById(id) {
|
|
return db.prepare(`
|
|
SELECT i.*, c.name as category_name
|
|
FROM inventory_items i
|
|
LEFT JOIN inventory_categories c ON i.category_id = c.id
|
|
WHERE i.id = ?
|
|
`).get(id);
|
|
}
|
|
|
|
function createItem(data) {
|
|
const { category_id, name, brand, model, unit, min_stock, description } = data;
|
|
return db.prepare(`
|
|
INSERT INTO inventory_items (category_id, name, brand, model, unit, min_stock, description)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
`).run(category_id, name, brand, model, unit, min_stock, description);
|
|
}
|
|
|
|
function updateItem(id, data) {
|
|
const { category_id, name, brand, model, unit, min_stock, description } = data;
|
|
return db.prepare(`
|
|
UPDATE inventory_items
|
|
SET category_id = ?, name = ?, brand = ?, model = ?, unit = ?, min_stock = ?, description = ?
|
|
WHERE id = ?
|
|
`).run(category_id, name, brand, model, unit, min_stock, description, id);
|
|
}
|
|
|
|
function deleteItem(id) {
|
|
return db.prepare('DELETE FROM inventory_items WHERE id = ?').run(id);
|
|
}
|
|
|
|
// ─── STOCK ───────────────────────────────────────────────────────────────
|
|
function getStockByItem(itemId) {
|
|
return db.prepare(`
|
|
SELECT s.*, cust.name as customer_name
|
|
FROM inventory_stock s
|
|
LEFT JOIN customers cust ON s.assigned_to_customer_id = cust.id
|
|
WHERE s.item_id = ?
|
|
ORDER BY s.created_at DESC
|
|
`).all(itemId);
|
|
}
|
|
|
|
function addStock(data, actor = 'Admin') {
|
|
const { item_id, serial_number, quantity, condition, location, note } = data;
|
|
|
|
const run = db.transaction(() => {
|
|
const result = db.prepare(`
|
|
INSERT INTO inventory_stock (item_id, serial_number, quantity, condition, location, note)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
`).run(item_id, serial_number || null, quantity, condition || 'new', location || 'Gudang Utama', note || '');
|
|
|
|
db.prepare(`
|
|
INSERT INTO inventory_logs (item_id, stock_id, type, quantity, actor, note)
|
|
VALUES (?, ?, 'in', ?, ?, ?)
|
|
`).run(item_id, result.lastInsertRowid, quantity, actor, note || 'Stock Masuk');
|
|
|
|
return result;
|
|
});
|
|
|
|
return run();
|
|
}
|
|
|
|
function assignStockToCustomer(stockId, customerId, actor = 'Admin', note = '') {
|
|
const stock = db.prepare('SELECT * FROM inventory_stock WHERE id = ?').get(stockId);
|
|
if (!stock) throw new Error('Stock tidak ditemukan');
|
|
if (stock.status === 'assigned') throw new Error('Stock sudah terpasang di pelanggan lain');
|
|
|
|
const run = db.transaction(() => {
|
|
db.prepare(`
|
|
UPDATE inventory_stock
|
|
SET status = 'assigned', assigned_to_customer_id = ?, updated_at = (NOW_LOCAL())
|
|
WHERE id = ?
|
|
`).run(customerId, stockId);
|
|
|
|
db.prepare(`
|
|
INSERT INTO inventory_logs (item_id, stock_id, type, quantity, actor, note)
|
|
VALUES (?, ?, 'out', ?, ?, ?)
|
|
`).run(stock.item_id, stockId, stock.quantity, actor, note || `Terpasang ke pelanggan ID: ${customerId}`);
|
|
});
|
|
|
|
return run();
|
|
}
|
|
|
|
function adjustStock(stockId, newQuantity, note, actor = 'Admin') {
|
|
const stock = db.prepare('SELECT * FROM inventory_stock WHERE id = ?').get(stockId);
|
|
if (!stock) throw new Error('Stock tidak ditemukan');
|
|
|
|
const run = db.transaction(() => {
|
|
const diff = newQuantity - stock.quantity;
|
|
db.prepare('UPDATE inventory_stock SET quantity = ?, updated_at = (NOW_LOCAL()) WHERE id = ?').run(newQuantity, stockId);
|
|
|
|
db.prepare(`
|
|
INSERT INTO inventory_logs (item_id, stock_id, type, quantity, actor, note)
|
|
VALUES (?, ?, 'adjust', ?, ?, ?)
|
|
`).run(stock.item_id, stockId, diff, actor, note || 'Penyesuaian Stock');
|
|
});
|
|
|
|
return run();
|
|
}
|
|
|
|
function getInventoryLogs(limit = 100) {
|
|
return db.prepare(`
|
|
SELECT l.*, i.name as item_name, s.serial_number
|
|
FROM inventory_logs l
|
|
LEFT JOIN inventory_items i ON l.item_id = i.id
|
|
LEFT JOIN inventory_stock s ON l.stock_id = s.id
|
|
ORDER BY l.created_at DESC
|
|
LIMIT ?
|
|
`).all(limit);
|
|
}
|
|
|
|
function getLowStockItems() {
|
|
return db.prepare(`
|
|
SELECT i.*, c.name as category_name, SUM(s.quantity) as current_stock
|
|
FROM inventory_items i
|
|
LEFT JOIN inventory_categories c ON i.category_id = c.id
|
|
LEFT JOIN inventory_stock s ON s.item_id = i.id AND s.status = 'available'
|
|
GROUP BY i.id
|
|
HAVING current_stock <= i.min_stock OR current_stock IS NULL
|
|
`).all();
|
|
}
|
|
|
|
module.exports = {
|
|
getAllCategories, createCategory, updateCategory, deleteCategory,
|
|
getAllItems, getItemById, createItem, updateItem, deleteItem,
|
|
getStockByItem, addStock, assignStockToCustomer, adjustStock,
|
|
getInventoryLogs, getLowStockItems
|
|
};
|