Fix dualpath propagation, getShopfloorPCs filtering, USB management, and printer features

- Fix dualpath PC propagation direction (Equipment->PC) in api.asp and db_helpers.asp
- Fix early exit in CreatePCMachineRelationship preventing propagation
- Fix getShopfloorPCs to filter machinetypeid IN (33,34,35) instead of >= 33
- Fix getShopfloorPCs to show equipment numbers via GROUP_CONCAT subquery
- Add detailed PropagateDP logging for dualpath debugging
- Default "Show on Shopfloor Dashboard" checkbox to checked in addnotification.asp
- Add USB label batch printing, single USB labels, and USB history pages
- Add printer supplies tracking and toner report enhancements
- Add uptime map visualization page
- Add dashboard/lobby display SQL migration
- Update CLAUDE.md with IIS 401 workaround documentation
- Update TODO.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-02-03 10:44:55 -05:00
parent 8945fe2a0a
commit e382a3246e
27 changed files with 1926 additions and 255 deletions

View File

@@ -302,19 +302,20 @@ Function PropagateControllerToDualpathMachines(conn, equipmentMachineid, pcMachi
dualpathMachineId = CLng(rsDP("related_machineid"))
' Check if this dualpath machine already has a Controls relationship with this PC
' Pattern: Equipment -> Controls -> PC (machineid=equipment, related_machineid=PC)
Set rsDPCheck = conn.Execute("SELECT relationshipid FROM machinerelationships " & _
"WHERE machineid = " & CLng(pcMachineid) & " " & _
"AND related_machineid = " & dualpathMachineId & " " & _
"WHERE machineid = " & dualpathMachineId & " " & _
"AND related_machineid = " & CLng(pcMachineid) & " " & _
"AND relationshiptypeid = " & controlsTypeID & " AND isactive = 1")
If rsDPCheck.EOF Then
' Create Controls relationship: PC -> Dualpath Machine
' Create Controls relationship: Dualpath Equipment -> PC (matches existing data pattern)
Dim cmdDP
Set cmdDP = Server.CreateObject("ADODB.Command")
cmdDP.ActiveConnection = conn
cmdDP.CommandText = "INSERT INTO machinerelationships (machineid, related_machineid, relationshiptypeid, isactive) VALUES (?, ?, ?, 1)"
cmdDP.Parameters.Append cmdDP.CreateParameter("@pcid", 3, 1, , CLng(pcMachineid))
cmdDP.Parameters.Append cmdDP.CreateParameter("@equipid", 3, 1, , dualpathMachineId)
cmdDP.Parameters.Append cmdDP.CreateParameter("@pcid", 3, 1, , CLng(pcMachineid))
cmdDP.Parameters.Append cmdDP.CreateParameter("@reltypeid", 3, 1, , controlsTypeID)
cmdDP.Execute
Set cmdDP = Nothing
@@ -360,28 +361,29 @@ Function PropagateControllerFromDualpathMachine(conn, machineId1, machineId2)
Set rsCtrl = Nothing
' Check if machine1 has a controller, copy to machine2 if machine2 doesn't have one
Set rsCtrl = conn.Execute("SELECT machineid FROM machinerelationships " & _
"WHERE related_machineid = " & CLng(machineId1) & " " & _
' Pattern: Equipment -> Controls -> PC (machineid=equipment, related_machineid=PC)
Set rsCtrl = conn.Execute("SELECT related_machineid FROM machinerelationships " & _
"WHERE machineid = " & CLng(machineId1) & " " & _
"AND relationshiptypeid = " & controlsTypeID & " AND isactive = 1")
If Not rsCtrl.EOF Then
pcMachineid = CLng(rsCtrl("machineid"))
pcMachineid = CLng(rsCtrl("related_machineid"))
rsCtrl.Close
Set rsCtrl = Nothing
' Check if machine2 already has this controller
Set rsCheck = conn.Execute("SELECT relationshipid FROM machinerelationships " & _
"WHERE machineid = " & pcMachineid & " AND related_machineid = " & CLng(machineId2) & " " & _
"WHERE machineid = " & CLng(machineId2) & " AND related_machineid = " & pcMachineid & " " & _
"AND relationshiptypeid = " & controlsTypeID & " AND isactive = 1")
If rsCheck.EOF Then
' Create: PC -> Controls -> machine2
' Create: Equipment -> Controls -> PC
Dim cmdCtrl
Set cmdCtrl = Server.CreateObject("ADODB.Command")
cmdCtrl.ActiveConnection = conn
cmdCtrl.CommandText = "INSERT INTO machinerelationships (machineid, related_machineid, relationshiptypeid, isactive) VALUES (?, ?, ?, 1)"
cmdCtrl.Parameters.Append cmdCtrl.CreateParameter("@pcid", 3, 1, , pcMachineid)
cmdCtrl.Parameters.Append cmdCtrl.CreateParameter("@equipid", 3, 1, , CLng(machineId2))
cmdCtrl.Parameters.Append cmdCtrl.CreateParameter("@pcid", 3, 1, , pcMachineid)
cmdCtrl.Parameters.Append cmdCtrl.CreateParameter("@reltypeid", 3, 1, , controlsTypeID)
cmdCtrl.Execute
Set cmdCtrl = Nothing
@@ -395,28 +397,28 @@ Function PropagateControllerFromDualpathMachine(conn, machineId1, machineId2)
End If
' Now check if machine2 has a controller, copy to machine1 if machine1 doesn't have one
Set rsCtrl = conn.Execute("SELECT machineid FROM machinerelationships " & _
"WHERE related_machineid = " & CLng(machineId2) & " " & _
Set rsCtrl = conn.Execute("SELECT related_machineid FROM machinerelationships " & _
"WHERE machineid = " & CLng(machineId2) & " " & _
"AND relationshiptypeid = " & controlsTypeID & " AND isactive = 1")
If Not rsCtrl.EOF Then
pcMachineid = CLng(rsCtrl("machineid"))
pcMachineid = CLng(rsCtrl("related_machineid"))
rsCtrl.Close
Set rsCtrl = Nothing
' Check if machine1 already has this controller
Set rsCheck = conn.Execute("SELECT relationshipid FROM machinerelationships " & _
"WHERE machineid = " & pcMachineid & " AND related_machineid = " & CLng(machineId1) & " " & _
"WHERE machineid = " & CLng(machineId1) & " AND related_machineid = " & pcMachineid & " " & _
"AND relationshiptypeid = " & controlsTypeID & " AND isactive = 1")
If rsCheck.EOF Then
' Create: PC -> Controls -> machine1
' Create: Equipment -> Controls -> PC
Dim cmdCtrl2
Set cmdCtrl2 = Server.CreateObject("ADODB.Command")
cmdCtrl2.ActiveConnection = conn
cmdCtrl2.CommandText = "INSERT INTO machinerelationships (machineid, related_machineid, relationshiptypeid, isactive) VALUES (?, ?, ?, 1)"
cmdCtrl2.Parameters.Append cmdCtrl2.CreateParameter("@pcid", 3, 1, , pcMachineid)
cmdCtrl2.Parameters.Append cmdCtrl2.CreateParameter("@equipid", 3, 1, , CLng(machineId1))
cmdCtrl2.Parameters.Append cmdCtrl2.CreateParameter("@pcid", 3, 1, , pcMachineid)
cmdCtrl2.Parameters.Append cmdCtrl2.CreateParameter("@reltypeid", 3, 1, , controlsTypeID)
cmdCtrl2.Execute
Set cmdCtrl2 = Nothing

View File

@@ -0,0 +1,181 @@
<%
' Returns both standard and metered part numbers as pipe-delimited string
' Format: "standard|metered" or just "standard" if no metered option
Function GetSupplyPartNumbers(printerModel, supplyName)
On Error Resume Next
Dim pn, sn, isDrum, isWaste, stdPN, metPN
pn = UCase(printerModel)
sn = UCase(supplyName)
isDrum = (InStr(sn, "DRUM") > 0 Or InStr(sn, "IMAGING") > 0)
isWaste = (InStr(sn, "WASTE") > 0)
stdPN = ""
metPN = ""
' VersaLink C415
If InStr(pn, "C415") > 0 Then
If isWaste Then
stdPN = "008R13335"
ElseIf isDrum Then
stdPN = "013R00701"
ElseIf InStr(sn, "BLACK") > 0 Then
stdPN = "006R04677"
metPN = "006R04681"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "006R04678"
metPN = "006R04682"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "006R04679"
metPN = "006R04683"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "006R04680"
metPN = "006R04684"
End If
' VersaLink C405
ElseIf InStr(pn, "C405") > 0 Then
If isWaste Then
stdPN = "108R01124"
ElseIf isDrum Then
stdPN = "101R00555"
ElseIf InStr(sn, "BLACK") > 0 Then
stdPN = "106R03500"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "106R03501"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "106R03502"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "106R03503"
End If
' VersaLink C7125/C7100
ElseIf InStr(pn, "C7125") > 0 Or InStr(pn, "C7100") > 0 Then
If isWaste Then
stdPN = "115R00129"
ElseIf isDrum Then
stdPN = "013R00688"
ElseIf InStr(sn, "BLACK") > 0 Then
stdPN = "006R01824"
metPN = "006R01820"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "006R01825"
metPN = "006R01821"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "006R01826"
metPN = "006R01822"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "006R01827"
metPN = "006R01823"
End If
' VersaLink B7125
ElseIf InStr(pn, "B7125") > 0 Then
If isWaste Then
stdPN = "115R00129"
ElseIf isDrum Then
stdPN = "013R00687"
ElseIf InStr(sn, "BLACK") > 0 Or InStr(sn, "TONER") > 0 Then
stdPN = "006R01818"
metPN = "006R01819"
End If
' VersaLink B405
ElseIf InStr(pn, "B405") > 0 Then
If isWaste Then
stdPN = "108R01124"
ElseIf isDrum Then
stdPN = "101R00554"
ElseIf InStr(sn, "BLACK") > 0 Or InStr(sn, "TONER") > 0 Then
stdPN = "106R03580"
End If
' AltaLink C8135
ElseIf InStr(pn, "C8135") > 0 Then
If isWaste Then
stdPN = "008R08101"
ElseIf isDrum Then
stdPN = "013R00681"
ElseIf InStr(sn, "BLACK") > 0 Then
stdPN = "006R01746"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "006R01747"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "006R01748"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "006R01749"
End If
' Xerox EC8036/AltaLink C8036 (compatible with WC7800 and AltaLink C80xx toner)
ElseIf InStr(pn, "EC8036") > 0 Or InStr(pn, "C8036") > 0 Then
Dim altPN
altPN = ""
If isWaste Then
stdPN = "008R13061"
ElseIf isDrum Then
stdPN = "013R00677"
ElseIf InStr(sn, "BLACK") > 0 Then
stdPN = "006R01509"
altPN = "006R01697"
metPN = "006R01701"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "006R01512"
altPN = "006R01698"
metPN = "006R01702"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "006R01511"
altPN = "006R01699"
metPN = "006R01703"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "006R01510"
altPN = "006R01700"
metPN = "006R01704"
End If
' Return all 3 options for EC8036
If altPN <> "" Then
GetSupplyPartNumbers = stdPN & "|" & altPN & "|" & metPN
Exit Function
End If
' HP LaserJet Pro M454dw / M454dn / MFP M479fdw / M479fdn (414A/414X series)
ElseIf InStr(pn, "M454") > 0 Or InStr(pn, "M479") > 0 Then
If InStr(sn, "BLACK") > 0 Then
stdPN = "W2020A"
metPN = "W2020X"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "W2021A"
metPN = "W2021X"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "W2023A"
metPN = "W2023X"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "W2022A"
metPN = "W2022X"
End If
' HP LaserJet Pro M251nw / M252dw / MFP M277dw (201A/201X series)
ElseIf InStr(pn, "M251") > 0 Or InStr(pn, "M252") > 0 Or InStr(pn, "M277") > 0 Then
If InStr(sn, "BLACK") > 0 Then
stdPN = "CF400A"
metPN = "CF400X"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "CF401A"
metPN = "CF401X"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "CF403A"
metPN = "CF403X"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "CF402A"
metPN = "CF402X"
End If
End If
If metPN <> "" Then
GetSupplyPartNumbers = stdPN & "|" & metPN
Else
GetSupplyPartNumbers = stdPN
End If
End Function
Function IsWasteSupply(supplyName)
IsWasteSupply = (InStr(1, UCase(supplyName), "WASTE") > 0)
End Function
%>

View File

@@ -1,10 +1,35 @@
<!--#include file="config.asp"-->
<%
' Employee database connection - uses centralized config
Dim objConn
Dim objConn, empConnError
empConnError = ""
Session.Timeout = APP_SESSION_TIMEOUT
On Error Resume Next
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.ConnectionString = GetEmployeeConnectionString()
objConn.Open
If Err.Number <> 0 Then
empConnError = "Employee DB Connection Error: " & Err.Number & " - " & Err.Description & " (Source: " & Err.Source & ")"
Err.Clear
End If
On Error Goto 0
Set rs = Server.CreateObject("ADODB.Recordset")
' If connection failed, display error and stop
If empConnError <> "" Then
Dim connType
If USE_EMP_DSN Then
connType = "DSN-based (wjf_employees)"
Else
connType = "Direct ODBC"
End If
Response.Write "<!DOCTYPE html><html><head><title>Database Error</title></head><body>"
Response.Write "<h2>Database Connection Error</h2>"
Response.Write "<p style='color:red;'>" & Server.HTMLEncode(empConnError) & "</p>"
Response.Write "<p>Connection String Type: " & connType & "</p>"
Response.Write "</body></html>"
Response.End
End If
%>

View File

@@ -1,54 +1,37 @@
<%
' Cached Zabbix API wrapper for ALL supply levels (toner, ink, drums, maintenance kits, etc.)
' Simplified caching - no background refresh, minimal locking
%>
<!--#include file="./zabbix_all_supplies.asp"-->
<%
' Cached function for all supply levels - returns data immediately, refreshes in background if stale
' Cached function for all supply levels - simple 5-minute cache
Function GetAllPrinterSuppliesCached(hostIP)
Dim cacheKey, cacheAge, forceRefresh
On Error Resume Next
Dim cacheKey, cacheTime, cacheAge, cachedData, forceRefresh
cacheKey = "zabbix_all_supplies_" & hostIP
' Check if manual refresh was requested
forceRefresh = (Request.QueryString("refresh") = "1" And Request.QueryString("ip") = hostIP)
If forceRefresh Then
' Clear cache for manual refresh
Application.Lock
Application(cacheKey) = Empty
Application(cacheKey & "_time") = Empty
Application(cacheKey & "_refreshing") = "false"
Application.Unlock
End If
' Check if valid cache exists (without locking)
If Not forceRefresh Then
cachedData = Application(cacheKey)
cacheTime = Application(cacheKey & "_time")
' Check if cache exists
If Not IsEmpty(Application(cacheKey)) And Not forceRefresh Then
cacheAge = DateDiff("n", Application(cacheKey & "_time"), Now())
' If cache is stale (>5 min) AND not already refreshing, trigger background update
If cacheAge >= 5 And Application(cacheKey & "_refreshing") <> "true" Then
' Mark as refreshing
Application.Lock
Application(cacheKey & "_refreshing") = "true"
Application.Unlock
' Trigger async background refresh (non-blocking)
On Error Resume Next
Dim http
Set http = Server.CreateObject("MSXML2.ServerXMLHTTP.6.0")
http.Open "GET", "http://localhost/refresh_all_supplies_cache.asp?ip=" & Server.URLEncode(hostIP), True
http.Send
Set http = Nothing
On Error Goto 0
If Not IsEmpty(cachedData) And Not IsEmpty(cacheTime) Then
cacheAge = DateDiff("n", cacheTime, Now())
If cacheAge < 5 Then
' Cache is fresh, return it
GetAllPrinterSuppliesCached = cachedData
Exit Function
End If
End If
' Return cached data immediately
GetAllPrinterSuppliesCached = Application(cacheKey)
Exit Function
End If
' No cache exists - fetch initial data
Dim freshData, zabbixConnected, pingStatus, suppliesJSON
' Cache miss or stale - fetch fresh data
Dim zabbixConnected, pingStatus, suppliesJSON
zabbixConnected = ZabbixLogin()
@@ -66,13 +49,13 @@ Function GetAllPrinterSuppliesCached(hostIP)
resultData(1) = pingStatus
resultData(2) = suppliesJSON
' Cache the result
' Cache the result (brief lock)
Application.Lock
Application(cacheKey) = resultData
Application(cacheKey & "_time") = Now()
Application(cacheKey & "_refreshing") = "false"
Application.Unlock
On Error Goto 0
GetAllPrinterSuppliesCached = resultData
End Function