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-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>