feat: add is_disney column for reliable Disney/Non-Disney filtering
- Added is_disney INTEGER DEFAULT 0 column to assets table - Backfilled 996 Disney assets based on D- customer prefix - Server: is_disney in AssetCreate/AssetUpdate models, INSERT/UPDATE - Server: disney_filter query param now uses is_disney column (no JOIN needed) - import_cantaloupe.py: sets is_disney=1 when customer starts with D- - import_machines.py: sets is_disney=1 when customer starts with D- - disney_classify.py: sets is_disney=1 when classifying - Frontend: new Location Type dropdown with Disney/Non-Disney toggle (replaces the __disney__/__non_disney__ park dropdown pseudo-options)
This commit is contained in:
+1
-1
@@ -277,7 +277,7 @@ def classify_all_assets(db_path: str) -> dict:
|
|||||||
|
|
||||||
if park:
|
if park:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE assets SET disney_park = ?, updated_at = datetime('now') WHERE id = ?",
|
"UPDATE assets SET disney_park = ?, is_disney = 1, updated_at = datetime('now') WHERE id = ?",
|
||||||
(park, row['id'])
|
(park, row['id'])
|
||||||
)
|
)
|
||||||
results["classified"].append({
|
results["classified"].append({
|
||||||
|
|||||||
@@ -141,17 +141,18 @@ def import_assets():
|
|||||||
# Customer lookup
|
# Customer lookup
|
||||||
customer_id = customer_map.get(customer_name)
|
customer_id = customer_map.get(customer_name)
|
||||||
|
|
||||||
|
is_disney = 1 if customer_name.startswith('D-') else 0
|
||||||
try:
|
try:
|
||||||
cursor = conn.execute("""
|
cursor = conn.execute("""
|
||||||
INSERT INTO assets
|
INSERT INTO assets
|
||||||
(machine_id, name, description, category, status,
|
(machine_id, name, description, category, status,
|
||||||
make, model, serial_number, address, room,
|
make, model, serial_number, address, room,
|
||||||
customer_id)
|
customer_id, is_disney)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""", (
|
""", (
|
||||||
machine_id, name, description, "Equipment", status,
|
machine_id, name, description, "Equipment", status,
|
||||||
make, model, serial, full_address, place,
|
make, model, serial, full_address, place,
|
||||||
customer_id,
|
customer_id, is_disney,
|
||||||
))
|
))
|
||||||
inserted += 1
|
inserted += 1
|
||||||
except sqlite3.IntegrityError as e:
|
except sqlite3.IntegrityError as e:
|
||||||
|
|||||||
+4
-3
@@ -63,6 +63,7 @@ def main(xlsx_path):
|
|||||||
machine_id = _sanitize_machine_id(str(int(row[2])))
|
machine_id = _sanitize_machine_id(str(int(row[2])))
|
||||||
location_str = str(row[1]) if row[1] else '' # "Company - Address"
|
location_str = str(row[1]) if row[1] else '' # "Company - Address"
|
||||||
customer_name = str(row[21]) if row[21] else '' # Customer
|
customer_name = str(row[21]) if row[21] else '' # Customer
|
||||||
|
is_disney = 1 if customer_name.strip().startswith('D-') else 0
|
||||||
address = str(row[6]) if row[6] else ''
|
address = str(row[6]) if row[6] else ''
|
||||||
city = str(row[5]) if row[5] else ''
|
city = str(row[5]) if row[5] else ''
|
||||||
state = str(row[33]) if row[33] else ''
|
state = str(row[33]) if row[33] else ''
|
||||||
@@ -136,10 +137,10 @@ def main(xlsx_path):
|
|||||||
cursor = conn.execute("""
|
cursor = conn.execute("""
|
||||||
INSERT INTO assets
|
INSERT INTO assets
|
||||||
(machine_id, name, serial_number, make, model, category,
|
(machine_id, name, serial_number, make, model, category,
|
||||||
status, address, customer_id, created_at, updated_at, description)
|
status, address, customer_id, is_disney, created_at, updated_at, description)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'), ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'), ?)
|
||||||
""", (machine_id, name, serial, make, model, cat,
|
""", (machine_id, name, serial, make, model, cat,
|
||||||
status, full_address, customer_id,
|
status, full_address, customer_id, is_disney,
|
||||||
device + ' | Branch: ' + branch if branch else device))
|
device + ' | Branch: ' + branch if branch else device))
|
||||||
aid = cursor.lastrowid
|
aid = cursor.lastrowid
|
||||||
existing_assets[machine_id] = aid
|
existing_assets[machine_id] = aid
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ def _create_v2_tables(conn: sqlite3.Connection):
|
|||||||
longitude REAL DEFAULT NULL,
|
longitude REAL DEFAULT NULL,
|
||||||
geofence_radius_meters INTEGER DEFAULT 50,
|
geofence_radius_meters INTEGER DEFAULT 50,
|
||||||
disney_park TEXT DEFAULT NULL,
|
disney_park TEXT DEFAULT NULL,
|
||||||
|
is_disney INTEGER DEFAULT 0,
|
||||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
);
|
);
|
||||||
@@ -629,6 +630,7 @@ class AssetCreate(BaseModel):
|
|||||||
latitude: Optional[float] = None
|
latitude: Optional[float] = None
|
||||||
longitude: Optional[float] = None
|
longitude: Optional[float] = None
|
||||||
geofence_radius_meters: Optional[int] = 50
|
geofence_radius_meters: Optional[int] = 50
|
||||||
|
is_disney: Optional[bool] = None
|
||||||
keys: Optional[List[AssetKey]] = []
|
keys: Optional[List[AssetKey]] = []
|
||||||
badges: Optional[List[str]] = []
|
badges: Optional[List[str]] = []
|
||||||
|
|
||||||
@@ -658,6 +660,7 @@ class AssetUpdate(BaseModel):
|
|||||||
latitude: Optional[float] = None
|
latitude: Optional[float] = None
|
||||||
longitude: Optional[float] = None
|
longitude: Optional[float] = None
|
||||||
geofence_radius_meters: Optional[int] = None
|
geofence_radius_meters: Optional[int] = None
|
||||||
|
is_disney: Optional[bool] = None
|
||||||
keys: Optional[List[AssetKey]] = None
|
keys: Optional[List[AssetKey]] = None
|
||||||
badges: Optional[List[str]] = None
|
badges: Optional[List[str]] = None
|
||||||
|
|
||||||
@@ -867,7 +870,7 @@ def _build_asset_insert(body: AssetCreate, machine_id: str, name: str):
|
|||||||
"make", "model", "address", "building_name", "building_number", "floor",
|
"make", "model", "address", "building_name", "building_number", "floor",
|
||||||
"room", "trailer_number", "walking_directions", "map_link",
|
"room", "trailer_number", "walking_directions", "map_link",
|
||||||
"parking_location", "photo_path", "customer_id", "location_id",
|
"parking_location", "photo_path", "customer_id", "location_id",
|
||||||
"assigned_to", "latitude", "longitude", "geofence_radius_meters",
|
"assigned_to", "latitude", "longitude", "geofence_radius_meters", "is_disney",
|
||||||
]
|
]
|
||||||
values = (
|
values = (
|
||||||
machine_id, body.serial_number or "", name, body.description or "",
|
machine_id, body.serial_number or "", name, body.description or "",
|
||||||
@@ -880,6 +883,7 @@ def _build_asset_insert(body: AssetCreate, machine_id: str, name: str):
|
|||||||
body.customer_id, body.location_id, body.assigned_to,
|
body.customer_id, body.location_id, body.assigned_to,
|
||||||
body.latitude, body.longitude,
|
body.latitude, body.longitude,
|
||||||
body.geofence_radius_meters if body.geofence_radius_meters is not None else 50,
|
body.geofence_radius_meters if body.geofence_radius_meters is not None else 50,
|
||||||
|
body.is_disney if body.is_disney is not None else None,
|
||||||
)
|
)
|
||||||
return columns, values
|
return columns, values
|
||||||
|
|
||||||
@@ -1010,9 +1014,9 @@ def list_assets(
|
|||||||
conditions.append("disney_park = ?")
|
conditions.append("disney_park = ?")
|
||||||
params.append(disney_park)
|
params.append(disney_park)
|
||||||
if disney_filter == "disney":
|
if disney_filter == "disney":
|
||||||
conditions.append("(disney_park IS NOT NULL OR c.name LIKE 'D-%')")
|
conditions.append("is_disney = 1")
|
||||||
elif disney_filter == "non_disney":
|
elif disney_filter == "non_disney":
|
||||||
conditions.append("(disney_park IS NULL AND (c.name IS NULL OR c.name NOT LIKE 'D-%'))")
|
conditions.append("(is_disney IS NULL OR is_disney = 0)")
|
||||||
if model:
|
if model:
|
||||||
conditions.append("model = ?")
|
conditions.append("model = ?")
|
||||||
params.append(model)
|
params.append(model)
|
||||||
@@ -1031,10 +1035,7 @@ def list_assets(
|
|||||||
params.extend([like, like, like, like])
|
params.extend([like, like, like, like])
|
||||||
|
|
||||||
where = " AND ".join(conditions)
|
where = " AND ".join(conditions)
|
||||||
join_clause = ""
|
sql = "SELECT * FROM assets"
|
||||||
if disney_filter:
|
|
||||||
join_clause = " LEFT JOIN customers c ON assets.customer_id = c.id"
|
|
||||||
sql = f"SELECT assets.* FROM assets{join_clause}"
|
|
||||||
if where:
|
if where:
|
||||||
sql += f" WHERE {where}"
|
sql += f" WHERE {where}"
|
||||||
sql += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
|
sql += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
|
||||||
@@ -1042,7 +1043,7 @@ def list_assets(
|
|||||||
|
|
||||||
rows = conn.execute(sql, params).fetchall()
|
rows = conn.execute(sql, params).fetchall()
|
||||||
# Get total count for pagination
|
# Get total count for pagination
|
||||||
count_sql = f"SELECT COUNT(*) FROM assets{join_clause}"
|
count_sql = "SELECT COUNT(*) FROM assets"
|
||||||
if where:
|
if where:
|
||||||
count_sql += f" WHERE {where}"
|
count_sql += f" WHERE {where}"
|
||||||
total = conn.execute(count_sql, params[:len(params)-2]).fetchone()[0]
|
total = conn.execute(count_sql, params[:len(params)-2]).fetchone()[0]
|
||||||
@@ -1206,6 +1207,8 @@ def update_asset(asset_id: int, body: AssetUpdate):
|
|||||||
updates["longitude"] = body.longitude
|
updates["longitude"] = body.longitude
|
||||||
if body.geofence_radius_meters is not None:
|
if body.geofence_radius_meters is not None:
|
||||||
updates["geofence_radius_meters"] = body.geofence_radius_meters
|
updates["geofence_radius_meters"] = body.geofence_radius_meters
|
||||||
|
if body.is_disney is not None:
|
||||||
|
updates["is_disney"] = 1 if body.is_disney else 0
|
||||||
|
|
||||||
# Auto-populate address from GPS when lat/lng are provided
|
# Auto-populate address from GPS when lat/lng are provided
|
||||||
lat = updates.get("latitude") if "latitude" in updates else None
|
lat = updates.get("latitude") if "latitude" in updates else None
|
||||||
|
|||||||
+19
-10
@@ -1129,13 +1129,22 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="fp-row">
|
||||||
|
<div style="flex:1">
|
||||||
|
<div class="fp-label">Location Type</div>
|
||||||
|
<select id="filterDisneyFilter" class="input-field" onchange="onFilterChange()">
|
||||||
|
<option value="">All locations</option>
|
||||||
|
<option value="disney">🏰 Disney</option>
|
||||||
|
<option value="non_disney">🏢 Non-Disney</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div style="flex:1"></div>
|
||||||
|
</div>
|
||||||
<div class="fp-row">
|
<div class="fp-row">
|
||||||
<div style="flex:1">
|
<div style="flex:1">
|
||||||
<div class="fp-label">Disney Park</div>
|
<div class="fp-label">Disney Park</div>
|
||||||
<select id="filterDisneyPark" class="input-field" onchange="onFilterChange()">
|
<select id="filterDisneyPark" class="input-field" onchange="onFilterChange()">
|
||||||
<option value="">All parks</option>
|
<option value="">All parks</option>
|
||||||
<option value="__disney__">🏰 Disney only</option>
|
|
||||||
<option value="__non_disney__">🏢 Non-Disney</option>
|
|
||||||
<option disabled>──────────</option>
|
<option disabled>──────────</option>
|
||||||
<option value="magic-kingdom">🏰 Magic Kingdom</option>
|
<option value="magic-kingdom">🏰 Magic Kingdom</option>
|
||||||
<option value="epcot">🌍 Epcot</option>
|
<option value="epcot">🌍 Epcot</option>
|
||||||
@@ -3203,6 +3212,7 @@
|
|||||||
function onFilterChange() {
|
function onFilterChange() {
|
||||||
var catEl = document.getElementById('filterCategory');
|
var catEl = document.getElementById('filterCategory');
|
||||||
var makeEl = document.getElementById('filterMake');
|
var makeEl = document.getElementById('filterMake');
|
||||||
|
var disneyFilterEl = document.getElementById('filterDisneyFilter');
|
||||||
var parkEl = document.getElementById('filterDisneyPark');
|
var parkEl = document.getElementById('filterDisneyPark');
|
||||||
var custEl = document.getElementById('filterCustomer');
|
var custEl = document.getElementById('filterCustomer');
|
||||||
var locEl = document.getElementById('filterLocation');
|
var locEl = document.getElementById('filterLocation');
|
||||||
@@ -3211,15 +3221,13 @@
|
|||||||
|
|
||||||
assetFilters.category = catEl.value || null;
|
assetFilters.category = catEl.value || null;
|
||||||
assetFilters.make = makeEl.value || null;
|
assetFilters.make = makeEl.value || null;
|
||||||
// Handle Disney/Non-Disney special options
|
// Location Type (Disney/Non-Disney) and specific park are mutually exclusive
|
||||||
var parkVal = parkEl.value;
|
var disneyFilterVal = disneyFilterEl.value;
|
||||||
if (parkVal === '__disney__') {
|
if (disneyFilterVal) {
|
||||||
|
assetFilters.disney_filter = disneyFilterVal;
|
||||||
assetFilters.disney_park = null;
|
assetFilters.disney_park = null;
|
||||||
assetFilters.disney_filter = 'disney';
|
|
||||||
} else if (parkVal === '__non_disney__') {
|
|
||||||
assetFilters.disney_park = null;
|
|
||||||
assetFilters.disney_filter = 'non_disney';
|
|
||||||
} else {
|
} else {
|
||||||
|
var parkVal = parkEl.value;
|
||||||
assetFilters.disney_park = parkVal || null;
|
assetFilters.disney_park = parkVal || null;
|
||||||
assetFilters.disney_filter = null;
|
assetFilters.disney_filter = null;
|
||||||
}
|
}
|
||||||
@@ -3329,7 +3337,7 @@
|
|||||||
document.getElementById('filterDisneyPark').value = '';
|
document.getElementById('filterDisneyPark').value = '';
|
||||||
assetFilters.disney_park = null;
|
assetFilters.disney_park = null;
|
||||||
} else if (key === 'disney_filter') {
|
} else if (key === 'disney_filter') {
|
||||||
document.getElementById('filterDisneyPark').value = '';
|
document.getElementById('filterDisneyFilter').value = '';
|
||||||
assetFilters.disney_filter = null;
|
assetFilters.disney_filter = null;
|
||||||
} else if (key === 'customer_id') {
|
} else if (key === 'customer_id') {
|
||||||
document.getElementById('filterCustomer').value = '';
|
document.getElementById('filterCustomer').value = '';
|
||||||
@@ -3357,6 +3365,7 @@
|
|||||||
document.getElementById('filterCategory').value = '';
|
document.getElementById('filterCategory').value = '';
|
||||||
document.getElementById('filterMake').value = '';
|
document.getElementById('filterMake').value = '';
|
||||||
document.getElementById('filterDisneyPark').value = '';
|
document.getElementById('filterDisneyPark').value = '';
|
||||||
|
document.getElementById('filterDisneyFilter').value = '';
|
||||||
document.getElementById('filterCustomer').value = '';
|
document.getElementById('filterCustomer').value = '';
|
||||||
document.getElementById('filterLocation').innerHTML = '<option value="">All locations</option>';
|
document.getElementById('filterLocation').innerHTML = '<option value="">All locations</option>';
|
||||||
document.getElementById('filterLocation').disabled = true;
|
document.getElementById('filterLocation').disabled = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user