Add related work orders to asset detail view (live extraction DB query). Add location filter to work orders list. Add Related Work Orders card in frontend.

This commit is contained in:
2026-07-28 20:49:45 -04:00
parent 96ed283163
commit 8a4f76b56e
2 changed files with 113 additions and 0 deletions
+72
View File
@@ -1292,6 +1292,69 @@ def get_asset(asset_id: int):
"work_orders": msfs.get("work_orders", []),
}
# Enrich with related work orders from extraction DB (live query)
try:
ext = _get_extraction_db()
if ext:
cur_ext = ext.cursor()
asset_name = result.get("name", "")
asset_company = result.get("company", "")
related_wos = []
# Match by customer asset name
if asset_name:
cur_ext.execute(
"""SELECT msdyn_name, "msdyn_serviceaccount!name" AS account_name,
msdyn_systemstatus, msdyn_datewindowstart,
"msdyn_functionallocation!name" AS functional_location,
msdyn_workordersummary
FROM msdyn_workorder
WHERE "msdyn_customerasset!name" = ?
ORDER BY msdyn_datewindowstart DESC
LIMIT 20""",
(asset_name,),
)
for r in cur_ext.fetchall():
related_wos.append({
"name": r["msdyn_name"],
"account_name": r["account_name"],
"status_code": str(r["msdyn_systemstatus"]),
"status": _status_label(r["msdyn_systemstatus"]),
"functional_location": r["functional_location"],
"summary": r["msdyn_workordersummary"],
"datewindow_start": r["msdyn_datewindowstart"],
})
# Also match by company name (account name)
if asset_company and not related_wos:
cur_ext.execute(
"""SELECT msdyn_name, "msdyn_serviceaccount!name" AS account_name,
msdyn_systemstatus, msdyn_datewindowstart,
"msdyn_functionallocation!name" AS functional_location,
msdyn_workordersummary
FROM msdyn_workorder
WHERE "msdyn_serviceaccount!name" = ?
ORDER BY msdyn_datewindowstart DESC
LIMIT 20""",
(asset_company,),
)
for r in cur_ext.fetchall():
related_wos.append({
"name": r["msdyn_name"],
"account_name": r["account_name"],
"status_code": str(r["msdyn_systemstatus"]),
"status": _status_label(r["msdyn_systemstatus"]),
"functional_location": r["functional_location"],
"summary": r["msdyn_workordersummary"],
"datewindow_start": r["msdyn_datewindowstart"],
})
if related_wos:
result["related_work_orders"] = related_wos
ext.close()
except Exception:
pass # Extraction DB might not be available, silently skip
# Enrich with Seed data (from mycantaloupe.com)
seed_row = conn.execute(
"SELECT raw_data, imported_at FROM seed_data WHERE asset_id = ?",
@@ -3585,6 +3648,7 @@ async def workorders_list(
q: str = Query("", description="Search term"),
status: str = Query("", description="Comma-separated status codes to include"),
tech: str = Query("", description="Comma-separated technician names"),
location: str = Query("", description="Filter by city, functional location, or account name"),
date_from: str = Query("", description="ISO date filter start (inclusive)"),
date_to: str = Query("", description="ISO date filter end (inclusive)"),
date_field: str = Query("createdon", description="Date field to filter on: createdon, msdyn_completedon"),
@@ -3642,6 +3706,14 @@ async def workorders_list(
where_clauses.append("(" + " OR ".join(tech_conditions) + ")")
params.extend(tech_names * 4)
# Location filter — matches city, functional location, or account name
if location:
like = f"%{location}%"
where_clauses.append(
"(a.address1_city LIKE ? OR w.\"msdyn_functionallocation!name\" LIKE ? OR w.\"msdyn_serviceaccount!name\" LIKE ?)"
)
params.extend([like, like, like])
# Date range filter
if date_from:
where_clauses.append(f"w.{date_field} >= ?")
+41
View File
@@ -1492,6 +1492,12 @@
<div id="detailSeedFields"></div>
</div>
<!-- Related Work Orders (from extraction DB) -->
<div class="card" id="detailRelatedWOCard" style="display:none;">
<div class="card-title"><svg class="gi" aria-hidden="true"><use href="#gi-wrench"/></svg> Related Work Orders</div>
<div id="detailRelatedWOList"></div>
</div>
<!-- Check-in History -->
<div class="card">
<div class="card-title">Check-in History <span id="checkinCount" style="color:var(--accent2);"></span></div>
@@ -5451,6 +5457,41 @@
seedCard.style.display = 'none';
}
// Related Work Orders from extraction DB
const rwoCard = document.getElementById('detailRelatedWOCard');
const rwoList = document.getElementById('detailRelatedWOList');
if (a.related_work_orders && a.related_work_orders.length) {
rwoCard.style.display = 'block';
rwoList.innerHTML = a.related_work_orders.map(function(wo) {
var statusColor = wo.status_code === '690970003' ? '#4ade80' :
wo.status_code === '690970004' ? '#4ade80' :
wo.status_code === '690970001' ? '#5b6ef7' :
wo.status_code === '690970002' ? '#fbbf24' : '#8b8fa3';
var statusIcon = wo.status_code === '690970003' ? '✅' :
wo.status_code === '690970004' ? '✅' :
wo.status_code === '690970001' ? '📅' :
wo.status_code === '690970002' ? '🔧' : '📋';
var locParts = [];
if (wo.functional_location) locParts.push(esc(wo.functional_location));
if (wo.account_name) locParts.push(esc(wo.account_name));
var dateStr = '';
if (wo.datewindow_start) {
try { var d = new Date(wo.datewindow_start); dateStr = d.toLocaleDateString('en-US', {month:'short', day:'numeric'}); } catch(e) {}
}
return '<div style="padding:6px 0;border-bottom:1px solid var(--border);font-size:12px;">' +
'<div style="display:flex;justify-content:space-between;">' +
'<span style="font-weight:600;">' + statusIcon + ' ' + esc(wo.name) + '</span>' +
'<span style="color:' + statusColor + ';font-size:11px;">' + esc(wo.status) + '</span>' +
'</div>' +
(locParts.length ? '<div style="color:var(--text2);font-size:11px;">📍 ' + locParts.join(' · ') + '</div>' : '') +
(dateStr ? '<div style="color:var(--text2);font-size:11px;">📅 ' + dateStr + '</div>' : '') +
(wo.summary ? '<div style="color:var(--text2);font-size:11px;">' + esc(wo.summary) + '</div>' : '') +
'</div>';
}).join('');
} else {
rwoCard.style.display = 'none';
}
await loadCheckinHistory(id);
} catch (e) {
showToast(e.message, true);