Fix material usage logging: remaining weight mode, edit modal, and consolidate duplicate logic
- InventoryController: extract RecordInventoryUsageAsync helper; both LogUsage (scan page) and LogMaterial (jobs modal, moved from JobsController) call it — no more duplicate save/GL logic across two controllers - Log Material modal: replace radio buttons with prominent toggle buttons so the active mode (Amount Used vs Amount Remaining) is always visually obvious; add always-visible preview line showing exactly what will be logged before saving - Edit Usage modal: add quantity field (pre-populated from existing transaction) with delta adjustment to InventoryItem.QuantityOnHand on save; include completed/terminal jobs in the dropdown so entries can be corrected after a job is marked done - Scan page job picker: include jobs completed within the last 7 days (marked with '(completed)') so usage can be logged after a job is finished Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,9 +6,64 @@
|
||||
let _items = [];
|
||||
let _jobPowderIds = new Set();
|
||||
let _modal = null;
|
||||
let _selectedItemId = 0;
|
||||
let _entryMethod = 'used'; // 'used' | 'remaining'
|
||||
|
||||
// ── Mode toggle ───────────────────────────────────────────────────────────
|
||||
|
||||
window.lmSetMethod = function (method) {
|
||||
_entryMethod = method;
|
||||
const btnUsed = document.getElementById('lmBtnUsed');
|
||||
const btnRemaining = document.getElementById('lmBtnRemaining');
|
||||
const hintEl = document.getElementById('lmMethodHint');
|
||||
const qtyLabel = document.getElementById('lmQtyLabel');
|
||||
|
||||
if (method === 'remaining') {
|
||||
btnUsed.className = 'btn btn-outline-primary';
|
||||
btnRemaining.className = 'btn btn-primary';
|
||||
hintEl.textContent = 'Enter how much is LEFT in the bag — the system calculates what was used.';
|
||||
qtyLabel.innerHTML = 'Weight Remaining in Bag <span class="text-danger">*</span>';
|
||||
} else {
|
||||
btnUsed.className = 'btn btn-primary';
|
||||
btnRemaining.className = 'btn btn-outline-primary';
|
||||
hintEl.textContent = 'Enter how much powder you took out of the bag.';
|
||||
qtyLabel.innerHTML = 'Quantity Used <span class="text-danger">*</span>';
|
||||
}
|
||||
lmUpdatePreview();
|
||||
};
|
||||
|
||||
// ── Live preview (always visible once qty + item are set) ─────────────────
|
||||
|
||||
function lmUpdatePreview() {
|
||||
const computedDiv = document.getElementById('lmComputedUsed');
|
||||
if (!_selectedItemId || !computedDiv) { computedDiv?.classList.add('d-none'); return; }
|
||||
const item = _items.find(it => it.id === _selectedItemId);
|
||||
const onHand = item ? (parseFloat(item.quantityOnHand) || 0) : 0;
|
||||
const qty = parseFloat(document.getElementById('lmQuantity').value) || 0;
|
||||
if (qty <= 0) { computedDiv.classList.add('d-none'); return; }
|
||||
|
||||
const uom = item?.unitOfMeasure || '';
|
||||
if (_entryMethod === 'remaining') {
|
||||
const used = onHand - qty;
|
||||
if (used <= 0) {
|
||||
computedDiv.className = 'form-text fw-semibold text-danger';
|
||||
computedDiv.textContent = 'Remaining cannot be ≥ current stock (' + onHand.toFixed(2) + ' ' + uom + ').';
|
||||
} else {
|
||||
computedDiv.className = 'form-text fw-semibold text-success';
|
||||
computedDiv.textContent =
|
||||
'Will log ' + used.toFixed(2) + ' ' + uom + ' used — new balance: ' + qty.toFixed(2) + ' ' + uom;
|
||||
}
|
||||
} else {
|
||||
const newBal = onHand - qty;
|
||||
const col = newBal < 0 ? 'text-danger' : 'text-success';
|
||||
computedDiv.className = 'form-text fw-semibold ' + col;
|
||||
computedDiv.textContent =
|
||||
'Will log ' + qty.toFixed(2) + ' ' + uom + ' used — new balance: ' + newBal.toFixed(2) + ' ' + uom;
|
||||
}
|
||||
computedDiv.classList.remove('d-none');
|
||||
}
|
||||
|
||||
// ── Combobox state ────────────────────────────────────────────────────────
|
||||
let _selectedItemId = 0;
|
||||
|
||||
function lmComboInput() {
|
||||
const q = document.getElementById('lmItemSearch')?.value?.toLowerCase() || '';
|
||||
@@ -16,7 +71,7 @@
|
||||
lmComboShow();
|
||||
_selectedItemId = 0;
|
||||
document.getElementById('lmItemBalance').classList.add('d-none');
|
||||
lmOnQtyInput();
|
||||
lmUpdatePreview();
|
||||
}
|
||||
|
||||
function lmComboOpen() {
|
||||
@@ -111,7 +166,7 @@
|
||||
const balDiv = document.getElementById('lmItemBalance');
|
||||
balDiv.textContent = 'Current stock: ' + qty.toFixed(2) + (uom ? ' ' + uom : '');
|
||||
balDiv.classList.remove('d-none');
|
||||
lmOnQtyInput();
|
||||
lmUpdatePreview();
|
||||
};
|
||||
|
||||
window.lmComboInput = lmComboInput;
|
||||
@@ -152,39 +207,14 @@
|
||||
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ── Quantity / label logic ────────────────────────────────────────────────
|
||||
|
||||
function lmOnQtyInput() {
|
||||
const method = document.querySelector('input[name="lmEntryMethod"]:checked')?.value;
|
||||
if (method !== 'remaining') {
|
||||
document.getElementById('lmComputedUsed').classList.add('d-none');
|
||||
return;
|
||||
}
|
||||
if (!_selectedItemId) {
|
||||
document.getElementById('lmComputedUsed').classList.add('d-none');
|
||||
return;
|
||||
}
|
||||
const item = _items.find(it => it.id === _selectedItemId);
|
||||
const onHand = item ? (parseFloat(item.quantityOnHand) || 0) : 0;
|
||||
const remaining = parseFloat(document.getElementById('lmQuantity').value) || 0;
|
||||
const used = onHand - remaining;
|
||||
const computedDiv = document.getElementById('lmComputedUsed');
|
||||
computedDiv.textContent = 'Usage = ' + onHand.toFixed(2) + ' − ' + remaining.toFixed(2) + ' = ' + used.toFixed(2) + (item?.unitOfMeasure ? ' ' + item.unitOfMeasure : '');
|
||||
computedDiv.classList.remove('d-none');
|
||||
}
|
||||
|
||||
window.lmUpdateQuantityLabel = function () {
|
||||
const method = document.querySelector('input[name="lmEntryMethod"]:checked')?.value;
|
||||
document.getElementById('lmQtyLabel').innerHTML =
|
||||
(method === 'remaining' ? 'Quantity Remaining' : 'Quantity Used') +
|
||||
' <span class="text-danger">*</span>';
|
||||
lmOnQtyInput();
|
||||
};
|
||||
// ── Kept for backward-compat with any inline onchange handlers that may exist ─
|
||||
window.lmUpdateQuantityLabel = function () { lmUpdatePreview(); };
|
||||
|
||||
// ── Modal open / save ─────────────────────────────────────────────────────
|
||||
|
||||
window.openLogMaterialModal = function () {
|
||||
_selectedItemId = 0;
|
||||
_entryMethod = 'used';
|
||||
document.getElementById('lmItemSearch').value = '';
|
||||
document.getElementById('lmItemBalance').classList.add('d-none');
|
||||
document.getElementById('lmQuantity').value = '';
|
||||
@@ -193,8 +223,7 @@
|
||||
document.getElementById('lmNotes').value = '';
|
||||
document.getElementById('lmAlert').classList.add('d-none');
|
||||
document.getElementById('lmSaveBtn').disabled = false;
|
||||
document.getElementById('lmMethodUsed').checked = true;
|
||||
window.lmUpdateQuantityLabel();
|
||||
lmSetMethod('used');
|
||||
lmComboClose();
|
||||
if (_modal) _modal.show();
|
||||
};
|
||||
@@ -214,14 +243,14 @@
|
||||
const qtyInput = parseFloat(document.getElementById('lmQuantity').value) || 0;
|
||||
if (qtyInput <= 0) { showError('Please enter a quantity greater than zero.'); return; }
|
||||
|
||||
const method = document.querySelector('input[name="lmEntryMethod"]:checked')?.value;
|
||||
const item = _items.find(it => it.id === _selectedItemId);
|
||||
const onHand = item ? (parseFloat(item.quantityOnHand) || 0) : 0;
|
||||
|
||||
let quantityUsed = qtyInput;
|
||||
if (method === 'remaining') {
|
||||
const item = _items.find(it => it.id === _selectedItemId);
|
||||
const onHand = item ? (parseFloat(item.quantityOnHand) || 0) : 0;
|
||||
if (_entryMethod === 'remaining') {
|
||||
quantityUsed = onHand - qtyInput;
|
||||
if (quantityUsed <= 0) {
|
||||
showError('Remaining quantity cannot be equal to or greater than the current stock (' + onHand.toFixed(2) + ').');
|
||||
showError('Remaining cannot be equal to or greater than the current stock (' + onHand.toFixed(2) + ').');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -269,9 +298,8 @@
|
||||
_jobPowderIds = new Set(cfg.jobPowderIds || []);
|
||||
_modal = new bootstrap.Modal(document.getElementById('logMaterialModal'));
|
||||
|
||||
document.getElementById('lmQuantity').addEventListener('input', lmOnQtyInput);
|
||||
document.getElementById('lmQuantity').addEventListener('input', lmUpdatePreview);
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!e.target.closest('#lmItemSearch') &&
|
||||
!e.target.closest('#lmItemDropdown') &&
|
||||
|
||||
Reference in New Issue
Block a user