Add bulk install-in-device from dashboard and unretire action

- Dashboard bulk toolbar: select batteries, pick a device, click 'Install
  in device'; confirms before moving already-installed batteries; enforces
  slot capacity and warns on brand mix
- Battery detail: 'Unretire Battery' button replaces 'Retire Battery' when
  battery is retired, restoring it to available status
- Tests: 3 new bulk-install-device tests (capacity block, move, success);
  42 total passing
This commit is contained in:
2026-04-12 21:08:48 -05:00
parent 81e87d2fe2
commit 0869ef3d5e
4 changed files with 137 additions and 0 deletions
+4
View File
@@ -185,6 +185,10 @@
<form method="post" action="{{ url_for('battery_retire', battery_id=battery.id) }}">
<button class="btn btn-secondary" type="submit">Retire Battery</button>
</form>
{% else %}
<form method="post" action="{{ url_for('battery_unretire', battery_id=battery.id) }}">
<button class="btn btn-secondary" type="submit">Unretire Battery</button>
</form>
{% endif %}
<a class="btn btn-danger" href="{{ url_for('battery_delete', battery_id=battery.id) }}">Delete Battery</a>
+28
View File
@@ -113,6 +113,17 @@
</span>
<button class="btn btn-sm btn-primary" name="action" value="set_field" type="submit">Apply</button>
</span>
<span style="display:flex;gap:0.35rem;align-items:center;">
<select id="bulk-device-select" name="device_id"
style="padding:0.25rem 0.5rem;font-size:0.85rem;border:1px solid #cbd5e1;border-radius:4px;">
<option value="">— select device —</option>
{% for d in devices %}
<option value="{{ d.id }}">{{ d.name }} ({{ d.installed_count() }}/{{ d.battery_slots }})</option>
{% endfor %}
</select>
<button class="btn btn-sm btn-primary" name="action" value="install_device" type="submit"
onclick="return confirmInstallDevice()">Install in device</button>
</span>
</div>
<div class="table-wrap">
@@ -260,6 +271,23 @@ function applyFilters() {
updateToolbar();
}
function confirmInstallDevice() {
var deviceSel = document.getElementById('bulk-device-select');
if (!deviceSel.value) { deviceSel.focus(); return false; }
var movers = Array.prototype.filter.call(
document.querySelectorAll('.row-cb:checked'),
function(cb) { return cb.closest('tr').dataset.status === 'installed'; }
);
if (movers.length > 0) {
var n = movers.length;
return confirm(
n + ' selected batter' + (n === 1 ? 'y is' : 'ies are') +
' already installed elsewhere. Unassign and move to the selected device?'
);
}
return true;
}
function quickAssign(action, batteryId) {
var sel = document.getElementById('qas-' + batteryId);
if (!sel.value) { sel.focus(); return; }