File: D:/web/hyperflight/apps/stockflow/index-v3.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 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 (duplicates are now safely filtered)
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
If productID > 0 Then
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
' Base Queries
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 " & _
"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 " & _
"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 = ? "
' The Audit Query now cleanly EXCLUDES any trigger event that perfectly matches an Order timestamp
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 " & _
"FROM stock_audit sa " & _
"WHERE sa.ProductID = ? AND sa.AuditType = 'Main' " & _
"AND NOT EXISTS ( " & _
" SELECT 1 FROM orders o INNER JOIN orderdetails od ON o.OrderNo = od.OrderNo " & _
" WHERE od.ProductID = sa.ProductID " & _
" AND o.DateTimeOrdered = sa.DateTimeUpdated " & _
") "
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"
Else
finalSql = sqlOrders & " UNION ALL " & sqlInvoices & " UNION ALL " & sqlAudit & " ORDER BY TransDate 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; }
</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 qty <> 0 Then
rowClass = ""
syncBadge = ""
' Calculate Math & Assign Classes
If transType = "IN" Then
currentCalculatedStock = currentCalculatedStock + qty
displayQty = "+" & qty
cssClass = "qty-in"
ElseIf transType = "OUT" Then
currentCalculatedStock = currentCalculatedStock - qty
displayQty = "-" & qty
cssClass = "qty-out"
ElseIf transType = "ADJ" Then
If qty > 0 Then
displayQty = "+" & qty
cssClass = "qty-adj"
Else
displayQty = CStr(qty)
cssClass = "qty-out"
End If
If auditSync = "1" And stockAfterSys <> "-" Then
' HARD SYNC: Override the calculated stock to exactly what the database recorded.
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
End If
' Drift checking for visual warning
If 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" 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>