335 lines
12 KiB
JavaScript
335 lines
12 KiB
JavaScript
// Room Inventory (KIR) Routes
|
|
import express from 'express';
|
|
import pool, { generateId } from '../db.js';
|
|
|
|
const router = express.Router();
|
|
|
|
// POST /api/getRoomInventory
|
|
router.post('/getRoomInventory', async (req, res) => {
|
|
try {
|
|
const { roomName } = req.body;
|
|
|
|
// Get or create room
|
|
let [rooms] = await pool.query(
|
|
'SELECT id, room_name, location_code FROM room_inventory WHERE room_name = ?',
|
|
[roomName]
|
|
);
|
|
|
|
if (rooms.length === 0) {
|
|
// Create new room inventory
|
|
const [result] = await pool.query(
|
|
'INSERT INTO room_inventory (room_name) VALUES (?)',
|
|
[roomName]
|
|
);
|
|
rooms = [{ id: result.insertId, room_name: roomName, location_code: '' }];
|
|
}
|
|
|
|
const room = rooms[0];
|
|
|
|
// Get items
|
|
const [items] = await pool.query(
|
|
'SELECT * FROM inventory_items WHERE room_inventory_id = ? ORDER BY name',
|
|
[room.id]
|
|
);
|
|
|
|
const formattedItems = items.map(item => ({
|
|
id: item.id,
|
|
name: item.name,
|
|
brand: item.brand,
|
|
year: item.year,
|
|
code: item.code,
|
|
unit: item.unit,
|
|
price: parseFloat(item.price),
|
|
conditionGood: item.condition_good,
|
|
conditionLess: item.condition_less,
|
|
conditionBad: item.condition_bad,
|
|
notes: item.notes,
|
|
serialNumber: item.serial_number,
|
|
size: item.size,
|
|
material: item.material
|
|
}));
|
|
|
|
res.json({
|
|
status: 'success',
|
|
data: {
|
|
items: formattedItems,
|
|
locationCode: room.location_code || ''
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Get room inventory error:', error);
|
|
res.json({ status: 'error', message: error.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/saveRoomInventory
|
|
router.post('/saveRoomInventory', async (req, res) => {
|
|
try {
|
|
const { roomName, locationCode, items } = req.body;
|
|
|
|
// Get or create room
|
|
let [rooms] = await pool.query(
|
|
'SELECT id FROM room_inventory WHERE room_name = ?',
|
|
[roomName]
|
|
);
|
|
|
|
let roomId;
|
|
if (rooms.length === 0) {
|
|
const [result] = await pool.query(
|
|
'INSERT INTO room_inventory (room_name, location_code) VALUES (?, ?)',
|
|
[roomName, locationCode || '']
|
|
);
|
|
roomId = result.insertId;
|
|
} else {
|
|
roomId = rooms[0].id;
|
|
await pool.query(
|
|
'UPDATE room_inventory SET location_code = ? WHERE id = ?',
|
|
[locationCode || '', roomId]
|
|
);
|
|
}
|
|
|
|
// Delete existing items
|
|
await pool.query('DELETE FROM inventory_items WHERE room_inventory_id = ?', [roomId]);
|
|
|
|
// Insert new items
|
|
if (items && items.length > 0) {
|
|
for (const item of items) {
|
|
const itemId = item.id || generateId();
|
|
await pool.query(
|
|
`INSERT INTO inventory_items
|
|
(id, room_inventory_id, name, brand, year, code, unit, price,
|
|
condition_good, condition_less, condition_bad, notes, serial_number, size, material)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
itemId, roomId, item.name, item.brand || '', item.year || '',
|
|
item.code || '', item.unit || 'Buah', item.price || 0,
|
|
item.conditionGood || 0, item.conditionLess || 0, item.conditionBad || 0,
|
|
item.notes || '', item.serialNumber || '', item.size || '', item.material || ''
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
res.json({ status: 'success', message: 'Inventaris ruangan berhasil disimpan' });
|
|
} catch (error) {
|
|
console.error('Save room inventory error:', error);
|
|
res.json({ status: 'error', message: error.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/lookupItemByQR
|
|
// Lookup an inventory item by code + year (from QR data format ITEM-{code}-{year}-{index})
|
|
router.post('/lookupItemByQR', async (req, res) => {
|
|
try {
|
|
const { code, year } = req.body;
|
|
|
|
if (!code || !year) {
|
|
return res.json({ status: 'error', message: 'Parameter code dan year wajib diisi.' });
|
|
}
|
|
|
|
const [items] = await pool.query(
|
|
`SELECT ii.*, ri.room_name
|
|
FROM inventory_items ii
|
|
JOIN room_inventory ri ON ii.room_inventory_id = ri.id
|
|
WHERE ii.code = ? AND ii.year = ?
|
|
LIMIT 10`,
|
|
[code, year]
|
|
);
|
|
|
|
if (items.length === 0) {
|
|
return res.json({ status: 'not_found', message: 'Barang tidak ditemukan.' });
|
|
}
|
|
|
|
const formattedItems = items.map(item => ({
|
|
id: item.id,
|
|
name: item.name,
|
|
brand: item.brand,
|
|
year: item.year,
|
|
code: item.code,
|
|
unit: item.unit,
|
|
price: parseFloat(item.price),
|
|
conditionGood: item.condition_good,
|
|
conditionLess: item.condition_less,
|
|
conditionBad: item.condition_bad,
|
|
notes: item.notes,
|
|
serialNumber: item.serial_number,
|
|
size: item.size,
|
|
material: item.material,
|
|
roomName: item.room_name
|
|
}));
|
|
|
|
res.json({ status: 'success', data: formattedItems });
|
|
} catch (error) {
|
|
console.error('Lookup item by QR error:', error);
|
|
res.json({ status: 'error', message: error.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/updateItemCondition
|
|
// Update condition counts for a single inventory item
|
|
router.post('/updateItemCondition', async (req, res) => {
|
|
try {
|
|
const { itemId, conditionGood, conditionLess, conditionBad } = req.body;
|
|
|
|
if (!itemId) {
|
|
return res.json({ status: 'error', message: 'Parameter itemId wajib diisi.' });
|
|
}
|
|
|
|
// Verify item exists
|
|
const [existing] = await pool.query('SELECT id FROM inventory_items WHERE id = ?', [itemId]);
|
|
if (existing.length === 0) {
|
|
return res.json({ status: 'error', message: 'Barang tidak ditemukan.' });
|
|
}
|
|
|
|
await pool.query(
|
|
`UPDATE inventory_items
|
|
SET condition_good = ?, condition_less = ?, condition_bad = ?
|
|
WHERE id = ?`,
|
|
[conditionGood || 0, conditionLess || 0, conditionBad || 0, itemId]
|
|
);
|
|
|
|
res.json({ status: 'success', message: 'Kondisi barang berhasil diperbarui.' });
|
|
} catch (error) {
|
|
console.error('Update item condition error:', error);
|
|
res.json({ status: 'error', message: error.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/getDamagedItems
|
|
// Get all items with condition_less > 0 or condition_bad > 0 in a specific room
|
|
router.post('/getDamagedItems', async (req, res) => {
|
|
try {
|
|
const { roomName } = req.body;
|
|
|
|
if (!roomName) {
|
|
return res.json({ status: 'error', message: 'Parameter roomName wajib diisi.' });
|
|
}
|
|
|
|
const [rooms] = await pool.query(
|
|
'SELECT id FROM room_inventory WHERE room_name = ?',
|
|
[roomName]
|
|
);
|
|
|
|
if (rooms.length === 0) {
|
|
return res.json({ status: 'success', data: [] });
|
|
}
|
|
|
|
const roomId = rooms[0].id;
|
|
|
|
const [items] = await pool.query(
|
|
`SELECT * FROM inventory_items
|
|
WHERE room_inventory_id = ? AND (condition_less > 0 OR condition_bad > 0)
|
|
ORDER BY name`,
|
|
[roomId]
|
|
);
|
|
|
|
const formattedItems = items.map(item => ({
|
|
id: item.id,
|
|
name: item.name,
|
|
brand: item.brand,
|
|
year: item.year,
|
|
code: item.code,
|
|
unit: item.unit,
|
|
price: parseFloat(item.price),
|
|
conditionGood: item.condition_good,
|
|
conditionLess: item.condition_less,
|
|
conditionBad: item.condition_bad,
|
|
notes: item.notes,
|
|
serialNumber: item.serial_number,
|
|
size: item.size,
|
|
material: item.material
|
|
}));
|
|
|
|
res.json({ status: 'success', data: formattedItems });
|
|
} catch (error) {
|
|
console.error('Get damaged items error:', error);
|
|
res.json({ status: 'error', message: error.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/getInventoryConditionStats
|
|
// Get summary of inventory condition across ALL rooms for dashboard
|
|
router.post('/getInventoryConditionStats', async (req, res) => {
|
|
try {
|
|
const [rows] = await pool.query(
|
|
`SELECT
|
|
COUNT(*) as totalItems,
|
|
SUM(condition_good) as totalGood,
|
|
SUM(condition_less) as totalLess,
|
|
SUM(condition_bad) as totalBad,
|
|
SUM(CASE WHEN condition_less > 0 OR condition_bad > 0 THEN 1 ELSE 0 END) as itemsNeedRepair
|
|
FROM inventory_items`
|
|
);
|
|
|
|
const data = rows[0] || {};
|
|
res.json({
|
|
status: 'success',
|
|
data: {
|
|
totalItems: parseInt(data.totalItems) || 0,
|
|
totalGood: parseInt(data.totalGood) || 0,
|
|
totalLess: parseInt(data.totalLess) || 0,
|
|
totalBad: parseInt(data.totalBad) || 0,
|
|
itemsNeedRepair: parseInt(data.itemsNeedRepair) || 0
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Get inventory condition stats error:', error);
|
|
res.json({ status: 'error', message: error.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/getConditionByRoom
|
|
// Get condition breakdown grouped by room for dashboard charts
|
|
router.post('/getConditionByRoom', async (req, res) => {
|
|
try {
|
|
const [rows] = await pool.query(
|
|
`SELECT
|
|
ri.room_name,
|
|
SUM(ii.condition_good) as total_good,
|
|
SUM(ii.condition_less) as total_less,
|
|
SUM(ii.condition_bad) as total_bad,
|
|
COUNT(*) as item_count,
|
|
SUM(CASE WHEN ii.condition_less > 0 OR ii.condition_bad > 0 THEN 1 ELSE 0 END) as damaged_items
|
|
FROM inventory_items ii
|
|
JOIN room_inventory ri ON ii.room_inventory_id = ri.id
|
|
GROUP BY ri.room_name
|
|
ORDER BY (SUM(ii.condition_less) + SUM(ii.condition_bad)) DESC`
|
|
);
|
|
|
|
// Also get recent damaged items with room info
|
|
const [recentDamaged] = await pool.query(
|
|
`SELECT ii.name, ii.brand, ii.code, ii.condition_less, ii.condition_bad, ri.room_name
|
|
FROM inventory_items ii
|
|
JOIN room_inventory ri ON ii.room_inventory_id = ri.id
|
|
WHERE ii.condition_less > 0 OR ii.condition_bad > 0
|
|
ORDER BY (ii.condition_less + ii.condition_bad) DESC
|
|
LIMIT 10`
|
|
);
|
|
|
|
const roomData = rows.map(r => ({
|
|
roomName: r.room_name,
|
|
totalGood: parseInt(r.total_good) || 0,
|
|
totalLess: parseInt(r.total_less) || 0,
|
|
totalBad: parseInt(r.total_bad) || 0,
|
|
itemCount: parseInt(r.item_count) || 0,
|
|
damagedItems: parseInt(r.damaged_items) || 0
|
|
}));
|
|
|
|
const damagedList = recentDamaged.map(d => ({
|
|
name: d.name,
|
|
brand: d.brand || '',
|
|
code: d.code || '',
|
|
conditionLess: d.condition_less,
|
|
conditionBad: d.condition_bad,
|
|
roomName: d.room_name
|
|
}));
|
|
|
|
res.json({ status: 'success', data: { rooms: roomData, recentDamaged: damagedList } });
|
|
} catch (error) {
|
|
console.error('Get condition by room error:', error);
|
|
res.json({ status: 'error', message: error.message });
|
|
}
|
|
});
|
|
|
|
export default router;
|