feat: automatically remove items from stock when empty (quantity <= 0)
Build and Push Fridger Container / Build & Push Fridger Image (push) Successful in 1m16s
Build and Push Fridger Container / Build & Push Fridger Image (push) Successful in 1m16s
This commit is contained in:
@@ -49,7 +49,7 @@ db.exec(`
|
|||||||
|
|
||||||
// Item functions
|
// Item functions
|
||||||
function getAllItems({ search = '', category = '', location = '', status = 'all' } = {}) {
|
function getAllItems({ search = '', category = '', location = '', status = 'all' } = {}) {
|
||||||
let query = `SELECT * FROM items WHERE 1=1`;
|
let query = `SELECT * FROM items WHERE quantity > 0`;
|
||||||
const params = [];
|
const params = [];
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
@@ -73,7 +73,7 @@ function getAllItems({ search = '', category = '', location = '', status = 'all'
|
|||||||
} else if (status === 'full') {
|
} else if (status === 'full') {
|
||||||
query += ` AND (is_opened = 0 AND quantity >= initial_quantity)`;
|
query += ` AND (is_opened = 0 AND quantity >= initial_quantity)`;
|
||||||
} else if (status === 'low') {
|
} else if (status === 'low') {
|
||||||
query += ` AND (quantity <= (initial_quantity * 0.25) OR quantity = 0)`;
|
query += ` AND quantity <= (initial_quantity * 0.25)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
query += ` ORDER BY updated_at DESC, name ASC`;
|
query += ` ORDER BY updated_at DESC, name ASC`;
|
||||||
@@ -148,6 +148,21 @@ function updateItem(id, item) {
|
|||||||
|
|
||||||
const qty = item.quantity !== undefined ? Number(item.quantity) : existing.quantity;
|
const qty = item.quantity !== undefined ? Number(item.quantity) : existing.quantity;
|
||||||
const init_qty = item.initial_quantity !== undefined ? Number(item.initial_quantity) : existing.initial_quantity;
|
const init_qty = item.initial_quantity !== undefined ? Number(item.initial_quantity) : existing.initial_quantity;
|
||||||
|
|
||||||
|
// If item updated to 0 or less, remove it from stock automatically
|
||||||
|
if (qty <= 0) {
|
||||||
|
deleteItem(id);
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: item.name || existing.name,
|
||||||
|
quantity: 0,
|
||||||
|
initial_quantity: init_qty,
|
||||||
|
unit: item.unit || existing.unit,
|
||||||
|
deleted: true,
|
||||||
|
is_empty: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const is_opened = item.is_opened !== undefined ? (item.is_opened ? 1 : 0) : existing.is_opened;
|
const is_opened = item.is_opened !== undefined ? (item.is_opened ? 1 : 0) : existing.is_opened;
|
||||||
let opened_at = existing.opened_at;
|
let opened_at = existing.opened_at;
|
||||||
if (is_opened && !existing.is_opened) {
|
if (is_opened && !existing.is_opened) {
|
||||||
@@ -218,8 +233,30 @@ function adjustQuantity(id, { newQuantity, delta, markOpened }) {
|
|||||||
targetQty = item.quantity + Number(delta);
|
targetQty = item.quantity + Number(delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent negative quantity
|
// If item is finished/empty (0 or less), automatically remove it from stock
|
||||||
if (targetQty < 0) targetQty = 0;
|
if (targetQty <= 0) {
|
||||||
|
logActivity({
|
||||||
|
item_id: id,
|
||||||
|
item_name: item.name,
|
||||||
|
action: 'consume_all',
|
||||||
|
amount_change: -item.quantity,
|
||||||
|
unit: item.unit,
|
||||||
|
note: `Consumed all (${item.quantity} ${item.unit}) - automatically removed from stock`
|
||||||
|
});
|
||||||
|
|
||||||
|
const delStmt = db.prepare(`DELETE FROM items WHERE id = ?`);
|
||||||
|
delStmt.run(id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: item.name,
|
||||||
|
quantity: 0,
|
||||||
|
initial_quantity: item.initial_quantity,
|
||||||
|
unit: item.unit,
|
||||||
|
deleted: true,
|
||||||
|
is_empty: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const is_opened = markOpened !== undefined ? (markOpened ? 1 : 0) : (item.is_opened || (targetQty < item.initial_quantity ? 1 : 0));
|
const is_opened = markOpened !== undefined ? (markOpened ? 1 : 0) : (item.is_opened || (targetQty < item.initial_quantity ? 1 : 0));
|
||||||
const opened_at = is_opened && !item.opened_at ? new Date().toISOString().slice(0, 10) : item.opened_at;
|
const opened_at = is_opened && !item.opened_at ? new Date().toISOString().slice(0, 10) : item.opened_at;
|
||||||
@@ -240,12 +277,10 @@ function adjustQuantity(id, { newQuantity, delta, markOpened }) {
|
|||||||
logActivity({
|
logActivity({
|
||||||
item_id: id,
|
item_id: id,
|
||||||
item_name: item.name,
|
item_name: item.name,
|
||||||
action: targetQty === 0 ? 'consume_all' : (diff < 0 ? 'consume_part' : 'restock'),
|
action: diff < 0 ? 'consume_part' : 'restock',
|
||||||
amount_change: diff,
|
amount_change: diff,
|
||||||
unit: item.unit,
|
unit: item.unit,
|
||||||
note: targetQty === 0
|
note: `${diff < 0 ? 'Consumed' : 'Added'} ${Math.abs(diff)} ${item.unit} (remaining: ${targetQty} ${item.unit})`
|
||||||
? `Consumed all (${item.quantity} ${item.unit})`
|
|
||||||
: `${diff < 0 ? 'Consumed' : 'Added'} ${Math.abs(diff)} ${item.unit} (remaining: ${targetQty} ${item.unit})`
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return getItemById(id);
|
return getItemById(id);
|
||||||
@@ -287,8 +322,29 @@ function removePacket(id, count = 1) {
|
|||||||
const removeAmount = packSize * count;
|
const removeAmount = packSize * count;
|
||||||
let newQty = item.quantity - removeAmount;
|
let newQty = item.quantity - removeAmount;
|
||||||
|
|
||||||
|
// If item is completely removed/empty, remove from stock automatically
|
||||||
if (newQty <= 0) {
|
if (newQty <= 0) {
|
||||||
newQty = 0;
|
logActivity({
|
||||||
|
item_id: id,
|
||||||
|
item_name: item.name,
|
||||||
|
action: 'remove_packet',
|
||||||
|
amount_change: -item.quantity,
|
||||||
|
unit: item.unit,
|
||||||
|
note: `Removed all remaining stock (${count} packet(s)) - automatically removed from stock`
|
||||||
|
});
|
||||||
|
|
||||||
|
const delStmt = db.prepare(`DELETE FROM items WHERE id = ?`);
|
||||||
|
delStmt.run(id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: item.name,
|
||||||
|
quantity: 0,
|
||||||
|
initial_quantity: item.initial_quantity,
|
||||||
|
unit: item.unit,
|
||||||
|
deleted: true,
|
||||||
|
is_empty: true
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const stmt = db.prepare(`
|
const stmt = db.prepare(`
|
||||||
|
|||||||
+12
-4
@@ -546,9 +546,13 @@ async function confirmAndFinishAction() {
|
|||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const updated = await res.json();
|
const updated = await res.json();
|
||||||
playBeepSound();
|
playBeepSound();
|
||||||
showToast(`✓ ${action === 'add' ? 'Added' : 'Removed'} ${state.manage.packetCount} pack(s) of ${updated.name}`, 'success');
|
if (updated.deleted || updated.is_empty || updated.quantity <= 0) {
|
||||||
|
showToast(`✓ Removed empty ${updated.name} from stock`, 'success');
|
||||||
|
} else {
|
||||||
|
showToast(`✓ ${action === 'add' ? 'Added' : 'Removed'} ${state.manage.packetCount} pack(s) of ${updated.name}`, 'success');
|
||||||
|
}
|
||||||
|
|
||||||
// Return to manage station
|
// Return to manager station
|
||||||
resetTabletManageStation();
|
resetTabletManageStation();
|
||||||
await loadStats();
|
await loadStats();
|
||||||
await loadItems();
|
await loadItems();
|
||||||
@@ -567,9 +571,13 @@ async function confirmAndFinishAction() {
|
|||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const updated = await res.json();
|
const updated = await res.json();
|
||||||
playBeepSound();
|
playBeepSound();
|
||||||
showToast(`✓ Set ${updated.name} to ${updated.quantity} ${updated.unit}`, 'success');
|
if (updated.deleted || updated.is_empty || updated.quantity <= 0) {
|
||||||
|
showToast(`✓ Finished ${updated.name} (0 ${updated.unit}): removed from stock`, 'success');
|
||||||
|
} else {
|
||||||
|
showToast(`✓ Set ${updated.name} to ${updated.quantity} ${updated.unit}`, 'success');
|
||||||
|
}
|
||||||
|
|
||||||
// Return to manage station
|
// Return to manager station
|
||||||
resetTabletManageStation();
|
resetTabletManageStation();
|
||||||
await loadStats();
|
await loadStats();
|
||||||
await loadItems();
|
await loadItems();
|
||||||
|
|||||||
@@ -76,15 +76,25 @@ console.log('Testing absolute set (100g remaining)...');
|
|||||||
const updatedAbs = db.adjustQuantity(newItem.id, { newQuantity: 100 });
|
const updatedAbs = db.adjustQuantity(newItem.id, { newQuantity: 100 });
|
||||||
assert.strictEqual(updatedAbs.quantity, 100);
|
assert.strictEqual(updatedAbs.quantity, 100);
|
||||||
|
|
||||||
// Consume remaining
|
// Consume remaining -> should automatically remove from stock
|
||||||
console.log('Testing consume all...');
|
console.log('Testing consume all (auto-remove on empty)...');
|
||||||
const finished = db.adjustQuantity(newItem.id, { newQuantity: 0 });
|
const finished = db.adjustQuantity(newItem.id, { newQuantity: 0 });
|
||||||
assert.strictEqual(finished.quantity, 0);
|
assert.strictEqual(finished.quantity, 0);
|
||||||
|
assert.strictEqual(finished.deleted, true, 'Result should indicate item was deleted');
|
||||||
|
assert.strictEqual(db.getItemById(newItem.id), undefined, 'Item should be automatically removed from DB when empty');
|
||||||
|
|
||||||
// Delete test item
|
// Test packet removal to 0 -> should automatically remove from stock
|
||||||
const deleted = db.deleteItem(newItem.id);
|
console.log('Testing packet removal to 0 (auto-remove on empty)...');
|
||||||
assert.strictEqual(deleted, true);
|
const autoRemovePack = db.createItem({
|
||||||
assert.strictEqual(db.getItemById(newItem.id), undefined);
|
name: 'Test Packet Auto-Remove',
|
||||||
|
quantity: 250,
|
||||||
|
initial_quantity: 250,
|
||||||
|
unit: 'g'
|
||||||
|
});
|
||||||
|
const packFinished = db.removePacket(autoRemovePack.id, 1);
|
||||||
|
assert.strictEqual(packFinished.quantity, 0);
|
||||||
|
assert.strictEqual(packFinished.deleted, true);
|
||||||
|
assert.strictEqual(db.getItemById(autoRemovePack.id), undefined, 'Packet should be automatically removed when 0 left');
|
||||||
|
|
||||||
// Test stats
|
// Test stats
|
||||||
const stats = db.getStats();
|
const stats = db.getStats();
|
||||||
|
|||||||
Reference in New Issue
Block a user