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:
@@ -1292,6 +1292,69 @@ def get_asset(asset_id: int):
|
|||||||
"work_orders": msfs.get("work_orders", []),
|
"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)
|
# Enrich with Seed data (from mycantaloupe.com)
|
||||||
seed_row = conn.execute(
|
seed_row = conn.execute(
|
||||||
"SELECT raw_data, imported_at FROM seed_data WHERE asset_id = ?",
|
"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"),
|
q: str = Query("", description="Search term"),
|
||||||
status: str = Query("", description="Comma-separated status codes to include"),
|
status: str = Query("", description="Comma-separated status codes to include"),
|
||||||
tech: str = Query("", description="Comma-separated technician names"),
|
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_from: str = Query("", description="ISO date filter start (inclusive)"),
|
||||||
date_to: str = Query("", description="ISO date filter end (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"),
|
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) + ")")
|
where_clauses.append("(" + " OR ".join(tech_conditions) + ")")
|
||||||
params.extend(tech_names * 4)
|
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
|
# Date range filter
|
||||||
if date_from:
|
if date_from:
|
||||||
where_clauses.append(f"w.{date_field} >= ?")
|
where_clauses.append(f"w.{date_field} >= ?")
|
||||||
|
|||||||
@@ -1492,6 +1492,12 @@
|
|||||||
<div id="detailSeedFields"></div>
|
<div id="detailSeedFields"></div>
|
||||||
</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 -->
|
<!-- Check-in History -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-title">Check-in History <span id="checkinCount" style="color:var(--accent2);"></span></div>
|
<div class="card-title">Check-in History <span id="checkinCount" style="color:var(--accent2);"></span></div>
|
||||||
@@ -5451,6 +5457,41 @@
|
|||||||
seedCard.style.display = 'none';
|
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);
|
await loadCheckinHistory(id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(e.message, true);
|
showToast(e.message, true);
|
||||||
|
|||||||
Reference in New Issue
Block a user