diff --git a/app.py b/app.py index 2bf9922..9de1d5e 100644 --- a/app.py +++ b/app.py @@ -634,8 +634,10 @@ def create_app(config_object="config"): devices = db.query(Device).order_by(Device.name).all() device_types = sorted({d.device_type for d in devices if d.device_type}) device_locations = sorted({d.location for d in devices if d.location}) + device_battery_sizes = sorted({d.battery_size for d in devices if d.battery_size}) return render_template("device_list.html", devices=devices, - device_types=device_types, device_locations=device_locations) + device_types=device_types, device_locations=device_locations, + device_battery_sizes=device_battery_sizes) # ------------------------------------------------------------------ # # Devices — add @@ -646,19 +648,31 @@ def create_app(config_object="config"): all_devices = db.query(Device).all() device_types = sorted({d.device_type for d in all_devices if d.device_type}) device_locations = sorted({d.location for d in all_devices if d.location}) + device_battery_sizes = sorted({d.battery_size for d in all_devices if d.battery_size}) if request.method == "POST": name = request.form.get("name", "").strip() slots_raw = request.form.get("battery_slots", "1").strip() notes = request.form.get("notes", "").strip() or None device_type = request.form.get("device_type", "").strip() or None + battery_size = request.form.get("battery_size", "").strip() or None location = request.form.get("location", "").strip() or None if not name: flash("Device name is required.", "error") return render_template("device_add.html", device_types=device_types, - device_locations=device_locations), 400 + device_locations=device_locations, + device_battery_sizes=device_battery_sizes), 400 + + if not battery_size: + flash("Battery size is required.", "error") + return render_template("device_add.html", + device_types=device_types, + device_locations=device_locations, + device_battery_sizes=device_battery_sizes, + form_name=name, form_notes=notes or "", + form_device_type=request.form.get("device_type", "")), 400 try: slots = int(slots_raw) @@ -669,6 +683,7 @@ def create_app(config_object="config"): return render_template("device_add.html", device_types=device_types, device_locations=device_locations, + device_battery_sizes=device_battery_sizes, form_name=name, form_notes=notes or "", form_device_type=request.form.get("device_type", "")), 400 @@ -677,19 +692,22 @@ def create_app(config_object="config"): return render_template("device_add.html", device_types=device_types, device_locations=device_locations, + device_battery_sizes=device_battery_sizes, form_name=name, form_slots=slots, form_notes=notes or "", form_device_type=request.form.get("device_type", "")), 400 device = Device(name=name, battery_slots=slots, notes=notes, - device_type=device_type, location=location) + device_type=device_type, battery_size=battery_size, + location=location) db.add(device) db.commit() flash(f"Device '{name}' added.", "success") return redirect(url_for("device_list")) return render_template("device_add.html", device_types=device_types, - device_locations=device_locations) + device_locations=device_locations, + device_battery_sizes=device_battery_sizes) # ------------------------------------------------------------------ # # Devices — detail @@ -700,12 +718,22 @@ def create_app(config_object="config"): device = db.get(Device, device_id) if device is None: abort(404) - brands = [r[0] for r in db.query(Battery.brand).distinct().order_by(Battery.brand).all()] - available_batteries = (db.query(Battery) - .filter_by(status="available") - .order_by(Battery.label).all()) - device_types = sorted({d.device_type for d in db.query(Device).all() if d.device_type}) - device_locations = sorted({d.location for d in db.query(Device).all() if d.location}) + all_devices = db.query(Device).all() + brands_q = db.query(Battery.brand).filter(Battery.status == "available") + if device.battery_size: + brands_q = brands_q.filter( + (Battery.size == device.battery_size) | (Battery.size == None) + ) + brands = [r[0] for r in brands_q.distinct().order_by(Battery.brand).all()] + avail_q = db.query(Battery).filter_by(status="available") + if device.battery_size: + avail_q = avail_q.filter( + (Battery.size == device.battery_size) | (Battery.size == None) + ) + available_batteries = avail_q.order_by(Battery.label).all() + device_types = sorted({d.device_type for d in all_devices if d.device_type}) + device_locations = sorted({d.location for d in all_devices if d.location}) + device_battery_sizes = sorted({d.battery_size for d in all_devices if d.battery_size}) ha_live_pct = None if ha_client.enabled and device.ha_entity_id: ha_live_pct = ha_client.get_state(device.ha_entity_id, timeout=1) @@ -727,6 +755,7 @@ def create_app(config_object="config"): available_batteries=available_batteries, device_types=device_types, device_locations=device_locations, + device_battery_sizes=device_battery_sizes, ha_enabled=ha_client.enabled, ha_live_pct=ha_live_pct) @@ -763,6 +792,7 @@ def create_app(config_object="config"): device.battery_slots = slots device.notes = notes device.device_type = device_type + device.battery_size = request.form.get("battery_size", "").strip() or None device.location = request.form.get("location", "").strip() or None device.ha_entity_id = request.form.get("ha_entity_id", "").strip() or None db.commit() @@ -807,11 +837,12 @@ def create_app(config_object="config"): # Validate availability before writing anything for brand, qty in pairs: - available_count = ( - db.query(func.count(Battery.id)) - .filter_by(brand=brand, status="available") - .scalar() - ) + avail_q = db.query(func.count(Battery.id)).filter_by(brand=brand, status="available") + if device.battery_size: + avail_q = avail_q.filter( + (Battery.size == device.battery_size) | (Battery.size == None) + ) + available_count = avail_q.scalar() if available_count < qty: flash( f"Need {qty} {brand}, but only {available_count} available.", @@ -822,13 +853,12 @@ def create_app(config_object="config"): # All checks passed — perform installs total_installed = 0 for brand, qty in pairs: - batch = ( - db.query(Battery) - .filter_by(brand=brand, status="available") - .order_by(Battery.id) - .limit(qty) - .all() - ) + batch_q = db.query(Battery).filter_by(brand=brand, status="available") + if device.battery_size: + batch_q = batch_q.filter( + (Battery.size == device.battery_size) | (Battery.size == None) + ) + batch = batch_q.order_by(Battery.id).limit(qty).all() for b in batch: b.status = "installed" b.device_id = device.id @@ -870,6 +900,12 @@ def create_app(config_object="config"): f"{', '.join(existing_brands)}. Mixing brands is not recommended.", "warning", ) + if device.battery_size and battery.size and battery.size != device.battery_size: + flash( + f"Warning: {device.name} requires {device.battery_size} batteries, " + f"but {battery.label} is {battery.size}.", + "warning", + ) battery.status = "installed" battery.device_id = device.id db.commit() diff --git a/models.py b/models.py index e7de76f..b45f68f 100644 --- a/models.py +++ b/models.py @@ -13,6 +13,7 @@ class Device(Base): name = Column(String(100), nullable=False, unique=True) battery_slots = Column(Integer, nullable=False, default=1) device_type = Column(String(50), nullable=True) + battery_size = Column(String(20), nullable=True) # AA, AAA, 9V, CR2032 … location = Column(String(100), nullable=True) notes = Column(Text, nullable=True) ha_entity_id = Column(String(100), nullable=True) # e.g. "sensor.tv_remote_battery" diff --git a/templates/device_add.html b/templates/device_add.html index eafb04b..e2c9447 100644 --- a/templates/device_add.html +++ b/templates/device_add.html @@ -18,6 +18,27 @@ value="{{ form_slots|default(1) }}" min="1" required> +
+ + {% set _preset_sizes = ['AA','AAA','C','D','9V','CR2032','CR2025','CR2016','18650','14500','16340','26650','LR44/AG13'] %} + {% set _cur_size = form_battery_size|default('') %} + + +
+
{% set _preset_types = ['Remote Control','Game Controller','Flashlight','Lock','Sensor','Toy','Clock','Smoke Detector'] %} diff --git a/templates/device_detail.html b/templates/device_detail.html index ed89f55..7b0a1be 100644 --- a/templates/device_detail.html +++ b/templates/device_detail.html @@ -39,6 +39,12 @@ {{ device.device_type }} {% endif %} + {% if device.battery_size %} + + Battery Size + {{ device.battery_size }} + + {% endif %} {% if device.has_mixed_brands() %} Warning @@ -79,7 +85,7 @@

Install Batteries

{% set free_slots = device.battery_slots - device.installed_count() %} -

{{ free_slots }} slot(s) free

+

{{ free_slots }} slot(s) free{% if device.battery_size %} — showing {{ device.battery_size }} batteries only{% endif %}

Brand @@ -115,6 +121,14 @@ function editTypeSelectChanged(sel) { input.style.display = 'none'; input.value = sel.value; } } +function editSizeSelectChanged(sel) { + var input = document.getElementById('edit-battery-size'); + if (sel.value === '__new__') { + input.style.display = ''; input.value = ''; input.focus(); + } else { + input.style.display = 'none'; input.value = sel.value; + } +} function editLocationSelectChanged(sel) { var input = document.getElementById('edit-location'); if (sel.value === '__new__') { @@ -186,7 +200,7 @@ function addInstallRow() {
-

Install Specific Battery

+

Install Specific Battery{% if device.battery_size %} ({{ device.battery_size }} only){% endif %}

{% if available_batteries %}
@@ -238,6 +252,29 @@ function addInstallRow() { placeholder="Enter device type" style="display:{% if device.device_type and device.device_type not in _preset_types %}''{% else %}none{% endif %};margin-top:0.4rem;">
+
+ + {% set _preset_sizes = ['AA','AAA','C','D','9V','CR2032','CR2025','CR2016','18650','14500','16340','26650','LR44/AG13'] %} + + +
+