File: D:/web/hyperflight/apps/stockflow/index-v5.asp
<%
Option Explicit
' ADO Constants for Parameterized Queries
Const adCmdText = 1
Const adParamInput = 1
Const adVarChar = 200
Const adInteger = 3
Dim productCode, conn, cmd, rs, rsSnap
Dim productID, recordedNumInStock, currentCalculatedStock, discrepancy
Dim transactionDate, transType, qty, ref, status, prevStockSys, stockAfterSys
Dim isMatchedOrder, isMatchedInvoice, isDuplicate
Dim startingStockInput, startingStock, startDate, snapshotInfo
Dim auditVisibility, auditSync
Dim sqlOrders, sqlInvoices, sqlAudit, finalSql
Dim displayQty, cssClass, rowClass, syncBadge
' Initialize variables from form submission
productCode = Trim(Request.Form("ProductCode"))
startingStockInput = Trim(Request.Form("StartingStock"))
startDate = Trim(Request.Form("StartDate"))
auditVisibility = Request.Form("AuditVisibility")
auditSync = Request.Form("AuditSync")
' Set defaults for first load
If auditVisibility = "" Then auditVisibility = "2" ' Default to ALL events
If auditSync = "" Then auditSync = "0" ' Default to Display Only
productID = 0
recordedNumInStock = 0
startingStock = 0
snapshotInfo = "Manual Baseline (or 0 if blank)"
If productCode <> "" Then
Set conn = Server.CreateObject("ADODB.Connection")
conn.Open "DSN=MySQL_hyperflight;"
' 1. Get ProductID and Current System Stock
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
cmd.CommandText = "SELECT ProductID, NumInStock FROM products WHERE ProductCode = ?"
cmd.Parameters.Append cmd.CreateParameter("@ProdCode", adVarChar, adParamInput, 20, productCode)
Set rs = cmd.Execute()
If Not rs.EOF Then
productID = rs("ProductID")
If Not IsNull(rs("NumInStock")) Then recordedNumInStock = CLng(rs("NumInStock"))
End If
rs.Close
Set cmd = Nothing
' 2. Auto-fetch Starting Stock from Snapshots
If productID > 0 Then
If startingStockInput <> "" And IsNumeric(startingStockInput) Then
startingStock = CLng(startingStockInput)
snapshotInfo = "Manual Input"
ElseIf startDate <> "" Then
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
cmd.CommandText = "SELECT NumInStock, DateOfStock, TimeOfStock FROM stock_snapshots WHERE ProductID = ? AND DateOfStock < ? ORDER BY DateOfStock DESC LIMIT 1"
cmd.Parameters.Append cmd.CreateParameter("@ProdID", adInteger, adParamInput, , productID)
cmd.Parameters.Append cmd.CreateParameter("@SnapDate", adVarChar, adParamInput, 20, startDate)
Set rsSnap = cmd.Execute()
If Not rsSnap.EOF Then
If Not IsNull(rsSnap("NumInStock")) Then startingStock = CLng(rsSnap("NumInStock"))
snapshotInfo = "Auto-Snapshot from " & rsSnap("DateOfStock")
Else
snapshotInfo = "No prior snapshot found. Defaulting to 0."
End If
rsSnap.Close
Set cmd = Nothing
End If
End If
currentCalculatedStock = startingStock
' 3. Dynamic Query Builder with Fuzzy Matching Flags & Sort Priority
If productID > 0 Then
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
' Base Queries (SortPriority 1 for human actions)
sqlOrders = "SELECT o.DateTimeOrdered AS TransDate, 'OUT' AS TransType, od.Qty AS Amount, CAST(o.OrderNo AS VARCHAR(50)) AS Ref, o.Status, od.PrevStock AS PrevStockSys, NULL AS StockAfterSys, 0 AS IsMatchedOrder, 0 AS IsMatchedInvoice, 1 AS SortPriority " & _
"FROM orders o " & _
"INNER JOIN orderdetails od ON o.OrderNo = od.OrderNo " & _
"WHERE od.ProductID = ? " & _
"AND o.Status IN ('COMPLETED', 'PAYMENT RECEIVED', 'AWAITING PAYMENT', 'PAYMENT ON ACCOUNT') " & _
"AND o.IsInvoice = 1 "
sqlInvoices = "SELECT CAST(pi.InvoiceDate AS DATETIME) AS TransDate, 'IN' AS TransType, pil.QtyAddedToStock AS Amount, CAST(pi.PurchaseInvoiceID AS VARCHAR(50)) AS Ref, pi.InvoiceStatus AS Status, NULL AS PrevStockSys, NULL AS StockAfterSys, 0 AS IsMatchedOrder, 0 AS IsMatchedInvoice, 1 AS SortPriority " & _
"FROM purchase_invoices pi " & _
"INNER JOIN purchase_invoice_lines pil ON pi.PurchaseInvoiceID = pil.PurchaseInvoiceID " & _
"INNER JOIN purchase_order_lines pol ON pil.PurchaseOrderLineID = pol.PurchaseOrderLineID " & _
"WHERE pol.ProductID = ? "
' Audit Query (SortPriority 2 for database triggers)
sqlAudit = "SELECT sa.DateTimeUpdated AS TransDate, 'ADJ' AS TransType, (sa.StockAfter - sa.StockBefore) AS Amount, sa.UserName AS Ref, sa.PCName AS Status, sa.StockBefore AS PrevStockSys, sa.StockAfter AS StockAfterSys, " & _
"IFNULL((SELECT 1 FROM orders o INNER JOIN orderdetails od ON o.OrderNo = od.OrderNo WHERE od.ProductID = sa.ProductID AND -od.Qty = (sa.StockAfter - sa.StockBefore) AND o.IsInvoice = 1 AND o.Status IN ('COMPLETED', 'PAYMENT RECEIVED', 'AWAITING PAYMENT', 'PAYMENT ON ACCOUNT') AND o.DateTimeOrdered BETWEEN sa.DateTimeUpdated - INTERVAL 2 MINUTE AND sa.DateTimeUpdated + INTERVAL 2 MINUTE LIMIT 1), 0) AS IsMatchedOrder, " & _
"IFNULL((SELECT 1 FROM purchase_invoice_lines pil INNER JOIN purchase_invoices pi ON pil.PurchaseInvoiceID = pi.PurchaseInvoiceID INNER JOIN purchase_order_lines pol ON pil.PurchaseOrderLineID = pol.PurchaseOrderLineID WHERE pol.ProductID = sa.ProductID AND pil.QtyAddedToStock = (sa.StockAfter - sa.StockBefore) AND pi.InvoiceDate BETWEEN sa.DateTimeUpdated - INTERVAL 60 DAY AND sa.DateTimeUpdated + INTERVAL 1 DAY LIMIT 1), 0) AS IsMatchedInvoice, " & _
"2 AS SortPriority " & _
"FROM stock_audit sa " & _
"WHERE sa.ProductID = ? AND sa.AuditType = 'Main' "
If auditVisibility = "1" Then
sqlAudit = sqlAudit & " AND IFNULL(sa.PCName, '') <> '-WEBSITE-' "
End If
' Apply Date Filters
If startDate <> "" Then
sqlOrders = sqlOrders & " AND o.DateTimeOrdered >= ? "
sqlInvoices = sqlInvoices & " AND pi.InvoiceDate >= ? "
sqlAudit = sqlAudit & " AND sa.DateTimeUpdated >= ? "
End If
' Combine based on visibility
If auditVisibility = "0" Then
finalSql = sqlOrders & " UNION ALL " & sqlInvoices & " ORDER BY TransDate ASC, SortPriority ASC"
Else
finalSql = sqlOrders & " UNION ALL " & sqlInvoices & " UNION ALL " & sqlAudit & " ORDER BY TransDate ASC, SortPriority ASC"
End If
cmd.CommandText = finalSql
' Append parameters sequentially
cmd.Parameters.Append cmd.CreateParameter("@ProdID1", adInteger, adParamInput, , productID)
If startDate <> "" Then cmd.Parameters.Append cmd.CreateParameter("@StartDate1", adVarChar, adParamInput, 20, startDate & " 00:00:00")
cmd.Parameters.Append cmd.CreateParameter("@ProdID2", adInteger, adParamInput, , productID)
If startDate <> "" Then cmd.Parameters.Append cmd.CreateParameter("@StartDate2", adVarChar, adParamInput, 20, startDate)
If auditVisibility <> "0" Then
cmd.Parameters.Append cmd.CreateParameter("@ProdID3", adInteger, adParamInput, , productID)
If startDate <> "" Then cmd.Parameters.Append cmd.CreateParameter("@StartDate3", adVarChar, adParamInput, 20, startDate & " 00:00:00")
End If
Set rs = cmd.Execute()
End If
End If
%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stock Flow Checker</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.table-responsive { max-width: 1200px; }
.qty-in { color: #198754; font-weight: bold; }
.qty-out { color: #dc3545; font-weight: bold; }
.qty-adj { color: #0dcaf0; font-weight: bold; }
.drift-warning { background-color: #fff3cd !important; }
.sync-row { background-color: #e2e3e5 !important; border-left: 4px solid #0d6efd; }
.duplicate-row { opacity: 0.55; background-color: #f8f9fa !important; font-style: italic; }
</style>
</head>
<body class="bg-light pb-5">
<div class="container mt-5">
<div class="card shadow-sm mb-4" style="max-width: 1200px;">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Stock Flow Checker (Hybrid Mode)</h4>
</div>
<div class="card-body bg-light">
<form method="post" action="" class="row g-3">
<div class="col-md-3">
<label for="ProductCode" class="form-label fw-bold mb-1">Product Code:</label>
<input type="text" id="ProductCode" name="ProductCode" class="form-control" value="<%=Server.HTMLEncode(productCode)%>" required>
</div>
<div class="col-md-3">
<label for="StartDate" class="form-label fw-bold mb-1">Start Date:</label>
<input type="date" id="StartDate" name="StartDate" class="form-control" value="<%=Server.HTMLEncode(startDate)%>">
</div>
<div class="col-md-2">
<label for="StartingStock" class="form-label fw-bold mb-1">Override Start:</label>
<input type="number" id="StartingStock" name="StartingStock" class="form-control" placeholder="Auto" value="<%=Server.HTMLEncode(startingStockInput)%>">
</div>
<div class="col-md-4 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100 fw-bold">Run Ledger</button>
</div>
<div class="col-md-4">
<label for="AuditVisibility" class="form-label fw-bold mb-1">Audit Trail Visibility:</label>
<select id="AuditVisibility" name="AuditVisibility" class="form-select">
<option value="0" <% If auditVisibility="0" Then Response.Write "selected" %>>Hide Audit Events</option>
<option value="1" <% If auditVisibility="1" Then Response.Write "selected" %>>Show PC Updates Only (NEIL, etc.)</option>
<option value="2" <% If auditVisibility="2" Then Response.Write "selected" %>>Show ALL Events (Includes -WEBSITE- Restocks)</option>
</select>
</div>
<div class="col-md-4">
<label for="AuditSync" class="form-label fw-bold mb-1">Audit Syncing:</label>
<select id="AuditSync" name="AuditSync" class="form-select">
<option value="0" <% If auditSync="0" Then Response.Write "selected" %>>Display Only (Don't alter math)</option>
<option value="1" <% If auditSync="1" Then Response.Write "selected" %>>Sync Calc. Stock to Audit (Checkpoints)</option>
</select>
</div>
</form>
</div>
</div>
<% If productCode <> "" Then %>
<% If productID = 0 Then %>
<div class="alert alert-warning" style="max-width: 1200px;">
Product code <strong><%=Server.HTMLEncode(productCode)%></strong> was not found in the database.
</div>
<% ElseIf IsObject(rs) Then %>
<h5 class="mb-3">Ledger for: <span class="text-primary"><%=Server.HTMLEncode(productCode)%></span></h5>
<div class="table-responsive bg-white shadow-sm rounded">
<table class="table table-hover table-bordered mb-0">
<thead class="table-dark">
<tr>
<th>Date</th>
<th>Type</th>
<th>Reference / User</th>
<th>Status / PC</th>
<th>Sys PrevStock</th>
<th>Qty Change</th>
<th>Calc. Stock</th>
</tr>
</thead>
<tbody>
<tr class="table-secondary">
<td><em><% If startDate <> "" Then Response.Write Server.HTMLEncode(startDate) Else Response.Write "Baseline" End If %></em></td>
<td colspan="5"><em><%=snapshotInfo%></em></td>
<td class="fw-bold fs-5"><%=startingStock%></td>
</tr>
<%
If rs.EOF Then
Response.Write "<tr><td colspan='7' class='text-center'>No transactions found.</td></tr>"
Else
Do While Not rs.EOF
If IsNull(rs("TransDate")) Then transactionDate = "Not Recorded" Else transactionDate = rs("TransDate")
transType = rs("TransType")
qty = 0
If Not IsNull(rs("Amount")) Then qty = CLng(rs("Amount"))
ref = rs("Ref")
status = rs("Status")
If Not IsNull(rs("PrevStockSys")) Then prevStockSys = CLng(rs("PrevStockSys")) Else prevStockSys = "-"
If Not IsNull(rs("StockAfterSys")) Then stockAfterSys = CLng(rs("StockAfterSys")) Else stockAfterSys = "-"
If Not IsNull(rs("IsMatchedOrder")) Then isMatchedOrder = CInt(rs("IsMatchedOrder")) Else isMatchedOrder = 0
If Not IsNull(rs("IsMatchedInvoice")) Then isMatchedInvoice = CInt(rs("IsMatchedInvoice")) Else isMatchedInvoice = 0
' Check if the audit row is a fuzzy match for an order/invoice we already counted
isDuplicate = False
If transType = "ADJ" And (isMatchedOrder = 1 Or isMatchedInvoice = 1) Then
isDuplicate = True
End If
If qty <> 0 Then
rowClass = ""
syncBadge = ""
' Only perform addition/subtraction if it is NOT a duplicate audit row
If Not isDuplicate Then
If transType = "IN" Then
currentCalculatedStock = currentCalculatedStock + qty
ElseIf transType = "OUT" Then
currentCalculatedStock = currentCalculatedStock - qty
ElseIf transType = "ADJ" Then
currentCalculatedStock = currentCalculatedStock + qty
End If
End If
' Hard Sync override happens unconditionally on audits to fix drift
If transType = "ADJ" And auditSync = "1" And stockAfterSys <> "-" Then
currentCalculatedStock = stockAfterSys
rowClass = "sync-row"
syncBadge = " <span class='badge bg-primary ms-1' title='Calculated stock was re-synced to match this audit log'>Synced</span>"
End If
' Set Display Qty Text
If transType = "OUT" Then
displayQty = "-" & qty
Else
If qty > 0 Then displayQty = "+" & qty Else displayQty = CStr(qty)
End If
' Styling for rows
If isDuplicate Then
cssClass = "text-muted fw-bold"
rowClass = rowClass & " duplicate-row"
If isMatchedOrder = 1 Then ref = ref & " (Order Match)"
If isMatchedInvoice = 1 Then ref = ref & " (Invoice Match)"
Else
If transType = "IN" Then cssClass = "qty-in"
If transType = "OUT" Then cssClass = "qty-out"
If transType = "ADJ" Then
If qty > 0 Then cssClass = "qty-adj" Else cssClass = "qty-out"
End If
End If
' Drift checking for visual warning (ignored on greyed-out rows)
If Not isDuplicate And rowClass = "" And prevStockSys <> "-" Then
Dim expectedStockBeforeTx : expectedStockBeforeTx = currentCalculatedStock
If transType = "OUT" Then expectedStockBeforeTx = currentCalculatedStock + qty
If transType = "ADJ" And auditSync = "0" Then expectedStockBeforeTx = currentCalculatedStock - qty
If expectedStockBeforeTx <> prevStockSys Then
rowClass = "drift-warning"
End If
End If
%>
<tr class="<%=rowClass%>">
<td><%=transactionDate%></td>
<td>
<% If transType = "IN" Then %>
<span class="badge bg-success">IN (Purchase)</span>
<% ElseIf transType = "OUT" Then %>
<span class="badge bg-danger">OUT (Order)</span>
<% ElseIf transType = "ADJ" And isDuplicate Then %>
<span class="badge bg-light text-secondary border border-secondary">ADJ (Matched)</span>
<% ElseIf transType = "ADJ" Then %>
<span class="badge border border-info text-dark">ADJ (Audit)</span>
<% End If %>
</td>
<td><%=ref%></td>
<td><span class="badge bg-secondary"><%=status%></span></td>
<td class="text-muted"><%=prevStockSys%></td>
<td class="<%=cssClass%>"><%=displayQty%></td>
<td class="fw-bold fs-5"><%=currentCalculatedStock%><%=syncBadge%></td>
</tr>
<%
End If
rs.MoveNext
Loop
End If
rs.Close
Set rs = Nothing
Set cmd = Nothing
conn.Close
Set conn = Nothing
%>
</tbody>
</table>
</div>
<% discrepancy = currentCalculatedStock - recordedNumInStock %>
<div class="card mt-4 shadow-sm" style="max-width: 500px;">
<div class="card-header bg-dark text-white fw-bold">
Audit Result
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center">
Starting Stock Baseline
<span class="badge bg-secondary rounded-pill fs-6"><%=startingStock%></span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
<strong>Expected Stock Level</strong>
<span class="badge bg-primary rounded-pill fs-6"><%=currentCalculatedStock%></span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
Database `NumInStock`
<span class="badge bg-info rounded-pill fs-6"><%=recordedNumInStock%></span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center fw-bold">
Discrepancy (Expected vs Database)
<% If discrepancy = 0 Then %>
<span class="badge bg-success rounded-pill fs-6">Match (0)</span>
<% Else %>
<span class="badge bg-warning text-dark rounded-pill fs-6"><%=discrepancy%></span>
<% End If %>
</li>
</ul>
</div>
<% End If %>
<% End If %>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>