Fix asset list filters: MAKE→manufacturer, populate customers, expand search, fix assigned_to type
This commit is contained in:
@@ -924,10 +924,10 @@ def list_categories():
|
||||
def list_makes():
|
||||
conn = get_db()
|
||||
rows = conn.execute(
|
||||
"SELECT DISTINCT make FROM assets WHERE make IS NOT NULL AND make != '' ORDER BY make"
|
||||
"SELECT DISTINCT manufacturer FROM assets WHERE manufacturer IS NOT NULL AND manufacturer != '' ORDER BY manufacturer"
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [r["make"] for r in rows]
|
||||
return [r["manufacturer"] for r in rows]
|
||||
|
||||
|
||||
# ─── Task 4: POST /api/assets ──────────────────────────────────────────────
|
||||
@@ -1062,7 +1062,7 @@ def list_assets(
|
||||
model: Optional[str] = Query(None),
|
||||
customer_id: Optional[int] = Query(None),
|
||||
location_id: Optional[int] = Query(None),
|
||||
assigned_to: Optional[int] = Query(None),
|
||||
assigned_to: Optional[str] = Query(None, description="Technician username to filter by"),
|
||||
disney_park: Optional[str] = Query(None),
|
||||
disney_filter: Optional[str] = Query(None),
|
||||
disney_group: Optional[str] = Query(None, description="Grouped park categories: park, resort, office, springs, other"),
|
||||
@@ -1084,7 +1084,7 @@ def list_assets(
|
||||
conditions.append("status = ?")
|
||||
params.append(status)
|
||||
if make:
|
||||
conditions.append("make = ?")
|
||||
conditions.append("manufacturer = ?")
|
||||
params.append(make)
|
||||
if disney_park:
|
||||
conditions.append("disney_park = ?")
|
||||
@@ -1116,12 +1116,12 @@ def list_assets(
|
||||
conditions.append("location_id = ?")
|
||||
params.append(location_id)
|
||||
if assigned_to is not None:
|
||||
conditions.append("assigned_to = ?")
|
||||
conditions.append("u.username = ?")
|
||||
params.append(assigned_to)
|
||||
if q:
|
||||
conditions.append("(a.name LIKE ? OR a.machine_id LIKE ? OR a.serial_number LIKE ? OR a.description LIKE ? OR a.company LIKE ?)")
|
||||
conditions.append("(a.name LIKE ? OR a.machine_id LIKE ? OR a.serial_number LIKE ? OR a.description LIKE ? OR a.company LIKE ? OR a.place LIKE ? OR a.customer_name LIKE ? OR a.address LIKE ?)")
|
||||
like = f"%{q}%"
|
||||
params.extend([like, like, like, like, like])
|
||||
params.extend([like, like, like, like, like, like, like, like])
|
||||
if no_dex_days is not None:
|
||||
conditions.append("(dex_report_date IS NULL OR REPLACE(dex_report_date, 'T', ' ') < datetime('now', '-' || ? || ' days'))")
|
||||
params.append(no_dex_days)
|
||||
@@ -1147,7 +1147,7 @@ def list_assets(
|
||||
conditions.append("1=0")
|
||||
|
||||
where = " AND ".join(conditions)
|
||||
from_clause = "FROM assets a LEFT JOIN customers c ON a.customer_id = c.id"
|
||||
from_clause = "FROM assets a LEFT JOIN customers c ON a.customer_id = c.id LEFT JOIN users u ON a.assigned_to = u.id"
|
||||
sql = f"SELECT a.*, COALESCE(c.name, a.customer_name) AS customer_name {from_clause}"
|
||||
if where:
|
||||
sql += f" WHERE {where}"
|
||||
@@ -1170,10 +1170,23 @@ def list_assets(
|
||||
@app.get("/api/assets/search")
|
||||
def search_by_machine_id(machine_id: str = Query(...)):
|
||||
conn = get_db()
|
||||
# Exact match first
|
||||
row = conn.execute(
|
||||
"SELECT * FROM assets WHERE machine_id = ? OR connect_id = ? OR serial_number = ?",
|
||||
(machine_id, machine_id, machine_id),
|
||||
).fetchone()
|
||||
|
||||
# If not found and value is all-numeric >= 5 chars, try LIKE suffix match
|
||||
# on connect_id and equipment_id (for partial OCR reads of Connect IDs)
|
||||
if row is None and len(machine_id) >= 5 and machine_id.isdigit():
|
||||
row = conn.execute(
|
||||
"""SELECT * FROM assets
|
||||
WHERE replace(replace(replace(replace(upper(connect_id), '-', ''), '.', ''), ' ', ''), '/', '') LIKE ?
|
||||
OR replace(replace(replace(replace(upper(equipment_id), '-', ''), '.', ''), ' ', ''), '/', '') LIKE ?
|
||||
""",
|
||||
(f"%{machine_id}", f"%{machine_id}"),
|
||||
).fetchone()
|
||||
|
||||
conn.close()
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail="Asset not found")
|
||||
@@ -1452,8 +1465,12 @@ def update_asset(asset_id: int, body: AssetUpdate):
|
||||
updates["assigned_to"] = body.assigned_to
|
||||
if body.latitude is not None:
|
||||
updates["latitude"] = body.latitude
|
||||
elif body.latitude is None and "latitude" in body.model_fields_set:
|
||||
updates["latitude"] = None
|
||||
if body.longitude is not None:
|
||||
updates["longitude"] = body.longitude
|
||||
elif body.longitude is None and "longitude" in body.model_fields_set:
|
||||
updates["longitude"] = None
|
||||
if body.geofence_radius_meters is not None:
|
||||
updates["geofence_radius_meters"] = body.geofence_radius_meters
|
||||
if body.is_disney is not None:
|
||||
@@ -3066,18 +3083,47 @@ def _solve_tsp(points, origin_idx=0):
|
||||
@app.get("/api/workorders/search")
|
||||
async def workorders_search(
|
||||
q: str = Query("", description="Search term"),
|
||||
status: str = Query("", description="Filter by status label (e.g. 'Posted', 'Completed', 'In Progress', 'Scheduled', 'On Hold')"),
|
||||
date_from: str = Query("", description="Filter by created date >= (ISO format, e.g. 2026-01-01)"),
|
||||
date_to: str = Query("", description="Filter by created date <= (ISO format, e.g. 2026-06-30)"),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
offset: int = Query(0, ge=0),
|
||||
):
|
||||
"""Search work orders by name, account, or city."""
|
||||
"""Search work orders by name, account, or city, with optional status and date filters."""
|
||||
conn = _get_extraction_db()
|
||||
if not conn:
|
||||
raise HTTPException(503, "Database not available — sync extraction DB missing")
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
like = f"%{q}%"
|
||||
params: list = []
|
||||
where_clauses: list = []
|
||||
|
||||
# Search term
|
||||
like = f"%{q}%" if q else None
|
||||
if like:
|
||||
where_clauses.append("(w.msdyn_name LIKE ? OR a.name LIKE ? OR a.address1_city LIKE ?)")
|
||||
params.extend([like, like, like])
|
||||
|
||||
# Status filter — map label back to code
|
||||
if status:
|
||||
status_label_map = {v: k for k, v in STATUS_LABELS.items()}
|
||||
code = status_label_map.get(status)
|
||||
if code:
|
||||
where_clauses.append("w.msdyn_systemstatus = ?")
|
||||
params.append(int(code))
|
||||
|
||||
# Date range
|
||||
if date_from:
|
||||
where_clauses.append("w.createdon >= ?")
|
||||
params.append(date_from)
|
||||
if date_to:
|
||||
where_clauses.append("w.createdon <= ?")
|
||||
params.append(date_to + "T23:59:59Z" if "T" not in date_to else date_to)
|
||||
|
||||
where_sql = " AND ".join(where_clauses) if where_clauses else "1=1"
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
f"""
|
||||
SELECT
|
||||
w.msdyn_workorderid, w.msdyn_name,
|
||||
w."msdyn_serviceaccount!name" AS account_name,
|
||||
@@ -3086,29 +3132,26 @@ async def workorders_search(
|
||||
w.hsl_onsiteduration,
|
||||
w."msdyn_priority!name" AS priority,
|
||||
w.msdyn_systemstatus,
|
||||
w.createdon,
|
||||
a.address1_line1, a.address1_city, a.address1_stateorprovince, a.address1_postalcode,
|
||||
a.address1_latitude, a.address1_longitude
|
||||
FROM msdyn_workorder w
|
||||
LEFT JOIN account a ON w."msdyn_serviceaccount!id" = a.accountid
|
||||
WHERE w.msdyn_name LIKE ?
|
||||
OR a.name LIKE ?
|
||||
OR a.address1_city LIKE ?
|
||||
ORDER BY w.msdyn_name
|
||||
WHERE {where_sql}
|
||||
ORDER BY w.createdon DESC
|
||||
LIMIT ? OFFSET ?
|
||||
""",
|
||||
(like, like, like, limit, offset),
|
||||
(*params, limit, offset),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
f"""
|
||||
SELECT COUNT(*) FROM msdyn_workorder w
|
||||
LEFT JOIN account a ON w."msdyn_serviceaccount!id" = a.accountid
|
||||
WHERE w.msdyn_name LIKE ?
|
||||
OR a.name LIKE ?
|
||||
OR a.address1_city LIKE ?
|
||||
WHERE {where_sql}
|
||||
""",
|
||||
(like, like, like),
|
||||
params,
|
||||
)
|
||||
total = cur.fetchone()[0]
|
||||
|
||||
@@ -3123,6 +3166,7 @@ async def workorders_search(
|
||||
"status": _status_label(r["msdyn_systemstatus"]),
|
||||
"priority": r["priority"],
|
||||
"onsite_duration": r["hsl_onsiteduration"],
|
||||
"created_on": r["createdon"],
|
||||
"address": {
|
||||
"line1": r["address1_line1"],
|
||||
"city": r["address1_city"],
|
||||
|
||||
Reference in New Issue
Block a user