Inline add-stock prompt when label scan finds existing inventory item

When a scanned label matches an item already in the tenant's inventory,
the scanner now opens an inline modal asking the user to add stock to the
existing item rather than navigating away or creating a duplicate.

- InventoryController.AddStock: new POST endpoint that creates a Purchase
  transaction, updates QuantityOnHand, and optionally updates UnitCost /
  LastPurchasePrice when a new cost is provided. Returns new balance as JSON.
- InventoryController.ScanLabel: extends the duplicate-detection response
  to include existingQuantityOnHand and existingUnitOfMeasure so the modal
  can display current stock level.
- _LabelScanModal.cshtml: adds #addStockModal with quantity (+ UOM label),
  optional unit cost (pre-filled from scan), optional notes, Add Stock CTA,
  and an escape hatch to create a new entry instead.
- inventory-label-scan.js: when scan returns existingInventoryId the JS
  opens addStockModal instead of a warning banner. Submitting POSTs to
  /Inventory/AddStock and shows the updated balance in a success bar with
  a link to the item. The 'new entry instead' path hides the modal and
  pre-fills the create form with a softer duplicate warning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-03 19:59:43 -04:00
parent 3aeec4ffb2
commit 5e3b0b9ddf
3 changed files with 260 additions and 37 deletions
@@ -1,3 +1,52 @@
<!-- Add-stock modal: shown when label scan matches an existing inventory item -->
<div class="modal fade" id="addStockModal" tabindex="-1" aria-labelledby="addStockModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" style="max-width:420px;">
<div class="modal-content">
<div class="modal-header py-2">
<h6 class="modal-title" id="addStockModalLabel">
<i class="bi bi-box-seam me-2 text-success"></i>Already in Inventory
</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body pb-2">
<p class="mb-1">
<strong id="add-stock-item-name" class="text-body"></strong> is already in your inventory.
</p>
<p class="text-muted small mb-3">
Current stock: <strong id="add-stock-current-qty"></strong>
</p>
<div class="mb-3">
<label class="form-label fw-semibold small">Quantity to Add <span class="text-danger">*</span></label>
<div class="input-group input-group-sm">
<input type="number" id="add-stock-qty" class="form-control" min="0.01" step="0.01" placeholder="e.g. 5">
<span class="input-group-text" id="add-stock-uom-label">lbs</span>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold small">Unit Cost <span class="text-muted fw-normal">(optional — updates item cost)</span></label>
<div class="input-group input-group-sm">
<span class="input-group-text">$</span>
<input type="number" id="add-stock-cost" class="form-control" min="0" step="0.01" placeholder="Leave blank to keep current">
</div>
</div>
<div class="mb-2">
<label class="form-label fw-semibold small">Notes <span class="text-muted fw-normal">(optional)</span></label>
<input type="text" id="add-stock-notes" class="form-control form-control-sm" placeholder="e.g. New bag received">
</div>
<div id="add-stock-status" class="d-none small mt-2"></div>
</div>
<div class="modal-footer flex-column align-items-stretch gap-2 py-2">
<button id="add-stock-confirm-btn" type="button" class="btn btn-success">
<i class="bi bi-plus-circle me-1"></i>Add Stock
</button>
<button id="add-stock-new-btn" type="button" class="btn btn-link btn-sm text-muted">
Add as a new entry instead (e.g. different lot)
</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="labelScanModal" tabindex="-1" aria-labelledby="labelScanModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" style="max-width:480px;">
<div class="modal-content">