HEX
Server: Microsoft-IIS/10.0
System: Windows NT ITPWINWEBSVR22 10.0 build 20348 (Windows Server 2022) AMD64
User: www.conferencesearch.co.uk (0)
PHP: 8.3.30
Disabled: NONE
Upload Files
File: D:/web/hyperflight/apps/stockflow/index-v7.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, showRow
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 = "3" ' Default to Clean View (Hide Matched)
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 queries based on visibility selection
        If auditVisibility = "0" Then
            finalSql = sqlOrders & " UNION ALL " & sqlInvoices & " ORDER BY TransDate ASC, SortPriority ASC"
        ElseIf auditVisibility = "4" Then
            finalSql = sqlAudit & " 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 to match exactly what is in the final query string
        If auditVisibility = "0" Then
            ' Only Orders and Invoices parameters
            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)
            
        ElseIf auditVisibility = "4" Then
            ' Only Audit parameters
            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")
            
        Else
            ' All Three (Hybrid Views)
            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)
            
            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 (Orders/Invoices Only)</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 (Show Matched as Grey)</option>
                        <option value="3" <% If auditVisibility="3" Then Response.Write "selected" %>>Show ALL Events (Hide Matched)</option>
                        <option value="4" <% If auditVisibility="4" Then Response.Write "selected" %>>Show Audit Events Only (Raw DB Triggers)</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

                                ' Determine if we should render this row to the screen
                                showRow = True
                                If isDuplicate And auditVisibility = "3" Then
                                    showRow = False
                                End If

                                If qty <> 0 Then
                                    rowClass = ""
                                    syncBadge = ""

                                    ' In Audit Only Mode (4), nothing is a "duplicate" because we hid the original orders.
                                    If auditVisibility = "4" Then isDuplicate = False

                                    ' 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
                                    
                                    ' Output the HTML row if it wasn't hidden by the user's dropdown choice
                                    If showRow Then
                        %>
                        <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 And auditVisibility <> "4" 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 ' End showRow check
                                End If ' End qty <> 0 check
                                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>