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

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : C:/Program Files/MariaDB 10.6/data/vegebasketdeliveryorder/wpstgbak_xyz_ips_short_code.ibd
��������S=<3NN��f������������������&&�����������������������������������������������������������������>����������������f����������������>��������������������S=<3�:����������Q��NQ���t����������S=<3N���������������������������������i�����������������������������������������������������������������������������������������������������������������������������L�������������������i���������������������������������������������������������������������������������!"#S=<3�1�w��������Q�1E�N���??N�N2infimumsupremum�'�*!�ր5EpcQ�1�l2������c�vE�N9�	�-Q? infimumsupremum0���'�MenuInfo-CurrentUser-Desktop<?php
if (is_user_logged_in()) {
    $user = wp_get_current_user();

    $display_name = !empty($user->display_name) ? $user->display_name : $user->user_login;

    $initial = function_exists('mb_substr')
        ? mb_strtoupper(mb_substr($display_name, 0, 1))
        : strtoupper(substr($display_name, 0, 1));

    $role = !empty($user->roles) ? $user->roles[0] : '';

    $role_labels = array(
        'administrator'   => 'Administrator',
        'editor'          => 'Editor',
        'author'          => 'Author',
        'contributor'     => 'Contributor',
        'subscriber'      => 'Operations Staff', // change if needed
        'operations_staff'=> 'Operations Staff',
        'delivery_driver' => 'Delivery Driver',
        'staff'           => 'Staff',
    );

    $role_text = isset($role_labels[$role])
        ? $role_labels[$role]
        : ucwords(str_replace(array('-', '_'), ' ', $role));

    echo '<div style="display:flex;align-items:center;gap:12px;padding:10px 14px;background:#fff;font-family:Arial,sans-serif;">';
        echo '<div style="width:36px;height:36px;min-width:36px;border-radius:50%;background:#0f6b43;color:#fff;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;line-height:1;">' . esc_html($initial) . '</div>';
        echo '<div style="display:flex;flex-direction:column;line-height:1.2;">';
            echo '<div style="font-size:13px;font-weight:600;color:#1f2937;">' . esc_html($display_name) . '</div>';
            echo '<div style="font-size:12px;color:#7a7a7a;margin-top:2px;">' . esc_html($role_text) . '</div>';
        echo '</div>';
    echo '</div>';
}
?>[xyz-ips snippet="MenuInfo-CurrentUser-Desktop"]����(���(�MenuInfo-CurrentPage<?php
if (is_front_page() || is_home()) {
    $page_name = 'Dashboard';
} elseif (is_page('create-delivery-order')) {
    $page_name = 'Create / Return';
} else {
    $page_name = get_the_title();

    if (empty($page_name)) {
        $page_name = wp_title('', false);
    }
}

echo '<div style="color:#ffffff;font-family:Roboto, Arial, sans-serif;font-weight:700;font-size:1rem;">' . esc_html($page_name) . '</div>';
?>[xyz-ips snippet="MenuInfo-CurrentPage"]����&�� �ŀ)�DashboardInfo-Good<?php
$hour = (int) current_time('G');

if ($hour >= 5 && $hour < 12) {
    $greeting = 'Good Morning';
} elseif ($hour >= 12 && $hour < 17) {
    $greeting = 'Good Afternoon';
} else {
    $greeting = 'Good Evening';
}

$user = wp_get_current_user();
$name = !empty($user->display_name) ? $user->display_name : $user->user_login;
?>

<div class="ac-greeting"><?php echo esc_html($greeting . ', ' . $name); ?></div>

<style>
.ac-greeting{
    color:#0B4A2D;
    font-family:Roboto, Arial, sans-serif;
    font-weight:700;
    font-size:1.5rem;
}

@media (max-width: 767px){
    .ac-greeting{
        font-size:1.25rem;
    }
}
</style>[xyz-ips snippet="DashboardInfo-Good"]����,	�(y�*�DashboardInfo-DO-Counter<?php
global $wpdb;

$table_name          = $wpdb->prefix . 'ac_jobs';
$job_type_column     = 'job_type';        // change if needed
$created_at_column   = 'created_at';      // change if needed
$delivery_order_type = 'DELIVERY_ORDER';  // change if needed

$today = current_time('Y-m-d');

$total_today = (int) $wpdb->get_var(
    $wpdb->prepare(
        "SELECT COUNT(*)
         FROM {$table_name}
         WHERE {$job_type_column} = %s
         AND DATE({$created_at_column}) = %s",
        $delivery_order_type,
        $today
    )
);
?>

<div id="ac-delivery-counter" data-target="<?php echo esc_attr($total_today); ?>">0</div>

<style>
#ac-delivery-counter{
    font-size: 2rem;
    font-weight: 700;
    font-family: Roboto, sans-serif;
    color: #000000;
    line-height: 1;
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function () {
    const counter = document.getElementById('ac-delivery-counter');
    if (!counter) return;

    const target = parseInt(counter.dataset.target) || 0;
    const duration = 1000;
    const start = performance.now();

    function animate(now) {
        const progress = Math.min((now - start) / duration, 1);
        const value = Math.floor(target * (1 - Math.pow(1 - progress, 3)));
        counter.textContent = value;

        if (progress < 1) {
            requestAnimationFrame(animate);
        } else {
            counter.textContent = target;
        }
    }

    requestAnimationFrame(animate);
});
</script>[xyz-ips snippet="DashboardInfo-DO-Counter"]����0Y�0р+�DashboardInfo-Return-Counter<?php
global $wpdb;

$table_name        = $wpdb->prefix . 'ac_basket_ledger';
$txn_type_column   = 'txn_type';
$created_at_column = 'created_at'; // change if needed
$qty_column        = 'qty'; // change if needed

$today = current_time('Y-m-d');

$total_today = (float) $wpdb->get_var(
    $wpdb->prepare(
        "SELECT COALESCE(SUM({$qty_column}), 0)
         FROM {$table_name}
         WHERE {$txn_type_column} = %s
         AND DATE({$created_at_column}) = %s",
        'RETURN',
        $today
    )
);
?>

<div id="ac-basket-return-counter" data-target="<?php echo esc_attr($total_today); ?>">0</div>

<style>
#ac-basket-return-counter{
    font-size: 2rem;
    font-weight: 700;
    font-family: Roboto, sans-serif;
    color: #000000;
    line-height: 1;
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function () {
    const counter = document.getElementById('ac-basket-return-counter');
    if (!counter) return;

    const target = parseFloat(counter.dataset.target) || 0;
    const duration = 1000;
    const start = performance.now();

    function animate(now) {
        const progress = Math.min((now - start) / duration, 1);
        const value = target * (1 - Math.pow(1 - progress, 3));
        counter.textContent = Number.isInteger(target) ? Math.floor(value) : value.toFixed(2);

        if (progress < 1) {
            requestAnimationFrame(animate);
        } else {
            counter.textContent = Number.isInteger(target) ? target : target.toFixed(2);
        }
    }

    requestAnimationFrame(animate);
});
</script>[xyz-ips snippet="DashboardInfo-Return-Counter"]����2�8l�,�DashboardInfo-Customer-Counter<?php
global $wpdb;

$table_name          = $wpdb->prefix . 'ac_jobs';
$job_type_column     = 'job_type';
$created_at_column   = 'created_at';
$payload_column      = 'payload';
$delivery_order_type = 'DELIVERY_ORDER';

$today = current_time('Y-m-d');

$total_today = (int) $wpdb->get_var(
    $wpdb->prepare(
        "SELECT COUNT(DISTINCT JSON_UNQUOTE(JSON_EXTRACT({$payload_column}, '$.customerCode')))
         FROM {$table_name}
         WHERE {$job_type_column} = %s
         AND DATE({$created_at_column}) = %s
         AND JSON_UNQUOTE(JSON_EXTRACT({$payload_column}, '$.customerCode')) IS NOT NULL
         AND JSON_UNQUOTE(JSON_EXTRACT({$payload_column}, '$.customerCode')) != ''",
        $delivery_order_type,
        $today
    )
);
?>

<div id="ac-customers-served-counter" data-target="<?php echo esc_attr($total_today); ?>">0</div>

<style>
#ac-customers-served-counter{
    font-size: 2rem;
    font-weight: 700;
    font-family: Roboto, sans-serif;
    color: #000000;
    line-height: 1;
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function () {
    const counter = document.getElementById('ac-customers-served-counter');
    if (!counter) return;

    const target = parseInt(counter.dataset.target) || 0;
    const duration = 1000;
    const start = performance.now();

    function animate(now) {
        const progress = Math.min((now - start) / duration, 1);
        const value = Math.floor(target * (1 - Math.pow(1 - progress, 3)));
        counter.textContent = value;

        if (progress < 1) {
            requestAnimationFrame(animate);
        } else {
            counter.textContent = target;
        }
    }

    requestAnimationFrame(animate);
});
</script>[xyz-ips snippet="DashboardInfo-Customer-Counter"]����.'�@�-�DashboardInfo-Latest-Entry<?php
global $wpdb;

$jobs_table   = $wpdb->prefix . 'ac_jobs';
$ledger_table = $wpdb->prefix . 'ac_basket_ledger';

$job_type_column     = 'job_type';
$job_time_column     = 'created_at';
$job_type_value      = 'DELIVERY_ORDER';

$ledger_type_column  = 'txn_type';
$ledger_time_column  = 'created_at';
$ledger_type_value   = 'RETURN';

function ac_pick_value($row, $keys = array(), $default = '-') {
    foreach ($keys as $key) {
        if (isset($row[$key]) && $row[$key] !== '' && $row[$key] !== null) {
            return $row[$key];
        }
    }
    return $default;
}

function ac_pick_json_value($json, $keys = array(), $default = '-') {
    if (empty($json)) return $default;

    $data = json_decode($json, true);
    if (!is_array($data)) return $default;

    foreach ($keys as $key) {
        if (isset($data[$key]) && $data[$key] !== '' && $data[$key] !== null) {
            return $data[$key];
        }
    }

    return $default;
}

function ac_format_time_only($datetime) {
    if (empty($datetime) || $datetime === '-') return '-';

    $timestamp = strtotime($datetime);
    if (!$timestamp) return $datetime;

    return wp_date('g:i A', $timestamp);
}

$job_rows = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT *
         FROM {$jobs_table}
         WHERE {$job_type_column} = %s
         ORDER BY {$job_time_column} DESC
         LIMIT 20",
        $job_type_value
    ),
    ARRAY_A
);

$ledger_rows = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT *
         FROM {$ledger_table}
         WHERE {$ledger_type_column} = %s
         ORDER BY {$ledger_time_column} DESC
         LIMIT 20",
        $ledger_type_value
    ),
    ARRAY_A
);

$combined = array();

if (!empty($job_rows)) {
    foreach ($job_rows as $row) {
        $payload  = isset($row['payload']) ? $row['payload'] : '';
        $result   = isset($row['result']) ? $row['result'] : '';
        $time_raw = ac_pick_value($row, array('created_at', 'created_time', 'CreatedAt'));

        $combined[] = array(
            'time_raw' => $time_raw,
            'time'     => ac_format_time_only($time_raw),
            'type'     => 'Sending',
            'customer' => ac_pick_json_value($payload, array('customerName', 'debtorName', 'DebtorName'), '-'),
            'ref'      => ac_pick_json_value($result, array('docNo'), '-'),
        );
    }
}

if (!empty($ledger_rows)) {
    foreach ($ledger_rows as $row) {
        $time_raw = ac_pick_value($row, array('created_at', 'created_time', 'txn_time', 'txn_date'));

        $combined[] = array(
            'time_raw' => $time_raw,
            'time'     => ac_format_time_only($time_raw),
            'type'     => 'Return',
            'customer' => ac_pick_value($row, array('customer_name', 'debtor_name', 'customer', 'debtor', 'customerCode', 'debtorCode'), '-'),
            'ref'      => ac_pick_value($row, array('reference_no', 'ref_no', 'doc_no', 'docno', 'reference', 'txn_ref'), '-'),
        );
    }
}

usort($combined, function($a, $b) {
    return strtotime($b['time_raw']) <=> strtotime($a['time_raw']);
});

$combined = array_slice($combined, 0, 5);
?>

<div class="ac-simple-log-wrap">
    <table class="ac-simple-log-table">
        <thead>
            <tr>
                <th class="col-time">Time</th>
                <th class="col-type">Type</th>
                <th class="col-customer">Customer</th>
                <th class="col-ref">Reference Number</th>
            </tr>
        </thead>
        <tbody>
            <?php if (!empty($combined)) : ?>
                <?php foreach ($combined as $item) : ?>
                    <tr>
                        <td class="col-time"><?php echo esc_html($item['time']); ?></td>
                        <td class="col-type"><?php echo esc_html($item['type']); ?></td>
                        <td class="col-customer"><?php echo esc_html($item['customer']); ?></td>
                        <td class="col-ref"><?php echo esc_html($item['ref']); ?></td>
                    </tr>
                <?php endforeach; ?>
            <?php else : ?>
                <tr>
                    <td colspan="4">No records found.</td>
                </tr>
            <?php endif; ?>
        </tbody>
    </table>
</div>

<style>
.ac-simple-log-wrap{
    width:100%;
    margin:0;
    overflow-x:auto;
    -webkit-overflow-scrolling:touch;
}

.ac-simple-log-table{
    width:100%;
    min-width:38rem;
    border-collapse:collapse;
    border-spacing:0;
    margin:0;
    font-family:Roboto, sans-serif;
    font-size:1rem;
    background:#FFFFFF;
}

.ac-simple-log-table th,
.ac-simple-log-table td{
    padding:0.45rem 0.4rem;
    border-bottom:1px solid #e5e7eb;
    text-align:left;
    vertical-align:top;
    color:#374151;
}

.ac-simple-log-table th{
    font-weight:700;
    background:#f8f9fa;
    color:#111827;
    white-space:nowrap;
}

.ac-simple-log-table .col-time{
    width:5.5rem;
    white-space:nowrap;
}

.ac-simple-log-table .col-type{
    width:5.5rem;
    white-space:nowrap;
}

.ac-simple-log-table .col-customer{
    min-width:10rem;
}

.ac-simple-log-table .col-ref{
    min-width:9rem;
    white-space:nowrap;
}

/* Tablet */
@media (max-width: 64rem){
    .ac-simple-log-table{
        min-width:34rem;
        font-size:0.92rem;
    }

    .ac-simple-log-table th,
    .ac-simple-log-table td{
        padding:0.38rem 0.32rem;
    }
}

/* Mobile */
@media (max-width: 48rem){
    .ac-simple-log-wrap{
        overflow-x:auto;
    }

    .ac-simple-log-table{
        min-width:30rem;
        font-size:0.84rem;
    }

    .ac-simple-log-table th,
    .ac-simple-log-table td{
        padding:0.32rem 0.28rem;
    }

    .ac-simple-log-table .col-time{
        width:4.8rem;
    }

    .ac-simple-log-table .col-type{
        width:4.8rem;
    }

    .ac-simple-log-table .col-customer{
        min-width:8rem;
    }

    .ac-simple-log-table .col-ref{
        min-width:8rem;
    }
}
</style>[xyz-ips snippet="DashboardInfo-Latest-Entry"]����pc�c�vp��ES=<CE�N5��1�3�? infimumsupremum,	�y�*�DashboardInfo-DO-Counter<?php
global $wpdb;

$table_name          = $wpdb->prefix . 'ac_jobs';
$job_type_column     = 'job_type';        // change if needed
$created_at_column   = 'created_at';      // change if needed
$delivery_order_type = 'DELIVERY_ORDER';  // change if needed

$today = current_time('Y-m-d');

$total_today = (int) $wpdb->get_var(
    $wpdb->prepare(
        "SELECT COUNT(*)
         FROM {$table_name}
         WHERE {$job_type_column} = %s
         AND DATE({$created_at_column}) = %s",
        $delivery_order_type,
        $today
    )
);
?>

<div id="ac-delivery-counter" data-target="<?php echo esc_attr($total_today); ?>">0</div>

<style>
#ac-delivery-counter{
    font-size: 2rem;
    font-weight: 700;
    font-family: Roboto, sans-serif;
    color: #000000;
    line-height: 1;
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function () {
    const counter = document.getElementById('ac-delivery-counter');
    if (!counter) return;

    const target = parseInt(counter.dataset.target) || 0;
    const duration = 1000;
    const start = performance.now();

    function animate(now) {
        const progress = Math.min((now - start) / duration, 1);
        const value = Math.floor(target * (1 - Math.pow(1 - progress, 3)));
        counter.textContent = value;

        if (progress < 1) {
            requestAnimationFrame(animate);
        } else {
            counter.textContent = target;
        }
    }

    requestAnimationFrame(animate);
});
</script>[xyz-ips snippet="DashboardInfo-DO-Counter"]����0Y�р+�DashboardInfo-Return-Counter<?php
global $wpdb;

$table_name        = $wpdb->prefix . 'ac_basket_ledger';
$txn_type_column   = 'txn_type';
$created_at_column = 'created_at'; // change if needed
$qty_column        = 'qty'; // change if needed

$today = current_time('Y-m-d');

$total_today = (float) $wpdb->get_var(
    $wpdb->prepare(
        "SELECT COALESCE(SUM({$qty_column}), 0)
         FROM {$table_name}
         WHERE {$txn_type_column} = %s
         AND DATE({$created_at_column}) = %s",
        'RETURN',
        $today
    )
);
?>

<div id="ac-basket-return-counter" data-target="<?php echo esc_attr($total_today); ?>">0</div>

<style>
#ac-basket-return-counter{
    font-size: 2rem;
    font-weight: 700;
    font-family: Roboto, sans-serif;
    color: #000000;
    line-height: 1;
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function () {
    const counter = document.getElementById('ac-basket-return-counter');
    if (!counter) return;

    const target = parseFloat(counter.dataset.target) || 0;
    const duration = 1000;
    const start = performance.now();

    function animate(now) {
        const progress = Math.min((now - start) / duration, 1);
        const value = target * (1 - Math.pow(1 - progress, 3));
        counter.textContent = Number.isInteger(target) ? Math.floor(value) : value.toFixed(2);

        if (progress < 1) {
            requestAnimationFrame(animate);
        } else {
            counter.textContent = Number.isInteger(target) ? target : target.toFixed(2);
        }
    }

    requestAnimationFrame(animate);
});
</script>[xyz-ips snippet="DashboardInfo-Return-Counter"]����2� l�,�DashboardInfo-Customer-Counter<?php
global $wpdb;

$table_name          = $wpdb->prefix . 'ac_jobs';
$job_type_column     = 'job_type';
$created_at_column   = 'created_at';
$payload_column      = 'payload';
$delivery_order_type = 'DELIVERY_ORDER';

$today = current_time('Y-m-d');

$total_today = (int) $wpdb->get_var(
    $wpdb->prepare(
        "SELECT COUNT(DISTINCT JSON_UNQUOTE(JSON_EXTRACT({$payload_column}, '$.customerCode')))
         FROM {$table_name}
         WHERE {$job_type_column} = %s
         AND DATE({$created_at_column}) = %s
         AND JSON_UNQUOTE(JSON_EXTRACT({$payload_column}, '$.customerCode')) IS NOT NULL
         AND JSON_UNQUOTE(JSON_EXTRACT({$payload_column}, '$.customerCode')) != ''",
        $delivery_order_type,
        $today
    )
);
?>

<div id="ac-customers-served-counter" data-target="<?php echo esc_attr($total_today); ?>">0</div>

<style>
#ac-customers-served-counter{
    font-size: 2rem;
    font-weight: 700;
    font-family: Roboto, sans-serif;
    color: #000000;
    line-height: 1;
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function () {
    const counter = document.getElementById('ac-customers-served-counter');
    if (!counter) return;

    const target = parseInt(counter.dataset.target) || 0;
    const duration = 1000;
    const start = performance.now();

    function animate(now) {
        const progress = Math.min((now - start) / duration, 1);
        const value = Math.floor(target * (1 - Math.pow(1 - progress, 3)));
        counter.textContent = value;

        if (progress < 1) {
            requestAnimationFrame(animate);
        } else {
            counter.textContent = target;
        }
    }

    requestAnimationFrame(animate);
});
</script>[xyz-ips snippet="DashboardInfo-Customer-Counter"]����.'�(��-�DashboardInfo-Latest-Entry<?php
global $wpdb;

$jobs_table   = $wpdb->prefix . 'ac_jobs';
$ledger_table = $wpdb->prefix . 'ac_basket_ledger';

$job_type_column     = 'job_type';
$job_time_column     = 'created_at';
$job_type_value      = 'DELIVERY_ORDER';

$ledger_type_column  = 'txn_type';
$ledger_time_column  = 'created_at';
$ledger_type_value   = 'RETURN';

function ac_pick_value($row, $keys = array(), $default = '-') {
    foreach ($keys as $key) {
        if (isset($row[$key]) && $row[$key] !== '' && $row[$key] !== null) {
            return $row[$key];
        }
    }
    return $default;
}

function ac_pick_json_value($json, $keys = array(), $default = '-') {
    if (empty($json)) return $default;

    $data = json_decode($json, true);
    if (!is_array($data)) return $default;

    foreach ($keys as $key) {
        if (isset($data[$key]) && $data[$key] !== '' && $data[$key] !== null) {
            return $data[$key];
        }
    }

    return $default;
}

function ac_format_time_only($datetime) {
    if (empty($datetime) || $datetime === '-') return '-';

    $timestamp = strtotime($datetime);
    if (!$timestamp) return $datetime;

    return wp_date('g:i A', $timestamp);
}

$job_rows = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT *
         FROM {$jobs_table}
         WHERE {$job_type_column} = %s
         ORDER BY {$job_time_column} DESC
         LIMIT 20",
        $job_type_value
    ),
    ARRAY_A
);

$ledger_rows = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT *
         FROM {$ledger_table}
         WHERE {$ledger_type_column} = %s
         ORDER BY {$ledger_time_column} DESC
         LIMIT 20",
        $ledger_type_value
    ),
    ARRAY_A
);

$combined = array();

if (!empty($job_rows)) {
    foreach ($job_rows as $row) {
        $payload  = isset($row['payload']) ? $row['payload'] : '';
        $result   = isset($row['result']) ? $row['result'] : '';
        $time_raw = ac_pick_value($row, array('created_at', 'created_time', 'CreatedAt'));

        $combined[] = array(
            'time_raw' => $time_raw,
            'time'     => ac_format_time_only($time_raw),
            'type'     => 'Sending',
            'customer' => ac_pick_json_value($payload, array('customerName', 'debtorName', 'DebtorName'), '-'),
            'ref'      => ac_pick_json_value($result, array('docNo'), '-'),
        );
    }
}

if (!empty($ledger_rows)) {
    foreach ($ledger_rows as $row) {
        $time_raw = ac_pick_value($row, array('created_at', 'created_time', 'txn_time', 'txn_date'));

        $combined[] = array(
            'time_raw' => $time_raw,
            'time'     => ac_format_time_only($time_raw),
            'type'     => 'Return',
            'customer' => ac_pick_value($row, array('customer_name', 'debtor_name', 'customer', 'debtor', 'customerCode', 'debtorCode'), '-'),
            'ref'      => ac_pick_value($row, array('reference_no', 'ref_no', 'doc_no', 'docno', 'reference', 'txn_ref'), '-'),
        );
    }
}

usort($combined, function($a, $b) {
    return strtotime($b['time_raw']) <=> strtotime($a['time_raw']);
});

$combined = array_slice($combined, 0, 5);
?>

<div class="ac-simple-log-wrap">
    <table class="ac-simple-log-table">
        <thead>
            <tr>
                <th class="col-time">Time</th>
                <th class="col-type">Type</th>
                <th class="col-customer">Customer</th>
                <th class="col-ref">Reference Number</th>
            </tr>
        </thead>
        <tbody>
            <?php if (!empty($combined)) : ?>
                <?php foreach ($combined as $item) : ?>
                    <tr>
                        <td class="col-time"><?php echo esc_html($item['time']); ?></td>
                        <td class="col-type"><?php echo esc_html($item['type']); ?></td>
                        <td class="col-customer"><?php echo esc_html($item['customer']); ?></td>
                        <td class="col-ref"><?php echo esc_html($item['ref']); ?></td>
                    </tr>
                <?php endforeach; ?>
            <?php else : ?>
                <tr>
                    <td colspan="4">No records found.</td>
                </tr>
            <?php endif; ?>
        </tbody>
    </table>
</div>

<style>
.ac-simple-log-wrap{
    width:100%;
    margin:0;
    overflow-x:auto;
    -webkit-overflow-scrolling:touch;
}

.ac-simple-log-table{
    width:100%;
    min-width:38rem;
    border-collapse:collapse;
    border-spacing:0;
    margin:0;
    font-family:Roboto, sans-serif;
    font-size:1rem;
    background:#FFFFFF;
}

.ac-simple-log-table th,
.ac-simple-log-table td{
    padding:0.45rem 0.4rem;
    border-bottom:1px solid #e5e7eb;
    text-align:left;
    vertical-align:top;
    color:#374151;
}

.ac-simple-log-table th{
    font-weight:700;
    background:#f8f9fa;
    color:#111827;
    white-space:nowrap;
}

.ac-simple-log-table .col-time{
    width:5.5rem;
    white-space:nowrap;
}

.ac-simple-log-table .col-type{
    width:5.5rem;
    white-space:nowrap;
}

.ac-simple-log-table .col-customer{
    min-width:10rem;
}

.ac-simple-log-table .col-ref{
    min-width:9rem;
    white-space:nowrap;
}

/* Tablet */
@media (max-width: 64rem){
    .ac-simple-log-table{
        min-width:34rem;
        font-size:0.92rem;
    }

    .ac-simple-log-table th,
    .ac-simple-log-table td{
        padding:0.38rem 0.32rem;
    }
}

/* Mobile */
@media (max-width: 48rem){
    .ac-simple-log-wrap{
        overflow-x:auto;
    }

    .ac-simple-log-table{
        min-width:30rem;
        font-size:0.84rem;
    }

    .ac-simple-log-table th,
    .ac-simple-log-table td{
        padding:0.32rem 0.28rem;
    }

    .ac-simple-log-table .col-time{
        width:4.8rem;
    }

    .ac-simple-log-table .col-type{
        width:4.8rem;
    }

    .ac-simple-log-table .col-customer{
        min-width:8rem;
    }

    .ac-simple-log-table .col-ref{
        min-width:8rem;
    }
}
</style>[xyz-ips snippet="DashboardInfo-Latest-Entry"]����/͂0$�.�MenuInfo-CurrentUser-Mobile<?php
if (is_user_logged_in()) {
    $user = wp_get_current_user();

    $display_name = !empty($user->display_name) ? $user->display_name : $user->user_login;

    $initial = function_exists('mb_substr')
        ? mb_strtoupper(mb_substr($display_name, 0, 1))
        : strtoupper(substr($display_name, 0, 1));

    echo '<div style="display:inline-flex;align-items:center;justify-content:center;">';
        echo '<div style="width:36px;height:36px;min-width:36px;border-radius:50%;background:#0f6b43;color:#fff;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;line-height:1;font-family:Arial,sans-serif;">' . esc_html($initial) . '</div>';
    echo '</div>';
}
?>[xyz-ips snippet="MenuInfo-CurrentUser-Mobile"]����+�8%�@v�0�BasketSummary-AllNG&�[xyz-ips snippet="BasketSummary-All"]����6�"H��1�basketdo-assigned-driver-dashboardN&�5[xyz-ips snippet="basketdo-assigned-driver-dashboard"]����"��PQ�2�Login-Redirect<?php
if (is_user_logged_in()) {

    $current_user = wp_get_current_user();

    // Allow User01 to stay on this page
    if ($current_user->user_login !== 'User01') {
        wp_safe_redirect(home_url('/'));
        exit;
    }
}
?>[xyz-ips snippet="Login-Redirect"]����-�X��3�Delivery-Order-Staff-ListN�&'l[xyz-ips snippet="Delivery-Order-Staff-List"]����'�`��4�Delivery-Order-EditN!&�'[xyz-ips snippet="Delivery-Order-Edit"]����8�$h���/�delivery-order-create-staff-workflowN�&w[xyz-ips snippet="delivery-order-create-staff-workflow"]����p9cS=<C�p�����������fj
N?�<?php
/**
 * RESPONSIVE COMBINED: Delivery Order + Basket Return
 * - Desktop: two-column layout for customer + add item
 * - Tablet/mobile: stacked cards with mobile chips for items
 * - Avatar header removed
 * - Modal pickers for customers and items (centered on all devices)
 * - Full button styles restored with strong CSS overrides
 * - Form resets only when user clicks "Clear / New DO"
 * - Sticky tab bar, customer dropdown inside Add Item card
 * - Customer sync between Delivery Order and Basket Return
 * - Basket Return keeps customer after successful save
 * - LINKS REDIRECT: Receipt page = /do-receipt/
 * - UPDATED: Delivery Order payload now includes basketQty, cartonQty, unitQty
 * - UPDATED: Bulk-first delivery order entry groups rows by customer + driver
 * - UPDATED (merge): Duplicate rows merge only when customer+driver+item+type+KG are equal.
 * - KG supports decimals (0.01 step), total KG displayed with 2 decimals.
 * - COMPAT: Keeps old ac/v1/job queue flow while sending WPDO/local-DO metadata for the new WordPress-first phase.
 */

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

if (!is_user_logged_in()) {
    echo '<div style="padding:1rem;border:1px solid #fecaca;background:#fff1f2;border-radius:.75rem;color:#991b1b;text-align:center;">
            Please log in to continue.
          </div>';
    return;
}

$current_user = wp_get_current_user();
$current_roles = is_array($current_user->roles ?? null) ? $current_user->roles : [];
$can_create_do = current_user_can('manage_options') || in_array('editor', $current_roles, true);
if (!$can_create_do) {
    echo '<div style="padding:1rem;border:1px solid #fecaca;background:#fff1f2;border-radius:.75rem;color:#991b1b;text-align:center;">
            You do not have permission to create delivery orders.
          </div>';
    return;
}

// -------------------------------------------------------------------
// Shared REST & AJAX data
// -------------------------------------------------------------------
$rest_nonce          = wp_create_nonce('wp_rest');
$ajax_url            = admin_url('admin-ajax.php');
$debtor_nonce        = wp_create_nonce('ac_cs_debtor_search');
$creditor_nonce      = wp_create_nonce('ac_cs_creditor_search');
$item_suggest_nonce  = wp_create_nonce('ac_itemcode_suggest');
$default_location    = 'HQ';
$today_date          = current_time('Y-m-d');

// Delivery Order endpoints
$receipt_page_base   = 'https://website.ipohserver.com/excellentvege/do-receipt/';
$records_page_base   = 'https://website.ipohserver.com/excellentvege/delivery-order-records/';
$REST_JOB_POST       = rest_url('ac/v1/job');
$REST_JOB_BASE       = rest_url('ac/v1/job/');
$REST_RECEIPT_TOKEN  = rest_url('ac/v1/do-receipt-token');

// Basket Return endpoint
$rest_return_post    = rest_url('ac/v1/basket/return');

$show_debtor_code    = false;
$show_creditor_code  = false;
$show_item_code      = false;
// Drivers are WordPress users with role=driver.
$driver_users        = get_users([
    'role'    => 'driver',
    'orderby' => 'display_name',
    'order'   => 'ASC',
]);
$driver_picker_items = array_map(function($driver) {
    $driver_login = trim((string) $driver->user_login);
    $driver_label = strtoupper($driver_login);

    return [
        'id' => (int) $driver->ID,
        'name' => $driver_label,
        'login' => $driver_login,
        'label' => $driver_label,
    ];
}, $driver_users);
?>

<div id="acd-resp-root" class="acd-resp-root"
     data-rest-nonce="<?php echo esc_attr($rest_nonce); ?>"
     data-receipt-base="<?php echo esc_attr($receipt_page_base); ?>"
     data-records-base="<?php echo esc_attr($records_page_base); ?>"
     data-rest-job-post="<?php echo esc_attr($REST_JOB_POST); ?>"
     data-rest-job-base="<?php echo esc_attr($REST_JOB_BASE); ?>"
     data-rest-receipt-token="<?php echo esc_attr($REST_RECEIPT_TOKEN); ?>"
     data-rest-return-post="<?php echo esc_attr($rest_return_post); ?>"
     data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
     data-debtor-nonce="<?php echo esc_attr($debtor_nonce); ?>"
     data-creditor-nonce="<?php echo esc_attr($creditor_nonce); ?>"
     data-item-nonce="<?php echo esc_attr($item_suggest_nonce); ?>"
     data-show-debtor-code="<?php echo $show_debtor_code ? '1' : '0'; ?>"
     data-show-creditor-code="<?php echo $show_creditor_code ? '1' : '0'; ?>"
     data-show-item-code="<?php echo $show_item_code ? '1' : '0'; ?>"
     data-default-location="<?php echo esc_attr($default_location); ?>"
     data-today="<?php echo esc_attr($today_date); ?>"
     data-drivers="<?php echo esc_attr(wp_json_encode($driver_picker_items)); ?>"
     data-local-do-mode="compat-v1"
     data-requested-doc-prefix="WPDO"
     data-grn-mode="compat-v1"
     data-grn-doc-prefix="WPGR">

    <!-- Tab Bar (Delivery Order | Goods Receive | Basket Return) -->
    <div class="acd-resp-tab-bar">
        <button type="button" class="acd-resp-tab-btn active" data-tab="delivery">Delivery Order</button>
        <button type="button" class="acd-resp-tab-btn" data-tab="goods">Goods Receive</button>
        <button type="button" class="acd-resp-tab-btn" data-tab="basket">Basket Return</button>
    </div>

    <!-- ==================== DELIVERY TAB ==================== -->
    <div id="acd-resp-delivery-tab" class="acd-resp-tab-pane active" data-tab="delivery">
        <!-- Quick entry form -->
        <div class="acd-resp-do-grid">
            <!-- Add Item Card -->
            <div class="acd-resp-card acd-resp-entry-card">
                <div class="acd-resp-card-header">
                    <h3>Add Item</h3>
                </div>
                <div class="acd-resp-card-body">
                    <div class="acd-resp-row-2">
                        <div class="acd-resp-field">
                            <label>Date</label>
                            <input type="date" id="acd_resp_do_date" class="acd-resp-input" value="<?php echo esc_attr($today_date); ?>" required>
                        </div>
                        <div class="acd-resp-field">
                            <label>Customer</label>
                            <div class="acd-resp-search-wrap" id="acdRespDebtorWrapper"
                                 data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
                                 data-nonce="<?php echo esc_attr($debtor_nonce); ?>">
                                <input type="text" id="acdRespDebtorInput" class="acd-resp-input" placeholder="Search customer..." autocomplete="off" readonly required>
                                <button type="button" id="acdRespDebtorClear" class="acd-resp-field-clear" aria-label="Clear customer">×</button>
                                <input type="hidden" id="acd_resp_do_customer" value="">
                                <input type="hidden" id="acd_resp_do_customer_name" value="">
                                <input type="hidden" id="acd_resp_do_sales_agent" value="">
                                <input type="hidden" id="acd_resp_do_location" value="<?php echo esc_attr($default_location); ?>">
                            </div>
                        </div>
                    </div>

                    <div class="acd-resp-row-2">
                        <div class="acd-resp-field">
                            <label>Driver</label>
                            <div class="acd-resp-search-wrap">
                                <input type="text" id="acd_resp_do_driver_name" class="acd-resp-input" placeholder="Select driver..." autocomplete="off" readonly required>
                                <button type="button" id="acdRespDriverClear" class="acd-resp-field-clear" aria-label="Clear driver">×</button>
                                <input type="hidden" id="acd_resp_do_driver" value="">
                                <input type="hidden" id="acd_resp_do_driver_login" value="">
                            </div>
                        </div>
                        <div class="acd-resp-field">
                            <label>Item Name</label>
                            <div class="acd-resp-search-wrap">
                                <input type="text" id="acd_resp_do_item_name" class="acd-resp-input" placeholder="Search item..." autocomplete="off" readonly required>
                                <button type="button" id="acdRespItemClear" class="acd-resp-field-clear" aria-label="Clear item">×</button>
                                <input type="hidden" id="acd_resp_do_item" value="">
                                <input type="hidden" id="acd_resp_do_item_display" value="">
                                <input type="hidden" id="acd_resp_do_item_price" value="0">
                            </div>
                        </div>
                    </div>

                    <div class="acd-resp-row-2">
                        <div class="acd-resp-field">
                            <label>Type</label>
                            <div class="acd-resp-type-toggle" id="acd_resp_do_pack_type_toggle">
                                <button type="button" class="acd-resp-type-btn active" data-pack-type="BASKET">Basket</button>
                                <button type="button" class="acd-resp-type-btn" data-pack-type="CARTON">Carton</button>
                            </div>
                            <select id="acd_resp_do_pack_type" style="display:none;" required>
                                <option value="BASKET" selected>Basket</option>
                                <option value="CARTON">Carton</option>
                            </select>
                        </div>
                        <div class="acd-resp-field">
                            <label>Qty</label>
                            <input type="number" id="acd_resp_do_qty" class="acd-resp-input" value="" step="1" min="0" placeholder="Qty" required>
                        </div>
                    </div>

                    <div class="acd-resp-row-2">
                        <div class="acd-resp-field">
                            <label>Weight (KG)</label>
                            <input type="number" id="acd_resp_do_kg" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Weight (KG)" required>
                        </div>
                        <div class="acd-resp-field">
                            <label>Price</label>
                            <input type="number" id="acd_resp_do_price" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Price">
                        </div>
                    </div>

                    <div class="acd-resp-preview" id="acd_resp_do_line_preview" style="display:none;"></div>
                    <button type="button" id="acd_resp_do_addline" class="acd-resp-btn-primary">Add Item</button>
                </div>
            </div>
        </div>

        <!-- Items Detail Card (full width) -->
        <div class="acd-resp-card acd-resp-items-card">
            <div class="acd-resp-card-header acd-resp-card-header-stack">
                <div class="acd-resp-lines-head">
                    <h3>Items Detail</h3>
                    <span id="acd_resp_do_lines_count_badge" class="acd-resp-lines-badge">0</span>
                </div>

                <button type="button" id="acd_resp_do_submit" class="acd-resp-btn-primary acd-resp-save-btn">Save Delivery Order</button>

                <!-- Success actions panel (hidden initially) -->
                <div id="acd_resp_do_success_actions" class="acd-resp-success-actions" style="display:none;" aria-live="polite">
                    <div class="acd-resp-success-text">
                        <span id="acd_resp_do_success_label">Saved batch</span>:
                        <strong id="acd_resp_do_success_docno">-</strong>
                    </div>
                    <div class="acd-resp-success-btns">
                        <a id="acd_resp_do_receipt_btn"
                           class="acd-resp-action-btn acd-resp-action-soft"
                           href="#"
                           target="_blank"
                           rel="noopener"
                           style="display:none;">
                            View Status
                        </a>
                        <button type="button"
                                id="acd_resp_do_clear_new_btn"
                                class="acd-resp-action-btn acd-resp-action-danger">
                            Clear / New DO
                        </button>
                    </div>
                </div>
            </div>
            <div class="acd-resp-card-body">
                <!-- Item details table header -->
                <div class="acd-resp-lines-header">
                    <span>Item</span>
                    <span>Customer</span>
                    <span>Driver</span>
                    <span>Type</span>
                    <span>Qty</span>
                    <span>KG</span>
                    <span>Total KG</span>
                    <span>Price</span>
                    <span>Total Price</span>
                    <span aria-label="Action">&#9998;</span>
                </div>
                <!-- Lines container -->
                <div id="acd_resp_do_lines" class="acd-resp-lines-container">
                    <div class="acd-resp-empty">No items added</div>
                </div>
            </div>
        </div>
    </div>

    <!-- ==================== GRN FORM ==================== -->
    <div id="acd-resp-grn-tab" class="acd-resp-tab-pane" data-tab="goods">
        <div class="acd-resp-do-grid">
            <!-- Add Item Card -->
            <div class="acd-resp-card acd-resp-entry-card">
                <div class="acd-resp-card-header">
                    <h3>Add Item</h3>
                </div>
                <div class="acd-resp-card-body">
                    <div class="acd-resp-field">
                        <label>Date</label>
                        <input type="date" id="acd_resp_grn_date" class="acd-resp-input" value="<?php echo esc_attr($today_date); ?>">
                    </div>

                    <div class="acd-resp-field">
                        <label>Creditor</label>
                        <div class="acd-resp-search-wrap" id="acdRespCreditorWrapper"
                             data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
                             data-nonce="<?php echo esc_attr($creditor_nonce); ?>">
                            <input type="text" id="acdRespCreditorInput" class="acd-resp-input" placeholder="Search creditor..." autocomplete="off" readonly>
                            <button type="button" id="acdRespCreditorClear" class="acd-resp-field-clear" aria-label="Clear creditor">&times;</button>
                            <input type="hidden" id="acd_resp_grn_creditor" value="">
                            <input type="hidden" id="acd_resp_grn_creditor_name" value="">
                            <input type="hidden" id="acd_resp_grn_location" value="<?php echo esc_attr($default_location); ?>">
                        </div>
                    </div>

                    <div class="acd-resp-field acd-resp-grn-type-field">
                        <label>Type</label>
                        <div class="acd-resp-type-toggle" id="acd_resp_grn_pack_type_toggle">
                            <button type="button" class="acd-resp-type-btn active" data-pack-type="BASKET">Basket</button>
                            <button type="button" class="acd-resp-type-btn" data-pack-type="CARTON">Carton</button>
                        </div>
                        <select id="acd_resp_grn_pack_type" style="display:none;">
                            <option value="BASKET" selected>Basket</option>
                            <option value="CARTON">Carton</option>
                        </select>
                    </div>

                    <div class="acd-resp-field">
                        <label>Item Name</label>
                        <div�fj������������fG�
N?� class="acd-resp-search-wrap">
                            <input type="text" id="acd_resp_grn_item_name" class="acd-resp-input" placeholder="Search item..." autocomplete="off" readonly>
                            <button type="button" id="acdRespGrnItemClear" class="acd-resp-field-clear" aria-label="Clear item">&times;</button>
                            <input type="hidden" id="acd_resp_grn_item" value="">
                            <input type="hidden" id="acd_resp_grn_item_display" value="">
                        </div>
                    </div>

                    <div class="acd-resp-field">
                        <label>Qty</label>
                        <input type="number" id="acd_resp_grn_qty" class="acd-resp-input" value="" step="1" min="0" placeholder="Qty">
                    </div>

                    <div class="acd-resp-field">
                        <label>Weight (KG)</label>
                        <input type="number" id="acd_resp_grn_kg" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Weight (KG)">
                    </div>

                    <div class="acd-resp-field">
                        <label>Price</label>
                        <input type="number" id="acd_resp_grn_price" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Price">
                    </div>

                    <div class="acd-resp-preview" id="acd_resp_grn_line_preview" style="display:none;"></div>
                    <button type="button" id="acd_resp_grn_addline" class="acd-resp-btn-primary">Add Item</button>
                </div>
            </div>
        </div>

        <!-- Items Detail Card -->
        <div class="acd-resp-card acd-resp-items-card">
            <div class="acd-resp-card-header acd-resp-card-header-stack">
                <div class="acd-resp-lines-head">
                    <h3>Items Detail</h3>
                    <span id="acd_resp_grn_lines_count_badge" class="acd-resp-lines-badge">0</span>
                </div>

                <button type="button" id="acd_resp_grn_submit" class="acd-resp-btn-primary acd-resp-save-btn">Save Goods Receive Note</button>

                <div id="acd_resp_grn_success_actions" class="acd-resp-success-actions" style="display:none;" aria-live="polite">
                    <div class="acd-resp-success-text">
                        <span id="acd_resp_grn_success_label">Saved batch</span>:
                        <strong id="acd_resp_grn_success_docno">-</strong>
                    </div>
                    <div class="acd-resp-success-btns">
                        <button type="button"
                                id="acd_resp_grn_clear_new_btn"
                                class="acd-resp-action-btn acd-resp-action-danger">
                            Clear / New GRN
                        </button>
                    </div>
                </div>
            </div>
            <div class="acd-resp-card-body">
                <div class="acd-resp-lines-header">
                    <span>Item</span>
                    <span>Creditor</span>
                    <span>Type</span>
                    <span>Qty</span>
                    <span>KG</span>
                    <span>Total KG</span>
                    <span>Price</span>
                    <span>Total Price</span>
                    <span aria-label="Action">&#9998;</span>
                </div>
                <div id="acd_resp_grn_lines" class="acd-resp-lines-container">
                    <div class="acd-resp-empty">No items added</div>
                </div>
            </div>
        </div>
    </div>

    <!-- Shared Picker Modal -->
    <div class="acd-resp-picker-modal" id="acd_resp_grn_picker_modal" aria-hidden="true">
        <div class="acd-resp-picker-backdrop" id="acd_resp_grn_picker_backdrop"></div>
        <div class="acd-resp-picker-sheet">
            <div class="acd-resp-picker-head">
                <div class="acd-resp-picker-title" id="acd_resp_grn_picker_title">Search</div>
                <button type="button" class="acd-resp-picker-close" id="acd_resp_grn_picker_close" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="acd-resp-picker-body">
                <input type="text" id="acd_resp_grn_picker_search" class="acd-resp-input acd-resp-picker-search" placeholder="Type to search..." autocomplete="off">
                <div class="acd-resp-picker-results" id="acd_resp_grn_picker_results"></div>
            </div>
        </div>
    </div>


    <!-- ==================== BASKET TAB ==================== -->
    <div id="acd-resp-basket-tab" class="acd-resp-tab-pane" data-tab="basket">
        <div class="acd-resp-stack">
            <div class="acd-resp-card acd-resp-basket-card">
                <div class="acd-resp-card-header">
                    <h3>Basket Return</h3>
                </div>
                <div class="acd-resp-card-body">
                    <input type="hidden" id="acd_resp_br_date" value="<?php echo esc_attr($today_date); ?>">

                    <div class="acd-resp-field">
                        <label>Customer</label>
                        <div class="acd-resp-search-wrap" id="acdRespBrDebtorWrapper"
                             data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
                             data-nonce="<?php echo esc_attr($debtor_nonce); ?>">
                            <input type="text" id="acdRespBrDebtorInput" class="acd-resp-input" placeholder="Search customer..." autocomplete="off" readonly>
                            <button type="button" id="acdRespBrDebtorClear" class="acd-resp-field-clear" aria-label="Clear customer">&times;</button>
                            <input type="hidden" id="acd_resp_br_debtor_code" value="">
                            <input type="hidden" id="acd_resp_br_debtor_name" value="">
                        </div>
                    </div>

                    <div class="acd-resp-field">
                        <label>Basket Return Qty</label>
                        <input type="number" id="acd_resp_br_qty" class="acd-resp-input" value="" min="1" step="1" placeholder="Basket Return Qty">
                    </div>

                    <div class="acd-resp-field">
                        <label>Proof Image <span class="acd-resp-label-note">Optional</span></label>
                        <input type="file" id="acd_resp_br_proof" class="acd-resp-file-input" accept="image/jpeg,image/png,image/webp" capture="environment">
                    </div>

                    <button type="button" id="acd_resp_br_submit" class="acd-resp-btn-primary">Save Basket Return</button>
                    <div id="acd_resp_br_status" class="acd-resp-status" aria-live="polite"></div>
                </div>
            </div>
        </div>

        <!-- Basket Customer Picker Modal -->
        <div class="acd-resp-picker-modal" id="acd_resp_br_picker_modal" aria-hidden="true">
            <div class="acd-resp-picker-backdrop" id="acd_resp_br_picker_backdrop"></div>
            <div class="acd-resp-picker-sheet">
                <div class="acd-resp-picker-head">
                    <div class="acd-resp-picker-title" id="acd_resp_br_picker_title">Select Customer</div>
                    <button type="button" class="acd-resp-picker-close" id="acd_resp_br_picker_close" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="acd-resp-picker-body">
                    <input type="text" id="acd_resp_br_picker_search" class="acd-resp-input acd-resp-picker-search" placeholder="Type to search..." autocomplete="off">
                    <div class="acd-resp-picker-results" id="acd_resp_br_picker_results"></div>
                </div>
            </div>
        </div>
    </div>

    <!-- Shared Picker Modal for Delivery Order -->
    <div class="acd-resp-picker-modal" id="acd_resp_do_picker_modal" aria-hidden="true">
        <div class="acd-resp-picker-backdrop" id="acd_resp_do_picker_backdrop"></div>
        <div class="acd-resp-picker-sheet">
            <div class="acd-resp-picker-head">
                <div class="acd-resp-picker-title" id="acd_resp_do_picker_title">Search</div>
                <button type="button" class="acd-resp-picker-close" id="acd_resp_do_picker_close" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="acd-resp-picker-body">
                <input type="text" id="acd_resp_do_picker_search" class="acd-resp-input acd-resp-picker-search" placeholder="Type to search..." autocomplete="off">
                <div class="acd-resp-picker-results" id="acd_resp_do_picker_results"></div>
            </div>
        </div>
    </div>
</div>

<style>
/* ----------------------------------------------
   RESPONSIVE STYLES (full original button effects + mobile chips)
   With stronger CSS overrides to beat theme styles
---------------------------------------------- */
#acd-resp-root {
    --acd-bg: #f8fafc;
    --acd-card-bg: #ffffff;
    --acd-border: #dbe4ee;
    --acd-border-strong: #c4d0dd;
    --acd-text: #0f172a;
    --acd-muted: #475569;
    --acd-green: #166534;
    --acd-green-light: #dcfce7;
    --acd-green-soft: #f0fdf4;
    --acd-green-dark: #14532d;
    --acd-danger: #dc2626;
    --acd-radius: 0.75rem;
    --acd-shadow: 0 0.75rem 1.75rem rgba(15, 23, 42, 0.08);
    font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
    color: var(--acd-text);
    background: var(--acd-bg);
    font-size: 1rem;
    margin: 0;
    padding: 0;
    max-width: none;
}

#acd-resp-root * {
    box-sizing: border-box;
}

/* Sticky tab bar */
#acd-resp-root .acd-resp-tab-bar {
    position: sticky;
    top: 0;
    z-index: 80;
    background: #ffffff !important;
    box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
}

/* Remove original two-column grid because customer moved into Add Item card */
#acd-resp-root .acd-resp-do-grid {
    display: block;
}

@media (min-width: 1024px) {
    #acd-resp-root .acd-resp-do-grid {
        display: block;
    }
}

/* Tab Bar - three tabs: Delivery Order | Goods Receive | Basket Return */
.acd-resp-tab-bar {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.45rem;
    background: #fff;
    border: 1px solid var(--acd-border);
    border-radius: 0.9rem;
    padding: 0.45rem;
    margin: 0 0 0.8rem;
}
@media (min-width: 1024px) {
    .acd-resp-tab-bar {
        display: grid;
        grid-template-columns: max-content max-content max-content;
        justify-content: start;
        background: #fff;
        border: 1px solid var(--acd-border);
        border-radius: 0.9rem;
        padding: 0.45rem;
        margin-bottom: 0.8rem;
    }
}
.acd-resp-tab-btn {
    min-width: 0;
    min-height: 3rem;
    padding: 0.65rem 0.75rem;
    font-size: 0.92rem;
    font-weight: 700;
    background: #f8fafc;
    border: 1px solid var(--acd-border);
    border-radius: 0.7rem;
    color: var(--acd-muted);
    cursor: pointer;
    transition: all 0.18s ease;
    text-align: center;
    white-space: normal;
}
.acd-resp-tab-btn:hover {
    background: var(--acd-green-soft);
    color: var(--acd-green);
}
.acd-resp-tab-btn.active {
    background: var(--acd-green-soft);
    border-color: #86efac;
    color: var(--acd-green);
}
@media (min-width: 1024px) {
    .acd-resp-tab-btn:hover {
        background: var(--acd-green-soft);
    }
}

/* Tab Panes */
.acd-resp-tab-pane {
    display: none;
    padding: 0;
}
.acd-resp-tab-pane.active {
    display: block;
}

/* Cards */
.acd-resp-card {
    background: var(--acd-card-bg);
    border: 1px solid var(--acd-border);
    border-radius: var(--acd-radius);
    box-shadow: var(--acd-shadow);
    overflow: hidden;
}
.acd-resp-items-card {
    margin-top: 0.9rem;
}
.acd-resp-stack {
    max-width: 42rem;
}
#acd-resp-basket-tab .acd-resp-stack {
    width: 100%;
    max-width: 64rem;
    margin-left: auto;
    margin-right: auto;
}
.acd-resp-basket-card {
    width: 100%;
}
.acd-resp-card-header {
    padding: 0.85rem 0.9rem;
    border-bottom: 1px solid var(--acd-border);
    background: #fcfdff;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
}
.acd-resp-card-header-stack {
    flex-direction: column;
    align-items: stretch;
}
.acd-resp-card-header h3 {
    margin: 0;
    font-size: 1rem;
    font-weight: 800;
}
.acd-resp-lines-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
}
.acd-resp-lines-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.8rem;
    min-height: 1.8rem;
    padding: 0 0.45rem;
    border-radius: 999px;
    background: var(--acd-green-soft);
    border: 1px solid #bbf7d0;
    color: var(--acd-green);
    font-size: 0.82rem;
    font-weight: 800;
}
.acd-resp-card-body {
    padding: 0.9rem;
}

@media (min-width: 768px) {
    #acd-resp-root .acd-resp-card-body {
        padding: 1rem;
    }

    #acd-resp-root .acd-resp-entry-card .acd-resp-card-body {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.9rem 1rem;
        align-items: end;
    }

    #acd-resp-root .acd-resp-entry-card .acd-resp-field,
    #acd-resp-root .acd-resp-entry-card .acd-resp-row-2,
    #acd-resp-root .acd-resp-entry-card .acd-resp-preview,
    #acd-resp-root .acd-resp-entry-card #acd_resp_do_addline,
    #acd-resp-root .acd-resp-entry-card #acd_resp_grn_addline {
        margin-bottom: 0;
    }

    /* Date field spans full width; Customer/Driver and Item/Type pair naturally */
    #acd-resp-root .acd-resp-entry-card .acd-resp-card-body > .acd-resp-field:first-child {
        grid-column: 1 / -1;
    }

    /* Goods Receive field positioning:
       Date | Creditor
       Type | Type
       Item | Qty
       Weight | Price */
    #acd-resp-root #acd-resp-grn-tab .acd-resp-entry-card .acd-resp-card-body > .acd-resp-field:first-child {
        grid-column: auto;
    }

    #acd-resp-root #acd-resp-grn-tab .acd-resp-grn-type-field {
        grid-column: 1 / -1;
    }

    #acd-resp-root .acd-resp-entry-card .acd-resp-row-2,
    #acd-resp-root .acd-resp-entry-card .acd-resp-preview,
    #acd-resp-root .acd-resp-entry-card #acd_resp_do_addline,
    #acd-resp-root .acd-resp-entry-card #acd_resp_grn_addline {
        grid-column: 1 / -1;
    }
}

/* Fields */
.acd-resp-field {
    margin-bottom: 0.85rem;
}
.acd-resp-field label {
    display: block;
    font-size: 0.88rem;
    font-weight: 700;
    color: var(--acd-muted);
    margin-bottom: 0.35rem;
}
.acd-resp-label-note {
    color: #64748b;
    font-size: 0.78rem;
    font-weight: 700;
}
.acd-resp-input {
    width: 100%;
    min-height: 3rem;
    padding: 0.72rem 0.85rem;
    border: 1px solid var(--acd-border-strong);
    border-radius: 0.65rem;
    background: #fff;
    font-size: 1rem;
    transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.acd-resp-file-input {
    width: 100%;
    min-height: 3rem;
    padding: 0.66rem 0.75rem;
    border: 1px dashed var(--acd-border-strong);
    border-radius: 0.65rem;
    background: #fff;
    font-size: 0.95rem;
}
.acd-resp-file-input:focus {
    outline: none;
    border-color: var(--acd-green);
    box-shadow: 0 0 0 0.2rem rgba(22, 101, 52, 0.10);
}
.acd-resp-input:focus {
    outline: none;
    border-color: var(--acd-green);
    box-shadow: 0 0 0 0.2rem rgba(22, 101, 52, 0.10);
}

/* Make date input fully clickable — expand the native calendar picker to full width */
#acd-resp-root input[type="date"].acd-resp-input,
#acd-resp-root input[type="date"] {
 �fG�iV�����������f��
N?�	   position: relative;
    cursor: pointer;
}
#acd-resp-root input[type="date"].acd-resp-input::-webkit-calendar-picker-indicator,
#acd-resp-root input[type="date"]::-webkit-calendar-picker-indicator {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}
/* Firefox fallback */
#acd-resp-root input[type="date"].acd-resp-input::-moz-calendar-picker-indicator,
#acd-resp-root input[type="date"]::-moz-calendar-picker-indicator {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

.acd-resp-search-wrap {
    position: relative;
}
.acd-resp-search-wrap .acd-resp-input {
    padding-right: 3.1rem;
    cursor: pointer;
}
.acd-resp-field-clear {
    position: absolute;
    top: 50%;
    right: 0.5rem;
    transform: translateY(-50%);
    width: 2.15rem;
    height: 2.15rem;
    border: 1px solid var(--acd-border);
    background: #fff;
    color: #64748b;
    border-radius: 0.5rem;
    display: none;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    cursor: pointer;
    transition: all 0.18s ease;
}
.acd-resp-field-clear.show {
    display: inline-flex;
}
.acd-resp-field-clear:hover {
    background: var(--acd-green-soft);
    border-color: #bbf7d0;
    color: var(--acd-green);
}

/* Type Toggle Buttons */
.acd-resp-type-toggle {
    display: flex;
    gap: 0.55rem;
}
.acd-resp-type-btn {
    flex: 1;
    min-height: 3rem;
    padding: 0.7rem 0.8rem;
    border: 1px solid var(--acd-border-strong);
    background: #f8fafc;
    color: #334155;
    border-radius: 0.65rem;
    font-weight: 700;
    font-size: 1rem;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}
.acd-resp-type-btn:hover {
    background: #ecfdf3;
    border-color: #86efac;
    color: var(--acd-green);
}
.acd-resp-type-btn.active {
    background: var(--acd-green-light);
    border-color: #16a34a;
    color: var(--acd-green);
    box-shadow: 0 0 0 1px rgba(22, 101, 52, 0.05) inset;
}
.acd-resp-row-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.8rem;
    margin-bottom: 0.5rem;
}
@media (max-width: 480px) {
    .acd-resp-row-2 {
        grid-template-columns: 1fr;
        gap: 0;
    }
}
.acd-resp-preview {
    background: var(--acd-green-soft);
    border: 1px solid #bbf7d0;
    border-radius: 0.65rem;
    padding: 0.7rem 0.8rem;
    margin: 0.6rem 0;
    font-size: 0.95rem;
}

/* ========== PRIMARY BUTTONS - STRONG OVERRIDES ========== */
#acd-resp-root .acd-resp-btn-primary,
#acd-resp-root button.acd-resp-btn-primary {
    width: 100%;
    min-height: 3.05rem;
    padding: 0.78rem 1rem;
    border: 1px solid var(--acd-green);
    border-radius: 0.7rem;
    background: var(--acd-green);
    color: #ffffff;
    font-weight: 800;
    font-size: 1rem;
    line-height: 1.2;
    font-family: inherit;
    text-align: center;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
    appearance: none;
    -webkit-appearance: none;
    box-shadow: none;
}

#acd-resp-root .acd-resp-btn-primary:hover,
#acd-resp-root button.acd-resp-btn-primary:hover {
    background: var(--acd-green-dark);
    border-color: var(--acd-green-dark);
    color: #ffffff;
    box-shadow: 0 4px 12px rgba(22, 101, 52, 0.14);
}

#acd-resp-root .acd-resp-btn-primary:focus,
#acd-resp-root button.acd-resp-btn-primary:focus {
    outline: none;
    box-shadow: 0 0 0 0.2rem rgba(22, 101, 52, 0.12);
}

#acd-resp-root .acd-resp-btn-primary:disabled,
#acd-resp-root button.acd-resp-btn-primary:disabled {
    background: #94a3b8;
    border-color: #94a3b8;
    color: #ffffff;
    cursor: not-allowed;
    opacity: 1;
    box-shadow: none;
}

/* Save button inside items card */
#acd-resp-root .acd-resp-save-btn {
    width: 100%;
}

/* Item details table */
.acd-resp-lines-header {
    display: grid;
    grid-template-columns: minmax(12rem, 2fr) 1fr 0.8fr 0.8fr 0.55fr 0.55fr 0.75fr 0.85fr 0.9fr 3rem;
    gap: 0.45rem;
    align-items: center;
    text-align: center;
    background: #f1f5f9;
    border: 1px solid var(--acd-border);
    border-radius: 0.65rem 0.65rem 0 0;
    padding: 0.72rem 0.8rem;
    font-size: 0.85rem;
    font-weight: 800;
    margin-bottom: 0.25rem;
}
.acd-resp-lines-header span:first-child {
    text-align: left;
}

.acd-resp-line {
    display: grid;
    grid-template-columns: minmax(12rem, 2fr) 1fr 0.8fr 0.8fr 0.55fr 0.55fr 0.75fr 0.85fr 0.9fr 3rem;
    gap: 0.45rem;
    align-items: center;
    text-align: center;
    padding: 0.7rem 0.8rem;
    border-right: 1px solid #eef2f6;
    border-left: 1px solid #eef2f6;
    border-bottom: 1px solid #eef2f6;
    font-size: 0.95rem;
}
.acd-resp-line > div:first-child {
    text-align: left;
}

.acd-resp-price-input {
    width: 100%;
    min-height: 2.35rem;
    padding: 0.45rem 0.55rem;
    border: 1px solid var(--acd-border-strong);
    border-radius: 0.55rem;
    background: #fff;
    color: var(--acd-text);
    font: inherit;
    font-weight: 700;
    text-align: center;
}
.acd-resp-price-input:focus {
    outline: none;
    border-color: var(--acd-green);
    box-shadow: 0 0 0 0.18rem rgba(22, 101, 52, 0.10);
}
.acd-resp-money-cell,
.acd-resp-number-cell {
    font-variant-numeric: tabular-nums;
}
.acd-resp-money-cell,
.acd-resp-price-cell,
.acd-resp-number-cell {
    text-align: center;
}
.acd-resp-type-pill {
    display: inline-flex;
    padding: 0.3rem 0.7rem;
    border-radius: 999px;
    background: var(--acd-green-soft);
    border: 1px solid #bbf7d0;
    color: var(--acd-green);
    font-size: 0.8rem;
    font-weight: 800;
}

/* ========== DESKTOP DELETE BUTTON STYLES ========== */
#acd-resp-root .acd-resp-delete-btn,
#acd-resp-root button.acd-resp-delete-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.4rem;
    min-width: 2.4rem;
    min-height: 2.35rem;
    padding: 0.45rem;
    border: 1px solid #fecaca;
    background: #fff5f5;
    color: #dc2626;
    border-radius: 0.65rem;
    font-size: 1rem;
    font-weight: 700;
    line-height: 1.2;
    font-family: inherit;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
    appearance: none;
    -webkit-appearance: none;
    box-shadow: none;
}

#acd-resp-root .acd-resp-delete-btn:hover,
#acd-resp-root button.acd-resp-delete-btn:hover {
    background: #fee2e2;
    border-color: #fca5a5;
    color: #b91c1c;
}

#acd-resp-root .acd-resp-delete-btn:focus,
#acd-resp-root button.acd-resp-delete-btn:focus {
    outline: none;
    box-shadow: 0 0 0 0.2rem rgba(220, 38, 38, 0.12);
}

.acd-resp-lines-container {
    max-height: min(32rem, 64vh);
    overflow: auto;
    padding: 0.15rem;
}
.acd-resp-lines-header,
.acd-resp-line {
    min-width: 88rem;
}
.acd-resp-empty {
    padding: 1.2rem;
    text-align: center;
    color: var(--acd-muted);
    font-style: italic;
}
.acd-resp-status {
    margin-top: 0.8rem;
    font-size: 0.9rem;
    text-align: center;
}

/* ========== SUCCESS ACTIONS PANEL ========== */
#acd-resp-root .acd-resp-success-actions {
    margin-top: 0.75rem;
    padding: 0.85rem;
    border: 1px solid #bbf7d0;
    background: var(--acd-green-soft);
    border-radius: 0.75rem;
}

#acd-resp-root .acd-resp-success-text {
    font-size: 0.9rem;
    font-weight: 700;
    color: var(--acd-green-dark);
    margin-bottom: 0.55rem;
}

#acd-resp-root .acd-resp-success-btns {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.5rem;
}

@media (min-width: 768px) {
    #acd-resp-root .acd-resp-success-btns {
        grid-template-columns: repeat(2, 1fr);
    }
}

#acd-resp-root .acd-resp-action-btn,
#acd-resp-root a.acd-resp-action-btn,
#acd-resp-root button.acd-resp-action-btn {
    min-height: 2.8rem;
    padding: 0.7rem 0.8rem;
    border-radius: 0.65rem;
    font-size: 0.92rem;
    font-weight: 800;
    line-height: 1.2;
    font-family: inherit;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    appearance: none;
    -webkit-appearance: none;
    box-shadow: none;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}

#acd-resp-root .acd-resp-action-green {
    background: var(--acd-green);
    border: 1px solid var(--acd-green);
    color: #ffffff;
}

#acd-resp-root .acd-resp-action-green:hover {
    background: var(--acd-green-dark);
    border-color: var(--acd-green-dark);
    color: #ffffff;
}

#acd-resp-root .acd-resp-action-soft {
    background: #ffffff;
    border: 1px solid #86efac;
    color: var(--acd-green);
}

#acd-resp-root .acd-resp-action-soft:hover {
    background: #dcfce7;
    border-color: #22c55e;
    color: var(--acd-green-dark);
}

#acd-resp-root .acd-resp-action-danger {
    background: #fff5f5;
    border: 1px solid #fecaca;
    color: var(--acd-danger);
}

#acd-resp-root .acd-resp-action-danger:hover {
    background: #fee2e2;
    border-color: #fca5a5;
    color: #b91c1c;
}


/* Picker Modal - Base styles (centered) */
.acd-resp-picker-modal {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 0.75rem;
}
.acd-resp-picker-modal.active {
    display: flex;
}
.acd-resp-picker-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
}
.acd-resp-picker-sheet {
    position: relative;
    width: 100%;
    max-width: 42rem;
    background: #fff;
    border-radius: 0.9rem;
    box-shadow: 0 1.4rem 2.4rem rgba(0, 0, 0, 0.18);
    overflow: hidden;
}
.acd-resp-picker-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.85rem 0.95rem;
    border-bottom: 1px solid var(--acd-border);
}
.acd-resp-picker-title {
    font-size: 1.05rem;
    font-weight: 800;
}
/* Picker close button - fixed alignment */
.acd-resp-picker-close {
    flex: 0 0 auto;
    width: 2.35rem;
    height: 2.35rem;
    padding: 0;
    border: 1px solid var(--acd-border-strong);
    background: #fff;
    color: var(--acd-text);
    border-radius: 0.55rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    font-size: 1.35rem;
    font-weight: 500;
    font-family: Arial, sans-serif;
    cursor: pointer;
    transition: all 0.18s ease;
    appearance: none;
    -webkit-appearance: none;
}
.acd-resp-picker-close span {
    display: block;
    line-height: 1;
    transform: translateY(-1px);
}
.acd-resp-picker-close:hover {
    background: var(--acd-green-soft);
    border-color: #bbf7d0;
    color: var(--acd-green);
}
.acd-resp-picker-body {
    padding: 0.85rem 0.95rem 0.95rem;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.acd-resp-picker-results {
    max-height: min(24rem, calc(86vh - 9rem));
    overflow-y: auto;
}
/* Override modal text colours to ensure dark text on white background */
.acd-resp-picker-title,
.acd-resp-picker-search,
.acd-resp-picker-results,
.acd-resp-picker-item,
.acd-resp-picker-item-main {
    color: var(--acd-text);
}
.acd-resp-picker-note,
.acd-resp-picker-item-sub {
    color: var(--acd-muted);
}
.acd-resp-picker-item {
    color: var(--acd-text);
}
.acd-resp-picker-item {
    display: block;
    width: 100%;
    text-align: left;
    min-height: 3rem;
    padding: 0.78rem 0.85rem;
    border: 1px solid var(--acd-border);
    border-radius: 0.65rem;
    background: #fff;
    margin-bottom: 0.5rem;
    cursor: pointer;
    transition: all 0.18s ease;
}
.acd-resp-picker-item:hover {
    background: var(--acd-green-soft);
    border-color: #bbf7d0;
}
.acd-resp-picker-item-main {
    font-weight: 800;
}
.acd-resp-picker-item-sub {
    font-size: 0.8rem;
    color: var(--acd-muted);
}

/* Force picker modal to stay centered on desktop, tablet, and mobile (overrides previous bottom-sheet behavior) */
#acd-resp-root .acd-resp-picker-modal {
    align-items: center !important;
    justify-content: center !important;
    padding: 0.75rem !important;
}

#acd-resp-root .acd-resp-picker-sheet {
    width: 100% !important;
    max-width: min(42rem, calc(100vw - 2rem)) !important;
    border-radius: 0.9rem !important;
    max-height: 86vh !important;
    overflow: hidden !important;
}
</style>

<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>

<script>
(function(){
    // --------------------------------------------------------------
    // TAB SWITCHING
    // --------------------------------------------------------------
    const tabs = document.querySelectorAll('#acd-resp-root .acd-resp-tab-btn');
    const panes = {
        delivery: document.getElementById('acd-resp-delivery-tab'),
        goods: document.getElementById('acd-resp-grn-tab'),
        basket: document.getElementById('acd-resp-basket-tab')
    };
    function activateTab(tabId) {
        tabs.forEach(btn => btn.classList.toggle('active', btn.dataset.tab === tabId));
        Object.keys(panes).forEach(id => panes[id].classList.toggle('active', id === tabId));
    }
    tabs.forEach(btn => btn.addEventListener('click', () => {
        const tabId = btn.dataset.tab;
        if (tabId && panes[tabId]) activateTab(tabId);
    }));

    // --------------------------------------------------------------
    // DELIVERY ORDER MODULE (with customer moved into Add Item)
    // --------------------------------------------------------------
    const root = document.getElementById('acd-resp-root');

    // Shared state between Delivery Order and Basket Return for customer sync
    const sharedCustomerState = {
        basketCustomerManuallyCleared: false
    };

    const doContainer = document.getElementById('acd-resp-delivery-tab');
    if (doContainer && !doContainer.dataset.doInit) {
        doContainer.dataset.doInit = '1';

        const REST_NONCE    = root.dataset.restNonce;
        const RECEIPT_BASE  = root.dataset.receiptBase;
        const RECORDS_BASE  = root.dataset.recordsBase || '';
        const REST_JOB_POST = root.dataset.restJobPost;
        const REST_JOB_BASE = root.dataset.restJobBase;
        const REST_RECEIPT_TOKEN = root.dataset.restReceiptToken;
        const LOCAL_DO_MODE = root.dataset.localDoMode || 'legacy';
        const REQUESTED_DOC_PREFIX = root.dataset.requestedDocPrefix || 'WPDO';
        const AJAX_URL      = root.dataset.ajaxUrl;
        const DEBTOR_NONCE  = root.dataset.debtorNonce;
        const ITEM_NONCE    = root.dataset.itemNonce;
        let DRIVER_ITEMS = [];
        try {
            DRIVER_ITEMS = JSON.parse(root.dataset.drivers || '[]');
        } catch(e) {
            DRIVER_ITEMS = [];
        }
        const DROPDOWN_META = {
            showDebtorCode: root.dataset.showDebtorCode === '1',
            showItemCode: root.dataset.showItemCode === '1',
            showSalesAgent: false
        };

        const state = {
            lines: [],
            jobFinished: false,
            isSubmitting: false,
            savedPendingClear: false,
        };
        const pickerState = { items: [], defaultItems: [], fetchFn: null, onPick: null };
        let pickerTimer = null;

        function $(id) { return document.getElementById(id); }
        function submitIdleText() { return 'Save Delivery Order'; }
        function submitDoneText() { return 'Saved - Ready for Next Batch'; }
        function submitProgressText() { return 'Queuing...'; }
        function successToastText(count = 1) { return count === 1 ? 'Delivery Order queued' : `${count}�f����k&	���������f˿
N?�
 Delivery Orders queued`; }

        function escapeHtml(s) {
            if (!s) return '';
            return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));
        }

        // NEW helper functions for quantity and KG (decimal support)
        function fmtQty(n) {
            const x = Number(n);
            return (!isFinite(x)) ? '0' : String(Math.round(x));
        }

        function fmtKg(n) {
            const x = Number(n);
            return (!isFinite(x)) ? '0.00' : x.toFixed(2);
        }

        function fmtMoney(n) {
            const x = Number(n);
            return (!isFinite(x)) ? '0.00' : x.toFixed(2);
        }

        function parseQty(n) {
            const x = Number(n);
            return (!isFinite(x) || x < 0) ? 0 : Math.round(x);
        }

        function parseKg(n) {
            const x = Number(n);
            return (!isFinite(x) || x < 0) ? 0 : Number(x.toFixed(2));
        }

        function parseMoney(n) {
            const x = Number(n);
            return (!isFinite(x) || x < 0) ? 0 : x;
        }

        function roundMoney(n) {
            return Number(parseMoney(n).toFixed(2));
        }

        function calcTotalKg(qty, kg) {
            return Number(((parseQty(qty) || 0) * (parseKg(kg) || 0)).toFixed(2));
        }

        function kgKey(n) {
            return fmtKg(parseKg(n));
        }

        function calcTotalPrice(line) {
            return parseMoney(line?.price) * (parseFloat(line?.total) || 0);
        }

        function normalizeBatchId(value) {
            return String(value || '').replace(/[^a-zA-Z0-9\-_.:]/g, '').slice(0, 80);
        }
        function makeBulkBatchId() {
            return normalizeBatchId(`DOBULK-${Date.now()}-${Math.random().toString(36).slice(2)}`);
        }
        function buildRecordsUrl(bulkBatchId) {
            const base = RECORDS_BASE || '/VegeBasketDO/delivery-order-records/';
            const url = new URL(base, window.location.origin);
            url.searchParams.set('bulkBatchId', normalizeBatchId(bulkBatchId));
            url.searchParams.set('print', '1');
            return url.toString();
        }

        function extractReturnedDocNo(response) {
            return response?.localDocNo
                || response?.local_doc_no
                || response?.sourceDocNo
                || response?.source_doc_no
                || response?.docNo
                || response?.doc_no
                || '';
        }

        function buildLocalDoCompatMeta(group, bulkBatchId, groupIndex) {
            return {
                mode: LOCAL_DO_MODE,
                schemaVersion: 'wpdo-local-v1',
                legacyQueueCompatible: true,
                sourceType: 'DELIVERY_ORDER',
                sourceSystem: 'WORDPRESS',
                requestedDocPrefix: REQUESTED_DOC_PREFIX,
                requestedDocNoMode: 'SERVER_GENERATED',
                requestedDocNoFormat: `${REQUESTED_DOC_PREFIX}{yyMM}/0000`,
                localDocNo: '',
                localDoId: null,
                bulkBatchId,
                groupIndex,
                customerCode: group?.customerCode || '',
                assignedDriverId: group?.assignedDriverId || 0
            };
        }

        function showToast(icon, title, text='') {
            if (window.Swal) Swal.fire({ toast: true, position: 'center', icon, title, text, showConfirmButton: false, timer: 2600, timerProgressBar: true });
        }
        function showModal(icon, title, html) {
            if (window.Swal) Swal.fire({ icon, title, html, confirmButtonText: 'OK' });
        }
        function showBulkSuccessModal(result) {
            const count = result?.count || 0;
            const recordsUrl = result?.recordsUrl || '#';
            const label = count === 1 ? '1 delivery order' : `${count} delivery orders`;

            if (window.Swal) {
                Swal.fire({
                    icon: 'success',
                    title: 'Delivery Orders Queued',
                    html: `<p>${escapeHtml(label)} queued for AutoCount.</p><p>DO numbers are still generating. Use the status page to print when ready.</p>`,
                    showCancelButton: true,
                    confirmButtonText: 'View Status / Print When Ready',
                    cancelButtonText: 'Close'
                }).then(res => {
                    if (res.isConfirmed && recordsUrl !== '#') {
                        window.open(recordsUrl, '_blank', 'noopener');
                    }
                });
            } else if (recordsUrl !== '#') {
                window.open(recordsUrl, '_blank', 'noopener');
            }
        }

        function savedJobListHtml(savedJobs) {
            if (!savedJobs.length) return '<p>No delivery orders were queued.</p>';

            const rows = savedJobs.map(job => {
                const customer = escapeHtml(job.customerName || job.customerCode || '-');
                const driver = escapeHtml(job.assignedDriverLabel || '-');
                const docNo = escapeHtml(job.docNo || 'Queued');
                const jobId = escapeHtml(job.jobId || '-');

                return `<li><strong>${docNo}</strong> | ${customer} | Driver: ${driver} | Job #${jobId}</li>`;
            }).join('');

            return `<ul style="text-align:left;margin:.75rem 0 0;padding-left:1.25rem;">${rows}</ul>`;
        }

        function showBulkPartialFailureModal(result) {
            const savedJobs = result?.savedJobs || [];
            const recordsUrl = result?.recordsUrl || '#';
            const errorMessage = result?.errorMessage || 'Submit failed';
            const savedCount = savedJobs.length;
            const title = savedCount
                ? `${savedCount} DO${savedCount === 1 ? '' : 's'} already queued`
                : 'Delivery Order submit failed';
            const html = `
                <p>${escapeHtml(errorMessage)}</p>
                ${savedCount ? '<p><strong>Do not resubmit these queued DOs.</strong> They were removed from the form, so retry will submit only the remaining unsaved items.</p>' : ''}
                ${savedJobListHtml(savedJobs)}
            `;

            if (window.Swal) {
                Swal.fire({
                    icon: savedCount ? 'warning' : 'error',
                    title,
                    html,
                    showCancelButton: savedCount && recordsUrl !== '#',
                    confirmButtonText: 'OK',
                    cancelButtonText: 'View Queued DOs'
                }).then(res => {
                    if (res.dismiss === Swal.DismissReason.cancel && recordsUrl !== '#') {
                        window.open(recordsUrl, '_blank', 'noopener');
                    }
                });
                return;
            }

            showModal(savedCount ? 'warning' : 'error', title, html);
        }

        function updateEntryTotal() {
            const itemCode = ($('acd_resp_do_item').value || '').trim();
            const itemName = ($('acd_resp_do_item_display').value || '').trim() || itemCode;
            const packType = ($('acd_resp_do_pack_type').value || '').trim();
            const qtyRaw = ($('acd_resp_do_qty').value || '').trim();
            const kgRaw = ($('acd_resp_do_kg').value || '').trim();
            const priceRaw = ($('acd_resp_do_price').value || '').trim();
            const qty = parseQty(qtyRaw || '0');
            const kg = parseKg(kgRaw || '0');
            const total = calcTotalKg(qty, kg);
            const defaultItemPrice = parseMoney($('acd_resp_do_item_price').value || '0');
            const enteredPrice = parseMoney(priceRaw);
            const price = enteredPrice > 0 ? enteredPrice : defaultItemPrice;
            const lineTotal = roundMoney(price * total);
            const pv = $('acd_resp_do_line_preview');
            if (!itemCode || qtyRaw === '' || kgRaw === '') {
                pv.style.display = 'none';
                pv.innerHTML = '';
                return;
            }
            pv.style.display = 'block';
            pv.innerHTML = `<div><b>${escapeHtml(itemName)}</b></div>
                            <div>Type: ${escapeHtml(packType)} | Qty: ${fmtQty(qty)} | KG: ${fmtKg(kg)} | Total KG: ${fmtKg(total)} | Price: ${fmtMoney(price)} | Line Total: ${fmtMoney(lineTotal)}</div>`;
        }

        function setPackType(type) {
            const nextType = String(type || '').toUpperCase() === 'BASKET' ? 'BASKET' : 'CARTON';
            $('acd_resp_do_pack_type').value = nextType;
            document.querySelectorAll('#acd_resp_do_pack_type_toggle .acd-resp-type-btn').forEach(btn => {
                btn.classList.toggle('active', (btn.dataset.packType || '').toUpperCase() === nextType);
            });
            updateEntryTotal();
        }

        function updateUI() {
            const lines = state.lines;
            const badge = document.getElementById('acd_resp_do_lines_count_badge');
            if (badge) badge.innerText = lines.length;

            const container = $('acd_resp_do_lines');
            if (!lines.length) {
                container.innerHTML = '<div class="acd-resp-empty">No items added</div>';
                return;
            }

            container.innerHTML = lines.map((l, idx) => `
                <div class="acd-resp-line" data-idx="${idx}">
                    <div><strong>${escapeHtml(l.itemName || l.itemCode)}</strong></div>
                    <div><strong>${escapeHtml(l.customerName || l.customerCode)}</strong></div>
                    <div>${escapeHtml(l.assignedDriverLabel || l.assignedDriverLogin)}</div>
                    <div><span class="acd-resp-type-pill">${l.packType}</span></div>
                    <div class="acd-resp-number-cell">${fmtQty(l.qty)}</div>
                    <div class="acd-resp-number-cell">${fmtKg(l.kg)}</div>
                    <div class="acd-resp-number-cell">${fmtKg(l.total)}</div>
                    <div class="acd-resp-price-cell"><input type="number" class="acd-resp-price-input" data-price-idx="${idx}" value="${fmtMoney(l.price)}" min="0" step="0.01" inputmode="decimal" aria-label="Price for ${escapeHtml(l.itemName || l.itemCode)}"></div>
                    <div class="acd-resp-money-cell" data-total-price-idx="${idx}">${fmtMoney(calcTotalPrice(l))}</div>
                    <div><button type="button" class="acd-resp-delete-btn" data-idx="${idx}" aria-label="Delete ${escapeHtml(l.itemName || l.itemCode)}" title="Delete">&#128465;</button></div>
                </div>
            `).join('');
        }

        function updateLinePrice(idx, value, shouldFormatInput = false) {
            if (isNaN(idx) || !state.lines[idx]) return;
            state.lines[idx].price = parseMoney(value);
            const nextTotal = fmtMoney(calcTotalPrice(state.lines[idx]));
            document.querySelectorAll(`[data-total-price-idx="${idx}"]`).forEach(el => {
                el.textContent = nextTotal;
            });
            if (shouldFormatInput) {
                document.querySelectorAll(`[data-price-idx="${idx}"]`).forEach(input => {
                    input.value = fmtMoney(state.lines[idx].price);
                });
            }
        }

        async function apiGet(url) {
            const res = await fetch(url, { method: 'GET', credentials: 'same-origin', headers: { 'Accept': 'application/json', 'X-WP-Nonce': REST_NONCE }, cache: 'no-store' });
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            const text = await res.text();
            return text ? JSON.parse(text) : null;
        }
        async function apiPost(url, body) {
            const res = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': REST_NONCE }, body: JSON.stringify(body) });
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            return await res.json();
        }

        async function createReceiptToken(jobId) {
            if (!REST_RECEIPT_TOKEN) {
                throw new Error('Receipt token endpoint missing');
            }
            return await apiPost(REST_RECEIPT_TOKEN, { job_id: jobId });
        }

        function buildPayloadLine(l, location) {
            const displayName = String(l.itemName || l.itemCode || '').trim();
            const isBasket = (l.packType === 'BASKET');
            const count = l.qty;
            const weightPerUnit = l.kg;
            const totalWeight = l.total;
            const unitPrice = roundMoney(l.price || 0);
            const amount = roundMoney(unitPrice * totalWeight);

            return {
                itemCode: l.itemCode,
                description: displayName,
                itemName: displayName,
                ItemName: displayName,
                itemDesc: displayName,
                uom: 'KG',
                unitPrice,
                amount,
                taxCode: 'SR-0',
                taxRate: 0,
                packType: l.packType,
                qty: totalWeight,
                kg: weightPerUnit,
                totalKg: totalWeight,
                unitQty: count,
                basketQty: isBasket ? count : null,
                cartonQty: !isBasket ? count : null,
                location
            };
        }

        function groupKey(customerCode, assignedDriverId) {
            return `${customerCode}::${assignedDriverId}`;
        }

        function groupLinesByCustomerDriver(lines) {
            const groups = new Map();
            lines.forEach(line => {
                const key = groupKey(line.customerCode, line.assignedDriverId);
                if (!groups.has(key)) {
                    groups.set(key, {
                        key,
                        customerCode: line.customerCode,
                        customerName: line.customerName,
                        salesAgent: line.salesAgent || '',
                        assignedDriverId: line.assignedDriverId,
                        assignedDriverLabel: line.assignedDriverLabel,
                        assignedDriverLogin: line.assignedDriverLogin,
                        lines: []
                    });
                }
                groups.get(key).lines.push(line);
            });
            return Array.from(groups.values());
        }

        function removeSavedGroupsFromForm(savedJobs) {
            const savedKeys = new Set(savedJobs.map(job => job.groupKey).filter(Boolean));
            if (!savedKeys.size) return;

            state.lines = state.lines.filter(line => !savedKeys.has(groupKey(line.customerCode, line.assignedDriverId)));
            updateUI();
        }

        function clearDeliveryFormAfterSave() {
            clearCustomerSelection();
            clearDriverSelection();
            clearLineEntry();
            state.lines = [];
            state.jobFinished = false;
            state.savedPendingClear = false;
            const submitBtn = $('acd_resp_do_submit');
            if (submitBtn) {
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            }
            updateUI();
            updateClearButtons();
        }

        async function searchItemsLive(q) {
            if (!AJAX_URL || !ITEM_NONCE) {
                console.warn('[Item Search] Missing AJAX_URL or ITEM_NONCE', { AJAX_URL, ITEM_NONCE });
                return [];
            }

            const url =
                `${AJAX_URL}?action=ac_itemcode_suggest` +
                `&nonce=${encodeURIComponent(ITEM_NONCE)}` +
                `&_ajax_nonce=${encodeURIComponent(ITEM_NONCE)}` +
                `&term=${encodeURIComponent(q)}` +
                `&q=${encodeURIComponent(q)}` +
                `&keyword=${encodeURIComponent(q)}`;

            const res = await fetch(url, {
                method: 'GET',
�f˿x@
���������g
�
N?�                credentials: 'same-origin',
                cache: 'no-store'
            });

            const text = await res.text();
            let data = null;

            try {
                data = text ? JSON.parse(text) : null;
            } catch (e) {
                console.error('[Item Search] Non-JSON response:', text);
                throw new Error('Item search returned invalid response.');
            }

            console.log('[Item Search] Response:', data);

            if (!data) {
                return [];
            }

            let rows = [];

            if (Array.isArray(data)) {
                rows = data;
            } else if (Array.isArray(data.items)) {
                rows = data.items;
            } else if (Array.isArray(data.data)) {
                rows = data.data;
            } else if (Array.isArray(data.data?.items)) {
                rows = data.data.items;
            } else if (Array.isArray(data.results)) {
                rows = data.results;
            } else if (Array.isArray(data.data?.results)) {
                rows = data.data.results;
            }

            return rows.map(it => {
                const code =
                    it.code ||
                    it.itemCode ||
                    it.ItemCode ||
                    it.item_code ||
                    it.value ||
                    '';

                const name =
                    it.desc ||
                    it.description ||
                    it.Description ||
                    it.name ||
                    it.itemName ||
                    it.ItemName ||
                    it.label ||
                    code;

                const price =
                    it.price ??
                    it.Price ??
                    it.unitPrice ??
                    it.UnitPrice ??
                    it.salesPrice ??
                    it.SalesPrice ??
                    0;

                return {
                    code: String(code || '').trim(),
                    name: String(name || code || '').trim(),
                    price: parseMoney(price)
                };
            }).filter(it => it.code || it.name);
        }

        async function searchDebtorsLive(q) {
            const wrapper = $('acdRespDebtorWrapper');
            const url = `${wrapper.dataset.ajaxUrl}?action=ac_cs_debtor_search&nonce=${encodeURIComponent(wrapper.dataset.nonce)}&q=${encodeURIComponent(q)}`;
            const res = await fetch(url, { credentials: 'same-origin' });
            const data = await res.json();
            if (!data.success) throw new Error(data.data?.error || 'Search failed');
            const items = data.data?.items || [];
            return items.map(it => {
                const name = it.name || it.debtorName || '';
                const code = it.code || it.debtorCode || '';
                const sa = (it.salesAgent || it.sales_agent || '').trim();
                const meta = [];
                if (DROPDOWN_META.showDebtorCode && code) meta.push(code);
                if (DROPDOWN_META.showSalesAgent && sa) meta.push('SA: ' + sa);
                return { label: name || code, meta: meta.join('  |  '), raw: { name, code, salesAgent: sa } };
            });
        }

        function renderPickerNote(msg) { $('acd_resp_do_picker_results').innerHTML = `<div class="acd-resp-picker-note">${escapeHtml(msg)}</div>`; }
        function renderPickerItems(items) {
            const box = $('acd_resp_do_picker_results');
            if (!items.length) { renderPickerNote('No result found'); return; }
            box.innerHTML = items.map((it, idx) => `<button type="button" class="acd-resp-picker-item" data-picker-idx="${idx}"><span class="acd-resp-picker-item-main">${escapeHtml(it.label || '')}</span>${it.meta ? `<span class="acd-resp-picker-item-sub">${escapeHtml(it.meta)}</span>` : ''}</button>`).join('');
        }
        async function runPickerSearch(q) {
            const query = (q || '').trim();
            clearTimeout(pickerTimer);
            if (query.length < 1) {
                pickerState.items = pickerState.defaultItems || [];
                if (pickerState.items.length) {
                    renderPickerItems(pickerState.items);
                } else {
                    renderPickerNote('Type to search');
                }
                return;
            }
            pickerTimer = setTimeout(async () => {
                renderPickerNote('Searching...');
                try {
                    const items = await pickerState.fetchFn(query);
                    pickerState.items = items || [];
                    renderPickerItems(pickerState.items);
                } catch(e) { pickerState.items = []; renderPickerNote('Failed to load'); }
            }, 220);
        }
        function openPicker(opts) {
            pickerState.defaultItems = opts.initialItems || [];
            pickerState.items = pickerState.defaultItems;
            pickerState.fetchFn = opts.fetchFn;
            pickerState.onPick = opts.onPick;
            $('acd_resp_do_picker_title').textContent = opts.title || 'Search';
            $('acd_resp_do_picker_search').placeholder = opts.placeholder || 'Type to search...';
            $('acd_resp_do_picker_search').value = '';
            $('acd_resp_do_picker_modal').classList.add('active');
            if (pickerState.items.length) {
                renderPickerItems(pickerState.items);
            } else {
                renderPickerNote('Type to search');
            }
            setTimeout(() => $('acd_resp_do_picker_search').focus(), 80);
        }
        function closePicker() {
            $('acd_resp_do_picker_modal').classList.remove('active');
            $('acd_resp_do_picker_search').value = '';
            $('acd_resp_do_picker_results').innerHTML = '';
            pickerState.items = []; pickerState.defaultItems = []; pickerState.fetchFn = null; pickerState.onPick = null;
        }
        function updateClearButtons() {
            const debtorHas = !!($('acdRespDebtorInput')?.value.trim());
            const driverHas = !!($('acd_resp_do_driver_name')?.value.trim());
            const itemHas = !!($('acd_resp_do_item_name')?.value.trim());
            $('acdRespDebtorClear')?.classList.toggle('show', debtorHas);
            $('acdRespDriverClear')?.classList.toggle('show', driverHas);
            $('acdRespItemClear')?.classList.toggle('show', itemHas);
        }

        // ---- Customer sync functions ----
        function setBasketCustomerFromDelivery(customer) {
            if (sharedCustomerState.basketCustomerManuallyCleared) {
                return;
            }
            const brInput = $('acdRespBrDebtorInput');
            const brCode = $('acd_resp_br_debtor_code');
            const brName = $('acd_resp_br_debtor_name');
            const brClear = $('acdRespBrDebtorClear');

            const name = customer?.name || '';
            const code = customer?.code || '';

            if (brInput) brInput.value = name || code || '';
            if (brCode) brCode.value = code;
            if (brName) brName.value = name;
            if (brClear) {
                brClear.classList.toggle('show', !!(name || code));
            }
        }

        function clearBasketCustomerFromDelivery() {
            const brInput = $('acdRespBrDebtorInput');
            const brCode = $('acd_resp_br_debtor_code');
            const brName = $('acd_resp_br_debtor_name');
            const brClear = $('acdRespBrDebtorClear');

            if (brInput) brInput.value = '';
            if (brCode) brCode.value = '';
            if (brName) brName.value = '';
            if (brClear) brClear.classList.remove('show');
        }

        function setDeliveryCustomer(picked) {
            const name = picked?.name || '';
            const code = picked?.code || '';
            const salesAgent = picked?.salesAgent || '';

            $('acdRespDebtorInput').value = name || code || '';
            $('acd_resp_do_customer').value = code;
            $('acd_resp_do_customer_name').value = name;
            $('acd_resp_do_sales_agent').value = salesAgent;

            sharedCustomerState.basketCustomerManuallyCleared = false;

            setBasketCustomerFromDelivery({
                name,
                code
            });

            updateClearButtons();
        }

        function clearCustomerSelection() {
            $('acdRespDebtorInput').value = '';
            $('acd_resp_do_customer').value = '';
            $('acd_resp_do_customer_name').value = '';
            $('acd_resp_do_sales_agent').value = '';

            sharedCustomerState.basketCustomerManuallyCleared = true;
            clearBasketCustomerFromDelivery();

            updateClearButtons();
        }

        function searchDriversLive(q) {
            const query = String(q || '').trim().toLowerCase();
            if (!query) return Promise.resolve(DRIVER_ITEMS);
            return Promise.resolve(DRIVER_ITEMS.filter(driver => {
                const haystack = [
                    String(driver.label || '').toUpperCase(),
                    String(driver.name || '').toUpperCase(),
                    driver.login || ''
                ].join(' ').toLowerCase();
                return haystack.includes(query);
            }));
        }

        function setDeliveryDriver(picked) {
            const id = parseInt(picked?.id || 0, 10) || 0;
            const label = String(picked?.login || picked?.label || picked?.name || '').toUpperCase();
            const login = picked?.login || '';
            $('acd_resp_do_driver_name').value = label;
            $('acd_resp_do_driver').value = id ? String(id) : '';
            $('acd_resp_do_driver_login').value = login;
            updateClearButtons();
        }

        function clearDriverSelection() {
            $('acd_resp_do_driver_name').value = '';
            $('acd_resp_do_driver').value = '';
            $('acd_resp_do_driver_login').value = '';
            updateClearButtons();
        }

        function openDebtorPicker() {
            openPicker({
                title: 'Select Customer',
                placeholder: 'Search customer...',
                fetchFn: searchDebtorsLive,
                onPick: (picked) => {
                    if (!picked) return;
                    setDeliveryCustomer(picked);
                    closePicker();
                }
            });
        }

        function openDriverPicker() {
            const driverOptions = DRIVER_ITEMS.map(driver => ({
                label: String(driver.login || '').toUpperCase(),
                meta: '',
                raw: driver
            }));
            openPicker({
                title: 'Select Driver',
                placeholder: 'Search driver...',
                initialItems: driverOptions,
                fetchFn: async (q) => {
                    const drivers = await searchDriversLive(q);
                    return drivers.map(driver => ({
                        label: String(driver.login || '').toUpperCase(),
                        meta: '',
                        raw: driver
                    }));
                },
                onPick: (picked) => {
                    if (!picked) return;
                    setDeliveryDriver(picked);
                    closePicker();
                }
            });
        }

        function clearItemSelection() {
            $('acd_resp_do_item_name').value = '';
            $('acd_resp_do_item').value = '';
            $('acd_resp_do_item_display').value = '';
            $('acd_resp_do_item_price').value = '0';
            updateEntryTotal();
            updateClearButtons();
        }

        function openItemPicker() {
            openPicker({
                title: 'Select Item',
                placeholder: 'Search item...',
                fetchFn: async (q) => {
                    const items = await searchItemsLive(q);
                    return items.map(it => ({
                        label: it.name || it.code,
                        meta: (DROPDOWN_META.showItemCode && it.code) ? it.code : '',
                        raw: { code: it.code, name: it.name || it.code, price: it.price || 0 }
                    }));
                },
                onPick: (picked) => {
                    if (!picked) return;
                    $('acd_resp_do_item_name').value = picked.name || picked.code || '';
                    $('acd_resp_do_item').value = picked.code || '';
                    $('acd_resp_do_item_display').value = picked.name || picked.code || '';
                    const rawPrice = Number(picked.price || 0);
                    $('acd_resp_do_item_price').value = fmtMoney(rawPrice);
                    $('acd_resp_do_price').value = rawPrice > 0 ? String(rawPrice.toFixed(2)) : '';
                    console.log('[DO item pick]', picked.code, 'price', rawPrice, 'field value', $('acd_resp_do_price').value);
                    updateEntryTotal();
                    updateClearButtons();
                    closePicker();
                }
            });
        }

        function initPickerModal() {
            $('acd_resp_do_picker_close').addEventListener('click', closePicker);
            $('acd_resp_do_picker_backdrop').addEventListener('click', closePicker);
            $('acd_resp_do_picker_search').addEventListener('input', function() { runPickerSearch(this.value); });
            $('acd_resp_do_picker_results').addEventListener('click', (e) => {
                const btn = e.target.closest('[data-picker-idx]');
                if (!btn) return;
                const idx = parseInt(btn.dataset.pickerIdx);
                if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) pickerState.onPick(pickerState.items[idx].raw);
            });
        }
        function initPickerTriggers() {
            $('acdRespDebtorInput').setAttribute('readonly', 'readonly');
            $('acd_resp_do_driver_name').setAttribute('readonly', 'readonly');
            $('acd_resp_do_item_name').setAttribute('readonly', 'readonly');
            $('acdRespDebtorInput').addEventListener('click', openDebtorPicker);
            $('acd_resp_do_driver_name').addEventListener('click', openDriverPicker);
            $('acd_resp_do_item_name').addEventListener('click', openItemPicker);
            $('acdRespDebtorClear')?.addEventListener('click', (e) => { e.preventDefault(); clearCustomerSelection(); });
            $('acdRespDriverClear')?.addEventListener('click', (e) => { e.preventDefault(); clearDriverSelection(); });
            $('acdRespItemClear')?.addEventListener('click', (e) => { e.preventDefault(); clearItemSelection(); });
        }
        function makeClientRequestId(prefix='DO') { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`; }

        function clearLineEntry() {
            $('acd_resp_do_qty').value = '';
            $('acd_resp_do_kg').value = '';
            $('acd_resp_do_price').value = '';
            $('acd_resp_do_item_name').value = '';
            $('acd_resp_do_item').value = '';
            $('acd_resp_do_item_display').value = '';
            $('acd_resp_do_item_price').value = '0';
            updateEntryTotal();
            updateClearButtons();
        }

        // ---- MERGE LOGIC (same customer+driver+item+type+KG) ----
        function findMergeableLineIndex(nextLine) {
            return state.lines.findIndex(line => {
                return String(line.customerCode || '') === String(nextLine.customerCode || '')
                    && String(line.assignedDriverId || '') === String(nextLine.assignedDriverId || '')
                    && String(line.itemCode || '').toUpperCase() === String(nextLine.itemCode || '').toUpperCase()
                    && String(line.packType || '').toUpperCase() === String(nextLin�g
�~�����������gO�
N?�e.packType || '').toUpperCase()
                    && kgKey(line.kg) === kgKey(nextLine.kg);
            });
        }

        function mergeLine(existingLine, nextLine) {
            const mergedQty = parseQty(existingLine.qty || 0) + parseQty(nextLine.qty || 0);
            const sameKg = parseKg(existingLine.kg || 0);

            existingLine.qty = mergedQty;
            existingLine.kg = sameKg;
            existingLine.total = calcTotalKg(mergedQty, sameKg);

            if (parseMoney(existingLine.price || 0) <= 0 && parseMoney(nextLine.price || 0) > 0) {
                existingLine.price = parseMoney(nextLine.price || 0);
            }

            return existingLine;
        }

        // ---- Success actions panel ----
        function hideDeliverySuccessActions() {
            const box = $('acd_resp_do_success_actions');
            const docNoEl = $('acd_resp_do_success_docno');
            const receiptBtn = $('acd_resp_do_receipt_btn');

            if (box) box.style.display = 'none';
            if (docNoEl) docNoEl.textContent = '-';
            if (receiptBtn) {
                receiptBtn.href = '#';
                receiptBtn.style.display = 'none';
            }
        }

        function showDeliverySuccessActions(data) {
            const box = $('acd_resp_do_success_actions');
            const docNoEl = $('acd_resp_do_success_docno');
            const receiptBtn = $('acd_resp_do_receipt_btn');

            const docNo = data?.docNo || data?.batchLabel || '-';
            const receiptUrl = data?.receiptUrl || '';

            if (docNoEl) docNoEl.textContent = docNo;
            if (receiptBtn && receiptUrl) {
                receiptBtn.href = receiptUrl;
                receiptBtn.style.display = 'inline-flex';
            }
            if (box) box.style.display = 'block';
        }

        function resetDeliveryOrderForm() {
            clearCustomerSelection();
            clearLineEntry();

            state.lines = [];
            state.jobFinished = false;
            state.savedPendingClear = false;

            const dateField = $('acd_resp_do_date');
            if (dateField) dateField.value = root.dataset.today || '';
            const driverSelect = $('acd_resp_do_driver');
            if (driverSelect) clearDriverSelection();

            const submitBtn = $('acd_resp_do_submit');
            if (submitBtn) {
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            }

            hideDeliverySuccessActions();
            updateUI();
            updateClearButtons();
        }

        // Init
        initPickerModal();
        initPickerTriggers();
        $('acd_resp_do_qty').addEventListener('input', updateEntryTotal);
        $('acd_resp_do_kg').addEventListener('input', updateEntryTotal);
        $('acd_resp_do_price').addEventListener('input', updateEntryTotal);
        $('acd_resp_do_pack_type').addEventListener('change', () => setPackType($('acd_resp_do_pack_type').value));
        document.querySelectorAll('#acd_resp_do_pack_type_toggle .acd-resp-type-btn').forEach(btn => btn.addEventListener('click', () => setPackType(btn.dataset.packType)));
        setPackType('BASKET');
        updateUI();

        // Add Item click with merge
        $('acd_resp_do_addline').addEventListener('click', () => {
            const itemCode = ($('acd_resp_do_item').value || '').trim();
            const itemName = ($('acd_resp_do_item_display').value || '').trim() || itemCode;
            const packType = ($('acd_resp_do_pack_type').value || '').trim();
            const qtyRaw = ($('acd_resp_do_qty').value || '').trim();
            const kgRaw = ($('acd_resp_do_kg').value || '').trim();
            const qty = parseQty(qtyRaw || '0');
            const kg = parseKg(kgRaw || '0');
            const defaultItemPrice = parseMoney($('acd_resp_do_item_price').value || '0');
            const enteredPrice = parseMoney($('acd_resp_do_price').value || '');
            const price = enteredPrice > 0 ? enteredPrice : defaultItemPrice;
            const customerCode = ($('acd_resp_do_customer').value || '').trim();
            const customerName = ($('acd_resp_do_customer_name').value || '').trim();
            const salesAgent = ($('acd_resp_do_sales_agent').value || '').trim();
            const assignedDriverId = parseInt($('acd_resp_do_driver')?.value || '0', 10) || 0;
            const assignedDriverLabel = ($('acd_resp_do_driver_name')?.value || '').trim();
            const assignedDriverLogin = ($('acd_resp_do_driver_login')?.value || '').trim();
            if (!customerCode) { showToast('error', 'Select customer'); return; }
            if (!assignedDriverId) { showToast('error', 'Select driver'); return; }
            if (!itemCode) { showToast('error', 'Select an item'); return; }
            if (qty <= 0) { showToast('error', 'Qty must be >0'); return; }
            if (kg <= 0) { showToast('error', 'KG must be >0'); return; }

            const nextLine = {
                customerCode,
                customerName,
                salesAgent,
                assignedDriverId,
                assignedDriverLabel,
                assignedDriverLogin,
                itemCode,
                itemName,
                packType,
                qty,
                kg,
                total: calcTotalKg(qty, kg),
                price
            };

            const existingIdx = findMergeableLineIndex(nextLine);

            if (existingIdx >= 0) {
                mergeLine(state.lines[existingIdx], nextLine);
                updateUI();
                clearLineEntry();
                showToast(
                    'warning',
                    'Same item + KG merged',
                    `${itemName} ${fmtKg(kg)}KG already exists for ${customerName || customerCode}. Quantity has been added into the same row.`
                );
                return;
            }

            state.lines.push(nextLine);
            updateUI();
            clearLineEntry();
            showToast('success', 'Item added');
        });

        // Item detail events for editable price and delete buttons.
        document.getElementById('acd_resp_do_lines').addEventListener('click', (e) => {
            const btn = e.target.closest('.acd-resp-delete-btn');
            if (!btn) return;
            const idx = parseInt(btn.dataset.idx);
            if (!isNaN(idx)) {
                state.lines.splice(idx, 1);
                updateUI();
                showToast('info', 'Item removed');
            }
        });
        document.getElementById('acd_resp_do_lines').addEventListener('input', (e) => {
            const input = e.target.closest('.acd-resp-price-input');
            if (!input) return;
            updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, false);
        });
        document.getElementById('acd_resp_do_lines').addEventListener('change', (e) => {
            const input = e.target.closest('.acd-resp-price-input');
            if (!input) return;
            updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, true);
        });

        const clearNewBtn = $('acd_resp_do_clear_new_btn');
        if (clearNewBtn) {
            clearNewBtn.addEventListener('click', () => {
                resetDeliveryOrderForm();
                showToast('info', 'Ready for new DO');
            });
        }

        $('acd_resp_do_submit').addEventListener('click', async () => {
            if (state.isSubmitting) { showToast('warning', 'Already submitting'); return; }

            const submitBtn = $('acd_resp_do_submit');
            let saveSucceeded = false;
            state.isSubmitting = true;
            state.jobFinished = false;
            submitBtn.disabled = true;
            submitBtn.textContent = submitProgressText();

            const savedJobs = [];
            let recordsUrl = '#';

            try {
                const location = ($('acd_resp_do_location').value || '').trim();
                const docDate = ($('acd_resp_do_date').value || '').trim();
                if (!state.lines.length) throw new Error('Add at least one item');

                const groups = groupLinesByCustomerDriver(state.lines);
                if (!groups.length) throw new Error('Add at least one valid item');

                groups.forEach((group, groupIdx) => {
                    if (!group.customerCode) throw new Error(`Group ${groupIdx + 1}: customer missing`);
                    if (!group.assignedDriverId) throw new Error(`Group ${groupIdx + 1}: driver missing`);
                    if (!group.lines.length) throw new Error(`Group ${groupIdx + 1}: item lines missing`);
                    group.lines.forEach((line, lineIdx) => {
                        if (!line.itemCode) throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: item missing`);
                        if (line.qty <= 0 || line.kg <= 0 || line.total <= 0) {
                            throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: quantity and KG must be >0`);
                        }
                    });
                });

                const bulkBatchId = makeBulkBatchId();
                recordsUrl = buildRecordsUrl(bulkBatchId);

                for (let i = 0; i < groups.length; i++) {
                    const group = groups[i];
                    const payloadLines = group.lines.map(line => buildPayloadLine(line, location));
                    const payload = {
                        bulkBatchId,
                        customerCode: group.customerCode,
                        customerName: group.customerName,
                        salesAgent: group.salesAgent,
                        debtorCode: group.customerCode,
                        DebtorCode: group.customerCode,
                        debtorName: group.customerName,
                        DebtorName: group.customerName,
                        SalesAgent: group.salesAgent,
                        location,
                        Location: location,
                        docDate,
                        remark: '',
                        assignedDriverId: group.assignedDriverId,
                        assignedDriverName: group.assignedDriverLabel,
                        assignedDriverLogin: group.assignedDriverLogin,
                        driverId: group.assignedDriverId,
                        driverName: group.assignedDriverLabel,
                        driverLogin: group.assignedDriverLogin,

                        // Compatibility metadata for the new WordPress-first DO structure.
                        // Current/old endpoint and bridge can safely ignore this.
                        // New endpoint will use it to create wp_vege_ac_do + wp_vege_ac_do_items first,
                        // then keep wp_vege_ac_jobs as the sync queue.
                        localDoCompat: buildLocalDoCompatMeta(group, bulkBatchId, i + 1),

                        // Server must generate WPDO number. Do not generate DocNo in browser.
                        localDocNo: '',
                        sourceType: 'DELIVERY_ORDER',
                        sourceSystem: 'WORDPRESS',
                        requestedDocPrefix: REQUESTED_DOC_PREFIX,
                        requestedDocNoMode: 'SERVER_GENERATED',

                        lines: payloadLines
                    };
                    const body = {
                        type: 'DELIVERY_ORDER',
                        bulkBatchId,
                        client_request_id: normalizeBatchId(`${bulkBatchId}-${i + 1}`),
                        source: 'wp-ui',
                        assignedDriverId: group.assignedDriverId,
                        payload
                    };
                    const r = await apiPost(REST_JOB_POST, body);
                    const jobId = r.jobId || r.id;
                    const returnedDocNo = extractReturnedDocNo(r);
                    if (!jobId) throw new Error(`No job ID returned for ${group.customerName || group.customerCode}`);
                    showToast('info', 'Job queued', `${group.customerName || group.customerCode}${returnedDocNo ? ' | ' + returnedDocNo : ' | Job #' + jobId}`);

                    savedJobs.push({
                        jobId,
                        groupKey: group.key,
                        docNo: returnedDocNo,
                        customerCode: group.customerCode,
                        customerName: group.customerName,
                        assignedDriverLabel: group.assignedDriverLabel
                    });
                }

                showDeliverySuccessActions({
                    batchLabel: `${savedJobs.length} DO${savedJobs.length === 1 ? '' : 's'} | ${bulkBatchId}`,
                    receiptUrl: recordsUrl
                });
                showBulkSuccessModal({ count: savedJobs.length, recordsUrl });
                clearDeliveryFormAfterSave();
                saveSucceeded = true;
                submitBtn.textContent = submitDoneText();
            } catch(err) {
                if (savedJobs.length) {
                    removeSavedGroupsFromForm(savedJobs);
                }
                showBulkPartialFailureModal({
                    savedJobs,
                    recordsUrl,
                    errorMessage: err.message
                });
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            } finally {
                state.isSubmitting = false;
                if (!saveSucceeded) {
                    submitBtn.disabled = false;
                    submitBtn.textContent = submitIdleText();
                }
            }
        });
    }

    // --------------------------------------------------------------
    // BASKET RETURN MODULE (unchanged except integer qty)
    // --------------------------------------------------------------
    const basketContainer = document.getElementById('acd-resp-basket-tab');
    if (basketContainer && !basketContainer.dataset.brInit) {
        basketContainer.dataset.brInit = '1';
        const REST_NONCE = root.dataset.restNonce;
        const REST_RETURN_URL = root.dataset.restReturnPost;
        const SHOW_DEBTOR_CODE = root.dataset.showDebtorCode === '1';
        const AJAX_URL = root.dataset.ajaxUrl;
        const DEBTOR_NONCE = root.dataset.debtorNonce;

        let isSubmitting = false;
        const pickerState = { items: [], fetchFn: null, onPick: null };
        let pickerTimer = null;

        function esc(s) { return s ? String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c])) : ''; }
        function toast(icon, title, text='') { if(window.Swal) Swal.fire({ toast: true, position: 'center', icon, title, text, showConfirmButton: false, timer: 2200, timerProgressBar: true }); }
        function whole(n) { const x = Number(n); return (!isFinite(x) || x < 0) ? 0 : Math.round(x); }

        function renderPickerNote(msg) { const div = $('acd_resp_br_picker_results'); if(div) div.innerHTML = `<div class="acd-resp-picker-note">${esc(msg)}</div>`; }
        function renderPickerItems(items) {
            const box = $('acd_resp_br_picker_results');
            if (!box) return;
            if (!items.length) { renderPickerNote('No result found'); return; }
            box.innerHTML = items.map((it, idx) => `<button type="button" class="acd-resp-picker-item" data-picker-idx="${idx}"><span class="acd-resp-picker-item-main">${esc(it.label || '')}</span>${it.meta ? `<span class="acd-resp-picker-item-sub">${esc(it.meta)}</span>` : ''}</button>`).join('');
        }
        async function runPickerSearch(q) {
            const query = (q || '').trim();
            clearTimeout(pickerTimer);
            if (query.length < 1) { pickerState.items = []; renderPickerNote('Type to search'); return; �gO�*y$*���������g�#
N?�
}
            pickerTimer = setTimeout(async () => {
                renderPickerNote('Searching...');
                try { const items = await pickerState.fetchFn(query); pickerState.items = items || []; renderPickerItems(pickerState.items); }
                catch(e) { pickerState.items = []; renderPickerNote('Failed to load'); }
            }, 220);
        }
        function openBasketPicker(opts) {
            pickerState.items = []; pickerState.fetchFn = opts.fetchFn; pickerState.onPick = opts.onPick;
            const titleEl = $('acd_resp_br_picker_title');
            const searchInput = $('acd_resp_br_picker_search');
            const modal = $('acd_resp_br_picker_modal');
            if (titleEl) titleEl.textContent = opts.title || 'Search';
            if (searchInput) { searchInput.placeholder = opts.placeholder || 'Type to search...'; searchInput.value = ''; }
            if (modal) { modal.classList.add('active'); renderPickerNote('Type to search'); setTimeout(() => searchInput?.focus(), 80); }
        }
        function closeBasketPicker() {
            const modal = $('acd_resp_br_picker_modal');
            const searchInput = $('acd_resp_br_picker_search');
            const resultsDiv = $('acd_resp_br_picker_results');
            if (modal) modal.classList.remove('active');
            if (searchInput) searchInput.value = '';
            if (resultsDiv) resultsDiv.innerHTML = '';
            pickerState.items = []; pickerState.fetchFn = null; pickerState.onPick = null;
        }
        async function searchBasketDebtorsLive(q) {
            const wrapper = $('acdRespBrDebtorWrapper');
            if (!wrapper) return [];
            const url = `${wrapper.dataset.ajaxUrl}?action=ac_cs_debtor_search&nonce=${encodeURIComponent(wrapper.dataset.nonce)}&q=${encodeURIComponent(q)}`;
            const res = await fetch(url, { credentials: 'same-origin' });
            const data = await res.json();
            if (!data.success) throw new Error(data.data?.error || 'Search failed');
            const items = data.data?.items || [];
            return items.map(it => {
                const name = it.name || it.debtorName || '';
                const code = it.code || it.debtorCode || '';
                return { label: name || code, meta: (SHOW_DEBTOR_CODE && code) ? code : '', raw: { name, code } };
            });
        }
        function initBasketPickerModal() {
            const closeBtn = $('acd_resp_br_picker_close');
            const backdrop = $('acd_resp_br_picker_backdrop');
            const searchInput = $('acd_resp_br_picker_search');
            const resultsDiv = $('acd_resp_br_picker_results');
            if (closeBtn) closeBtn.addEventListener('click', closeBasketPicker);
            if (backdrop) backdrop.addEventListener('click', closeBasketPicker);
            if (searchInput) searchInput.addEventListener('input', function() { runPickerSearch(this.value); });
            if (resultsDiv) resultsDiv.addEventListener('click', (e) => {
                const btn = e.target.closest('[data-picker-idx]');
                if (!btn) return;
                const idx = parseInt(btn.dataset.pickerIdx);
                if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) pickerState.onPick(pickerState.items[idx].raw);
            });
        }
        function updateBasketClearButton() {
            const input = $('acdRespBrDebtorInput');
            const clearBtn = $('acdRespBrDebtorClear');
            if (clearBtn) clearBtn.classList.toggle('show', !!(input?.value.trim()));
        }
        function clearBasketCustomer(manual = false) {
            const input = $('acdRespBrDebtorInput');
            const hiddenCode = $('acd_resp_br_debtor_code');
            const hiddenName = $('acd_resp_br_debtor_name');

            if (input) input.value = '';
            if (hiddenCode) hiddenCode.value = '';
            if (hiddenName) hiddenName.value = '';

            if (manual) {
                sharedCustomerState.basketCustomerManuallyCleared = true;
            }

            updateBasketClearButton();
        }
        function openBasketDebtorPicker() {
            openBasketPicker({
                title: 'Select Customer',
                placeholder: 'Search customer...',
                fetchFn: searchBasketDebtorsLive,
                onPick: (picked) => {
                    if (!picked) return;
                    const input = $('acdRespBrDebtorInput');
                    const hiddenCode = $('acd_resp_br_debtor_code');
                    const hiddenName = $('acd_resp_br_debtor_name');

                    if (input) input.value = picked.name || picked.code || '';
                    if (hiddenCode) hiddenCode.value = picked.code || '';
                    if (hiddenName) hiddenName.value = picked.name || '';

                    sharedCustomerState.basketCustomerManuallyCleared = false;

                    updateBasketClearButton();
                    closeBasketPicker();
                }
            });
        }

        initBasketPickerModal();
        const input = $('acdRespBrDebtorInput');
        const clearBtn = $('acdRespBrDebtorClear');
        if (input) { input.setAttribute('readonly', 'readonly'); input.addEventListener('click', openBasketDebtorPicker); }
        if (clearBtn) {
            clearBtn.addEventListener('click', (e) => {
                e.preventDefault();
                clearBasketCustomer(true);
            });
        }
        updateBasketClearButton();

        $('acd_resp_br_submit').addEventListener('click', async () => {
            if (isSubmitting) return;
            const debtorCode = ($('acd_resp_br_debtor_code').value || '').trim();
            const debtorName = ($('acd_resp_br_debtor_name').value || '').trim();
            const docDate = $('acd_resp_br_date').value;
            const basketQty = whole($('acd_resp_br_qty').value);
            const proofInput = $('acd_resp_br_proof');
            const proofFile = proofInput?.files?.[0] || null;
            if (!debtorCode) { toast('error', 'Select customer'); return; }
            if (basketQty <= 0) { toast('error', 'Quantity must be >0'); return; }
            isSubmitting = true;
            const btn = $('acd_resp_br_submit');
            btn.disabled = true;
            btn.textContent = 'Saving...';
            try {
                let body;
                let headers = { 'X-WP-Nonce': REST_NONCE };

                if (proofFile) {
                    body = new FormData();
                    body.append('debtorCode', debtorCode);
                    body.append('debtorName', debtorName);
                    body.append('docDate', docDate);
                    body.append('basketQty', String(basketQty));
                    body.append('basketReturnProof', proofFile);
                } else {
                    body = JSON.stringify({ debtorCode, debtorName, docDate, basketQty });
                    headers['Content-Type'] = 'application/json';
                }

                const res = await fetch(REST_RETURN_URL, { method: 'POST', headers, body });
                if (!res.ok) throw new Error(await res.text());
                const data = await res.json();

                toast('success', 'Basket return saved', `${debtorName} | Qty ${basketQty}${data?.proofSaved ? ' | Proof saved' : ''}`);

                $('acd_resp_br_qty').value = '';
                if (proofInput) proofInput.value = '';

                const dateField = $('acd_resp_br_date');
                if (dateField) dateField.value = root.dataset.today || '';
            } catch(err) { toast('error', 'Save failed', err.message); }
            finally { isSubmitting = false; btn.disabled = false; btn.textContent = 'Save Basket Return'; }
        });
    }
})();
</script>

<script>
(function(){
    // --------------------------------------------------------------
    // GOODS RECEIVE NOTE MODULE
    // --------------------------------------------------------------
    const root = document.getElementById('acd-resp-root');
    const grnContainer = document.getElementById('acd-resp-grn-tab');
    if (!grnContainer || grnContainer.dataset.grnInit) return;
    grnContainer.dataset.grnInit = '1';

    const REST_NONCE    = root.dataset.restNonce;
    const REST_JOB_POST = root.dataset.restJobPost;
    const REST_JOB_BASE = root.dataset.restJobBase;
    const GRN_MODE      = root.dataset.grnMode || 'compat-v1';
    const REQUESTED_DOC_PREFIX = root.dataset.grnDocPrefix || 'WPGR';
    const AJAX_URL      = root.dataset.ajaxUrl;
    const CREDITOR_NONCE = root.dataset.creditorNonce;
    const ITEM_NONCE    = root.dataset.itemNonce;

    const DROPDOWN_META = {
        showCreditorCode: root.dataset.showCreditorCode === '1',
        showItemCode: root.dataset.showItemCode === '1'
    };

    const state = {
        lines: [],
        jobFinished: false,
        isSubmitting: false,
        savedPendingClear: false,
    };
    const pickerState = { items: [], defaultItems: [], fetchFn: null, onPick: null };
    let pickerTimer = null;

    function $(id) { return document.getElementById(id); }
    function submitIdleText() { return 'Save Goods Receive Note'; }
    function submitDoneText() { return 'Saved - Ready for Next Batch'; }
    function submitProgressText() { return 'Queuing...'; }
    function successToastText(count = 1) { return count === 1 ? 'Goods Receive Note queued' : `${count} Goods Receive Notes queued`; }

    function escapeHtml(s) {
        if (!s) return '';
        return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));
    }

    function fmtQty(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0' : String(Math.round(x));
    }

    function fmtKg(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0.00' : x.toFixed(2);
    }

    function fmtMoney(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0.00' : x.toFixed(2);
    }

    function parseQty(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : Math.round(x);
    }

    function parseKg(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : Number(x.toFixed(2));
    }

    function parseMoney(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : x;
    }

    function roundMoney(n) {
        return Number(parseMoney(n).toFixed(2));
    }

    function calcTotalKg(qty, kg) {
        return Number(((parseQty(qty) || 0) * (parseKg(kg) || 0)).toFixed(2));
    }

    function kgKey(n) {
        return fmtKg(parseKg(n));
    }

    function moneyKey(n) {
        return fmtMoney(parseMoney(n));
    }

    function calcTotalPrice(line) {
        return roundMoney(parseMoney(line?.price || 0) * (parseFloat(line?.total) || 0));
    }

    function normalizeBatchId(value) {
        return String(value || '').replace(/[^a-zA-Z0-9\-_.:]/g, '').slice(0, 80);
    }
    function makeBulkBatchId() {
        return normalizeBatchId(`GRNBULK-${Date.now()}-${Math.random().toString(36).slice(2)}`);
    }

    function extractReturnedDocNo(response) {
        return response?.localDocNo
            || response?.local_doc_no
            || response?.sourceDocNo
            || response?.source_doc_no
            || response?.docNo
            || response?.doc_no
            || '';
    }

    function buildGrnCompatMeta(group, bulkBatchId, groupIndex) {
        return {
            mode: GRN_MODE,
            schemaVersion: 'wpgrn-local-v1',
            legacyQueueCompatible: true,
            sourceType: 'GOODS_RECEIVE_NOTE',
            sourceSystem: 'WORDPRESS',
            requestedDocPrefix: REQUESTED_DOC_PREFIX,
            requestedDocNoMode: 'SERVER_GENERATED',
            requestedDocNoFormat: `${REQUESTED_DOC_PREFIX}{yyMM}/0000`,
            localDocNo: '',
            deliveryStatus: '',
            delivery_status: '',
            localGrnId: null,
            bulkBatchId,
            groupIndex,
            creditorCode: group?.creditorCode || ''
        };
    }

    function showToast(icon, title, text='') {
        if (window.Swal) {
            Swal.fire({ toast: true, position: 'center', icon, title, text, showConfirmButton: false, timer: 2600, timerProgressBar: true });
        } else {
            alert(title + (text ? '\n' + text : ''));
        }
    }
    function showModal(icon, title, html) {
        if (window.Swal) {
            Swal.fire({ icon, title, html, confirmButtonText: 'OK' });
        } else {
            alert(title + '\n' + html);
        }
    }

    function savedJobListHtml(savedJobs) {
        if (!savedJobs.length) return '<p>No Goods Receive Notes were queued.</p>';
        const rows = savedJobs.map(job => {
            const creditor = escapeHtml(job.creditorName || job.creditorCode || '-');
            const docNo = escapeHtml(job.docNo || 'Queued');
            const jobId = escapeHtml(job.jobId || '-');
            return `<li><strong>${docNo}</strong> | ${creditor} | Job #${jobId}</li>`;
        }).join('');
        return `<ul style="text-align:left;margin:.75rem 0 0;padding-left:1.25rem;">${rows}</ul>`;
    }

    function showBulkSuccessModal(result) {
        const count = result?.count || 0;
        const label = count === 1 ? '1 Goods Receive Note' : `${count} Goods Receive Notes`;
        if (window.Swal) {
            Swal.fire({
                icon: 'success',
                title: 'Goods Receive Notes Queued',
                html: `<p>${escapeHtml(label)} queued for AutoCount.</p><p>GRN numbers are still generating.</p>`,
                confirmButtonText: 'OK'
            });
        } else {
            alert(label + ' queued for AutoCount. GRN numbers are still generating.');
        }
    }

    function showBulkPartialFailureModal(result) {
        const savedJobs = result?.savedJobs || [];
        const errorMessage = result?.errorMessage || 'Submit failed';
        const savedCount = savedJobs.length;
        const title = savedCount
            ? `${savedCount} GRN${savedCount === 1 ? '' : 's'} already queued`
            : 'Goods Receive Note submit failed';
        const html = `
            <p>${escapeHtml(errorMessage)}</p>
            ${savedCount ? '<p><strong>Do not resubmit these queued GRNs.</strong> They were removed from the form, so retry will submit only the remaining unsaved items.</p>' : ''}
            ${savedJobListHtml(savedJobs)}
        `;
        showModal(savedCount ? 'warning' : 'error', title, html);
    }

    function updateEntryTotal() {
        const itemCode = ($('acd_resp_grn_item').value || '').trim();
        const itemName = ($('acd_resp_grn_item_display').value || '').trim() || itemCode;
        const packType = ($('acd_resp_grn_pack_type').value || '').trim();
        const qtyRaw = ($('acd_resp_grn_qty').value || '').trim();
        const kgRaw = ($('acd_resp_grn_kg').value || '').trim();
        const priceRaw = ($('acd_resp_grn_price').value || '').trim();
        const qty = parseQty(qtyRaw || '0');
        const kg = parseKg(kgRaw || '0');
        const price = parseMoney(priceRaw || '0');
        const total = calcTotalKg(qty, kg);
        const totalPrice = roundMoney(price * total);
        const pv = $('acd_resp_grn_line_preview');
        if (!itemCode || qtyRaw === '' || kgRaw === '') {
            pv.style.display = 'none';
            pv.innerHTML = '';
            return;
        }
        pv.style.display = 'block';
        pv.innerHTML = `<div><b>${escapeHtml(itemName)}</b></div>
                        <div>Type: ${escapeHtml(packType)} | Qty: ${fmtQty(qty)} | KG: ${fmtKg(kg)} | Total KG: ${fmtKg(total)} | Price: ${fmtMoney(price)} | Total: ${fmtMoney(totalPrice)}</div>`;
    }

    function setPackType(type) {
        const nextType = String(type || '').toUpperCase() === 'BASKET�g�#�f�/
���������g��
N?�' ? 'BASKET' : 'CARTON';
        $('acd_resp_grn_pack_type').value = nextType;
        document.querySelectorAll('#acd_resp_grn_pack_type_toggle .acd-resp-type-btn').forEach(btn => {
            btn.classList.toggle('active', (btn.dataset.packType || '').toUpperCase() === nextType);
        });
        updateEntryTotal();
    }

    function updateUI() {
        const lines = state.lines;
        const badge = document.getElementById('acd_resp_grn_lines_count_badge');
        if (badge) badge.innerText = lines.length;

        const container = $('acd_resp_grn_lines');
        if (!lines.length) {
            container.innerHTML = '<div class="acd-resp-empty">No items added</div>';
            return;
        }

        container.innerHTML = lines.map((l, idx) => `
            <div class="acd-resp-line" data-idx="${idx}">
                <div><strong>${escapeHtml(l.itemName || l.itemCode)}</strong></div>
                <div><strong>${escapeHtml(l.creditorName || l.creditorCode)}</strong></div>
                <div><span class="acd-resp-type-pill">${l.packType}</span></div>
                <div class="acd-resp-number-cell">${fmtQty(l.qty)}</div>
                <div class="acd-resp-number-cell">${fmtKg(l.kg)}</div>
                <div class="acd-resp-number-cell">${fmtKg(l.total)}</div>
                <div class="acd-resp-price-cell"><input type="number" class="acd-resp-price-input" data-price-idx="${idx}" value="${fmtMoney(l.price)}" min="0" step="0.01" inputmode="decimal" aria-label="Price for ${escapeHtml(l.itemName || l.itemCode)}"></div>
                <div class="acd-resp-money-cell" data-total-price-idx="${idx}">${fmtMoney(calcTotalPrice(l))}</div>
                <div><button type="button" class="acd-resp-delete-btn" data-idx="${idx}" aria-label="Delete ${escapeHtml(l.itemName || l.itemCode)}" title="Delete">&#128465;</button></div>
            </div>
        `).join('');
    }

    function updateLinePrice(idx, value, shouldFormatInput = false) {
        if (isNaN(idx) || !state.lines[idx]) return;
        state.lines[idx].price = parseMoney(value);
        const nextTotal = fmtMoney(calcTotalPrice(state.lines[idx]));
        document.querySelectorAll(`[data-total-price-idx="${idx}"]`).forEach(el => {
            el.textContent = nextTotal;
        });
        if (shouldFormatInput) {
            document.querySelectorAll(`[data-price-idx="${idx}"]`).forEach(input => {
                input.value = fmtMoney(state.lines[idx].price);
            });
        }
    }

    async function apiGet(url) {
        const res = await fetch(url, { method: 'GET', credentials: 'same-origin', headers: { 'Accept': 'application/json', 'X-WP-Nonce': REST_NONCE }, cache: 'no-store' });
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const text = await res.text();
        return text ? JSON.parse(text) : null;
    }
    async function apiPost(url, body) {
        const res = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': REST_NONCE }, body: JSON.stringify(body) });
        let data = null;
        const text = await res.text();
        try { data = text ? JSON.parse(text) : null; } catch (e) { data = { raw: text }; }
        if (!res.ok) {
            const message = data?.message || data?.error || `HTTP ${res.status}`;
            const err = new Error(message);
            err.status = res.status;
            err.data = data;
            throw err;
        }
        return data;
    }

    function buildPayloadLine(l, location) {
        const displayName = String(l.itemName || l.itemCode || '').trim();
        const isBasket = (l.packType === 'BASKET');
        const count = l.qty;
        const weightPerUnit = l.kg;
        const totalWeight = l.total;
        const unitPrice = roundMoney(l.price || 0);
        const amount = roundMoney(unitPrice * totalWeight);

        return {
            itemCode: l.itemCode,
            description: displayName,
            itemName: displayName,
            ItemName: displayName,
            itemDesc: displayName,
            uom: 'KG',
            unitPrice,
            amount,
            taxCode: 'SR-0',
            taxRate: 0,
            packType: l.packType,
            qty: totalWeight,
            kg: weightPerUnit,
            totalKg: totalWeight,
            unitQty: count,
            basketQty: isBasket ? count : null,
            cartonQty: !isBasket ? count : null,
            location
        };
    }

    function groupKey(creditorCode) {
        return `${creditorCode}`;
    }

    function groupLinesByCreditor(lines) {
        const groups = new Map();
        lines.forEach(line => {
            const key = groupKey(line.creditorCode);
            if (!groups.has(key)) {
                groups.set(key, {
                    key,
                    creditorCode: line.creditorCode,
                    creditorName: line.creditorName,
                    lines: []
                });
            }
            groups.get(key).lines.push(line);
        });
        return Array.from(groups.values());
    }

    function removeSavedGroupsFromForm(savedJobs) {
        const savedKeys = new Set(savedJobs.map(job => job.groupKey).filter(Boolean));
        if (!savedKeys.size) return;
        state.lines = state.lines.filter(line => !savedKeys.has(groupKey(line.creditorCode)));
        updateUI();
    }

    function clearGrnFormAfterSave() {
        clearCreditorSelection();
        clearLineEntry();
        state.lines = [];
        state.jobFinished = false;
        state.savedPendingClear = false;
        const submitBtn = $('acd_resp_grn_submit');
        if (submitBtn) {
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        }
        updateUI();
        updateClearButtons();
    }

    async function searchItemsLive(q) {
        if (!AJAX_URL || !ITEM_NONCE) {
            console.warn('[Item Search] Missing AJAX_URL or ITEM_NONCE', { AJAX_URL, ITEM_NONCE });
            return [];
        }

        const url =
            `${AJAX_URL}?action=ac_itemcode_suggest` +
            `&nonce=${encodeURIComponent(ITEM_NONCE)}` +
            `&_ajax_nonce=${encodeURIComponent(ITEM_NONCE)}` +
            `&term=${encodeURIComponent(q)}` +
            `&q=${encodeURIComponent(q)}` +
            `&keyword=${encodeURIComponent(q)}`;

        const res = await fetch(url, {
            method: 'GET',
            credentials: 'same-origin',
            cache: 'no-store'
        });

        const text = await res.text();
        let data = null;

        try {
            data = text ? JSON.parse(text) : null;
        } catch (e) {
            console.error('[Item Search] Non-JSON response:', text);
            throw new Error('Item search returned invalid response.');
        }

        console.log('[Item Search] Response:', data);

        if (!data) {
            return [];
        }

        let rows = [];

        if (Array.isArray(data)) {
            rows = data;
        } else if (Array.isArray(data.items)) {
            rows = data.items;
        } else if (Array.isArray(data.data)) {
            rows = data.data;
        } else if (Array.isArray(data.data?.items)) {
            rows = data.data.items;
        } else if (Array.isArray(data.results)) {
            rows = data.results;
        } else if (Array.isArray(data.data?.results)) {
            rows = data.data.results;
        }

        return rows.map(it => {
            const code =
                it.code ||
                it.itemCode ||
                it.ItemCode ||
                it.item_code ||
                it.value ||
                '';

            const name =
                it.desc ||
                it.description ||
                it.Description ||
                it.name ||
                it.itemName ||
                it.ItemName ||
                it.label ||
                code;

            const price =
                it.price ??
                it.Price ??
                it.unitPrice ??
                it.UnitPrice ??
                it.salesPrice ??
                it.SalesPrice ??
                0;

            return {
                code: String(code || '').trim(),
                name: String(name || code || '').trim(),
                price: parseMoney(price)
            };
        }).filter(it => it.code || it.name);
    }

    async function searchCreditorsLive(q) {
        const wrapper = $('acdRespCreditorWrapper');
        const url = `${wrapper.dataset.ajaxUrl}?action=ac_cs_creditor_search&nonce=${encodeURIComponent(wrapper.dataset.nonce)}&q=${encodeURIComponent(q)}`;
        const res = await fetch(url, { credentials: 'same-origin' });
        const data = await res.json();
        if (!data.success) throw new Error(data.data?.error || 'Search failed');
        const items = data.data?.items || [];
        return items.map(it => {
            const name = it.name || it.creditorName || '';
            const code = it.code || it.creditorCode || '';
            const meta = [];
            if (DROPDOWN_META.showCreditorCode && code) meta.push(code);
            return { label: name || code, meta: meta.join('  |  '), raw: { name, code } };
        });
    }

    function renderPickerNote(msg) { $('acd_resp_grn_picker_results').innerHTML = `<div class="acd-resp-picker-note">${escapeHtml(msg)}</div>`; }
    function renderPickerItems(items) {
        const box = $('acd_resp_grn_picker_results');
        if (!items.length) { renderPickerNote('No result found'); return; }
        box.innerHTML = items.map((it, idx) => `<button type="button" class="acd-resp-picker-item" data-picker-idx="${idx}"><span class="acd-resp-picker-item-main">${escapeHtml(it.label || '')}</span>${it.meta ? `<span class="acd-resp-picker-item-sub">${escapeHtml(it.meta)}</span>` : ''}</button>`).join('');
    }
    async function runPickerSearch(q) {
        const query = (q || '').trim();
        clearTimeout(pickerTimer);
        if (query.length < 1) {
            pickerState.items = pickerState.defaultItems || [];
            if (pickerState.items.length) {
                renderPickerItems(pickerState.items);
            } else {
                renderPickerNote('Type to search');
            }
            return;
        }
        pickerTimer = setTimeout(async () => {
            renderPickerNote('Searching...');
            try {
                const items = await pickerState.fetchFn(query);
                pickerState.items = items || [];
                renderPickerItems(pickerState.items);
            } catch(e) { pickerState.items = []; renderPickerNote('Failed to load'); }
        }, 220);
    }
    function openPicker(opts) {
        pickerState.defaultItems = opts.initialItems || [];
        pickerState.items = pickerState.defaultItems;
        pickerState.fetchFn = opts.fetchFn;
        pickerState.onPick = opts.onPick;
        $('acd_resp_grn_picker_title').textContent = opts.title || 'Search';
        $('acd_resp_grn_picker_search').placeholder = opts.placeholder || 'Type to search...';
        $('acd_resp_grn_picker_search').value = '';
        $('acd_resp_grn_picker_modal').classList.add('active');
        if (pickerState.items.length) {
            renderPickerItems(pickerState.items);
        } else {
            renderPickerNote('Type to search');
        }
        setTimeout(() => $('acd_resp_grn_picker_search').focus(), 80);
    }
    function closePicker() {
        $('acd_resp_grn_picker_modal').classList.remove('active');
        $('acd_resp_grn_picker_search').value = '';
        $('acd_resp_grn_picker_results').innerHTML = '';
        pickerState.items = []; pickerState.defaultItems = []; pickerState.fetchFn = null; pickerState.onPick = null;
    }
    function updateClearButtons() {
        const creditorHas = !!($('acdRespCreditorInput')?.value.trim());
        const itemHas = !!($('acd_resp_grn_item_name')?.value.trim());
        $('acdRespCreditorClear')?.classList.toggle('show', creditorHas);
        $('acdRespGrnItemClear')?.classList.toggle('show', itemHas);
    }

    function setCreditor(picked) {
        const name = picked?.name || '';
        const code = picked?.code || '';
        $('acdRespCreditorInput').value = name || code || '';
        $('acd_resp_grn_creditor').value = code;
        $('acd_resp_grn_creditor_name').value = name;
        updateClearButtons();
    }

    function clearCreditorSelection() {
        $('acdRespCreditorInput').value = '';
        $('acd_resp_grn_creditor').value = '';
        $('acd_resp_grn_creditor_name').value = '';
        updateClearButtons();
    }

    function clearItemSelection() {
        $('acd_resp_grn_item_name').value = '';
        $('acd_resp_grn_item').value = '';
        $('acd_resp_grn_item_display').value = '';
        $('acd_resp_grn_price').value = '';
        updateEntryTotal();
        updateClearButtons();
    }

    function openCreditorPicker() {
        openPicker({
            title: 'Select Creditor',
            placeholder: 'Search creditor...',
            fetchFn: searchCreditorsLive,
            onPick: (picked) => {
                if (!picked) return;
                setCreditor(picked);
                closePicker();
            }
        });
    }

    function openItemPicker() {
        openPicker({
            title: 'Select Item',
            placeholder: 'Search item...',
            fetchFn: async (q) => {
                const items = await searchItemsLive(q);
                return items.map(it => ({
                    label: it.name || it.code,
                    meta: (DROPDOWN_META.showItemCode && it.code) ? it.code : '',
                    raw: { code: it.code, name: it.name || it.code, price: it.price || 0 }
                }));
            },
            onPick: (picked) => {
                if (!picked) return;
                $('acd_resp_grn_item_name').value = picked.name || picked.code || '';
                $('acd_resp_grn_item').value = picked.code || '';
                $('acd_resp_grn_item_display').value = picked.name || picked.code || '';
                const rawPrice = Number(picked.price || 0);
                $('acd_resp_grn_price').value = rawPrice > 0 ? String(rawPrice.toFixed(2)) : '';
                updateEntryTotal();
                updateClearButtons();
                closePicker();
            }
        });
    }

    function initPickerModal() {
        $('acd_resp_grn_picker_close').addEventListener('click', closePicker);
        $('acd_resp_grn_picker_backdrop').addEventListener('click', closePicker);
        $('acd_resp_grn_picker_search').addEventListener('input', function() { runPickerSearch(this.value); });
        $('acd_resp_grn_picker_results').addEventListener('click', (e) => {
            const btn = e.target.closest('[data-picker-idx]');
            if (!btn) return;
            const idx = parseInt(btn.dataset.pickerIdx);
            if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) pickerState.onPick(pickerState.items[idx].raw);
        });
    }
    function initPickerTriggers() {
        $('acdRespCreditorInput').setAttribute('readonly', 'readonly');
        $('acd_resp_grn_item_name').setAttribute('readonly', 'readonly');
        $('acdRespCreditorInput').addEventListener('click', openCreditorPicker);
        $('acd_resp_grn_item_name').addEventListener('click', openItemPicker);
        $('acdRespCreditorClear')?.addEventListener('click', (e) => { e.preventDefault(); clearCreditorSelection(); });
        $('acdRespGrnItemClear')?.addEventListener('click', (e) => { e.preventDefault(); clearItemSelection(); });
    }
    function makeClientRequestId(prefix='GRN') { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`; }

    function clearLineEntry() {
        $('acd_resp_grn_qty�g�ڄ�D���������g��
N+����').value = '';
        $('acd_resp_grn_kg').value = '';
        $('acd_resp_grn_price').value = '';
        $('acd_resp_grn_item_name').value = '';
        $('acd_resp_grn_item').value = '';
        $('acd_resp_grn_item_display').value = '';
        updateEntryTotal();
        updateClearButtons();
    }

    // ---- MERGE LOGIC (same creditor + item + type + KG + price) ----
    function findMergeableLineIndex(nextLine) {
        return state.lines.findIndex(line => {
            return String(line.creditorCode || '') === String(nextLine.creditorCode || '')
                && String(line.itemCode || '').toUpperCase() === String(nextLine.itemCode || '').toUpperCase()
                && String(line.packType || '').toUpperCase() === String(nextLine.packType || '').toUpperCase()
                && kgKey(line.kg) === kgKey(nextLine.kg)
                && moneyKey(line.price) === moneyKey(nextLine.price);
        });
    }

    function mergeLine(existingLine, nextLine) {
        const mergedQty = parseQty(existingLine.qty || 0) + parseQty(nextLine.qty || 0);
        const sameKg = parseKg(existingLine.kg || 0);
        existingLine.qty = mergedQty;
        existingLine.kg = sameKg;
        existingLine.total = calcTotalKg(mergedQty, sameKg);
        return existingLine;
    }

    function hideGrnSuccessActions() {
        const box = $('acd_resp_grn_success_actions');
        const docNoEl = $('acd_resp_grn_success_docno');
        if (box) box.style.display = 'none';
        if (docNoEl) docNoEl.textContent = '-';
    }

    function showGrnSuccessActions(data) {
        const box = $('acd_resp_grn_success_actions');
        const docNoEl = $('acd_resp_grn_success_docno');
        const docNo = data?.docNo || data?.batchLabel || '-';
        if (docNoEl) docNoEl.textContent = docNo;
        if (box) box.style.display = 'block';
    }

    function resetGrnForm() {
        clearCreditorSelection();
        clearLineEntry();
        state.lines = [];
        state.jobFinished = false;
        state.savedPendingClear = false;
        const dateField = $('acd_resp_grn_date');
        if (dateField) dateField.value = root.dataset.today || '';
        const submitBtn = $('acd_resp_grn_submit');
        if (submitBtn) {
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        }
        hideGrnSuccessActions();
        updateUI();
        updateClearButtons();
    }

    initPickerModal();
    initPickerTriggers();
    $('acd_resp_grn_qty').addEventListener('input', updateEntryTotal);
    $('acd_resp_grn_kg').addEventListener('input', updateEntryTotal);
    $('acd_resp_grn_price').addEventListener('input', updateEntryTotal);
    $('acd_resp_grn_pack_type').addEventListener('change', () => setPackType($('acd_resp_grn_pack_type').value));
    document.querySelectorAll('#acd_resp_grn_pack_type_toggle .acd-resp-type-btn').forEach(btn => btn.addEventListener('click', () => setPackType(btn.dataset.packType)));
    setPackType('BASKET');
    updateUI();

    $('acd_resp_grn_addline').addEventListener('click', () => {
        const itemCode = ($('acd_resp_grn_item').value || '').trim();
        const itemName = ($('acd_resp_grn_item_display').value || '').trim() || itemCode;
        const packType = ($('acd_resp_grn_pack_type').value || '').trim();
        const qtyRaw = ($('acd_resp_grn_qty').value || '').trim();
        const kgRaw = ($('acd_resp_grn_kg').value || '').trim();
        const priceRaw = ($('acd_resp_grn_price').value || '').trim();
        const qty = parseQty(qtyRaw || '0');
        const kg = parseKg(kgRaw || '0');
        const price = parseMoney(priceRaw || '0');
        const creditorCode = ($('acd_resp_grn_creditor').value || '').trim();
        const creditorName = ($('acd_resp_grn_creditor_name').value || '').trim();

        if (!creditorCode) { showToast('error', 'Select creditor'); return; }
        if (!itemCode) { showToast('error', 'Select an item'); return; }
        if (qty <= 0) { showToast('error', 'Qty must be >0'); return; }
        if (kg <= 0) { showToast('error', 'KG must be >0'); return; }

        const nextLine = {
            creditorCode,
            creditorName,
            itemCode,
            itemName,
            packType,
            qty,
            kg,
            total: calcTotalKg(qty, kg),
            price
        };

        const existingIdx = findMergeableLineIndex(nextLine);
        if (existingIdx >= 0) {
            mergeLine(state.lines[existingIdx], nextLine);
            updateUI();
            clearLineEntry();
            showToast(
                'warning',
                'Same item + KG + price merged',
                `${itemName} ${fmtKg(kg)}KG @ ${fmtMoney(price)} already exists for ${creditorName || creditorCode}. Quantity has been added into the same row.`
            );
            return;
        }

        state.lines.push(nextLine);
        updateUI();
        clearLineEntry();
        showToast('success', 'Item added');
    });

    document.getElementById('acd_resp_grn_lines').addEventListener('click', (e) => {
        const btn = e.target.closest('.acd-resp-delete-btn');
        if (!btn) return;
        const idx = parseInt(btn.dataset.idx);
        if (!isNaN(idx)) {
            state.lines.splice(idx, 1);
            updateUI();
            showToast('info', 'Item removed');
        }
    });
    document.getElementById('acd_resp_grn_lines').addEventListener('input', (e) => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, false);
    });
    document.getElementById('acd_resp_grn_lines').addEventListener('change', (e) => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, true);
    });

    const clearNewBtn = $('acd_resp_grn_clear_new_btn');
    if (clearNewBtn) {
        clearNewBtn.addEventListener('click', () => {
            resetGrnForm();
            showToast('info', 'Ready for new GRN');
        });
    }

    $('acd_resp_grn_submit').addEventListener('click', async () => {
        if (state.isSubmitting) { showToast('warning', 'Already submitting'); return; }

        const submitBtn = $('acd_resp_grn_submit');
        let saveSucceeded = false;
        state.isSubmitting = true;
        state.jobFinished = false;
        submitBtn.disabled = true;
        submitBtn.textContent = submitProgressText();

        const savedJobs = [];

        try {
            const location = ($('acd_resp_grn_location').value || '').trim();
            const docDate = ($('acd_resp_grn_date').value || '').trim();
            if (!state.lines.length) throw new Error('Add at least one item');

            const groups = groupLinesByCreditor(state.lines);
            if (!groups.length) throw new Error('Add at least one valid item');

            groups.forEach((group, groupIdx) => {
                if (!group.creditorCode) throw new Error(`Group ${groupIdx + 1}: creditor missing`);
                if (!group.lines.length) throw new Error(`Group ${groupIdx + 1}: item lines missing`);
                group.lines.forEach((line, lineIdx) => {
                    if (!line.itemCode) throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: item missing`);
                    if (line.qty <= 0 || line.kg <= 0 || line.total <= 0) {
                        throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: quantity and KG must be >0`);
                    }
                });
            });

            const bulkBatchId = makeBulkBatchId();

            for (let i = 0; i < groups.length; i++) {
                const group = groups[i];
                const payloadLines = group.lines.map(line => buildPayloadLine(line, location));
                const payload = {
                    bulkBatchId,
                    creditorCode: group.creditorCode,
                    creditorName: group.creditorName,
                    CreditorCode: group.creditorCode,
                    CreditorName: group.creditorName,
                    location,
                    Location: location,
                    docDate,
                    remark: '',

                    localGrnCompat: buildGrnCompatMeta(group, bulkBatchId, i + 1),

                    localDocNo: '',
                    deliveryStatus: '',
                    delivery_status: '',
                    sourceType: 'GOODS_RECEIVE_NOTE',
                    sourceSystem: 'WORDPRESS',
                    requestedDocPrefix: REQUESTED_DOC_PREFIX,
                    requestedDocNoMode: 'SERVER_GENERATED',

                    lines: payloadLines
                };
                const body = {
                    type: 'GOODS_RECEIVE_NOTE',
                    bulkBatchId,
                    client_request_id: normalizeBatchId(`${bulkBatchId}-${i + 1}`),
                    source: 'wp-ui',
                    payload
                };
                const r = await apiPost(REST_JOB_POST, body);
                const jobId = r.jobId || r.id;
                const returnedDocNo = extractReturnedDocNo(r);
                if (!jobId) throw new Error(`No job ID returned for ${group.creditorName || group.creditorCode}`);
                showToast('info', 'Job queued', `${group.creditorName || group.creditorCode}${returnedDocNo ? ' | ' + returnedDocNo : ' | Job #' + jobId}`);

                savedJobs.push({
                    jobId,
                    groupKey: group.key,
                    docNo: returnedDocNo,
                    creditorCode: group.creditorCode,
                    creditorName: group.creditorName
                });
            }

            showGrnSuccessActions({
                batchLabel: `${savedJobs.length} GRN${savedJobs.length === 1 ? '' : 's'} | ${bulkBatchId}`
            });
            showBulkSuccessModal({ count: savedJobs.length });
            clearGrnFormAfterSave();
            saveSucceeded = true;
            submitBtn.textContent = submitDoneText();
        } catch(err) {
            if (savedJobs.length) {
                removeSavedGroupsFromForm(savedJobs);
            }
            showBulkPartialFailureModal({
                savedJobs,
                errorMessage: err.message
            });
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        } finally {
            state.isSubmitting = false;
            if (!saveSucceeded) {
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            }
        }
    });
})();
</script>�g��r	2����������l/�
N?�<?php
/**
 * BASKET STAFF RETURN LIST
 *
 * Staff-facing basket summary and movement history page.
 * Basket return receipts use the same A5 visual style as the driver basket receipt.
 */

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

if (!is_user_logged_in()) {
    echo '<div style="padding:14px;border:1px solid #fecaca;background:#fff1f2;border-radius:12px;color:#991b1b;">Please log in to view Basket Summary.</div>';
    return;
}

if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
    echo '<div style="padding:14px;border:1px solid #fecaca;background:#fff1f2;border-radius:12px;color:#991b1b;">You do not have permission to view Basket Summary.</div>';
    return;
}

$rest_nonce         = wp_create_nonce('wp_rest');
$rest_summary_url   = rest_url('ac/v1/basket/summary');
$rest_ledger_url    = rest_url('ac/v1/basket/ledger');
$ajax_url           = admin_url('admin-ajax.php');
$debtor_nonce       = wp_create_nonce('ac_cs_debtor_search');
$basket_proof_nonce = wp_create_nonce('ac_bs_basket_proof');
$receipt_logo_url   = 'https://website.ipohserver.com/excellentvege/wp-content/uploads/2026/05/Untitled-design-15.png';
$show_debtor_code   = false;
?>

<div id="ac-basket-summary-root"
     class="ac-bs-wrap bs-container"
     data-rest-nonce="<?php echo esc_attr($rest_nonce); ?>"
     data-rest-summary-url="<?php echo esc_attr($rest_summary_url); ?>"
     data-rest-ledger-url="<?php echo esc_attr($rest_ledger_url); ?>"
     data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
     data-debtor-nonce="<?php echo esc_attr($debtor_nonce); ?>"
     data-basket-proof-nonce="<?php echo esc_attr($basket_proof_nonce); ?>"
     data-receipt-logo-url="<?php echo esc_url($receipt_logo_url); ?>"
     data-show-debtor-code="<?php echo $show_debtor_code ? '1' : '0'; ?>">

  <div class="bs-head">
    <h1>Basket Summary</h1>
  </div>

  <div class="bs-card">
    <div class="bs-grid">
      <div class="bs-field">
        <label class="bs-label">Customer</label>
        <div class="bs-search-wrap">
          <input type="text" id="ac_bs_customer_input" class="bs-input" placeholder="Search customer to add..." autocomplete="off" readonly>
          <button type="button" id="ac_bs_customer_clear" class="bs-field-clear" aria-label="Clear customer">x</button>
          <input type="hidden" id="ac_bs_debtor_code" value="">
          <input type="hidden" id="ac_bs_debtor_name" value="">
        </div>
        <div id="ac_bs_selected_customers" class="bs-selected-customers"></div>
        <div id="ac_bs_manage_selected" class="bs-manage-selected" aria-hidden="true">
          <div class="bs-manage-head">
            <div>
              <strong>Selected Customers</strong>
              <span id="ac_bs_manage_count">0 selected</span>
            </div>
            <button type="button" class="bs-mini-btn" id="ac_bs_manage_done">Done</button>
          </div>
          <div class="bs-manage-actions">
            <button type="button" class="bs-mini-btn primary" id="ac_bs_manage_add">Add Customer</button>
            <button type="button" class="bs-mini-btn danger" id="ac_bs_manage_clear">Clear All</button>
          </div>
          <div id="ac_bs_manage_list" class="bs-manage-list"></div>
        </div>
      </div>

      <div class="bs-field">
        <label class="bs-label" for="ac_bs_date_from">Date From</label>
        <input id="ac_bs_date_from" type="date" class="bs-input">
      </div>

      <div class="bs-field">
        <label class="bs-label" for="ac_bs_date_to">Date To</label>
        <input id="ac_bs_date_to" type="date" class="bs-input">
      </div>

      <div class="bs-actions">
        <button id="ac_bs_refresh" class="bs-btn" type="button">Refresh Summary</button>
      </div>
    </div>

    <div id="ac_bs_status" class="bs-status"></div>
  </div>

  <div class="bs-card">
    <div class="bs-totals" id="ac_bs_totals"></div>

    <div class="bs-table-wrap">
      <table class="bs-table">
        <thead>
          <tr>
            <th style="width:60px;">No</th>
            <th style="width:140px;">Customer Code</th>
            <th>Customer Name</th>
            <th style="width:120px;">Basket Sent</th>
            <th style="width:130px;">Basket Returned</th>
            <th style="width:150px;">Outstanding Basket</th>
            <th style="width:130px;">Last Activity</th>
            <th style="width:90px;">Action</th>
          </tr>
        </thead>
        <tbody id="ac_bs_rows_table">
          <tr><td colspan="8" class="bs-empty-cell">No data</td></tr>
        </tbody>
      </table>
    </div>
  </div>

  <div class="bs-ledger-modal" id="ac_bs_ledger_modal" aria-hidden="true">
    <div class="bs-ledger-backdrop" id="ac_bs_ledger_backdrop"></div>
    <div class="bs-ledger-dialog">
      <div class="bs-ledger-head">
        <h2 class="bs-subtitle" id="ac_bs_ledger_title">Basket Movement History</h2>
        <button type="button" class="bs-ledger-close" id="ac_bs_ledger_close" aria-label="Close">x</button>
      </div>

      <div class="bs-ledger-body">
        <div class="bs-ledger-toolbar" id="ac_bs_ledger_toolbar" style="display:none;">
          <div class="bs-ledger-filter">
            <div class="bs-ledger-filter-group">
              <label>From <input type="date" id="ac_bs_ledger_date_from" class="bs-input"></label>
              <label>To <input type="date" id="ac_bs_ledger_date_to" class="bs-input"></label>
            </div>
            <div class="bs-ledger-filter-group">
              <select id="ac_bs_ledger_movement_filter" class="bs-input"></select>
            </div>
            <div class="bs-ledger-filter-group bs-ledger-action-group">
              <button type="button" id="ac_bs_ledger_select_all" class="bs-mini-btn">Select All</button>
              <button type="button" id="ac_bs_ledger_print" class="bs-view-btn bs-receipt-btn">Print PDF</button>
              <button type="button" id="ac_bs_ledger_share" class="bs-view-btn bs-receipt-btn bs-ledger-share-btn">Share PDF</button>
            </div>
          </div>
        </div>
        <div class="bs-table-wrap">
          <table class="bs-table bs-ledger-table">
            <thead>
              <tr>
                <th style="width:40px;"></th>
                <th style="width:50px;">No</th>
                <th style="width:120px;">Date</th>
                <th style="width:120px;">Movement</th>
                <th style="width:80px;">Qty</th>
                <th style="width:140px;">From</th>
                <th style="width:130px;">Document No.</th>
                <th>Note</th>
                <th style="width:140px;">Receipt</th>
              </tr>
            </thead>
            <tbody id="ac_bs_ledger_table">
              <tr><td colspan="9" class="bs-empty-cell">Select a customer to view basket movement history.</td></tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>

  <div id="ac_bs_receipt_mount"></div>

  <div class="bs-picker-modal" id="ac_bs_picker_modal" aria-hidden="true">
    <div class="bs-picker-backdrop" id="ac_bs_picker_backdrop"></div>
    <div class="bs-picker-sheet">
      <div class="bs-picker-head">
        <div class="bs-picker-title" id="ac_bs_picker_title">Select Customer</div>
        <button type="button" class="bs-picker-close" id="ac_bs_picker_close" aria-label="Close">x</button>
      </div>

      <div class="bs-picker-body">
        <input type="text" id="ac_bs_picker_search" class="bs-input bs-picker-search" placeholder="Search customer..." autocomplete="off">
        <div class="bs-picker-results" id="ac_bs_picker_results"></div>
      </div>
    </div>
  </div>
</div>

<style>
.bs-container{
  --bs-border:#dbe4ee;
  --bs-border-strong:#c4d0dd;
  --bs-text:#0f172a;
  --bs-muted:#475569;
  --bs-green:#0B4A2D;
  --bs-green-2:#166534;
  --bs-green-3:#16a34a;
  --bs-green-soft:#f0fdf4;
  --bs-bg:#f5faf7;
  --bs-danger:#991b1b;
  max-width:1360px;
  margin:0 auto;
  padding:16px;
  font-family:"Segoe UI",Roboto,Arial,sans-serif;
  background:linear-gradient(180deg,#f4faf5 0%,#eef7f1 100%);
  border-radius:16px;
  color:var(--bs-text);
  box-sizing:border-box;
}
.bs-container *{box-sizing:border-box;}
.bs-head{display:none;}
.bs-card{background:#fff;border:1px solid var(--bs-border);border-radius:16px;box-shadow:0 8px 28px rgba(15,23,42,.06);padding:16px;margin-bottom:14px;box-sizing:border-box;}
.bs-grid{display:grid;grid-template-columns:minmax(260px,1.4fr) minmax(150px,.75fr) minmax(150px,.75fr) auto;gap:12px;align-items:end;}
.bs-label{display:block;font-size:13px;color:var(--bs-muted);margin-bottom:6px;font-weight:800;letter-spacing:.01em;}
.bs-input{width:100%;min-height:46px;font-size:15px;padding:10px 12px;border-radius:12px;border:1px solid var(--bs-border-strong);box-sizing:border-box;background:#fff;color:#111;transition:border-color .16s ease, box-shadow .16s ease, background .16s ease;}
.bs-input:focus,.bs-btn:focus,.bs-view-btn:focus,.bs-mini-btn:focus{outline:none;border-color:var(--bs-green);box-shadow:0 0 0 3px rgba(11,74,45,.12);}
#ac-basket-summary-root #ac_bs_refresh.bs-btn{min-width:170px;min-height:46px;border:0!important;border-radius:12px!important;background:linear-gradient(135deg,#16a34a,#0B4A2D)!important;color:#fff!important;font-size:15px!important;font-weight:900!important;padding:10px 18px!important;cursor:pointer;box-shadow:0 10px 22px rgba(22,101,52,.20)!important;transition:transform .16s ease, box-shadow .16s ease, filter .16s ease;}
#ac-basket-summary-root #ac_bs_refresh.bs-btn:hover{filter:brightness(.96);transform:translateY(-1px);box-shadow:0 14px 28px rgba(22,101,52,.23)!important;}
.bs-status{display:none;margin-top:10px;font-size:14px;color:#334155;}
.bs-totals{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:14px;}
.bs-total-box{border:1px solid #e5e7eb;background:linear-gradient(180deg,#fff,#f8fafc);border-radius:14px;padding:12px;box-shadow:0 3px 12px rgba(15,23,42,.035);}
.bs-total-box.issue{border-color:#fecaca;background:#fff7f7;}
.bs-total-label{font-size:12px;color:#64748b;font-weight:800;letter-spacing:.01em;}
.bs-total-value{font-size:24px;font-weight:950;color:#0f172a;margin-top:4px;line-height:1.05;}
.bs-total-sub{font-size:12px;color:#64748b;font-weight:750;margin-top:5px;line-height:1.25;}
.bs-table-wrap{width:100%;overflow:auto;border:1px solid #e5e7eb;border-radius:14px;background:#fff;box-shadow:inset 0 1px 0 rgba(255,255,255,.7);}
.bs-table{width:100%;min-width:980px;border-collapse:separate;border-spacing:0;background:#fff;}
.bs-table thead th{position:sticky;top:0;z-index:1;background:#f8fafc;color:#334155;font-size:13px;font-weight:950;text-align:left;padding:12px 12px;border-bottom:1px solid #e5e7eb;white-space:nowrap;letter-spacing:.01em;}
.bs-table tbody td{padding:11px 12px;font-size:14px;line-height:1.25;color:#0f172a;border-bottom:1px solid #edf2f7;vertical-align:top;}
.bs-table tbody tr:nth-child(odd){background:#ffffff;}
.bs-table tbody tr:nth-child(even){background:#f6fbf7;}
.bs-table tbody tr:hover{background:#edf8f1;}
.bs-empty-cell{color:#64748b;text-align:center;padding:22px!important;font-weight:800;}
.bs-view-btn{-webkit-appearance:none!important;appearance:none!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;min-height:36px!important;border:1px solid #16a34a!important;border-radius:10px!important;background:#f0fdf4!important;color:#166534!important;font-size:12px!important;font-weight:900!important;line-height:1.15!important;padding:7px 11px!important;text-shadow:none!important;box-shadow:none!important;cursor:pointer!important;transition:background .16s ease, color .16s ease, border-color .16s ease, box-shadow .16s ease, transform .16s ease;}
.bs-view-btn:hover,.bs-view-btn:focus{border-color:#166534!important;background:#166534!important;color:#fff!important;text-decoration:none!important;box-shadow:0 0 0 3px rgba(22,101,52,.16)!important;transform:translateY(-1px);}
.bs-receipt-btn{min-width:120px!important;border:0!important;background:#166534!important;color:#fff!important;white-space:normal!important;text-align:center!important;box-shadow:0 7px 16px rgba(22,101,52,.18)!important;}
.bs-ledger-action-group{justify-content:flex-end;}
.bs-ledger-share-btn{background:#128C7E!important;color:#fff!important;border:0!important;box-shadow:0 7px 16px rgba(18,140,126,.18)!important;}
.bs-row-selected,.bs-row-selected td{background:#ecfdf3!important;}
.bs-chip{display:inline-flex;align-items:center;justify-content:center;border-radius:999px;padding:5px 11px;font-size:12px;font-weight:900;border:1px solid;white-space:nowrap;}
.bs-chip.ok{color:#166534;background:#dcfce7;border-color:#86efac;}
.bs-chip.warn{color:#9a3412;background:#ffedd5;border-color:#fdba74;}
.bs-chip.neg{color:#991b1b;background:#fee2e2;border-color:#fca5a5;}
.bs-type-send{color:#166534;font-weight:900;}
.bs-type-return{color:#9a3412;font-weight:900;}
.bs-search-wrap{position:relative;}
.bs-search-wrap .bs-input{padding-right:2.35rem;cursor:pointer;}
.bs-field-clear{position:absolute;top:50%;right:.45rem;transform:translateY(-50%);width:1.75rem;height:1.75rem;border:1px solid var(--bs-border)!important;background:#fff!important;color:#64748b!important;border-radius:.55rem!important;display:none;align-items:center;justify-content:center;font-size:.9rem;font-weight:900;cursor:pointer;padding:0!important;line-height:1!important;}
.bs-field-clear.show{display:inline-flex;}
.bs-selected-customers{display:flex;flex-wrap:nowrap;align-items:center;gap:6px;margin-top:8px;min-height:30px;overflow:hidden;}
.bs-selected-customers:empty{display:none;}
.bs-selected-chip{display:inline-flex;align-items:center;gap:6px;max-width:170px;min-width:0;border:1px solid #bbf7d0;background:#f0fdf4;color:#166534;border-radius:999px;padding:5px 8px;font-size:12px;font-weight:900;line-height:1.15;}
.bs-selected-chip span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.bs-selected-more{display:inline-flex;align-items:center;flex:0 0 auto;border:1px solid #cbd5e1;background:#f8fafc;color:#334155;border-radius:999px;padding:5px 9px;font-size:12px;font-weight:900;line-height:1.15;}
.bs-manage-toggle{flex:0 0 auto;border:1px solid #166534!important;background:#166534!important;color:#fff!important;border-radius:999px!important;padding:5px 10px!important;font-size:12px!important;font-weight:950!important;line-height:1.15!important;cursor:pointer!important;}
.bs-manage-toggle:hover,.bs-manage-toggle:focus{background:#0f4f2e!important;border-color:#0f4f2e!important;outline:none!important;box-shadow:0 0 0 3px rgba(22,101,52,.16)!important;}
.bs-selected-remove{position:relative;width:18px;height:18px;flex:0 0 18px;border:1px solid #86efac!important;background:#fff!important;color:#166534!important;border-radius:999px!important;display:inline-block!important;padding:0!important;font-size:0!important;line-height:0!important;cursor:pointer!important;vertical-align:middle!important;}
.bs-selected-remove::before,.bs-selected-remove::after{content:"";position:absolute;left:50%;top:50%;width:8px;height:2px;background:currentColor;border-radius:999px;transform-origin:center;}
.bs-selected-remove::before{transform:translate(-50%,-50%) rotate(45deg);}
.bs-selected-remove::after{transform:translate(-50%,-50%) rotate(-45deg);}
.bs-selected-remove:hover,.bs-selected-remove:focus{background:#166534!important;color:#fff!important;border-color:#166534!important;outline:none!important;}
.bs-field{position:relative;}
.bs-manage-selected{position:absolute;z-index:30;left:0;top:calc(100% + 8px);width:min(460px, calc(100vw - 48px));display:none;background:#fff;border:1px solid #cbd5e1;border-radius:14px;box-shadow:0 22px 48px rgba(15,23,42,.20);padding:10px;box-sizing:border-box;}
.bs-manage-selected.active{display:block;}
.bs-manage-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-bottom:8px;border-bottom:1px solid #e5e7eb;}
.bs-manage-head strong{display:block;font-size:13px;color:�l/��h�\���������lr'
N?�#0f172a;line-height:1.2;}
.bs-manage-head span{display:block;margin-top:2px;font-size:12px;color:#64748b;font-weight:800;}
.bs-manage-actions{display:flex;gap:8px;margin:10px 0;}
.bs-mini-btn{border:1px solid #cbd5e1!important;background:#fff!important;color:#334155!important;border-radius:10px!important;padding:8px 11px!important;font-size:12px!important;font-weight:950!important;cursor:pointer!important;line-height:1.15!important;}
.bs-mini-btn.primary{background:#166534!important;border-color:#166534!important;color:#fff!important;}
.bs-mini-btn.danger{background:#fff1f2!important;border-color:#fecaca!important;color:#991b1b!important;}
.bs-mini-btn:hover,.bs-mini-btn:focus{filter:brightness(.97);outline:none!important;box-shadow:0 0 0 3px rgba(15,23,42,.08)!important;}
.bs-manage-list{max-height:220px;overflow:auto;display:flex;flex-direction:column;gap:6px;}
.bs-manage-row{display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid #e5e7eb;background:#f8fafc;border-radius:10px;padding:8px 9px;}
.bs-manage-row-name{min-width:0;font-size:13px;font-weight:900;color:#0f172a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.bs-manage-empty{padding:14px;text-align:center;color:#64748b;font-size:13px;font-weight:800;background:#f8fafc;border-radius:10px;}
.bs-ledger-modal,.bs-picker-modal{position:fixed;inset:0;z-index:99990;display:none;align-items:center;justify-content:center;padding:18px;box-sizing:border-box;}
.bs-ledger-modal.active,.bs-picker-modal.active{display:flex;}
.bs-ledger-backdrop,.bs-picker-backdrop{position:absolute;inset:0;background:rgba(15,23,42,.62);backdrop-filter:blur(4px);}
.bs-ledger-dialog{position:relative;width:min(1120px, calc(100vw - 36px));max-height:calc(100dvh - 36px);background:#fff;border-radius:22px;box-shadow:0 28px 80px rgba(15,23,42,.32);display:flex;flex-direction:column;overflow:hidden;border:1px solid rgba(255,255,255,.62);}
.bs-ledger-head{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:18px 20px;border-bottom:1px solid rgba(255,255,255,.14);background:linear-gradient(135deg,#0B4A2D,#166534);color:#fff;}
.bs-ledger-head .bs-subtitle{margin:0;font-size:19px;line-height:1.25;font-weight:950;letter-spacing:-.01em;color:#fff;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;}
.bs-ledger-close{width:38px;height:38px;flex:0 0 38px;border:1px solid rgba(255,255,255,.38)!important;border-radius:12px!important;background:rgba(255,255,255,.12)!important;color:#fff!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;padding:0!important;font-size:18px!important;font-weight:950!important;cursor:pointer;line-height:1!important;}
.bs-ledger-close:hover,.bs-ledger-close:focus{background:#fff!important;color:#0B4A2D!important;}
.bs-ledger-body{padding:14px;background:#f8fafc;overflow:auto;}
.bs-ledger-toolbar{padding:0;margin:0 0 12px;display:flex;align-items:center;gap:10px;}
.bs-ledger-filter{width:100%;display:grid;grid-template-columns:minmax(290px,1fr) minmax(180px,.45fr) auto;align-items:end;gap:10px;padding:12px;border:1px solid #e2e8f0;border-radius:16px;background:#fff;box-shadow:0 6px 18px rgba(15,23,42,.045);}
.bs-ledger-filter-group{display:flex;flex-wrap:wrap;align-items:end;gap:8px;padding:0;}
.bs-ledger-filter label{display:flex;align-items:center;gap:7px;font-size:13px;color:#334155;font-weight:850;white-space:nowrap;padding:0;}
.bs-ledger-filter .bs-input{min-height:38px;padding:7px 9px;font-size:13px;border-radius:10px;}
.bs-ledger-filter label .bs-input{width:150px;}
.bs-ledger-filter select.bs-input{min-width:170px;}
.bs-ledger-filter .bs-mini-btn,.bs-ledger-filter .bs-view-btn{min-height:38px;margin-top:0;}
#ac_bs_ledger_table td input[type=checkbox]{width:18px;height:18px;cursor:pointer;accent-color:#166534;}
.bs-ledger-table th:first-child,.bs-ledger-table td:first-child{text-align:center;}
#ac_bs_ledger_select_all.toggled{background:#166534!important;border-color:#166534!important;color:#fff!important;}
.bs-ledger-toolbar.error-message{color:#991b1b;background:#fee2e2;border:1px solid #fecaca;padding:8px 10px;border-radius:8px;font-size:13px;font-weight:700;margin-bottom:10px;}
.bs-ledger-table{min-width:900px!important;table-layout:fixed;}
.bs-ledger-table th,.bs-ledger-table td{padding:10px 12px!important;font-size:13px!important;line-height:1.28!important;vertical-align:middle!important;word-break:break-word;}
.bs-ledger-loading-cell{padding:42px 18px!important;text-align:center!important;background:#fff!important;}
.bs-ledger-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;color:#334155;font-weight:900;}
.bs-ledger-spinner{width:36px;height:36px;border:4px solid #dbe4ee;border-top-color:#166534;border-radius:50%;animation:bsSpin .8s linear infinite;}
@keyframes bsSpin{to{transform:rotate(360deg);}}
body.bs-ledger-open,body.bs-br-open{overflow:hidden;}
.bs-picker-sheet{position:relative;width:100%;max-width:36rem;background:#fff;border-radius:16px;box-shadow:0 24px 70px rgba(15,23,42,.28);overflow:hidden;border:1px solid rgba(255,255,255,.6);}
.bs-picker-head{display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:14px 16px;border-bottom:1px solid #e5e7eb;background:#f8fafc;}
.bs-picker-title{font-size:.95rem;font-weight:900;}
.bs-picker-close{width:34px;height:34px;border:1px solid #dbe4ee!important;border-radius:10px!important;background:#fff!important;color:#334155!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;padding:0!important;font-size:16px!important;font-weight:900!important;cursor:pointer;line-height:1!important;}
.bs-picker-body{padding:.8rem;display:flex;flex-direction:column;gap:.55rem;}
.bs-picker-results{max-height:18rem;overflow-y:auto;display:flex;flex-direction:column;gap:.4rem;}
.bs-picker-note{text-align:center;padding:.7rem;color:var(--bs-muted);font-size:.82rem;font-weight:800;}
.bs-picker-item{display:block;width:100%;text-align:left;padding:.7rem .75rem;border:1px solid var(--bs-border)!important;border-radius:.65rem!important;background:#fff!important;color:var(--bs-text)!important;cursor:pointer;}
.bs-picker-item:hover,.bs-picker-item:focus{border-color:#166534!important;box-shadow:0 0 0 3px rgba(22,101,52,.10)!important;outline:none!important;}
.bs-picker-item-main{display:block;font-weight:900;font-size:.9rem;color:var(--bs-text);}
.bs-picker-item-sub{display:block;font-size:.72rem;color:var(--bs-muted);margin-top:.12rem;}

/* Driver-style Basket Return Receipt */
.bs-br-overlay{position:fixed;inset:0;z-index:100001;background:rgba(15,23,42,.58);overflow:auto;padding:24px;display:flex;align-items:flex-start;justify-content:center;font-family:Arial,Helvetica,sans-serif;color:#111;backdrop-filter:blur(4px);}
.bs-br-modal{width:min(580px, calc(100vw - 40px));background:#f3f4f6;border-radius:18px;padding:14px;box-shadow:0 28px 80px rgba(0,0,0,.35);}
.bs-br-actions{position:sticky;top:0;z-index:5;display:grid;grid-template-columns:1fr 1fr 1fr;gap:9px;margin:0 0 12px;background:#f3f4f6;padding-bottom:8px;}
.bs-br-actions button{border:0!important;border-radius:14px!important;min-height:46px!important;padding:10px 14px!important;font-size:14px!important;font-weight:950!important;cursor:pointer!important;font-family:inherit!important;}
.bs-br-actions button:disabled{opacity:.7;cursor:wait!important;}
.bs-br-print,.bs-br-share{background:#e9f7ee!important;color:#0B4A2D!important;border:1px solid #cce8d6!important;}
.bs-br-close{background:#0B4A2D!important;color:#fff!important;}
.bs-br-card{border:1px solid #dbe4ee;border-radius:18px;padding:14px;background:#fff;box-shadow:0 8px 22px rgba(10,45,29,.045);}
.bs-br-paper{border:1px solid #d1d5db;background:#fff;padding:16px;color:#111;font-family:Arial,sans-serif;box-sizing:border-box;}
.bs-br-head{display:flex;justify-content:space-between;align-items:center;gap:12px;border-bottom:2px solid #111;padding-bottom:10px;margin-bottom:12px;}
.bs-br-logo{width:176px;height:64px;object-fit:contain;object-position:left center;display:block;}
.bs-br-title{text-align:right;font-size:12px;font-weight:900;letter-spacing:.08em;}
.bs-br-no{text-align:right;font-size:15px;font-weight:900;margin-top:4px;}
.bs-br-info{display:grid;grid-template-columns:1fr 1fr;gap:12px;border-bottom:1px solid #e5e7eb;padding:8px 0 10px;}
.bs-br-field{min-width:0;}
.bs-br-field span{display:block;font-size:12px;font-weight:800;color:#111;margin-bottom:4px;}
.bs-br-field strong{display:block;font-size:15px;font-weight:900;line-height:1.2;word-break:break-word;color:#111;}
.bs-br-qty{font-size:34px;font-weight:950;text-align:center;color:#111;padding:18px 0;}
.bs-br-proof{margin-top:6px;border:1px dashed #cbd5e1;padding:10px;text-align:center;font-size:12px;font-weight:800;color:#111;min-height:58px;}
.bs-br-proof img{display:block;width:100%;max-height:330px;object-fit:contain;margin-top:8px;}
.bs-br-loading{padding:24px;text-align:center;font-weight:900;color:#334155;}
#bsBrPrintArea{display:none!important;}

@media (max-width:1024px){
  .bs-container{max-width:none;margin:0;border-radius:0;padding:.7rem;}
  .bs-card{padding:.75rem;margin-bottom:.65rem;border-radius:14px;box-shadow:0 2px 10px rgba(15,23,42,.04);}
  .bs-head{display:flex;align-items:center;margin-bottom:12px;}
  .bs-head h1{font-size:1.35rem;margin:0;font-weight:950;}
  .bs-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:.55rem;}
  .bs-grid .bs-field:first-child{grid-column:1 / -1;}
  .bs-actions{grid-column:1 / -1;}
  #ac-basket-summary-root #ac_bs_refresh.bs-btn{width:100%;min-height:44px;border-radius:.75rem!important;}
  .bs-table{min-width:880px;}
  .bs-table thead th,.bs-table tbody td{font-size:.82rem;padding:.55rem .65rem;}
  .bs-totals{gap:.55rem;margin-bottom:.65rem;}
  .bs-total-value{font-size:1.2rem;}
}

@media (max-width:760px){
  .bs-container{padding:.5rem;background:#f6faf7;}
  .bs-card{border-radius:14px;padding:.65rem;}
  .bs-grid{grid-template-columns:1fr;gap:.55rem;}
  .bs-grid .bs-field:first-child,.bs-actions{grid-column:auto;}
  .bs-label{font-size:.78rem;margin-bottom:.28rem;}
  .bs-input{min-height:42px;padding:.55rem .65rem;font-size:.92rem;border-radius:10px;}
  .bs-totals{grid-template-columns:repeat(2,minmax(0,1fr));gap:.45rem;margin-bottom:.55rem;}
  .bs-total-box{padding:.62rem;border-radius:12px;}
  .bs-total-label{font-size:.62rem;}
  .bs-total-value{font-size:1rem;}
  .bs-total-sub{font-size:.66rem;}
  .bs-selected-customers{overflow:auto;padding-bottom:2px;}
  .bs-selected-chip{max-width:210px;}
  .bs-manage-selected{position:fixed;left:10px;right:10px;top:auto;bottom:10px;width:auto;max-height:65dvh;overflow:auto;z-index:100000;border-radius:16px;}

  .bs-table-wrap{border:0;overflow:visible;background:transparent;box-shadow:none;}
  .bs-table{display:block;width:100%;min-width:0!important;background:transparent;border-collapse:separate;}
  .bs-table thead{display:none;}
  .bs-table tbody{display:block;width:100%;}
  .bs-table tbody tr{display:block;width:100%;margin:0 0 .62rem;border:1px solid #e2e8f0;border-radius:14px;background:#fff!important;box-shadow:0 5px 18px rgba(15,23,42,.055);overflow:hidden;}
  .bs-table tbody td{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;width:100%;padding:.72rem .78rem!important;border-bottom:1px solid #eef2f7!important;text-align:right;font-size:.86rem!important;line-height:1.28!important;}
  .bs-table tbody td:last-child{border-bottom:0!important;}
  .bs-table tbody td[data-label]::before{content:attr(data-label);flex:0 0 42%;text-align:left;color:#475569;font-weight:950;}
  .bs-table tbody td:not([data-label]){display:block;text-align:center;}
  .bs-table tbody td:not([data-label])::before{content:none;}
  .bs-table .bs-empty-cell{display:block!important;width:100%;text-align:center!important;padding:18px!important;border:0!important;}
  .bs-view-btn,.bs-receipt-btn{width:auto!important;min-height:36px!important;}
  .bs-chip{padding:4px 9px;}

  .bs-ledger-modal{padding:0;align-items:stretch;justify-content:stretch;}
  .bs-ledger-dialog{width:100%;max-width:none;height:100dvh;max-height:100dvh;border-radius:0;border:0;}
  .bs-ledger-head{padding:14px 12px;align-items:flex-start;}
  .bs-ledger-head .bs-subtitle{font-size:16px;-webkit-line-clamp:3;}
  .bs-ledger-close{width:36px;height:36px;flex-basis:36px;border-radius:10px!important;}
  .bs-ledger-body{padding:10px;overflow:auto;}
  .bs-ledger-toolbar{margin-bottom:10px;}
  .bs-ledger-filter{grid-template-columns:1fr;gap:9px;padding:10px;border-radius:14px;}
  .bs-ledger-filter-group{display:grid;grid-template-columns:1fr;gap:8px;width:100%;}
  .bs-ledger-filter-group:last-child{grid-template-columns:repeat(3,minmax(0,1fr));}
  .bs-ledger-filter label{display:grid;grid-template-columns:42px 1fr;align-items:center;width:100%;}
  .bs-ledger-filter label .bs-input,.bs-ledger-filter select.bs-input{width:100%;min-width:0;}
  .bs-ledger-filter .bs-mini-btn,.bs-ledger-filter .bs-view-btn{width:100%!important;min-width:0!important;min-height:42px!important;}
  .bs-ledger-table tbody td:first-child{justify-content:space-between;text-align:right;}
  .bs-ledger-table tbody td:first-child input{margin-left:auto;}

  .bs-picker-modal{padding:0;align-items:flex-end;}
  .bs-picker-sheet{max-width:none;width:100%;border-radius:18px 18px 0 0;}
  .bs-picker-results{max-height:55dvh;}
  .bs-br-overlay{padding:10px;align-items:flex-start;}
  .bs-br-modal{width:100%;border-radius:14px;padding:10px;}
  .bs-br-info,.bs-br-actions{grid-template-columns:1fr;}
  .bs-br-actions{position:relative;top:auto;}
  .bs-br-logo{width:140px;height:52px;}
  .bs-br-qty{font-size:28px;}
}

@media (max-width:420px){
  .bs-totals{grid-template-columns:1fr;}
  .bs-ledger-filter-group:last-child{grid-template-columns:1fr;}
  .bs-table tbody td[data-label]::before{flex-basis:46%;}
}

@media print{
  html,body{background:#fff!important;width:148mm;min-height:0!important;height:auto!important;overflow:hidden!important;}
  body > *:not(#bsBrPrintArea){display:none!important;}
  body *{visibility:hidden!important;}
  #bsBrPrintArea,#bsBrPrintArea *{visibility:visible!important;}
  #bsBrPrintArea{display:block!important;position:absolute!important;left:0!important;top:0!important;width:100%!important;max-height:190mm!important;overflow:hidden!important;page-break-after:avoid!important;break-after:avoid!important;}
  #bsBrPrintArea .bs-br-card{border:0!important;box-shadow:none!important;margin:0!important;padding:0!important;page-break-inside:avoid!important;break-inside:avoid!important;}
  #bsBrPrintArea .bs-br-paper{height:188mm!important;overflow:hidden!important;page-break-inside:avoid!important;break-inside:avoid!important;}
  .bs-br-actions{display:none!important;}
  @page{size:A5 portrait;margin:6mm;}
}
</style>

<script>
(function(){
  const wrap = document.getElementById('ac-basket-summary-root');
  if (!wrap || wrap.dataset.init === '1') return;
  wrap.dataset.init = '1';

  const REST_NONCE       = wrap.dataset.restNonce || '';
  const REST_SUMMARY_URL = wrap.dataset.restSummaryUrl || '';
  const REST_LEDGER_URL  = wrap.dataset.restLedgerUrl || '';
  const AJAX_URL         = wrap.dataset.ajaxUrl || '';
  const DEBTOR_NONCE     = wrap.dataset.debtorNonce || '';
  const SHOW_DEBTOR_CODE = wrap.dataset.showDebtorCode === '1';
  const RECEIPT_LOGO_URL = wrap.dataset.receiptLogoUrl || '';

  const $ = id => wrap.querySelector('#' + id);
  const pickerState = { items: [], fetchFn: null, onPick: null };
  const selectedDebtors = [];
  const ledgerCache = {};
  let pickerTimer = null;
  let currentLedgerRows = [];
  let currentLedgerCustomer = { code: '', name: '' };
  let currentReceiptForShare = null;
  let brJsPdfPromise = null;
  let loadSummarySeq = 0;

  function fmtQty(n){
    const x = Number(n);
    return Number.isFinite(x) ? Math.round(x) : '0';
  }

  function esc(s){
    if (s === null || s === undefined) return '';
    return String(s).replace(/[&<�lr'�q� ���������l�C
N?�>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));
  }

  function showError(title, message){
    if (window.Swal && typeof Swal.fire === 'function') {
      Swal.fire({ icon: 'error', title: title || 'Error', text: message || 'Something went wrong' });
    } else {
      alert((title || 'Error') + '\n' + (message || 'Something went wrong'));
    }
  }

  function showInfo(title, message){
    if (window.Swal && typeof Swal.fire === 'function') {
      Swal.fire({ icon:'info', title:title || 'Info', text:message || '' });
    } else {
      alert((title || 'Info') + '\n' + (message || ''));
    }
  }

  function setLedgerButtonBusy(button, text){
    if (!button) return '';
    const originalText = button.textContent || '';
    button.disabled = true;
    button.textContent = text || 'Preparing...';
    return originalText;
  }

  function restoreLedgerButton(button, originalText, fallbackText){
    if (!button) return;
    button.disabled = false;
    button.textContent = originalText || fallbackText || button.textContent;
  }

  function chipClass(outstanding){
    const v = Number(outstanding) || 0;
    if (v < 0) return 'neg';
    if (v > 0) return 'warn';
    return 'ok';
  }

  function outstandingMeaning(outstanding){
    const v = Number(outstanding) || 0;
    if (v < 0) return 'Over-return: returned ' + fmtQty(Math.abs(v)) + ' more than sent';
    if (v > 0) return 'Outstanding: customer still has ' + fmtQty(v) + ' basket';
    return 'Balanced: sent and returned baskets match';
  }

  function dateSortValue(value){
    if (!value) return 0;
    const parsed = Date.parse(String(value).replace(' ', 'T'));
    return Number.isNaN(parsed) ? 0 : parsed;
  }

  function compareText(a, b){
    return String(a || '').localeCompare(String(b || ''), undefined, { sensitivity:'base', numeric:true });
  }

  function compareSummaryByLastActivity(a, b){
    const dateDiff = dateSortValue(b.lastTxnDate || b.last_txn_date) - dateSortValue(a.lastTxnDate || a.last_txn_date);
    if (dateDiff !== 0) return dateDiff;

    const nameDiff = compareText(a.debtorName || a.debtor_name, b.debtorName || b.debtor_name);
    if (nameDiff !== 0) return nameDiff;

    return compareText(a.debtorCode || a.debtor_code, b.debtorCode || b.debtor_code);
  }

  function compareLedgerByLastActivity(a, b){
    const dateDiff = dateSortValue(b.txnDate || b.txn_date || b.date) - dateSortValue(a.txnDate || a.txn_date || a.date);
    if (dateDiff !== 0) return dateDiff;

    return compareText(b.id || b.ledgerId || b.ledger_id, a.id || a.ledgerId || a.ledger_id);
  }

  function pick(row, keys, fallback=''){
    if (!row) return fallback;
    for (const key of keys) {
      if (row[key] !== undefined && row[key] !== null && row[key] !== '') return row[key];
    }
    return fallback;
  }

  function normalizeSummaryRow(row){
    const debtorCode = String(pick(row, ['debtorCode', 'debtor_code', 'customerCode', 'customer_code'], '')).trim();
    const debtorName = String(pick(row, ['debtorName', 'debtor_name', 'customerName', 'customer_name'], '')).trim();
    return {
      ...row,
      debtorCode,
      debtorName,
      sendQty: Number(pick(row, ['sendQty', 'send_qty', 'basketSent', 'basket_sent'], 0)) || 0,
      returnQty: Number(pick(row, ['returnQty', 'return_qty', 'basketReturned', 'basket_returned'], 0)) || 0,
      outstandingQty: Number(pick(row, ['outstandingQty', 'outstanding_qty', 'outstandingBasket', 'outstanding_basket'], 0)) || 0,
      lastTxnDate: String(pick(row, ['lastTxnDate', 'last_txn_date', 'lastActivity', 'last_activity'], '')).trim()
    };
  }

  function normalizeLedgerRow(row){
    return {
      ...row,
      id: pick(row, ['id', 'ledgerId', 'ledger_id'], ''),
      txnDate: String(pick(row, ['txnDate', 'txn_date', 'date'], '')).trim(),
      txnType: String(pick(row, ['txnType', 'txn_type', 'type'], '')).trim(),
      qty: Number(pick(row, ['qty', 'quantity'], 0)) || 0,
      sourceType: String(pick(row, ['sourceType', 'source_type'], '')).trim(),
      sourceRef: String(pick(row, ['sourceRef', 'source_ref', 'refNo', 'ref_no', 'docNo', 'doc_no'], '')).trim(),
      remark: String(pick(row, ['remark', 'note'], '')).trim()
    };
  }

  function dedupeSummaryRowsByDebtor(rows){
    const byCode = new Map();
    rows.forEach(row => {
      const key = debtorKey(row.debtorCode || row.debtorName);
      if (!key) return;
      const existing = byCode.get(key);
      if (!existing || dateSortValue(row.lastTxnDate) > dateSortValue(existing.lastTxnDate)) {
        byCode.set(key, row);
      }
    });
    return Array.from(byCode.values());
  }

  function debtorKey(value){
    return String(value || '').trim().toUpperCase();
  }

  function selectedDebtorCodes(){
    return selectedDebtors.map(d => debtorKey(d.code)).filter(Boolean);
  }

  function syncCustomerFilterInputs(){
    const first = selectedDebtors[0] || { code: '', name: '' };
    $('ac_bs_debtor_code').value = first.code || '';
    $('ac_bs_debtor_name').value = first.name || '';

    const input = $('ac_bs_customer_input');
    if (!selectedDebtors.length) {
      input.value = '';
    } else if (selectedDebtors.length === 1) {
      input.value = selectedDebtors[0].name || selectedDebtors[0].code || '';
    } else {
      input.value = selectedDebtors.length + ' customers selected';
    }
  }

  function renderSelectedCustomers(){
    const mount = $('ac_bs_selected_customers');
    if (!mount) return;

    if (!selectedDebtors.length) {
      mount.innerHTML = '';
      renderManageSelectedCustomers();
      return;
    }

    const visibleDebtors = selectedDebtors.slice(0, 2);
    const hiddenCount = Math.max(0, selectedDebtors.length - visibleDebtors.length);
    const chips = visibleDebtors.map(d => {
      const label = d.name || d.code || 'Customer';
      const meta = SHOW_DEBTOR_CODE && d.code ? ' (' + d.code + ')' : '';
      return `<span class="bs-selected-chip" title="${esc(label + meta)}">
        <span>${esc(label + meta)}</span>
        <button type="button" class="bs-selected-remove" data-remove-selected-customer="${esc(d.code)}" aria-label="Remove ${esc(label)}"></button>
      </span>`;
    });

    if (hiddenCount > 0) {
      chips.push('<span class="bs-selected-more">+' + fmtQty(hiddenCount) + ' more</span>');
    }

    chips.push('<button type="button" class="bs-manage-toggle" data-toggle-selected-customers>Manage</button>');
    mount.innerHTML = chips.join('');
    renderManageSelectedCustomers();
  }

  function renderManageSelectedCustomers(){
    const list = $('ac_bs_manage_list');
    const count = $('ac_bs_manage_count');
    if (count) count.textContent = selectedDebtors.length + (selectedDebtors.length === 1 ? ' selected' : ' selected');
    if (!list) return;

    if (!selectedDebtors.length) {
      list.innerHTML = '<div class="bs-manage-empty">No customer selected.</div>';
      return;
    }

    list.innerHTML = selectedDebtors.map(d => {
      const label = d.name || d.code || 'Customer';
      const meta = SHOW_DEBTOR_CODE && d.code ? ' (' + d.code + ')' : '';
      return `<div class="bs-manage-row">
        <div class="bs-manage-row-name" title="${esc(label + meta)}">${esc(label + meta)}</div>
        <button type="button" class="bs-selected-remove" data-remove-selected-customer="${esc(d.code)}" aria-label="Remove ${esc(label)}"></button>
      </div>`;
    }).join('');
  }

  function openSelectedCustomerManager(){
    const panel = $('ac_bs_manage_selected');
    if (!panel) return;
    renderManageSelectedCustomers();
    panel.classList.add('active');
    panel.setAttribute('aria-hidden', 'false');
  }

  function closeSelectedCustomerManager(){
    const panel = $('ac_bs_manage_selected');
    if (!panel) return;
    panel.classList.remove('active');
    panel.setAttribute('aria-hidden', 'true');
  }

  function toggleSelectedCustomerManager(){
    const panel = $('ac_bs_manage_selected');
    if (!panel) return;
    if (panel.classList.contains('active')) {
      closeSelectedCustomerManager();
    } else {
      openSelectedCustomerManager();
    }
  }

  function updateCustomerSelectionUi(){
    syncCustomerFilterInputs();
    renderSelectedCustomers();
    updateCustomerClearButton();
  }

  function addSelectedCustomer(customer){
    if (!customer) return false;
    const code = String(customer.code || '').trim();
    const name = String(customer.name || '').trim();
    if (!code && !name) return false;

    const key = debtorKey(code || name);
    if (selectedDebtors.some(d => debtorKey(d.code || d.name) === key)) return false;

    selectedDebtors.push({ code, name });
    updateCustomerSelectionUi();
    return true;
  }

  function removeSelectedCustomer(code){
    const key = debtorKey(code);
    const idx = selectedDebtors.findIndex(d => debtorKey(d.code) === key);
    if (idx < 0) return;
    selectedDebtors.splice(idx, 1);
    updateCustomerSelectionUi();
    loadSummary();
  }

  function filterRowsBySelectedCustomers(rows){
    const selectedCodes = selectedDebtorCodes();
    if (!selectedCodes.length) return rows;
    const selectedSet = new Set(selectedCodes);
    return rows.filter(r => selectedSet.has(debtorKey(r.debtorCode || r.debtor_code)));
  }

  async function apiGet(url){
    const res = await fetch(url, {
      method:'GET',
      credentials:'same-origin',
      headers: { 'Accept':'application/json', 'X-WP-Nonce': REST_NONCE },
      cache:'no-store'
    });

    if (!res.ok) {
      let errMsg = 'HTTP ' + res.status;
      try {
        const errData = await res.json();
        errMsg = errData.message || errData.error || errMsg;
      } catch(e) {}
      throw new Error(errMsg);
    }

    return await res.json();
  }

  function buildSummaryUrl(selectedDebtor=null){
    const url = new URL(REST_SUMMARY_URL, window.location.origin);
    const dateFrom = ($('ac_bs_date_from').value || '').trim();
    const dateTo   = ($('ac_bs_date_to').value || '').trim();
    const debtorCode = selectedDebtor
      ? (selectedDebtor.code || '').trim()
      : (selectedDebtors.length === 1 ? (selectedDebtors[0].code || '').trim() : '');
    const debtorName = selectedDebtor
      ? (selectedDebtor.name || '').trim()
      : (selectedDebtors.length === 1 ? (selectedDebtors[0].name || '').trim() : '');

    if (debtorCode) url.searchParams.set('debtorCode', debtorCode);
    if (!debtorCode && debtorName) url.searchParams.set('q', debtorName);
    if (dateFrom) url.searchParams.set('dateFrom', dateFrom);
    if (dateTo) url.searchParams.set('dateTo', dateTo);
    url.searchParams.set('limit', '500');
    return url.toString();
  }

  function buildLedgerUrl(debtorCode){
    const url = new URL(REST_LEDGER_URL, window.location.origin);
    const dateFrom = ($('ac_bs_date_from').value || '').trim();
    const dateTo   = ($('ac_bs_date_to').value || '').trim();

    url.searchParams.set('debtorCode', debtorCode);
    if (dateFrom) url.searchParams.set('dateFrom', dateFrom);
    if (dateTo) url.searchParams.set('dateTo', dateTo);
    url.searchParams.set('limit', '200');
    return url.toString();
  }

  function dateRangeError(){
    const dateFrom = ($('ac_bs_date_from').value || '').trim();
    const dateTo = ($('ac_bs_date_to').value || '').trim();
    if (dateFrom && dateTo && dateFrom > dateTo) {
      return 'Date From cannot be later than Date To.';
    }
    return '';
  }

  function renderTotals(rows){
    const totalsEl = $('ac_bs_totals');
    if (!rows.length) {
      if (totalsEl) totalsEl.style.display = 'none';
      return;
    }

    if (totalsEl) totalsEl.style.display = 'grid';

    const totals = rows.reduce((acc, r) => ({
      send: acc.send + Number(r.sendQty || 0),
      returned: acc.returned + Number(r.returnQty || 0),
      outstanding: acc.outstanding + Number(r.outstandingQty || 0),
      positiveOutstanding: acc.positiveOutstanding + Math.max(0, Number(r.outstandingQty || 0)),
      overReturn: acc.overReturn + Math.abs(Math.min(0, Number(r.outstandingQty || 0)))
    }), { send: 0, returned: 0, outstanding: 0, positiveOutstanding: 0, overReturn: 0 });

    const selectedCustomer = selectedDebtors.length > 0;
    const issueRows = rows.filter(r => Number(r.outstandingQty || 0) < 0);
    if (selectedCustomer) {
      const latestActivity = rows.reduce((latest, r) => {
        const date = r.lastTxnDate || r.last_txn_date || '';
        return dateSortValue(date) > dateSortValue(latest) ? date : latest;
      }, '');
      const customerOutstanding = totals.positiveOutstanding;
      const outstandingSub = totals.overReturn > 0
        ? 'Excludes ' + fmtQty(totals.overReturn) + ' over-return'
        : (customerOutstanding > 0 ? 'Needs follow-up' : 'Balanced');

      const selectedCountCard = selectedDebtors.length > 1
        ? '<div class="bs-total-box"><div class="bs-total-label">Selected Customers</div><div class="bs-total-value">' + fmtQty(selectedDebtors.length) + '</div><div class="bs-total-sub">Combined basket position</div></div>'
        : '';

      totalsEl.innerHTML =
        selectedCountCard +
        '<div class="bs-total-box"><div class="bs-total-label">Basket Sent</div><div class="bs-total-value">' + fmtQty(totals.send) + '</div></div>' +
        '<div class="bs-total-box"><div class="bs-total-label">Basket Returned</div><div class="bs-total-value">' + fmtQty(totals.returned) + '</div></div>' +
        '<div class="bs-total-box"><div class="bs-total-label">Outstanding Basket</div><div class="bs-total-value">' + fmtQty(customerOutstanding) + '</div><div class="bs-total-sub">' + esc(outstandingSub) + '</div></div>' +
        '<div class="bs-total-box"><div class="bs-total-label">Last Activity</div><div class="bs-total-value">' + esc(latestActivity || '-') + '</div></div>' +
        (totals.overReturn > 0
          ? '<div class="bs-total-box issue"><div class="bs-total-label">Data Issues</div><div class="bs-total-value">' + fmtQty(issueRows.length) + '</div><div class="bs-total-sub">Over-return total: ' + fmtQty(totals.overReturn) + '</div></div>'
          : '');
      return;
    }

    const outstandingRows = rows.filter(r => Number(r.outstandingQty || 0) > 0);
    const highest = outstandingRows.slice().sort((a, b) => {
      const qtyDiff = Number(b.outstandingQty || 0) - Number(a.outstandingQty || 0);
      if (qtyDiff !== 0) return qtyDiff;
      return compareSummaryByLastActivity(a, b);
    })[0] || null;
    const highestName = highest ? (highest.debtorName || highest.debtor_name || highest.debtorCode || highest.debtor_code || '-') : 'None';
    const highestQty = highest ? Number(highest.outstandingQty || 0) : 0;

    totalsEl.innerHTML =
      '<div class="bs-total-box"><div class="bs-total-label">Customers Active</div><div class="bs-total-value">' + fmtQty(rows.length) + '</div><div class="bs-total-sub">Has basket movement in range</div></div>' +
      '<div class="bs-total-box"><div class="bs-total-label">Customers With Outstanding</div><div class="bs-total-value">' + fmtQty(outstandingRows.length) + '</div><div class="bs-total-sub">Needs follow-up</div></div>' +
      '<div class="bs-total-box"><div class="bs-total-label">Total Outstanding</div><div class="bs-total-value">' + fmtQty(totals.positiveOutstanding) + '</div><div class="bs-total-sub">Excludes over-return rows</div></div>' +
      '<div class="bs-total-box"><div class="bs-total-label">Highest Outstanding</div><div class="bs-total-value">' + fmtQty(highestQty) + '</div><div class="bs-total-sub">' + esc(highestName) + '</div></div>' +
      (issueRows.length
        ? '<div class="bs-total-box issue"><div class="bs-total-label">Data Issues</div><div class="bs-total-value">' + fmtQty(issueRows.length) + '</div><div class="bs-total-sub">Over-return total: ' + fmtQty(totals.overReturn) + '</div></div>'
        : '');
  }

  function renderSummary(rows){
    const sortedRows = rows.sl�l�C
�+y���������l�`
N?�ice().sort(compareSummaryByLastActivity);
    wrap._lastRows = sortedRows;
    renderTotals(rows);
    const table = $('ac_bs_rows_table');

    if (!sortedRows.length) {
      table.innerHTML = '<tr><td colspan="8" class="bs-empty-cell">No basket records found.</td></tr>';
      return;
    }

    table.innerHTML = sortedRows.map((r, i) => {
      const code = r.debtorCode || '';
      const name = r.debtorName || '';
      const send = Number(r.sendQty || 0);
      const ret  = Number(r.returnQty || 0);
      const out  = Number(r.outstandingQty || 0);
      const lastDate = r.lastTxnDate || '-';
      const cls = chipClass(out);
      const outTitle = outstandingMeaning(out);

      return `<tr data-summary-row="1" data-debtor-code="${esc(code)}">
        <td data-label="No">${i+1}</td>
        <td data-label="Customer Code">${esc(code)}</td>
        <td data-label="Customer Name">${esc(name)}</td>
        <td data-label="Basket Sent">${fmtQty(send)}</td>
        <td data-label="Basket Returned">${fmtQty(ret)}</td>
        <td data-label="Outstanding Basket"><span class="bs-chip ${cls}" title="${esc(outTitle)}" aria-label="${esc(outTitle)}">${fmtQty(out)}</span></td>
        <td data-label="Last Activity">${esc(lastDate)}</td>
        <td data-label="Action"><button class="bs-view-btn" type="button" data-debtor-code="${esc(code)}" data-debtor-name="${esc(name)}">View</button></td>
      </tr>`;
    }).join('');
  }

  function setSummaryLoading(isLoading, message=''){
    const table = $('ac_bs_rows_table');
    const status = $('ac_bs_status');
    if (status) {
      status.style.display = message ? 'block' : 'none';
      status.textContent = message || '';
    }
    if (isLoading && table) {
      table.innerHTML = '<tr><td colspan="8" class="bs-empty-cell">Loading basket summary...</td></tr>';
    }
  }

  function getRowTxnType(row){
    return String(row?.txnType || row?.txn_type || '').toUpperCase();
  }

  function getRowSourceType(row){
    return String(row?.sourceType || row?.source_type || '').toUpperCase();
  }

  function movementLabel(row){
    const txnType = getRowTxnType(row);
    const sourceType = getRowSourceType(row);
    if (txnType === 'RETURN' && (sourceType === 'GOODS_RECEIVE_NOTE' || sourceType === 'GOODS_RECEIVED_NOTE')) {
      return 'GRN Basket Return';
    }
    if (txnType === 'RETURN') return 'Basket Returned';
    return 'Sent Out';
  }

  function sourceTypeLabel(row){
    const sourceType = getRowSourceType(row);
    if (sourceType === 'DELIVERY_ORDER') return 'Delivery Order';
    if (sourceType === 'BASKET_RETURN') return 'Basket Return';
    if (sourceType === 'GOODS_RECEIVE_NOTE' || sourceType === 'GOODS_RECEIVED_NOTE') return 'Goods Receive';
    return row.sourceType || row.source_type || '-';
  }

  function getRowProofImage(row){
    if (!row) return '';
    const possible = [
      row.proofImage, row.proof_image, row.proofImageUrl, row.proof_image_url,
      row.imageUrl, row.image_url, row.proofUrl, row.proof_url,
      row.returnProofImage, row.return_proof_image, row.basketProofImage,
      row.basket_proof_image, row.attachmentUrl, row.attachment_url
    ];

    for (const v of possible) {
      if (v && String(v).trim() !== '') return String(v).trim();
    }

    if (Array.isArray(row.proofImages) && row.proofImages.length) {
      const first = row.proofImages[0];
      return first.imageUrl || first.image_url || first.url || '';
    }

    if (Array.isArray(row.images) && row.images.length) {
      const first = row.images[0];
      return first.imageUrl || first.image_url || first.url || '';
    }

    return '';
  }

  async function fetchBasketProofImage(row){
    const existing = getRowProofImage(row);
    if (existing) return existing;

    const nonce = wrap.dataset.basketProofNonce || '';
    if (!AJAX_URL || !nonce || !row) return '';

    const fd = new FormData();
    fd.append('action', 'ac_bs_get_basket_proof');
    fd.append('nonce', nonce);
    fd.append('ledgerId', row.id || row.ledgerId || row.ledger_id || row.basketLedgerId || row.basket_ledger_id || row.returnLedgerId || row.return_ledger_id || '');
    fd.append('sourceRef', row.sourceRef || row.source_ref || row.refNo || row.ref_no || row.docNo || row.doc_no || '');
    fd.append('debtorCode', row.debtorCode || row.debtor_code || currentLedgerCustomer.code || '');
    fd.append('debtorName', row.debtorName || row.debtor_name || currentLedgerCustomer.name || '');
    fd.append('txnDate', row.txnDate || row.txn_date || row.date || '');

    try {
      const res = await fetch(AJAX_URL, { method:'POST', credentials:'same-origin', body:fd, cache:'no-store' });
      const data = await res.json();
      if (data && data.success && data.data && data.data.imageUrl) return data.data.imageUrl;
    } catch(e) {
      console.error('Basket proof AJAX failed:', e);
    }

    return '';
  }

  function receiptRef(row){
    return row?.sourceRef || row?.source_ref || row?.refNo || row?.ref_no || row?.docNo || row?.doc_no || row?.id || 'basket-return';
  }

  function receiptFileName(receipt){
    const ref = String(receipt?.ref || receipt?.id || 'basket-return').replace(/[^A-Za-z0-9_-]/g, '-');
    return `Basket-Return-${ref}.pdf`;
  }

  function getRowDriverName(row){
    const possible = [
      row?.driverLogin, row?.driver_login,
      row?.assignedDriverLogin, row?.assigned_driver_login,
      row?.driverName, row?.driver_name,
      row?.createdByLogin, row?.created_by_login,
      row?.createdByUserLogin, row?.created_by_user_login,
      row?.userLogin, row?.user_login,
      row?.createdByName, row?.created_by_name,
      row?.userName, row?.user_name,
      row?.vehiclePlate, row?.vehicle_plate
    ];

    for (const value of possible) {
      const s = String(value || '').trim();
      if (s) return s.toUpperCase();
    }

    return '';
  }

  function basketReceiptCardHtml(receipt){
    const proofHtml = receipt.proofImage
      ? `Image Proof<img src="${esc(receipt.proofImage)}" alt="Basket return proof" decoding="sync">`
      : 'No image proof uploaded';

    return `
      <div class="bs-br-card">
        <div class="bs-br-paper">
          <div class="bs-br-head">
            <div><img class="bs-br-logo" src="${esc(RECEIPT_LOGO_URL)}" alt="Company logo"></div>
            <div>
              <div class="bs-br-title">BASKET RETURN</div>
              <div class="bs-br-no">${esc(receipt.ref)}</div>
            </div>
          </div>
          <div class="bs-br-info">
            <div class="bs-br-field"><span>Customer</span><strong>${esc(receipt.customerName || 'Customer')}</strong></div>
            <div class="bs-br-field"><span>Driver</span><strong>${esc(receipt.driverName || '')}</strong></div>
          </div>
          <div class="bs-br-qty">${esc(receipt.qty)} BASKETS</div>
          <div class="bs-br-proof">${proofHtml}</div>
        </div>
      </div>`;
  }

  function basketReceiptHtml(row, proofImage){
    const receipt = {
      ref: receiptRef(row),
      customerName: row?.debtorName || row?.debtor_name || currentLedgerCustomer.name || currentLedgerCustomer.code || 'Customer',
      driverName: getRowDriverName(row),
      qty: fmtQty(row?.qty || 0),
      proofImage: proofImage || ''
    };

    return `
      <div class="bs-br-overlay" id="acBsBasketReceiptOverlay">
        <div class="bs-br-modal" role="dialog" aria-modal="true" aria-label="Basket Return Receipt">
          <div class="bs-br-actions">
            <button type="button" class="bs-br-print" data-print-current-basket-receipt="1">Print / Save PDF</button>
            <button type="button" class="bs-br-share" data-share-current-basket-receipt="1">Share PDF</button>
            <button type="button" class="bs-br-close" onclick="document.getElementById('ac_bs_receipt_mount').innerHTML='';document.body.classList.remove('bs-br-open')">Close</button>
          </div>
          ${basketReceiptCardHtml(receipt)}
        </div>
      </div>`;
  }

  function basketReceiptLoadingHtml(){
    return `
      <div class="bs-br-overlay" id="acBsBasketReceiptOverlay">
        <div class="bs-br-modal" role="dialog" aria-modal="true" aria-label="Loading Basket Return Receipt">
          <div class="bs-br-actions">
            <button type="button" class="bs-br-close" onclick="document.getElementById('ac_bs_receipt_mount').innerHTML='';document.body.classList.remove('bs-br-open')">Close</button>
          </div>
          <div class="bs-br-card"><div class="bs-br-paper"><div class="bs-br-loading">Loading basket receipt...</div></div></div>
        </div>
      </div>`;
  }

  async function openBasketReceiptByIndex(idx){
    const row = currentLedgerRows[Number(idx)];
    if (!row || getRowTxnType(row) !== 'RETURN') {
      showError('Receipt not available', 'Basket receipt is only available for returned basket movement.');
      return;
    }

    const mount = $('ac_bs_receipt_mount');
    if (!mount) return;

    document.body.classList.add('bs-br-open');
    mount.innerHTML = basketReceiptLoadingHtml();
    const proofImage = await fetchBasketProofImage(row);
    currentReceiptForShare = {
      id: row.id || row.ledgerId || row.ledger_id || idx,
      ref: receiptRef(row),
      customerName: row?.debtorName || row?.debtor_name || currentLedgerCustomer.name || currentLedgerCustomer.code || 'Customer',
      driverName: getRowDriverName(row),
      qty: fmtQty(row?.qty || 0),
      proofUrl: proofImage || ''
    };
    mount.innerHTML = basketReceiptHtml(row, proofImage);
  }

  function loadJsPdf(){
    if (window.jspdf && window.jspdf.jsPDF) return Promise.resolve(window.jspdf.jsPDF);
    if (brJsPdfPromise) return brJsPdfPromise;

    brJsPdfPromise = new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
      script.onload = () => window.jspdf && window.jspdf.jsPDF ? resolve(window.jspdf.jsPDF) : reject(new Error('PDF library did not load.'));
      script.onerror = () => reject(new Error('PDF library could not be loaded.'));
      document.head.appendChild(script);
    });

    return brJsPdfPromise;
  }

  function loadCanvasImage(url){
    if (!url) return Promise.resolve(null);
    return new Promise(resolve => {
      const img = new Image();
      img.crossOrigin = 'anonymous';
      img.onload = () => resolve(img);
      img.onerror = () => resolve(null);
      img.src = url;
    });
  }

  function drawCanvasText(ctx, text, x, y, size=18, color='#111', weight='400', align='left'){
    ctx.fillStyle = color;
    ctx.font = `${weight} ${size}px Arial, sans-serif`;
    ctx.textAlign = align;
    ctx.textBaseline = 'top';
    ctx.fillText(String(text || ''), x, y);
  }

  function drawWrappedCanvasText(ctx, text, x, y, maxWidth, lineHeight, size=18, color='#111', weight='400'){
    const words = String(text || '').split(/\s+/).filter(Boolean);
    let line = '';

    words.forEach(word => {
      const testLine = line ? `${line} ${word}` : word;
      if (ctx.measureText(testLine).width > maxWidth && line) {
        drawCanvasText(ctx, line, x, y, size, color, weight);
        line = word;
        y += lineHeight;
      } else {
        line = testLine;
      }
    });

    if (line) drawCanvasText(ctx, line, x, y, size, color, weight);
    return y + lineHeight;
  }

  function drawCanvasImageContained(ctx, image, x, y, maxW, maxH){
    if (!image) return;
    const ratio = Math.min(maxW / image.width, maxH / image.height);
    const imgW = image.width * ratio;
    const imgH = image.height * ratio;
    ctx.drawImage(image, x + (maxW - imgW) / 2, y + (maxH - imgH) / 2, imgW, imgH);
  }

  function makeReceiptCanvas(receipt, proofImage=null, logoImage=null){
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = 1240;
    canvas.height = 1754;

    ctx.fillStyle = '#ffffff';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.strokeStyle = '#111111';
    ctx.lineWidth = 2;
    ctx.strokeRect(82, 70, 1076, 1614);

    if (logoImage) {
      drawCanvasImageContained(ctx, logoImage, 138, 112, 360, 128);
    } else {
      drawCanvasText(ctx, 'BASKET RETURN', 140, 130, 30, '#111', '900');
    }

    drawCanvasText(ctx, 'BASKET RETURN', 1100, 130, 24, '#111', '900', 'right');
    drawCanvasText(ctx, receipt.ref || ('BR-' + receipt.id), 1100, 172, 22, '#111', '900', 'right');

    ctx.strokeStyle = '#111111';
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.moveTo(140, 278);
    ctx.lineTo(1100, 278);
    ctx.stroke();

    let y = 350;
    drawCanvasText(ctx, 'Customer', 160, y, 22, '#555', '700');
    drawWrappedCanvasText(ctx, receipt.customerName || 'Customer', 160, y + 34, 410, 30, 24, '#111', '800');
    drawCanvasText(ctx, 'Driver', 660, y, 22, '#555', '700');
    drawWrappedCanvasText(ctx, receipt.driverName || '', 660, y + 34, 410, 30, 24, '#111', '800');

    y += 105;
    ctx.strokeStyle = '#e5e7eb';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(160, y);
    ctx.lineTo(1080, y);
    ctx.stroke();

    drawCanvasText(ctx, `${receipt.qty} BASKETS`, 620, y + 54, 74, '#111', '900', 'center');
    y += 210;

    ctx.strokeStyle = '#cbd5e1';
    ctx.setLineDash([12, 10]);
    ctx.strokeRect(160, y, 920, 560);
    ctx.setLineDash([]);

    if (proofImage) {
      drawCanvasText(ctx, 'Image Proof', 620, y + 28, 22, '#334155', '800', 'center');
      drawCanvasImageContained(ctx, proofImage, 210, y + 82, 820, 420);
    } else {
      drawCanvasText(ctx, 'No image proof uploaded', 620, y + 255, 28, '#64748b', '800', 'center');
    }

    return canvas;
  }

  function buildBasketReceiptPdf(receipt){
    return Promise.all([loadJsPdf(), loadCanvasImage(receipt.proofUrl), loadCanvasImage(RECEIPT_LOGO_URL)])
      .then(([jsPDF, proofImage, logoImage]) => {
        const warnings = [];
        if (receipt.proofUrl && !proofImage) warnings.push('Proof image could not be included in the PDF.');
        if (RECEIPT_LOGO_URL && !logoImage) warnings.push('Logo could not be included in the PDF.');
        if (warnings.length && window.Swal && typeof Swal.fire === 'function') {
          Swal.fire({ icon:'warning', title:'PDF image warning', text:warnings.join(' ') });
        }
        const pdf = new jsPDF({orientation:'portrait', unit:'mm', format:'a5'});
        const canvas = makeReceiptCanvas(receipt, proofImage, logoImage);
        pdf.addImage(canvas.toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, 148, 210);
        return pdf.output('blob');
      });
  }

  function downloadBlob(blob, fileName){
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  }

  function preloadReceiptImage(url){
    if (!url) return Promise.resolve();

    return new Promise(resolve => {
      const img = new Image();
      const done = () => resolve();
      img.onload = done;
      img.onerror = done;
      img.src = url;

      if (img.complete) resolve();
      setTimeout(done, 2500);
    });
  }

  function waitForElementImages(el){
    const images = Array.from(el.querySelectorAll('img'));
    if (!images.length) return Promise.resolve();

    return Promise.all(images.map(img => new Promise(resolve => {
      if (img.complete && img.naturalWidth > 0) {
        resolve();
        return;
      }

      const done = () => resolve();
      img.addEventListener('load', done, { once:true });
      img.addEventListener('error', done, { once:true });
      setTimeout(done, 2500);
    }))).then(() => undefined);
  }

  function printCurrentBasketReceipt(){
    const receipt = currentReceiptForShare;�l�`>�aw���������m8|
N?�
    if (!receipt) {
      showError('Receipt not found', 'Please reopen the basket receipt and try again.');
      return;
    }

    buildBasketReceiptPdf(receipt)
      .then(blob => {
        const fileName = receiptFileName(receipt);
        const url = URL.createObjectURL(blob);
        const opened = window.open(url, '_blank', 'noopener');

        if (!opened) {
          downloadBlob(blob, fileName);
          if (window.Swal && typeof Swal.fire === 'function') {
            Swal.fire({ icon:'info', title:'PDF downloaded', text:'Open the downloaded PDF to print or share.' });
          }
        }

        setTimeout(() => URL.revokeObjectURL(url), 60000);
      })
      .catch(() => {
        showError('Unable to prepare PDF', 'Please try again.');
      });
  }

  function shareCurrentBasketReceipt(button){
    const receipt = currentReceiptForShare;
    if (!receipt) {
      showError('Receipt not found', 'Please reopen the basket receipt and try again.');
      return;
    }

    const originalText = button ? button.textContent : '';
    if (button) {
      button.disabled = true;
      button.textContent = 'Preparing...';
    }

    buildBasketReceiptPdf(receipt)
      .then(blob => {
        const fileName = receiptFileName(receipt);
        const file = new File([blob], fileName, {type:'application/pdf'});

        if (!navigator.share || !navigator.canShare || !navigator.canShare({files:[file]})) {
          downloadBlob(blob, fileName);
          if (window.Swal && typeof Swal.fire === 'function') {
            Swal.fire({ icon:'info', title:'PDF downloaded', text:'Attach the downloaded PDF in WhatsApp.' });
          }
          return null;
        }

        return navigator.share({
          title: fileName.replace(/\.pdf$/i, ''),
          text: 'Basket Return PDF',
          files: [file]
        });
      })
      .catch(error => {
        if (error && error.name === 'AbortError') return;
        showError('Unable to prepare PDF', 'Please print or save PDF, then share it in WhatsApp.');
      })
      .finally(() => {
        if (button) {
          button.disabled = false;
          button.textContent = originalText || 'Share PDF';
        }
      });
  }

  function markSelectedDebtorRow(debtorCode){
    wrap.querySelectorAll('[data-summary-row="1"]').forEach(row => {
      row.classList.toggle('bs-row-selected', (row.dataset.debtorCode || '') === debtorCode);
    });
  }

  async function loadSummary(){
    if (!REST_SUMMARY_URL) {
      showError('Missing configuration', 'Summary endpoint missing.');
      return;
    }

    const rangeError = dateRangeError();
    if (rangeError) {
      showError('Invalid date range', rangeError);
      return;
    }

    const requestSeq = ++loadSummarySeq;
    setSummaryLoading(true, 'Loading basket summary...');

    try {
      let rows = [];
      if (selectedDebtors.length > 1) {
        const selectedResults = await Promise.all(selectedDebtors.map(debtor => apiGet(buildSummaryUrl(debtor))));
        rows = selectedResults.flatMap(data => Array.isArray(data && data.rows) ? data.rows : []);
      } else {
        const data = await apiGet(buildSummaryUrl());
        rows = Array.isArray(data && data.rows) ? data.rows : [];
      }

      if (requestSeq !== loadSummarySeq) return;

      rows = dedupeSummaryRowsByDebtor(filterRowsBySelectedCustomers(rows.map(normalizeSummaryRow)));
      Object.keys(ledgerCache).forEach(k => delete ledgerCache[k]);
      renderSummary(rows);
      setSummaryLoading(false);

      const receiptMount = $('ac_bs_receipt_mount');
      if (receiptMount) receiptMount.innerHTML = '';

      currentLedgerRows = [];
      currentLedgerCustomer = { code: '', name: '' };
      $('ac_bs_ledger_title').textContent = 'Basket Movement History';
      $('ac_bs_ledger_table').innerHTML = '<tr><td colspan="8" class="bs-empty-cell">Select a customer to view basket movement history.</td></tr>';
    } catch(err) {
      if (requestSeq !== loadSummarySeq) return;
      showError('Failed to load summary', err && err.message ? err.message : 'Please try again.');
      setSummaryLoading(false);
      $('ac_bs_rows_table').innerHTML = '<tr><td colspan="8" class="bs-empty-cell">Failed to load summary.</td></tr>';
    }
  }

  function showLedgerLoading(debtorCode, debtorName){
    currentLedgerRows = [];
    currentLedgerCustomer = { code: debtorCode || '', name: debtorName || '' };
    $('ac_bs_ledger_title').textContent = 'Basket Movement History: ' + (debtorName || debtorCode);
    $('ac_bs_ledger_table').innerHTML = `
      <tr>
        <td colspan="9" class="bs-ledger-loading-cell">
          <div class="bs-ledger-loading">
            <div class="bs-ledger-spinner"></div>
            <div>Loading basket movement...</div>
            <div style="font-size:12px;color:#64748b;">${esc(debtorName || debtorCode || 'Customer')}</div>
          </div>
        </td>
      </tr>`;
    const toolbar = $('ac_bs_ledger_toolbar');
    if (toolbar) toolbar.style.display = 'none';
    resetLedgerFilters();
  }

  function resetLedgerFilters(){
    $('ac_bs_ledger_date_from').value = '';
    $('ac_bs_ledger_date_to').value = '';
    const select = $('ac_bs_ledger_movement_filter');
    if (select) {
      select.innerHTML = '<option value="">All Movement</option>';
    }
    const selectAll = $('ac_bs_ledger_select_all');
    if (selectAll) selectAll.classList.remove('toggled');
  }

  function renderLedger(debtorCode, debtorName, rows){
    currentLedgerRows = Array.isArray(rows) ? rows.map(normalizeLedgerRow).sort(compareLedgerByLastActivity) : [];
    currentLedgerCustomer = { code: debtorCode || '', name: debtorName || '' };
    $('ac_bs_ledger_title').textContent = 'Basket Movement History: ' + (debtorName || debtorCode);
    const toolbar = $('ac_bs_ledger_toolbar');
    if (toolbar) toolbar.style.display = currentLedgerRows.length ? 'flex' : 'none';
    populateLedgerMovementFilter(currentLedgerRows);

    const table = $('ac_bs_ledger_table');
    if (!currentLedgerRows.length) {
      table.innerHTML = '<tr><td colspan="9" class="bs-empty-cell">No basket movement found for this customer.</td></tr>';
      return;
    }

    applyLedgerRender(currentLedgerRows);
  }

  function populateLedgerMovementFilter(rows){
    const select = $('ac_bs_ledger_movement_filter');
    if (!select) return;
    const labels = new Set();
    rows.forEach(r => labels.add(movementLabel(r)));
    const current = select.value;
    select.innerHTML = '<option value="">All Movement</option>' + Array.from(labels).sort().map(l => `<option value="${esc(l)}">${esc(l)}</option>`).join('');
    if (current && Array.from(labels).includes(current)) select.value = current;
  }

  function applyLedgerRender(rows){
    const dateFrom = ($('ac_bs_ledger_date_from').value || '').trim();
    const dateTo = ($('ac_bs_ledger_date_to').value || '').trim();
    const movementFilter = ($('ac_bs_ledger_movement_filter').value || '').trim();

    let filtered = rows.filter(r => {
      const okDate = matchDateRange(r.txnDate || r.txn_date || r.date, dateFrom, dateTo);
      const okMovement = !movementFilter || movementLabel(r) === movementFilter;
      return okDate && okMovement;
    });

    const table = $('ac_bs_ledger_table');
    if (!filtered.length) {
      table.innerHTML = '<tr><td colspan="9" class="bs-empty-cell">No basket movement matches the selected filters.</td></tr>';
      return;
    }

    table.innerHTML = filtered.map((r, i) => {
      const txnType = getRowTxnType(r);
      const sourceType = getRowSourceType(r);
      const label = movementLabel(r);
      const typeClass = txnType === 'RETURN' ? 'bs-type-return' : 'bs-type-send';
      const idx = currentLedgerRows.indexOf(r);
      const ledgerIdx = idx >= 0 ? idx : i;
      const canViewReceipt = txnType === 'RETURN' && sourceType === 'BASKET_RETURN';
      const receiptBtn = canViewReceipt
        ? `<button class="bs-view-btn bs-receipt-btn" type="button" data-basket-receipt-idx="${ledgerIdx}">View Basket Receipt</button>`
        : '<span style="color:#94a3b8;">-</span>';

      return `<tr data-ledger-idx="${ledgerIdx}">
        <td data-label="Select"><input type="checkbox" class="bs-ledger-row-check" data-ledger-idx="${ledgerIdx}" aria-label="Select row ${i+1}"></td>
        <td data-label="No">${i+1}</td>
        <td data-label="Date">${esc(r.txnDate || r.txn_date || '-')}</td>
        <td data-label="Movement"><span class="${typeClass}">${esc(label)}</span></td>
        <td data-label="Qty">${fmtQty(r.qty || 0)}</td>
        <td data-label="From">${esc(sourceTypeLabel(r))}</td>
        <td data-label="Document No.">${esc(r.sourceRef || r.source_ref || '-')}</td>
        <td data-label="Note">${esc(r.remark || r.note || '-')}</td>
        <td data-label="Receipt">${receiptBtn}</td>
      </tr>`;
    }).join('');

    const selectAllBtn = $('ac_bs_ledger_select_all');
    if (selectAllBtn) {
      selectAllBtn.classList.remove('toggled');
      selectAllBtn.textContent = 'Select All';
    }
  }

  function matchDateRange(value, from, to){
    if (!value) return true;
    const d = String(value).split(' ')[0];
    if (from && d < from) return false;
    if (to && d > to) return false;
    return true;
  }

  function getSelectedLedgerRows(){
    const selected = [];
    wrap.querySelectorAll('.bs-ledger-row-check:checked').forEach(cb => {
      const idx = parseInt(cb.dataset.ledgerIdx, 10);
      const row = currentLedgerRows[idx];
      if (row) selected.push(row);
    });
    return selected;
  }

  function toggleSelectAllLedgerRows(){
    const checks = Array.from(wrap.querySelectorAll('.bs-ledger-row-check'));
    const anyUnchecked = checks.some(cb => !cb.checked);
    checks.forEach(cb => cb.checked = anyUnchecked);
    const btn = $('ac_bs_ledger_select_all');
    if (btn) btn.classList.toggle('toggled', anyUnchecked);
    if (btn) btn.textContent = anyUnchecked ? 'Deselect All' : 'Select All';
  }

  function formatLedgerPrintDateRange(){
    const from = ($('ac_bs_ledger_date_from').value || '').trim();
    const to = ($('ac_bs_ledger_date_to').value || '').trim();
    if (!from && !to) return '';
    if (from === to) return from;
    if (from && !to) return 'From ' + from;
    if (!from && to) return 'Until ' + to;
    return from + ' - ' + to;
  }

  function ledgerRowDateOnly(row){
    const raw = String(row?.txnDate || row?.txn_date || row?.date || '').trim();
    if (!raw) return '';
    return raw.split(' ')[0];
  }

  function formatSelectedLedgerDateRange(rows){
    const dates = Array.from(new Set((rows || [])
      .map(ledgerRowDateOnly)
      .filter(Boolean)))
      .sort();

    if (!dates.length) return '-';
    if (dates.length === 1) return dates[0];
    return dates[0] + ' - ' + dates[dates.length - 1];
  }

  function getOverallOutstandingBalance(){
    const customerCode = debtorKey(currentLedgerCustomer.code || '');
    const customerName = debtorKey(currentLedgerCustomer.name || '');
    const summaryRows = Array.isArray(wrap._lastRows) ? wrap._lastRows : [];

    const summaryRow = summaryRows.find(row => {
      const rowCode = debtorKey(row.debtorCode || row.debtor_code || '');
      const rowName = debtorKey(row.debtorName || row.debtor_name || '');
      return (customerCode && rowCode === customerCode) || (!customerCode && customerName && rowName === customerName);
    });

    if (summaryRow) {
      const summaryOutstanding = Number(summaryRow.outstandingQty ?? summaryRow.outstanding_qty ?? summaryRow.outstandingBasket ?? summaryRow.outstanding_basket);
      if (Number.isFinite(summaryOutstanding)) return summaryOutstanding;
    }

    return (Array.isArray(currentLedgerRows) ? currentLedgerRows : []).reduce((sum, row) => {
      const qty = Number(row.qty || 0) || 0;
      return sum + (getRowTxnType(row) === 'RETURN' ? -qty : qty);
    }, 0);
  }

  async function buildLedgerStatementPdfBlob(){
    const selected = getSelectedLedgerRows();

    if (!selected.length) {
      showError('No rows selected', 'Please tick at least one row to print or share.');
      return null;
    }

    const customerName = currentLedgerCustomer.name || currentLedgerCustomer.code || 'Customer';
    const customerCode = currentLedgerCustomer.code || '';
    const dateRange = formatSelectedLedgerDateRange(selected);
    const selectedRows = selected.slice().sort(compareLedgerByLastActivity);

    const sendQty = selectedRows
      .filter(r => getRowTxnType(r) !== 'RETURN')
      .reduce((sum, r) => sum + (Number(r.qty) || 0), 0);

    const returnQty = selectedRows
      .filter(r => getRowTxnType(r) === 'RETURN')
      .reduce((sum, r) => sum + (Number(r.qty) || 0), 0);

    const overallOutstandingQty = getOverallOutstandingBalance();
    const generatedAt = new Date().toLocaleString('en-MY', {
      year:'numeric', month:'2-digit', day:'2-digit',
      hour:'2-digit', minute:'2-digit'
    });

    const jsPDF = await loadJsPdf();
    const pdf = new jsPDF({ orientation:'portrait', unit:'mm', format:'a4' });

    if (pdf.setProperties) {
      pdf.setProperties({
        title: 'Basket Movement Statement - ' + customerName,
        subject: 'Basket Movement Statement',
        author: 'Basket Summary'
      });
    }

    const pageW = pdf.internal.pageSize.getWidth();
    const pageH = pdf.internal.pageSize.getHeight();
    const margin = 12;
    const contentW = pageW - margin * 2;
    let y = 0;

    function cleanText(value){
      const text = String(value === null || value === undefined || value === '' ? '-' : value);
      return text.replace(/\s+/g, ' ').trim();
    }

    function fileSafe(value){
      return String(value || 'customer')
        .replace(/[^A-Za-z0-9_-]+/g, '-')
        .replace(/-+/g, '-')
        .replace(/^-|-$/g, '') || 'customer';
    }

    function drawHeader(){
      pdf.setFillColor(11, 74, 45);
      pdf.rect(0, 0, pageW, 34, 'F');

      pdf.setTextColor(255, 255, 255);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(18);
      pdf.text('Basket Movement Statement', margin, 16);

      pdf.setFontSize(9);
      pdf.setFont(undefined, 'normal');
      pdf.text('Generated: ' + generatedAt, margin, 24);

      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(10);
      pdf.text('Excellent Vege Basket Record', pageW - margin, 16, { align:'right' });

      pdf.setFont(undefined, 'normal');
      pdf.text('Selected movements only', pageW - margin, 24, { align:'right' });

      y = 44;

      pdf.setTextColor(15, 23, 42);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(11);
      pdf.text('Customer', margin, y);
      pdf.text('Date Range', pageW - margin - 62, y);

      pdf.setFont(undefined, 'normal');
      pdf.setFontSize(10);

      const customerLines = pdf.splitTextToSize(cleanText(customerName), 88);
      pdf.text(customerLines, margin, y + 6);

      if (customerCode) {
        pdf.setTextColor(71, 85, 105);
        pdf.text('Code: ' + customerCode, margin, y + 6 + customerLines.length * 4.5);
        pdf.setTextColor(15, 23, 42);
      }

      pdf.text(cleanText(dateRange), pageW - margin - 62, y + 6);
      y += Math.max(24, 8 + customerLines.length * 4.5 + (customerCode ? 5 : 0));
    }

    function drawSummaryBox(x, label, value, width){
      pdf.setFillColor(248, 250, 252);
      pdf.setDrawColor(226, 232, 240);
      pdf.roundedRect(x, y, width, 18, 2, 2, 'FD');

      pdf.setTextColor(100, 116, 139);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(7.5);
      pdf.text(label, x + 4, y + 6);

      pdf.setTextColor(15, 23, 42);
      pdf.setFontSize(13);
      pdf.text(String(value), x + 4, y + 14);
    }

    function drawSummary(){
      const gap = 4;
      const boxW = (contentW - gap * 3) / 4;

      drawSummaryBox(margin, 'TOTAL SENT', fmtQty(sendQty), boxW);
      dra�m8|�/u����������m:�
N?�wSummaryBox(margin + (boxW + gap), 'TOTAL RETURNED', fmtQty(returnQty), boxW);
      drawSummaryBox(margin + (boxW + gap) * 2, 'OUTSTANDING BALANCE', fmtQty(overallOutstandingQty), boxW);
      drawSummaryBox(margin + (boxW + gap) * 3, 'MOVEMENT ROWS', fmtQty(selectedRows.length), boxW);
      y += 26;
    }

    const tableRight = pageW - margin - 10;
    const tableW = tableRight - margin;
    const columns = [
      { title:'Date', x:margin, w:30, key:'date' },
      { title:'Document No.', x:margin + 32, w:68, key:'doc' },
      { title:'Movement', x:margin + 104, w:46, key:'movement' },
      { title:'Qty', x:tableRight - 24, w:20, key:'qty', align:'right' }
    ];

    function drawTableHeader(){
      pdf.setFillColor(22, 101, 52);
      pdf.rect(margin, y, tableW, 8, 'F');

      pdf.setTextColor(255, 255, 255);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(8.5);

      columns.forEach(col => {
        const tx = col.align === 'right' ? col.x + col.w : col.x + 2;
        pdf.text(col.title, tx, y + 5.3, col.align === 'right' ? { align:'right' } : undefined);
      });

      y += 8;
    }

    function addNewPageWithTableHeader(){
      pdf.addPage();
      y = 18;
      drawTableHeader();
    }

    function drawRow(row, index){
      const values = {
        date: cleanText(row.txnDate || row.txn_date || '-'),
        doc: cleanText(row.sourceRef || row.source_ref || '-'),
        movement: cleanText(movementLabel(row)),
        qty: String(fmtQty(row.qty || 0))
      };

      const lineHeight = 4.3;
      const cellLines = columns.map(col => {
        const maxW = col.align === 'right' ? col.w : col.w - 2;
        return pdf.splitTextToSize(values[col.key], maxW);
      });
      const rowHeight = Math.max(8, Math.max(...cellLines.map(lines => lines.length)) * lineHeight + 4);

      if (y + rowHeight > pageH - 18) addNewPageWithTableHeader();

      if (index % 2 === 0) {
        pdf.setFillColor(249, 250, 251);
        pdf.rect(margin, y, tableW, rowHeight, 'F');
      }

      pdf.setDrawColor(226, 232, 240);
      pdf.line(margin, y + rowHeight, margin + tableW, y + rowHeight);
      pdf.setTextColor(15, 23, 42);
      pdf.setFont(undefined, 'normal');
      pdf.setFontSize(8.2);

      columns.forEach((col, colIndex) => {
        const lines = cellLines[colIndex];
        if (col.align === 'right') {
          pdf.text(lines, col.x + col.w, y + 5, { align:'right' });
        } else {
          pdf.text(lines, col.x + 2, y + 5);
        }
      });

      y += rowHeight;
    }

    function drawTotals(){
      if (y + 22 > pageH - 18) {
        pdf.addPage();
        y = 18;
      }

      y += 7;
      pdf.setDrawColor(226, 232, 240);
      pdf.line(margin, y, margin + contentW, y);
      y += 7;

      pdf.setTextColor(15, 23, 42);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(10);
      pdf.text('Statement Totals', margin, y);
      y += 6;

      pdf.setFontSize(9);
      pdf.text('Sent: ' + fmtQty(sendQty), margin, y);
      pdf.text('Returned: ' + fmtQty(returnQty), margin + 42, y);
      pdf.text('Total Outstanding Balance: ' + fmtQty(overallOutstandingQty), margin + 96, y);
      y += 10;
    }

    function drawFooter(){
      const totalPages = pdf.internal.getNumberOfPages();
      for (let page = 1; page <= totalPages; page++) {
        pdf.setPage(page);
        pdf.setDrawColor(226, 232, 240);
        pdf.line(margin, pageH - 12, pageW - margin, pageH - 12);
        pdf.setTextColor(100, 116, 139);
        pdf.setFont(undefined, 'normal');
        pdf.setFontSize(8);
        pdf.text('Basket Movement Statement', margin, pageH - 7);
        pdf.text('Page ' + page + ' of ' + totalPages, pageW - margin, pageH - 7, { align:'right' });
      }
    }

    drawHeader();
    drawSummary();
    drawTableHeader();
    selectedRows.forEach(drawRow);
    drawTotals();
    drawFooter();

    return {
      blob: pdf.output('blob'),
      fileName: 'basket-movement-' + fileSafe(customerCode || customerName) + '.pdf'
    };
  }

  async function printLedgerPdf(button){
    const originalText = setLedgerButtonBusy(button, 'Preparing...');

    try {
      const result = await buildLedgerStatementPdfBlob();
      if (!result) return;
      downloadBlob(result.blob, result.fileName);
    } catch(err) {
      showError('PDF failed', err && err.message ? err.message : 'Could not generate PDF.');
    } finally {
      restoreLedgerButton(button, originalText, 'Print PDF');
    }
  }

  async function shareLedgerPdf(button){
    const originalText = setLedgerButtonBusy(button, 'Preparing...');

    try {
      if (!navigator.share) {
        showInfo('Share not supported', 'This browser cannot open the native share menu. The PDF will be downloaded instead.');
        const fallbackResult = await buildLedgerStatementPdfBlob();
        if (fallbackResult) downloadBlob(fallbackResult.blob, fallbackResult.fileName);
        return;
      }

      const result = await buildLedgerStatementPdfBlob();
      if (!result) return;

      const file = new File([result.blob], result.fileName, { type:'application/pdf' });

      if (!navigator.canShare || !navigator.canShare({ files:[file] })) {
        downloadBlob(result.blob, result.fileName);
        showInfo('PDF downloaded', 'This browser cannot share PDF files directly. Attach the downloaded PDF in WhatsApp.');
        return;
      }

      await navigator.share({
        title: result.fileName.replace(/\.pdf$/i, ''),
        text: 'Basket Movement Statement PDF',
        files: [file]
      });
    } catch(error) {
      if (error && error.name === 'AbortError') return;
      showError('Unable to share PDF', 'Please print or save PDF, then share it in WhatsApp.');
    } finally {
      restoreLedgerButton(button, originalText, 'Share PDF');
    }
  }

  async function loadLedger(debtorCode, debtorName){
    if (!REST_LEDGER_URL) {
      showError('Missing configuration', 'Ledger endpoint missing.');
      return;
    }

    const rangeError = dateRangeError();
    if (rangeError) {
      showError('Invalid date range', rangeError);
      return;
    }

    const cacheKey = debtorCode + '|' + ($('ac_bs_date_from').value || '') + '|' + ($('ac_bs_date_to').value || '');
    showLedgerLoading(debtorCode, debtorName);
    markSelectedDebtorRow(debtorCode);
    openLedgerModal();

    if (ledgerCache[cacheKey]) {
      renderLedger(debtorCode, debtorName, ledgerCache[cacheKey]);
      return;
    }

    try {
      const data = await apiGet(buildLedgerUrl(debtorCode));
      const rows = Array.isArray(data && data.rows) ? data.rows.map(normalizeLedgerRow) : [];
      ledgerCache[cacheKey] = rows;
      renderLedger(debtorCode, debtorName, rows);
    } catch(err) {
      showError('Failed to load movement history', err && err.message ? err.message : 'Please try again.');
      $('ac_bs_ledger_table').innerHTML = '<tr><td colspan="9" class="bs-empty-cell">Failed to load movement history.</td></tr>';
    }
  }

  function updateCustomerClearButton(){
    const hasValue = selectedDebtors.length > 0;
    $('ac_bs_customer_clear')?.classList.toggle('show', hasValue);
  }

  function clearCustomerSelection(){
    selectedDebtors.splice(0, selectedDebtors.length);
    updateCustomerSelectionUi();
    closeSelectedCustomerManager();
    loadSummary();
  }

  async function searchCustomersLive(q){
    if (!AJAX_URL || !DEBTOR_NONCE) return [];

    const url = AJAX_URL + '?action=ac_cs_debtor_search&nonce=' + encodeURIComponent(DEBTOR_NONCE) + '&q=' + encodeURIComponent(q);
    const res = await fetch(url, { credentials:'same-origin', cache:'no-store' });
    const data = await res.json();
    if (!data.success) throw new Error((data.data && data.data.error) ? data.data.error : 'Search failed');

    const items = data.data?.items || [];
    return items.map(it => {
      const name = it.name || it.debtorName || '';
      const code = it.code || it.debtorCode || '';
      return { label: name || code, meta: (SHOW_DEBTOR_CODE && code) ? code : '', raw: { name, code } };
    });
  }

  function renderPickerNote(msg){
    $('ac_bs_picker_results').innerHTML = '<div class="bs-picker-note">' + esc(msg) + '</div>';
  }

  function renderPickerItems(items){
    if (!items.length) {
      renderPickerNote('No result found');
      return;
    }

    $('ac_bs_picker_results').innerHTML = items.map((it, idx) => `<button type="button" class="bs-picker-item" data-picker-idx="${idx}">
      <span class="bs-picker-item-main">${esc(it.label || '')}</span>
      ${it.meta ? '<span class="bs-picker-item-sub">' + esc(it.meta) + '</span>' : ''}
    </button>`).join('');
  }

  function runPickerSearch(q){
    const query = (q || '').trim();
    clearTimeout(pickerTimer);

    if (query.length < 1) {
      pickerState.items = [];
      renderPickerNote('Type to search');
      return;
    }

    pickerTimer = setTimeout(async function(){
      renderPickerNote('Searching...');
      try {
        pickerState.items = await pickerState.fetchFn(query) || [];
        renderPickerItems(pickerState.items);
      } catch(e) {
        pickerState.items = [];
        renderPickerNote('Failed to load');
      }
    }, 220);
  }

  function openPicker(opts){
    pickerState.items = [];
    pickerState.fetchFn = opts.fetchFn;
    pickerState.onPick = opts.onPick;
    $('ac_bs_picker_title').textContent = opts.title || 'Search';
    $('ac_bs_picker_search').placeholder = opts.placeholder || 'Type to search...';
    $('ac_bs_picker_search').value = '';
    $('ac_bs_picker_modal').classList.add('active');
    renderPickerNote('Type to search');
    setTimeout(() => $('ac_bs_picker_search').focus(), 80);
  }

  function closePicker(){
    $('ac_bs_picker_modal').classList.remove('active');
    $('ac_bs_picker_search').value = '';
    $('ac_bs_picker_results').innerHTML = '';
    pickerState.items = [];
    pickerState.fetchFn = null;
    pickerState.onPick = null;
  }

  function openCustomerPicker(){
    openPicker({
      title:'Select Customer',
      placeholder:'Search customer...',
      fetchFn: searchCustomersLive,
      onPick: function(picked){
        if (!picked) return;
        addSelectedCustomer(picked);
        closePicker();
        loadSummary();
      }
    });
  }

  function setDefaultDateRange(){
    const dateFromEl = $('ac_bs_date_from');
    const dateToEl = $('ac_bs_date_to');
    if (!dateFromEl || !dateToEl) return;

    const today = new Date();
    const oneMonthAgo = new Date(today);
    oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);

    function toYmd(d){
      return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
    }

    if (!dateToEl.value) dateToEl.value = toYmd(today);
    if (!dateFromEl.value) dateFromEl.value = toYmd(oneMonthAgo);
  }

  function openLedgerModal(){
    const modal = $('ac_bs_ledger_modal');
    if (!modal) return;
    modal.classList.add('active');
    modal.setAttribute('aria-hidden', 'false');
    document.body.classList.add('bs-ledger-open');
  }

  function closeLedgerModal(){
    const modal = $('ac_bs_ledger_modal');
    if (!modal) return;
    modal.classList.remove('active');
    modal.setAttribute('aria-hidden', 'true');
    document.body.classList.remove('bs-ledger-open');
  }

  $('ac_bs_refresh') && $('ac_bs_refresh').addEventListener('click', e => { e.preventDefault(); loadSummary(); });

  // Auto-refresh summary when date range changes
  const dateFromEl = $('ac_bs_date_from');
  const dateToEl = $('ac_bs_date_to');
  let dateLoadTimer = null;
  function onDateChange() {
    clearTimeout(dateLoadTimer);
    dateLoadTimer = setTimeout(loadSummary, 300);
  }
  if (dateFromEl) dateFromEl.addEventListener('input', onDateChange);
  if (dateToEl) dateToEl.addEventListener('input', onDateChange);

  $('ac_bs_rows_table').addEventListener('click', e => {
    const btn = e.target.closest('[data-debtor-code]');
    if (!btn) return;
    const code = btn.dataset.debtorCode || '';
    const name = btn.dataset.debtorName || '';
    if (code) loadLedger(code, name);
  });
  $('ac_bs_ledger_table').addEventListener('click', e => {
    const receiptBtn = e.target.closest('[data-basket-receipt-idx]');
    if (receiptBtn) {
      e.preventDefault();
      e.stopPropagation();
      openBasketReceiptByIndex(receiptBtn.dataset.basketReceiptIdx);
      return;
    }
  });
  $('ac_bs_ledger_date_from')?.addEventListener('input', () => applyLedgerRender(currentLedgerRows));
  $('ac_bs_ledger_date_to')?.addEventListener('input', () => applyLedgerRender(currentLedgerRows));
  $('ac_bs_ledger_movement_filter')?.addEventListener('change', () => applyLedgerRender(currentLedgerRows));
  $('ac_bs_ledger_select_all')?.addEventListener('click', e => { e.preventDefault(); toggleSelectAllLedgerRows(); });
  $('ac_bs_ledger_print')?.addEventListener('click', e => {
    e.preventDefault();
    printLedgerPdf(e.currentTarget);
  });
  $('ac_bs_ledger_share')?.addEventListener('click', e => {
    e.preventDefault();
    shareLedgerPdf(e.currentTarget);
  });
  $('ac_bs_receipt_mount').addEventListener('click', e => {
    const printBtn = e.target.closest('[data-print-current-basket-receipt]');
    if (printBtn) {
      e.preventDefault();
      printCurrentBasketReceipt();
      return;
    }

    const btn = e.target.closest('[data-share-current-basket-receipt]');
    if (!btn) return;
    e.preventDefault();
    shareCurrentBasketReceipt(btn);
  });
  $('ac_bs_customer_input').addEventListener('click', openCustomerPicker);
  $('ac_bs_customer_clear').addEventListener('click', e => {
    e.preventDefault();
    e.stopPropagation();
    clearCustomerSelection();
  });
  $('ac_bs_selected_customers').addEventListener('click', e => {
    const toggleBtn = e.target.closest('[data-toggle-selected-customers]');
    if (toggleBtn) {
      e.preventDefault();
      e.stopPropagation();
      toggleSelectedCustomerManager();
      return;
    }

    const btn = e.target.closest('[data-remove-selected-customer]');
    if (!btn) return;
    e.preventDefault();
    e.stopPropagation();
    removeSelectedCustomer(btn.dataset.removeSelectedCustomer || '');
  });
  $('ac_bs_manage_list').addEventListener('click', e => {
    const btn = e.target.closest('[data-remove-selected-customer]');
    if (!btn) return;
    e.preventDefault();
    e.stopPropagation();
    removeSelectedCustomer(btn.dataset.removeSelectedCustomer || '');
  });
  $('ac_bs_manage_add').addEventListener('click', e => {
    e.preventDefault();
    openCustomerPicker();
  });
  $('ac_bs_manage_clear').addEventListener('click', e => {
    e.preventDefault();
    clearCustomerSelection();
  });
  $('ac_bs_manage_done').addEventListener('click', e => {
    e.preventDefault();
    closeSelectedCustomerManager();
  });
  document.addEventListener('click', function(e){
    const panel = $('ac_bs_manage_selected');
    const selectedArea = $('ac_bs_selected_customers');
    if (!panel || !panel.classList.contains('active')) return;
    if (panel.contains(e.target) || selectedArea.contains(e.target)) return;
    closeSelectedCustomerManager();
  });
  $('ac_bs_picker_close').addEventListener('click', closePicker);
  $('ac_bs_picker_backdrop').addEventListener('click', closePicker);
  $('ac_bs_picker_search').addEventListener('input', function(){ runPickerSearch(this.value); });
  $('ac_bs_picker_results').addEventListener('click', e => {
    const btn = e.target.closest('[data-picker-idx]');
    if (!btn) return;
    const idx = parseInt(btn.dataset.pickerIdx, 10);
    if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) {
      pickerState.onPick(pickerState.items[idx].raw);
    }
  });
  $('ac_bs_ledger_close').addEventListener('click', closeLedgerModal);
  $('ac_bs_ledger_b�m:�$������������m:�
N����ackdrop').addEventListener('click', closeLedgerModal);
  document.addEventListener('keydown', function(e){
    if (e.key === 'Escape') {
      closeSelectedCustomerManager();
      closeLedgerModal();
      const receiptMount = $('ac_bs_receipt_mount');
      if (receiptMount && receiptMount.innerHTML) {
        receiptMount.innerHTML = '';
        document.body.classList.remove('bs-br-open');
      }
    }
  });

  setDefaultDateRange();
  updateCustomerClearButton();
  loadSummary();
})();
</script>�m:��eߘ���������oE�
N?�<?php
/**
 * BasketDO Assigned Driver Dashboard.
 * Paste into XYZ Insert PHP Code Snippet PHP code box.
 * Paste as-is. Do not create a shortcode inside this code.
 *
 * Workflow:
 * - Staff creates the DO and assigns it to a driver.
 * - Driver sees only jobs assigned to their WordPress user.
 * - Job appears directly as Out For Delivery.
 * - Driver can mark delivered immediately.
 * - Driver cannot create or edit delivery orders from this page.
 */

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

if (!is_user_logged_in()) {
    echo '<div style="max-width:420px;margin:40px auto;padding:22px;border:1px solid #fecaca;border-radius:18px;background:#fff1f2;color:#991b1b;font-family:Arial,sans-serif;text-align:center;">Please log in to continue.</div>';
    return;
}

global $wpdb;

add_filter('show_admin_bar', '__return_false');

$current_user    = wp_get_current_user();
$current_user_id = get_current_user_id();
$user_roles      = (array) $current_user->roles;
$is_driver       = in_array('driver', $user_roles, true);

if (!$is_driver) {
    echo '<div style="max-width:420px;margin:40px auto;padding:22px;border:1px solid #fecaca;border-radius:18px;background:#fff1f2;color:#991b1b;font-family:Arial,sans-serif;text-align:center;">This page is for driver accounts only.</div>';
    return;
}

if (!function_exists('bdo_drv_page_url')) {
    function bdo_drv_page_url() {
        $scheme = is_ssl() ? 'https://' : 'http://';
        $host   = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '';
        $uri    = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';

        return remove_query_arg(['bdo_msg', 'bdo_err', 'bdo_job_id', 'bdo_br_id', 'bdo_tab', 'bdo_filter'], $scheme . $host . $uri);
    }
}

if (!function_exists('bdo_drv_table_exists')) {
    function bdo_drv_table_exists($table_name) {
        global $wpdb;

        return $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table_name)) === $table_name;
    }
}

if (!function_exists('bdo_drv_table_columns')) {
    function bdo_drv_table_columns($table_name) {
        global $wpdb;
        static $cache = [];

        if (isset($cache[$table_name])) {
            return $cache[$table_name];
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table_name);
        $cols       = $wpdb->get_col("SHOW COLUMNS FROM `{$safe_table}`", 0);

        $cache[$table_name] = is_array($cols) ? array_flip($cols) : [];
        return $cache[$table_name];
    }
}

if (!function_exists('bdo_drv_has_columns')) {
    function bdo_drv_has_columns($cols, $required) {
        foreach ($required as $col) {
            if (!isset($cols[$col])) {
                return false;
            }
        }

        return true;
    }
}

if (!function_exists('bdo_drv_pick')) {
    function bdo_drv_pick($arr, $keys, $fallback = '') {
        if (!is_array($arr)) {
            return $fallback;
        }

        foreach ($keys as $key) {
            if (isset($arr[$key]) && $arr[$key] !== '' && $arr[$key] !== null) {
                return $arr[$key];
            }
        }

        return $fallback;
    }
}

if (!function_exists('bdo_drv_json')) {
    function bdo_drv_json($value) {
        if (is_array($value)) {
            return $value;
        }

        if (!is_string($value) || trim($value) === '') {
            return [];
        }

        $decoded = json_decode($value, true);
        return is_array($decoded) ? $decoded : [];
    }
}

if (!function_exists('bdo_drv_sum_lines')) {
    function bdo_drv_sum_lines($payload) {
        $summary = [
            'items'   => 0,
            'baskets' => 0,
            'cartons' => 0,
            'kg'      => 0,
            'lines'   => [],
        ];

        $lines = [];
        if (isset($payload['lines']) && is_array($payload['lines'])) {
            $lines = $payload['lines'];
        } elseif (isset($payload['Lines']) && is_array($payload['Lines'])) {
            $lines = $payload['Lines'];
        }

        foreach ($lines as $line) {
            if (!is_array($line)) {
                continue;
            }

            $pack_type = strtoupper(trim((string) bdo_drv_pick($line, ['packType', 'PackType'], '')));
            $qty       = (float) bdo_drv_pick($line, ['qty', 'Qty', 'quantity', 'Quantity'], 0);
            $unit_qty  = (float) bdo_drv_pick($line, ['unitQty', 'UnitQty'], 0);
            $basket    = (float) bdo_drv_pick($line, ['basketQty', 'basket_qty', 'BasketQty', 'UDF_BASKET'], 0);
            $carton    = (float) bdo_drv_pick($line, ['cartonQty', 'carton_qty', 'CartonQty', 'UDF_CARTON'], 0);
            $kg_each   = (float) bdo_drv_pick($line, ['kg', 'Kg', 'UDF_WEIGHTKG'], 0);
            $total_kg  = (float) bdo_drv_pick($line, ['totalKg', 'TotalKg', 'totalKG'], 0);

            if ($basket <= 0 && $pack_type === 'BASKET') {
                $basket = $unit_qty > 0 ? $unit_qty : $qty;
            }

            if ($carton <= 0 && $pack_type === 'CARTON') {
                $carton = $unit_qty > 0 ? $unit_qty : $qty;
            }

            if ($total_kg <= 0) {
                $total_kg = $kg_each > 0 && $unit_qty > 0 ? $kg_each * $unit_qty : ($kg_each > 0 ? $kg_each : $qty);
            }

            $item_code = (string) bdo_drv_pick($line, ['itemCode', 'ItemCode'], '');
            $item_name = (string) bdo_drv_pick($line, ['itemName', 'ItemName', 'description', 'Description', 'itemDesc'], $item_code);

            $summary['items']++;
            $summary['baskets'] += $basket;
            $summary['cartons'] += $carton;
            $summary['kg'] += $total_kg;
            $summary['lines'][] = [
                'itemCode' => $item_code,
                'itemName' => $item_name ?: 'Item',
                'packType' => $pack_type ?: '-',
                'qty'      => (int) round($unit_qty > 0 ? $unit_qty : $qty),
                'basket'   => (int) round($basket),
                'carton'   => (int) round($carton),
                'kg'       => (int) round($total_kg),
                'unitKg'   => (int) round($kg_each > 0 ? $kg_each : $total_kg),
                'totalKg'  => (int) round($total_kg),
            ];
        }

        $summary['baskets'] = (int) round($summary['baskets']);
        $summary['cartons'] = (int) round($summary['cartons']);
        $summary['kg']      = (int) round($summary['kg']);

        return $summary;
    }
}

if (!function_exists('bdo_drv_upload_image')) {
    function bdo_drv_upload_image($field_name, $required = true) {
        if (empty($_FILES[$field_name]) || empty($_FILES[$field_name]['name'])) {
            if ($required) {
                return new WP_Error('missing_file', 'Photo proof is required.');
            }

            return [
                'attachment_id' => 0,
                'image_url'     => '',
                'image_path'    => '',
                'mime_type'     => '',
                'file_size'     => 0,
                'uploaded'      => false,
            ];
        }

        $file = $_FILES[$field_name];

        if (!empty($file['error'])) {
            if ((int) $file['error'] === UPLOAD_ERR_NO_FILE && !$required) {
                return [
                    'attachment_id' => 0,
                    'image_url'     => '',
                    'image_path'    => '',
                    'mime_type'     => '',
                    'file_size'     => 0,
                    'uploaded'      => false,
                ];
            }

            return new WP_Error('upload_error', 'Upload failed. Error code: ' . (int) $file['error']);
        }

        if (empty($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
            return new WP_Error('invalid_upload', 'Invalid upload request.');
        }

        $max_size = 4 * 1024 * 1024;
        if (!empty($file['size']) && (int) $file['size'] > $max_size) {
            return new WP_Error('file_too_large', 'Photo is too large. Please retake or choose a smaller photo.');
        }

        $allowed_mimes = [
            'jpg|jpeg|jpe' => 'image/jpeg',
            'png'          => 'image/png',
            'webp'         => 'image/webp',
        ];

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

        $checked = wp_check_filetype_and_ext($file['tmp_name'], $file['name'], $allowed_mimes);
        if (empty($checked['type']) || !in_array($checked['type'], $allowed_mimes, true)) {
            return new WP_Error('invalid_file_type', 'Only JPG, PNG, or WebP photos are allowed.');
        }

        $uploaded = wp_handle_upload($file, [
            'test_form' => false,
            'mimes'     => $allowed_mimes,
        ]);

        if (isset($uploaded['error'])) {
            return new WP_Error('upload_error', $uploaded['error']);
        }

        $file_path = (string) ($uploaded['file'] ?? '');

        return [
            'attachment_id' => 0,
            'image_url'     => esc_url_raw((string) ($uploaded['url'] ?? '')),
            'image_path'    => $file_path,
            'mime_type'     => sanitize_mime_type((string) ($uploaded['type'] ?? '')),
            'file_size'     => ($file_path && file_exists($file_path)) ? (int) filesize($file_path) : 0,
            'uploaded'      => true,
        ];
    }
}

if (!function_exists('bdo_drv_insert_proof')) {
    function bdo_drv_insert_proof($data) {
        global $wpdb;

        $table = $wpdb->prefix . 'ac_do_proof_images';
        if (!bdo_drv_table_exists($table)) {
            return false;
        }

        $cols = bdo_drv_table_columns($table);
        $base = [
            'job_id'           => isset($data['job_id']) ? (int) $data['job_id'] : 0,
            'do_id'            => isset($data['do_id']) ? (int) $data['do_id'] : null,
            'doc_no'           => isset($data['doc_no']) ? sanitize_text_field((string) $data['doc_no']) : '',
            'doc_key'          => isset($data['doc_key']) ? (int) $data['doc_key'] : 0,
            'receipt_token_id' => null,
            'proof_type'       => 'DELIVERY_PROOF',
            'attachment_id'    => isset($data['attachment_id']) ? (int) $data['attachment_id'] : 0,
            'image_url'        => isset($data['image_url']) ? esc_url_raw((string) $data['image_url']) : '',
            'image_path'       => isset($data['image_path']) ? sanitize_text_field((string) $data['image_path']) : '',
            'mime_type'        => isset($data['mime_type']) ? sanitize_mime_type((string) $data['mime_type']) : '',
            'file_size'        => isset($data['file_size']) ? (int) $data['file_size'] : 0,
            'captured_by'      => get_current_user_id(),
            'captured_at'      => current_time('mysql'),
            'created_at'       => current_time('mysql'),
        ];

        $insert = [];
        foreach ($base as $key => $value) {
            if (isset($cols[$key])) {
                $insert[$key] = $value;
            }
        }

        return !empty($insert) && $wpdb->insert($table, $insert) ? (int) $wpdb->insert_id : false;
    }
}

if (!function_exists('bdo_drv_get_do_proof_url')) {
    function bdo_drv_get_do_proof_url($job_id, $doc_key, $doc_no) {
        global $wpdb;

        $table = $wpdb->prefix . 'ac_do_proof_images';
        if (!bdo_drv_table_exists($table)) {
            return '';
        }

        $cols  = bdo_drv_table_columns($table);
        $where = [];
        $args  = [];

        if ((int) $job_id > 0 && isset($cols['job_id'])) {
            $where[] = 'job_id = %d';
            $args[] = (int) $job_id;
        }

        if ((int) $doc_key > 0 && isset($cols['doc_key'])) {
            $where[] = 'doc_key = %d';
            $args[] = (int) $doc_key;
        }

        $doc_no = trim((string) $doc_no);
        if ($doc_no !== '' && isset($cols['doc_no'])) {
            $where[] = 'doc_no = %s';
            $args[] = $doc_no;
        }

        if (empty($where)) {
            return '';
        }

        $select = [];
        foreach (['attachment_id', 'image_url'] as $col) {
            if (isset($cols[$col])) {
                $select[] = "`{$col}`";
            }
        }

        if (empty($select)) {
            return '';
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $where_sql  = '(' . implode(' OR ', $where) . ')';

        if (isset($cols['proof_type'])) {
            $where_sql .= ' AND proof_type = %s';
            $args[] = 'DELIVERY_PROOF';
        }

        if (isset($cols['deleted_at'])) {
            $where_sql .= ' AND deleted_at IS NULL';
        }

        $order_col = isset($cols['captured_at']) ? 'captured_at' : 'id';
        $proof = $wpdb->get_row($wpdb->prepare(
            "SELECT " . implode(', ', $select) . " FROM `{$safe_table}` WHERE {$where_sql} ORDER BY `{$order_col}` ASC LIMIT 1",
            $args
        ), ARRAY_A);

        if (!$proof) {
            return '';
        }

        if (!empty($proof['attachment_id'])) {
            $attachment_url = wp_get_attachment_url((int) $proof['attachment_id']);
            if (!empty($attachment_url)) {
                return esc_url_raw($attachment_url);
            }
        }

        return !empty($proof['image_url']) ? esc_url_raw((string) $proof['image_url']) : '';
    }
}

if (!function_exists('bdo_drv_insert_basket_return_proof')) {
    function bdo_drv_insert_basket_return_proof($data) {
        global $wpdb;

        $table = $wpdb->prefix . 'ac_basket_return_proof_images';
        if (!bdo_drv_table_exists($table)) {
            return false;
        }

        $cols = bdo_drv_table_columns($table);
        $base = [
            'ledger_id'     => isset($data['ledger_id']) ? (int) $data['ledger_id'] : 0,
            'source_ref'    => isset($data['source_ref']) ? sanitize_text_field((string) $data['source_ref']) : '',
            'debtor_code'   => isset($data['debtor_code']) ? sanitize_text_field((string) $data['debtor_code']) : '',
            'debtor_name'   => isset($data['debtor_name']) ? sanitize_text_field((string) $data['debtor_name']) : '',
            'attachment_id' => isset($data['attachment_id']) ? (int) $data['attachment_id'] : 0,
            'image_url'     => isset($data['image_url']) ? esc_url_raw((string) $data['image_url']) : '',
            'image_path'    => isset($data['image_path']) ? sanitize_text_field((string) $data['image_path']) : '',
            'mime_type'     => isset($data['mime_type']) ? sanitize_mime_type((string) $data['mime_type']) : '',
            'file_size'     => isset($data['file_size']) ? (int) $data['file_size'] : 0,
            'captured_by'   => get_current_user_id(),
            'captured_at'   => current_time('mysql'),
            'created_at'    => current_time('mysql'),
        ];

        $insert = [];
        foreach ($base as $key => $value) {
            if (isset($cols[$key])) {
                $insert[$key] = $value;
            }
        }

        return !empty($insert) && $wpdb->insert($table, $insert) ? (int) $wpdb->insert_id : false;
    }
}

if (!function_exists('bdo_drv_get_basket_return_receipt')) {
    function bdo_drv_get_basket_return_receipt($ledger_id, $ledger_table, $ledger_table_safe, $proof_table, $proof_table_safe, $current_user_id) {
        if (!$ledger_id || !bdo_drv_table_exists($ledger_table)) {
            return null;
        }

        $ledger_cols = bdo_drv_table_columns($ledger_table);
        if (!bdo_drv_has_columns($ledger_cols, ['id', 'txn_type'])) {
            return null;
        }

        $where       = 'id = %d AND txn_type = %s';
        $args        = [(int) $ledger_id, 'RETURN'];

        if (isset($ledger_cols['created_by'])) {
            $where .= ' AND created_by = %d';
            $args[] = (int) $current_user_id;
        }

        $row = $GLOBALS['wpdb']->get_row($GLOBALS['wpdb']->prepare("SE�oE���ߴ���������o��
N?�LECT * FROM `{$ledger_table_safe}` WHERE {$where} LIMIT 1", $args), ARRAY_A);
        if (!$row) {
            return null;
        }

        $proof_url = '';
        if (bdo_drv_table_exists($proof_table)) {
            $proof_cols = bdo_drv_table_columns($proof_table);
            $proof_where = '';
            $proof_args  = [];

            if (isset($proof_cols['ledger_id'])) {
                $proof_where = 'ledger_id = %d';
                $proof_args[] = (int) $ledger_id;
            } elseif (isset($proof_cols['source_ref']) && !empty($row['source_ref'])) {
                $proof_where = 'source_ref = %s';
                $proof_args[] = (string) $row['source_ref'];
            }

            if ($proof_where !== '') {
                $proof = $GLOBALS['wpdb']->get_row($GLOBALS['wpdb']->prepare("SELECT * FROM `{$proof_table_safe}` WHERE {$proof_where} ORDER BY id DESC LIMIT 1", $proof_args), ARRAY_A);
                if ($proof && !empty($proof['image_url'])) {
                    $proof_url = esc_url_raw((string) $proof['image_url']);
                }
            }
        }

        $date_value = (string) ($row['created_at'] ?? ($row['txn_date'] ?? current_time('mysql')));
        $date_ts    = strtotime($date_value);

        return [
            'id'           => (int) $ledger_id,
            'sourceRef'    => (string) ($row['source_ref'] ?? ('BR-' . (int) $ledger_id)),
            'customerCode' => (string) ($row['debtor_code'] ?? ''),
            'customerName' => (string) ($row['debtor_name'] ?? 'Customer'),
            'qty'          => (int) round((float) ($row['qty'] ?? 0)),
            'date'         => $date_value,
            'displayDate'  => $date_ts ? date('d/m/Y h:i A', $date_ts) : $date_value,
            'driverName'   => strtoupper(trim(wp_get_current_user()->display_name ?: wp_get_current_user()->user_login ?: 'DRIVER')),
            'proofUrl'     => $proof_url,
        ];
    }
}

if (!function_exists('bdo_drv_get_canonical_debtor')) {
    function bdo_drv_get_canonical_debtor($debtor_code) {
        $debtor_code = trim((string) $debtor_code);
        if ($debtor_code === '') {
            return new WP_Error('missing_debtor_code', 'Please select customer for basket return.');
        }

        if (!function_exists('get_mssql')) {
            return new WP_Error('missing_autocount_connection', 'AutoCount customer validation is not available.');
        }

        $conn = get_mssql();
        if (!$conn) {
            return new WP_Error('autocount_connection_failed', 'Failed to connect to AutoCount customer records.');
        }

        if (!function_exists('sqlsrv_query')) {
            return new WP_Error('missing_sqlsrv', 'SQL Server driver is not available for customer validation.');
        }

        $stmt = sqlsrv_query(
            $conn,
            "SELECT TOP 1 AccNo, CompanyName FROM Debtor WHERE IsActive = 'T' AND AccNo = ?",
            [$debtor_code],
            ['QueryTimeout' => 8]
        );

        if ($stmt === false) {
            return new WP_Error('debtor_lookup_failed', 'Failed to validate selected customer.');
        }

        $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
        sqlsrv_free_stmt($stmt);

        if (!$row) {
            return new WP_Error('debtor_not_found', 'Selected customer is not active or does not exist.');
        }

        return [
            'code' => trim((string) ($row['AccNo'] ?? '')),
            'name' => trim((string) ($row['CompanyName'] ?? '')),
        ];
    }
}

if (!function_exists('bdo_drv_do_line_summary')) {
    function bdo_drv_do_line_summary($rows) {
        $summary = [
            'items'   => 0,
            'baskets' => 0,
            'cartons' => 0,
            'kg'      => 0,
            'lines'   => [],
        ];

        foreach ((array) $rows as $line) {
            $pack_type = strtoupper(trim((string) ($line['pack_type'] ?? '')));
            $qty       = (float) ($line['qty'] ?? 0);
            $unit_qty  = (float) ($line['unit_qty'] ?? 0);
            $basket    = (float) ($line['basket_qty'] ?? 0);
            $carton    = (float) ($line['carton_qty'] ?? 0);
            $kg_each   = (float) ($line['weight_kg'] ?? 0);
            $total_kg  = (float) ($line['total_weight_kg'] ?? 0);

            if ($basket <= 0 && $pack_type === 'BASKET') {
                $basket = $unit_qty > 0 ? $unit_qty : $qty;
            }
            if ($carton <= 0 && $pack_type === 'CARTON') {
                $carton = $unit_qty > 0 ? $unit_qty : $qty;
            }
            if ($total_kg <= 0) {
                $total_kg = $kg_each > 0 && $unit_qty > 0 ? $kg_each * $unit_qty : ($kg_each > 0 ? $kg_each : $qty);
            }

            $item_code = (string) ($line['item_code'] ?? '');
            $item_name = (string) ($line['description'] ?? $item_code);

            $summary['items']++;
            $summary['baskets'] += $basket;
            $summary['cartons'] += $carton;
            $summary['kg'] += $total_kg;
            $summary['lines'][] = [
                'itemCode' => $item_code,
                'itemName' => $item_name ?: 'Item',
                'packType' => $pack_type ?: '-',
                'qty'      => (int) round($unit_qty > 0 ? $unit_qty : $qty),
                'basket'   => (int) round($basket),
                'carton'   => (int) round($carton),
                'kg'       => (int) round($total_kg),
                'unitKg'   => (int) round($kg_each > 0 ? $kg_each : $total_kg),
                'totalKg'  => (int) round($total_kg),
            ];
        }

        $summary['baskets'] = (int) round($summary['baskets']);
        $summary['cartons'] = (int) round($summary['cartons']);
        $summary['kg']      = (int) round($summary['kg']);

        return $summary;
    }
}

if (!function_exists('bdo_drv_load_do_lines')) {
    function bdo_drv_load_do_lines($do_id, $items_table, $items_table_safe) {
        if (!$do_id || !bdo_drv_table_exists($items_table)) {
            return [];
        }

        return (array) $GLOBALS['wpdb']->get_results(
            $GLOBALS['wpdb']->prepare(
                "SELECT * FROM `{$items_table_safe}` WHERE do_id = %d ORDER BY line_no ASC, id ASC",
                (int) $do_id
            ),
            ARRAY_A
        );
    }
}

if (!function_exists('bdo_drv_do_summary')) {
    function bdo_drv_do_summary($do, $items_table, $items_table_safe) {
        $do_id = (int) ($do->id ?? 0);
        $lines = bdo_drv_do_line_summary(bdo_drv_load_do_lines($do_id, $items_table, $items_table_safe));
        $doc_no = trim((string) ($do->local_doc_no ?? ''));
        if ($doc_no === '') {
            $doc_no = trim((string) ($do->autocount_doc_no ?? ('DO-' . $do_id)));
        }
        $doc_key = (int) ($do->autocount_doc_key ?? 0);
        $doc_date = (string) ($do->doc_date ?? '');
        $date_ts = strtotime($doc_date);

        return [
            'id'             => $do_id,
            'jobId'          => (int) ($do->source_job_id ?? 0),
            'docNo'          => $doc_no,
            'docKey'         => $doc_key,
            'customerName'   => (string) ($do->debtor_name ?? 'Customer'),
            'customerCode'   => (string) ($do->debtor_code ?? ''),
            'address'        => '',
            'location'       => (string) ($do->location ?? 'HQ'),
            'status'         => (string) ($do->sync_status ?? ''),
            'deliveryStatus' => (string) ($do->delivery_status ?? ''),
            'createdAt'      => (string) ($do->created_at ?? ''),
            'displayDate'    => $date_ts ? date('d/m/Y', $date_ts) : date('d/m/Y'),
            'proofUrl'       => bdo_drv_get_do_proof_url((int) ($do->source_job_id ?? 0), $doc_key, $doc_no),
            'summary'        => $lines,
        ];
    }
}

if (!function_exists('bdo_drv_driver_where')) {
    function bdo_drv_driver_where($table, $table_safe, $current_user_id) {
        $cols = bdo_drv_table_columns($table);

        if (!isset($cols['assigned_driver_id'])) {
            return [
                'sql'    => ' AND 1 = 0 ',
                'args'   => [],
                'usable' => false,
            ];
        }

        return [
            'sql'    => ' AND assigned_driver_id = %d ',
            'args'   => [(int) $current_user_id],
            'usable' => true,
        ];
    }
}

if (!function_exists('bdo_drv_get_assigned_do')) {
    function bdo_drv_get_assigned_do($do_id, $do_table, $do_table_safe, $current_user_id) {
        if (!$do_id || !bdo_drv_table_exists($do_table)) {
            return null;
        }

        $cols = bdo_drv_table_columns($do_table);
        if (!isset($cols['assigned_driver_id'])) {
            return null;
        }

        $where = 'id = %d AND assigned_driver_id = %d';
        $args  = [(int) $do_id, (int) $current_user_id];
        if (isset($cols['deleted_at'])) {
            $where .= ' AND deleted_at IS NULL';
        }

        return $GLOBALS['wpdb']->get_row($GLOBALS['wpdb']->prepare("SELECT * FROM `{$do_table_safe}` WHERE {$where} LIMIT 1", $args));
    }
}

$do_table           = $wpdb->prefix . 'ac_do';
$do_items_table     = $wpdb->prefix . 'ac_do_items';
$ledger_table       = $wpdb->prefix . 'ac_basket_ledger';
$basket_proof_table = $wpdb->prefix . 'ac_basket_return_proof_images';
$do_table_safe      = preg_replace('/[^A-Za-z0-9_]/', '', $do_table);
$do_items_table_safe = preg_replace('/[^A-Za-z0-9_]/', '', $do_items_table);
$ledger_table_safe  = preg_replace('/[^A-Za-z0-9_]/', '', $ledger_table);
$basket_proof_table_safe = preg_replace('/[^A-Za-z0-9_]/', '', $basket_proof_table);
$do_cols            = bdo_drv_table_exists($do_table) ? bdo_drv_table_columns($do_table) : [];
$required_do_cols   = ['id', 'local_doc_no', 'doc_date', 'debtor_code', 'debtor_name', 'delivery_status', 'assigned_driver_id', 'created_at', 'updated_at'];
$required_ledger_cols = ['id', 'txn_type', 'debtor_code', 'debtor_name', 'qty'];
$schema_error       = '';

if (!bdo_drv_table_exists($do_table) || !bdo_drv_table_exists($do_items_table)) {
    $schema_error = 'Delivery assignment table is not ready.';
} elseif (!bdo_drv_has_columns($do_cols, $required_do_cols)) {
    $schema_error = 'Delivery assignment schema is missing required columns. Please update the local DO schema.';
}

$today       = current_time('Y-m-d');
$current_url = bdo_drv_page_url();
$form_nonce  = wp_create_nonce('bdo_driver_page_action');

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['bdo_driver_action'])) {
    $action = sanitize_key(wp_unslash($_POST['bdo_driver_action']));

    if (!isset($_POST['bdo_driver_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['bdo_driver_nonce'])), 'bdo_driver_page_action')) {
        wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Security check failed.')], $current_url));
        exit;
    }

    if ($schema_error !== '' && in_array($action, ['complete_delivery'], true)) {
        wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode($schema_error)], $current_url));
        exit;
    }

        if ($action === 'complete_delivery') {
            $do_id = isset($_POST['job_id']) ? absint($_POST['job_id']) : 0; // Using ac_do.id
            $do    = bdo_drv_get_assigned_do($do_id, $do_table, $do_table_safe, $current_user_id);

            if (!$do) {
                wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Assigned delivery not found.')], $current_url));
                exit;
            }

            $delivery_status = strtoupper((string) ($do->delivery_status ?? ''));
            $doc_date = trim((string) ($do->doc_date ?? ''));
            if ($delivery_status === 'DELIVERED' || ($doc_date !== '' && date('Y-m-d', strtotime($doc_date)) > $today)) {
                wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('This delivery is not ready to be marked delivered.')], $current_url));
                exit;
            }

            $upload = bdo_drv_upload_image('delivery_proof', false);
            if (is_wp_error($upload)) {
                wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode($upload->get_error_message())], $current_url));
                exit;
            }

            if (!empty($upload['uploaded']) && !empty($upload['image_url'])) {
                $summary = bdo_drv_do_summary($do, $do_items_table, $do_items_table_safe);
                bdo_drv_insert_proof(array_merge($upload, [
                    'job_id'  => (int) ($do->source_job_id ?? 0),
                    'do_id'   => $do_id,
                    'doc_no'  => $summary['docNo'],
                    'doc_key' => $summary['docKey'],
                ]));
            }

            $update = [];
            if (isset($do_cols['delivery_status'])) {
                $update['delivery_status'] = 'DELIVERED';
            }
            if (isset($do_cols['delivery_completed_by'])) {
                $update['delivery_completed_by'] = $current_user_id;
            }
            if (isset($do_cols['delivery_completed_at'])) {
                $update['delivery_completed_at'] = current_time('mysql');
            }
            if (isset($do_cols['delivery_note'])) {
                $update['delivery_note'] = !empty($upload['uploaded']) ? 'Completed with proof of delivery' : 'Completed without proof of delivery';
            }
            if (isset($do_cols['updated_at'])) {
                $update['updated_at'] = current_time('mysql');
            }
            if (isset($do_cols['updated_by'])) {
                $update['updated_by'] = $current_user_id;
            }

            if (!empty($update)) {
                $updated = $wpdb->update(
                    $do_table,
                    $update,
                    [
                        'id' => $do_id,
                        'assigned_driver_id' => $current_user_id,
                    ],
                    null,
                    ['%d', '%d']
                );

                if ($updated === false) {
                    wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Failed to update delivery status. Please refresh and try again.')], $current_url));
                    exit;
                }
            }

            wp_safe_redirect(add_query_arg(['bdo_msg' => 'delivered', 'bdo_job_id' => $do_id, 'bdo_tab' => 'deliveries', 'bdo_filter' => 'delivered'], $current_url));
            exit;
        }

    if ($action === 'basket_return') {
        $debtor_code = isset($_POST['br_debtor_code']) ? sanitize_text_field(wp_unslash($_POST['br_debtor_code'])) : '';
        $basket_qty  = isset($_POST['br_basket_qty']) ? absint($_POST['br_basket_qty']) : 0;

        $debtor = bdo_drv_get_canonical_debtor($debtor_code);
        if (is_wp_error($debtor)) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode($debtor->get_error_message())], $current_url));
            exit;
        }

        $debtor_code = $debtor['code'];
        $debtor_name = $debtor['name'];

        if ($basket_qty <= 0 || $basket_qty > 9999) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Basket return quantity must be between 1 and 9999.')], $current_url));
            exit;
        }

        if (!bdo_drv_table_exists($ledger_table)) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Basket return table is not ready.')], $current_url));
            exit;
        }

        if (!empty($_FILES['basket_return_proof']['name']) && !bdo_drv_table_exists($basket_proof_table)) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Basket return proof table is not ready.')], $current_url));
            exit;
        }

        $upload = bdo_drv_upload_image('basket_return_proof', false);
        if (is_wp_error($upload)) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode($upload->get_error_message())], $current_url)�o�ϚL:u���������o��
N?�);
            exit;
        }

        $cols       = bdo_drv_table_columns($ledger_table);
        if (!bdo_drv_has_columns($cols, $required_ledger_cols)) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Basket return schema is missing required columns.')], $current_url));
            exit;
        }

        $source_ref = 'BR-' . current_time('YmdHis') . '-' . $current_user_id . '-' . wp_generate_password(6, false, false);
        $base       = [
            'source_ref'  => $source_ref,
            'txn_type'    => 'RETURN',
            'debtor_code' => $debtor_code,
            'debtor_name' => $debtor_name,
            'txn_date'    => $today,
            'qty'         => $basket_qty,
            'created_by'  => $current_user_id,
            'created_at'  => current_time('mysql'),
            'updated_at'  => current_time('mysql'),
            'remark'      => 'Basket return by assigned driver',
            'note'        => 'Basket return by assigned driver',
        ];

        $insert = [];
        foreach ($base as $key => $value) {
            if (isset($cols[$key])) {
                $insert[$key] = $value;
            }
        }

        if (empty($insert) || !$wpdb->insert($ledger_table, $insert)) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Failed to save basket return.')], $current_url));
            exit;
        }

        $ledger_id = (int) $wpdb->insert_id;
        if (!empty($upload['uploaded'])) {
            bdo_drv_insert_basket_return_proof(array_merge($upload, [
                'ledger_id'   => $ledger_id,
                'source_ref'  => $source_ref,
                'debtor_code' => $debtor_code,
                'debtor_name' => $debtor_name,
            ]));
        }

        wp_safe_redirect(add_query_arg(['bdo_msg' => 'br_saved', 'bdo_br_id' => $ledger_id, 'bdo_tab' => 'return'], $current_url));
        exit;
    }
}

$ajax_url     = admin_url('admin-ajax.php');
$debtor_nonce = wp_create_nonce('ac_cs_debtor_search');
$driver_where = bdo_drv_table_exists($do_table) ? bdo_drv_driver_where($do_table, $do_table_safe, $current_user_id) : ['sql' => ' AND 1 = 0 ', 'args' => [], 'usable' => false];

$stats = [
    'received'  => 0,
    'delivered' => 0,
    'returns'   => 0,
];

$active_jobs    = [];
$delivered_jobs = [];
$return_rows    = [];
$basket_receipts = [];
$last_delivered_customer = null;

if ($schema_error === '' && bdo_drv_table_exists($do_table) && $driver_where['usable']) {
    $base_args = $driver_where['args'];

    // Drivers see only assigned DOs due today or earlier. Future-dated DOs stay scheduled/hidden from driver action.
    $due_date_sql = " AND (`doc_date` IS NULL OR DATE(`doc_date`) <= %s) ";
    $due_date_args = [$today];
    $not_deleted_sql = isset($do_cols['deleted_at']) ? " AND deleted_at IS NULL " : "";
    $not_hidden_sql = isset($do_cols['hidden_from_staff_list']) ? " AND hidden_from_staff_list = 0 " : "";

    $stats['received'] = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM `{$do_table_safe}` WHERE delivery_status <> 'DELIVERED' {$driver_where['sql']} {$due_date_sql} {$not_deleted_sql} {$not_hidden_sql}",
        array_merge($base_args, $due_date_args)
    ));

    $stats['delivered'] = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM `{$do_table_safe}` WHERE delivery_status = 'DELIVERED' AND DATE(COALESCE(delivery_completed_at, updated_at, created_at)) = %s {$driver_where['sql']} {$not_deleted_sql}",
        array_merge([$today], $base_args)
    ));

    $active_sql = "SELECT * FROM `{$do_table_safe}`
        WHERE delivery_status <> 'DELIVERED'
        {$driver_where['sql']}
        {$due_date_sql}
        {$not_deleted_sql}
        {$not_hidden_sql}
        ORDER BY DATE(`doc_date`) ASC, id DESC
        LIMIT 100";

    foreach ((array) $wpdb->get_results($wpdb->prepare($active_sql, array_merge($base_args, $due_date_args))) as $row) {
        $active_jobs[] = bdo_drv_do_summary($row, $do_items_table, $do_items_table_safe);
    }

    $delivered_sql = "SELECT * FROM `{$do_table_safe}`
        WHERE delivery_status = 'DELIVERED'
        AND DATE(COALESCE(delivery_completed_at, updated_at, created_at)) = %s
        {$driver_where['sql']}
        {$not_deleted_sql}
        ORDER BY COALESCE(delivery_completed_at, updated_at, created_at) DESC
        LIMIT 100";

    foreach ((array) $wpdb->get_results($wpdb->prepare($delivered_sql, array_merge([$today], $base_args))) as $row) {
        $delivered_jobs[] = bdo_drv_do_summary($row, $do_items_table, $do_items_table_safe);
    }

    $last_delivered_sql = "SELECT * FROM `{$do_table_safe}`
        WHERE delivery_status = 'DELIVERED'
        {$driver_where['sql']}
        {$not_deleted_sql}
        ORDER BY COALESCE(delivery_completed_at, updated_at, created_at) DESC
        LIMIT 1";

    $last_delivered_row = $wpdb->get_row($wpdb->prepare($last_delivered_sql, $base_args));
    if ($last_delivered_row) {
        $last_delivered_summary = bdo_drv_do_summary($last_delivered_row, $do_items_table, $do_items_table_safe);
        $last_delivered_customer = [
            'code' => (string) ($last_delivered_summary['customerCode'] ?? ''),
            'name' => (string) ($last_delivered_summary['customerName'] ?? ''),
        ];
    }
}

if (bdo_drv_table_exists($ledger_table)) {
    $ledger_cols = bdo_drv_table_columns($ledger_table);
    $date_col    = isset($ledger_cols['created_at']) ? 'created_at' : (isset($ledger_cols['txn_date']) ? 'txn_date' : '');

    if ($date_col !== '' && bdo_drv_has_columns($ledger_cols, $required_ledger_cols)) {
        $ledger_driver_sql  = isset($ledger_cols['created_by']) ? ' AND created_by = %d ' : '';
        $ledger_driver_args = isset($ledger_cols['created_by']) ? [$current_user_id] : [];
        $qty_expr           = isset($ledger_cols['qty']) ? 'COALESCE(SUM(ABS(qty)),0)' : 'COUNT(*)';

        $stats['returns'] = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT {$qty_expr} FROM `{$ledger_table_safe}` WHERE txn_type = 'RETURN' AND DATE(`{$date_col}`) = %s {$ledger_driver_sql}",
            array_merge([$today], $ledger_driver_args)
        ));

        $return_sql = "SELECT * FROM `{$ledger_table_safe}`
            WHERE txn_type = 'RETURN'
            AND DATE(`{$date_col}`) = %s
            {$ledger_driver_sql}
            ORDER BY id DESC
            LIMIT 100";

        foreach ((array) $wpdb->get_results($wpdb->prepare($return_sql, array_merge([$today], $ledger_driver_args)), ARRAY_A) as $row) {
            $receipt = bdo_drv_get_basket_return_receipt((int) ($row['id'] ?? 0), $ledger_table, $ledger_table_safe, $basket_proof_table, $basket_proof_table_safe, $current_user_id);
            if (!$receipt) {
                continue;
            }

            $return_rows[] = $receipt;
            $basket_receipts[(string) $receipt['id']] = $receipt;
        }
    }
}

$selected_basket_return_id = isset($_GET['bdo_br_id']) ? absint($_GET['bdo_br_id']) : 0;
$selected_basket_receipt   = $selected_basket_return_id > 0
    ? bdo_drv_get_basket_return_receipt($selected_basket_return_id, $ledger_table, $ledger_table_safe, $basket_proof_table, $basket_proof_table_safe, $current_user_id)
    : null;

if ($selected_basket_receipt) {
    $basket_receipts[(string) $selected_basket_receipt['id']] = $selected_basket_receipt;
}

$active_job = !empty($active_jobs) ? $active_jobs[0] : null;

$message     = isset($_GET['bdo_msg']) ? sanitize_key(wp_unslash($_GET['bdo_msg'])) : '';
$error       = isset($_GET['bdo_err']) ? sanitize_text_field(wp_unslash($_GET['bdo_err'])) : '';
$initial_tab = isset($_GET['bdo_tab']) ? sanitize_key(wp_unslash($_GET['bdo_tab'])) : 'home';
$initial_filter = isset($_GET['bdo_filter']) ? sanitize_key(wp_unslash($_GET['bdo_filter'])) : '';
$driver_name = strtoupper(trim($current_user->display_name ?: $current_user->user_login ?: 'DRIVER'));
$hour        = (int) current_time('H');
$greeting    = 'Good evening,';
if ($hour < 12) {
    $greeting = 'Good morning,';
} elseif ($hour < 18) {
    $greeting = 'Good afternoon,';
}

$alert_html = '';
if ($message === 'delivered') {
    $alert_html = '<div class="bdo-alert ok">Delivery marked as delivered.</div>';
} elseif ($message === 'br_saved') {
    $alert_html = '<div class="bdo-alert ok">Basket return saved.</div>';
}

if ($error !== '') {
    $alert_html = '<div class="bdo-alert err">' . esc_html($error) . '</div>';
}

if ($schema_error !== '') {
    $alert_html = '<div class="bdo-alert err">' . esc_html($schema_error) . '</div>';
} elseif (!$driver_where['usable']) {
    $alert_html = '<div class="bdo-alert err">Driver assignment column is missing. Please update the bridge schema before using this page.</div>';
}

$html = <<<'HTML'
<style>
html,body{background:#eef4ef!important}
#wpadminbar,header,footer,.site-header,.site-footer,.elementor-location-header,.elementor-location-footer{display:none!important}
html{margin-top:0!important}
#bdo-driver-app{--main:#0B4A2D;--main-2:#0f6b42;--soft:#eaf5ef;--soft-2:#d7efe2;--ink:#102019;--muted:#617067;--line:#d9e5dd;--danger:#dc2626;min-height:100vh;background:linear-gradient(180deg,#0B4A2D 0,#0B4A2D 210px,#eef4ef 210px,#eef4ef 100%);font-family:Segoe UI,Roboto,Arial,sans-serif;color:var(--ink)}
#bdo-driver-app *{box-sizing:border-box}
.bdo-wrap{max-width:560px;margin:0 auto;min-height:100vh;padding:16px 14px 96px}
.bdo-top{color:#fff;display:flex;justify-content:space-between;align-items:center;gap:12px;padding:4px 2px 16px}
.bdo-brand{font-size:22px;font-weight:900;letter-spacing:-.02em}
.bdo-top-actions{display:flex;align-items:center;gap:8px;flex:0 0 auto}
.bdo-logout{min-height:42px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;font-size:13px;font-weight:900;line-height:1;white-space:nowrap}
.bdo-logout{background:rgba(255,255,255,.16);border:1px solid rgba(255,255,255,.28);color:#fff!important;text-decoration:none!important;padding:0 16px}
.bdo-logout:hover,.bdo-logout:focus{background:rgba(255,255,255,.26)!important;border-color:rgba(255,255,255,.42)!important;color:#fff!important;outline:none}
.bdo-hero,.bdo-card{background:#fff;border:1px solid var(--line);box-shadow:0 10px 26px rgba(10,45,29,.07);overflow:hidden}
.bdo-hero{border-radius:24px;padding:16px;box-shadow:0 18px 40px rgba(7,32,20,.18);border-color:rgba(255,255,255,.8);margin-bottom:12px}
.bdo-card{border-radius:22px;margin-bottom:12px}
.bdo-driver-row{display:flex;justify-content:space-between;gap:12px;align-items:center;margin-bottom:14px}
.bdo-avatar-row{display:flex;gap:11px;align-items:center;min-width:0}
.bdo-avatar{width:48px;height:48px;border-radius:18px;background:var(--soft);display:grid;place-items:center;font-size:25px;flex:0 0 auto}
.bdo-greet{color:var(--muted);font-size:12px;font-weight:700}
.bdo-name{font-size:20px;font-weight:900;letter-spacing:-.02em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bdo-stats{display:grid;grid-template-columns:repeat(3,1fr);border:1px solid var(--line);border-radius:18px;overflow:hidden;background:#fbfdfb}
.bdo-stat{padding:12px 6px;text-align:center;border:0;border-right:1px solid var(--line);background:transparent;cursor:pointer;font-family:inherit}
.bdo-stat:last-child{border-right:0}
.bdo-stat:hover,.bdo-stat.active{background:var(--soft)}
.bdo-stat strong{display:block;font-size:23px;line-height:1;font-weight:950;color:var(--main);letter-spacing:-.03em}
.bdo-stat span{display:block;margin-top:5px;font-size:10.5px;line-height:1.15;color:#46564c;font-weight:800}
.bdo-panel{display:none;animation:bdoFade .16s ease}
.bdo-panel.active{display:block}
@keyframes bdoFade{from{opacity:.5;transform:translateY(4px)}to{opacity:1;transform:none}}
.bdo-card-head{padding:14px 15px;display:flex;justify-content:space-between;align-items:center;gap:10px;border-bottom:1px solid #eef4f0}
.bdo-card-title{font-weight:950;font-size:16px;letter-spacing:-.01em}
.bdo-card-sub{color:var(--muted);font-size:12px;font-weight:700;margin-top:2px}
.bdo-card-body{padding:15px}
.bdo-quick-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:12px}
.bdo-quick{border:1px solid var(--line);background:#fff;border-radius:20px;padding:14px;text-align:left;cursor:pointer;min-height:95px;box-shadow:0 10px 24px rgba(10,45,29,.05);transition:background .12s ease,border-color .12s ease,box-shadow .12s ease,transform .12s ease}
.bdo-quick:hover,.bdo-quick:focus{background:var(--soft)!important;border-color:var(--soft-2)!important;box-shadow:0 12px 26px rgba(11,74,45,.10)!important;outline:none;transform:translateY(-1px)}
.bdo-quick:active{background:var(--soft-2)!important;transform:none}

.bdo-quick-icon{width:38px;height:38px;border-radius:15px;background:var(--soft);display:grid;place-items:center;color:var(--main);font-size:20px;margin-bottom:10px}
.bdo-quick strong{display:block;font-size:14px;font-weight:950;color:var(--ink)}
.bdo-quick span{display:block;color:var(--muted);font-size:11.5px;font-weight:700;margin-top:3px;line-height:1.25}
.bdo-alert{margin-bottom:12px;padding:12px 14px;border-radius:16px;font-weight:850;font-size:13px}
.bdo-alert.ok{background:#dcfce7;color:#14532d;border:1px solid #bbf7d0}
.bdo-alert.err{background:#fff1f2;color:#9f1239;border:1px solid #fecdd3}
.bdo-empty{padding:18px;text-align:center;color:var(--muted);font-weight:700;background:#f8fbf9;border-radius:16px}
.bdo-delivery-list{display:grid;gap:10px}
.bdo-delivery-row,.bdo-active-mini{border:1px solid var(--line);border-radius:18px;padding:13px;background:#fff;box-shadow:0 8px 22px rgba(10,45,29,.045)}
.bdo-delivery-top{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:8px}
.bdo-delivery-title{font-size:15px;font-weight:950;letter-spacing:-.01em}
.bdo-delivery-sub{font-size:12px;color:var(--muted);font-weight:800;margin-top:3px}
.bdo-delivery-metrics,.bdo-job-metrics{display:flex;flex-wrap:wrap;justify-content:center;gap:6px;margin:10px 0}
.bdo-delivery-metrics > span,.bdo-metric{flex:1 1 calc(25% - 6px);max-width:calc(25% - 5px);min-width:92px;background:#f8fbf9;border:1px solid #e6f0ea;border-radius:12px;padding:8px 4px;text-align:center;color:var(--main);font-weight:950;font-size:12px}
.bdo-status-chip{display:inline-flex;align-items:center;gap:6px;padding:7px 10px;border-radius:999px;background:var(--soft);color:var(--main);font-size:11px;font-weight:950}
.bdo-status-chip.wait{background:#fff7ed;color:#b45309}
.bdo-job-main{margin:12px 0}
.bdo-job-main h3{margin:0;font-size:20px;font-weight:950;letter-spacing:-.02em}
.bdo-job-main p{margin:5px 0 0;color:var(--muted);font-size:13px;font-weight:700;line-height:1.35}
.bdo-job-note{margin:8px 0 0;color:#43534a;font-size:12px;font-weight:800;line-height:1.35;background:#f8fbf9;border:1px solid #e6f0ea;border-radius:12px;padding:8px 10px}
.bdo-metric strong{display:block;font-size:18px;font-weight:950;color:var(--main);line-height:1}
.bdo-metric span{display:block;font-size:10.5px;color:var(--muted);font-weight:800;margin-top:5px;background:transparent;border:0;padding:0}
.bdo-btn,.bdo-btn-soft{width:100%;border:0;border-radius:15px;min-height:48px;padding:12px 14px;font-size:15px;font-weight:950;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;text-decoration:none!important;gap:8px;font-family:inherit}
.bdo-btn{background:var(--main);color:#fff!important;box-shadow:0 12px 22px rgba(11,74,45,.18)}
.bdo-btn:hover{background:var(--main-2)}
.bdo-btn:disabled{background:#94a3b8;box-shadow:none;cursor:not-allowed}
.bdo-btn-warn{margin-top:9px;background:#fff7ed!important;color:#9a3412!important;border:1px solid #fed7aa!important;box-shadow:none!important}
.bdo-btn-warn:hover,.bdo-btn-warn:focus{background:#ffedd5!important;color:#7c2d12!important;border-color:#fdba74!important;outline:none}
.bdo-btn-soft{background:var(--soft);color:var(--main)!important;border:1px solid var(--soft-2)}
.bdo-btn-soft:hover,.bdo-btn-soft:focus{background:var(--soft-2)!important;border-color:#b9ddc�o��n�H���������p
N?�8!important;color:var(--main)!important;outline:none}
.bdo-two{display:grid;grid-template-columns:1fr 1fr;gap:9px}
.bdo-field{margin-bottom:12px}
.bdo-field label{display:block;font-size:12px;color:var(--muted);font-weight:900;margin-bottom:5px}
.bdo-input,.bdo-file{width:100%;min-height:46px;border:1px solid #cddbd2;border-radius:14px;padding:10px 12px;background:#fff;font-size:15px;color:var(--ink);font-family:inherit}
.bdo-input:focus{outline:none;border-color:var(--main);box-shadow:0 0 0 3px rgba(11,74,45,.1)}
.bdo-proof-box{background:#f8fbf9;border:1px dashed #aec8b9;border-radius:18px;padding:13px;margin-top:12px}
.bdo-proof-box strong{display:block;color:var(--main);margin-bottom:5px}
.bdo-proof-box p{margin:0 0 10px;color:var(--muted);font-size:12px;font-weight:700}
.bdo-file-hint{margin:8px 0 0;color:var(--muted);font-size:11px;font-weight:800;line-height:1.3}
.bdo-receipt-card{border:1px solid var(--line);border-radius:18px;padding:14px;background:#fff;margin-top:14px;box-shadow:0 8px 22px rgba(10,45,29,.045)}
.bdo-receipt-paper{border:1px solid #d1d5db;background:#fff;padding:16px;color:#111;font-family:Arial,sans-serif}
.bdo-receipt-head{display:flex;justify-content:space-between;align-items:center;gap:12px;border-bottom:2px solid #111;padding-bottom:10px;margin-bottom:12px}
.bdo-receipt-logo{width:176px;height:64px;object-fit:contain;object-position:left center;display:block}
.bdo-receipt-title{text-align:right;font-size:12px;font-weight:900;letter-spacing:.08em}
.bdo-receipt-no{text-align:right;font-size:15px;font-weight:900;margin-top:4px}
.bdo-receipt-info{display:grid;grid-template-columns:1fr 1fr;gap:12px;border-bottom:1px solid #e5e7eb;padding:8px 0 10px}
.bdo-receipt-field{min-width:0}
.bdo-receipt-field span{display:block;font-size:12px;font-weight:800;color:#555;margin-bottom:4px}
.bdo-receipt-field strong{display:block;font-size:15px;font-weight:900;line-height:1.2;word-break:break-word}
.bdo-receipt-qty{font-size:34px;font-weight:950;text-align:center;color:var(--main);padding:18px 0}
.bdo-receipt-proof{margin-top:6px;border:1px dashed #cbd5e1;padding:10px;text-align:center;font-size:12px;font-weight:800;color:#64748b;min-height:58px}
.bdo-receipt-proof img{display:block;width:100%;max-height:330px;object-fit:contain;margin-top:8px}
.bdo-receipt-actions{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:12px}
.bdo-items{display:grid;gap:9px;margin-top:12px}
.bdo-item{border:1px solid #e6f0ea;background:#fff;border-radius:15px;padding:10px}
.bdo-item-name{font-size:14px;font-weight:950;line-height:1.25;margin-bottom:8px;word-break:break-word}
.bdo-item-grid{display:flex;justify-content:center;gap:6px}
.bdo-item-grid div{flex:1 1 0;background:#f8fbf9;border:1px solid #edf4ef;border-radius:11px;padding:7px 4px;text-align:center;min-width:0}
.bdo-item-grid small{display:block;color:var(--muted);font-size:9px;font-weight:900;text-transform:uppercase;line-height:1}
.bdo-item-grid strong{display:block;color:var(--main);font-size:12px;font-weight:950;margin-top:4px;line-height:1.1}
.bdo-search-wrap{position:relative}
.bdo-search-wrap .bdo-input{padding-right:52px}
.bdo-clear{display:none;position:absolute;right:9px;top:50%;transform:translateY(-50%);width:34px;height:34px;border-radius:11px;border:1px solid var(--line);background:#fff;color:var(--muted);font-size:22px;line-height:1;cursor:pointer}
.bdo-clear.show{display:inline-flex;align-items:center;justify-content:center}
.bdo-bottom-nav{position:fixed;left:50%;transform:translateX(-50%);bottom:0;width:100%;max-width:560px;background:rgba(255,255,255,.96);backdrop-filter:blur(12px);border-top:1px solid var(--line);display:grid;grid-template-columns:repeat(3,1fr);padding:8px 8px 12px;z-index:9999;box-shadow:0 -12px 28px rgba(10,45,29,.09)}
.bdo-nav{border:0;background:transparent;color:#53645a;font-size:11px;font-weight:900;min-height:50px;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px}
.bdo-nav i{font-style:normal;font-size:20px}
.bdo-nav:hover,.bdo-nav:focus{color:var(--main)!important;background:var(--soft)!important;border-radius:14px;outline:none}
.bdo-nav.active{color:var(--main)!important;background:var(--soft)!important;border-radius:14px}
.bdo-picker-modal{display:none;position:fixed;inset:0;z-index:99999;align-items:center;justify-content:center;padding:14px}
.bdo-picker-modal.active{display:flex}
.bdo-picker-backdrop{position:absolute;inset:0;background:rgba(5,20,13,.58)}
.bdo-picker-sheet{position:relative;width:100%;max-width:520px;max-height:86vh;background:#fff;border-radius:22px;overflow:hidden;box-shadow:0 24px 60px rgba(0,0,0,.28)}
.bdo-picker-head{padding:14px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:center}
.bdo-picker-title{font-weight:950;font-size:17px}
.bdo-picker-close{
    width:40px;
    height:40px;
    padding:0;
    border:0;
    border-radius:14px;
    background:var(--main);
    color:#fff;
    cursor:pointer;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    font-size:0;
    line-height:1;
}

.bdo-picker-close::before{
    content:"×";
    font-size:30px;
    font-weight:800;
    line-height:1;
    transform:translateY(-1px);
}
.bdo-picker-body{padding:14px}
.bdo-picker-results{max-height:55vh;overflow-y:auto;margin-top:10px}
.bdo-picker-item{display:block;width:100%;text-align:left;background:#fff;border:1px solid var(--line);border-radius:15px;padding:12px;margin-bottom:8px;cursor:pointer}
.bdo-picker-item:hover,.bdo-picker-item:focus{background:var(--soft)!important;border-color:var(--soft-2)!important;outline:none}
.bdo-picker-main{display:block;font-weight:950;color:var(--ink)}
.bdo-picker-note{padding:18px;text-align:center;color:var(--muted);font-weight:800}
#bdoPrintArea{display:none!important}
@media print{html,body{background:#fff!important;width:148mm;min-height:0!important}body *{visibility:hidden!important}#bdoPrintArea,#bdoPrintArea *{visibility:visible!important}#bdoPrintArea{display:block!important;position:absolute!important;left:0;top:0;width:100%!important;max-height:190mm!important;overflow:hidden!important}.bdo-receipt-card{border:0!important;box-shadow:none!important;margin:0!important;padding:0!important}.bdo-receipt-paper{height:188mm!important;overflow:hidden!important}.bdo-receipt-actions,.bdo-bottom-nav,.bdo-top{display:none!important}@page{size:A5 portrait;margin:6mm}}
@media(max-width:390px){.bdo-stat strong{font-size:20px}.bdo-stat span{font-size:9.5px}.bdo-two,.bdo-quick-grid,.bdo-receipt-actions{grid-template-columns:1fr}.bdo-delivery-metrics > span,.bdo-metric{flex-basis:calc(50% - 6px);max-width:calc(50% - 5px);min-width:0}}
</style>

<div id="bdo-driver-app"
     data-ajax-url="__AJAX_URL__"
     data-debtor-nonce="__DEBTOR_NONCE__"
     data-active-jobs="__ACTIVE_JOBS_JSON__"
     data-delivered-jobs="__DELIVERED_JOBS_JSON__"
     data-return-rows="__RETURN_ROWS_JSON__"
     data-basket-receipts="__BASKET_RECEIPTS_JSON__"
     data-selected-basket-receipt-id="__SELECTED_BASKET_RECEIPT_ID__"
     data-last-delivered-customer="__LAST_DELIVERED_CUSTOMER_JSON__"
     data-stats="__STATS_JSON__"
     data-initial-tab="__INITIAL_TAB__"
     data-initial-filter="__INITIAL_FILTER__">
    <div class="bdo-wrap">
        <div class="bdo-top">
            <div class="bdo-brand">BasketDO Driver</div>
            <div class="bdo-top-actions">
                <a class="bdo-logout" href="__LOGOUT_URL__">Logout</a>
            </div>
        </div>

        __ALERT_HTML__

        <div class="bdo-hero">
            <div class="bdo-driver-row">
                <div class="bdo-avatar-row">
                    <div class="bdo-avatar">DO</div>
                    <div>
                        <div class="bdo-greet">__GREETING__</div>
                        <div class="bdo-name">__DRIVER_NAME__</div>
                    </div>
                </div>
            </div>
            <div class="bdo-stats">
                <button type="button" class="bdo-stat" data-stat-filter="received"><strong id="bdoStatReceived">0</strong><span>Out For<br>Delivery</span></button>
                <button type="button" class="bdo-stat" data-stat-filter="delivered"><strong id="bdoStatDelivered">0</strong><span>Delivered<br>Today</span></button>
                <button type="button" class="bdo-stat" data-stat-filter="returns"><strong id="bdoStatReturn">0</strong><span>Basket<br>Returns</span></button>
            </div>
        </div>

        <div class="bdo-panel active" data-panel="home">
            <div class="bdo-quick-grid">
                <button type="button" class="bdo-quick" data-open-panel="deliveries" data-set-filter="received"><div class="bdo-quick-icon">POD</div><strong>Mark Delivered</strong><span>Use after drop-off, with optional POD photo.</span></button>
                <button type="button" class="bdo-quick" data-open-panel="return"><div class="bdo-quick-icon">BR</div><strong>Basket Return</strong><span>Record returned baskets with photo proof.</span></button>
            </div>

            <div class="bdo-card">
                <div class="bdo-card-head">
                    <div><div class="bdo-card-title">Next Delivery</div><div class="bdo-card-sub">Staff-created DO assigned to this driver</div></div>
                </div>
                <div class="bdo-card-body">
                    <div id="bdoHomeActiveMount"></div>
                </div>
            </div>
        </div>

        <div class="bdo-panel" data-panel="deliveries">
            <div class="bdo-card">
                <div class="bdo-card-head">
                    <div><div class="bdo-card-title">Out For Delivery</div><div class="bdo-card-sub">Only DOs assigned to this driver account</div></div>
                </div>
                <div class="bdo-card-body">
                    <div id="bdoDeliveryListMount" class="bdo-delivery-list"></div>
                </div>
            </div>
        </div>

        <div class="bdo-panel" data-panel="active">
            <div class="bdo-card">
                <div class="bdo-card-head">
                    <div><div class="bdo-card-title">Delivery Action</div><div class="bdo-card-sub">Mark delivery completed after drop-off</div></div>
                </div>
                <div class="bdo-card-body">
                    <div id="bdoActiveMount"></div>
                </div>
            </div>
        </div>

        <div class="bdo-panel" data-panel="return">
            <div class="bdo-card">
                <div class="bdo-card-head">
                    <div><div class="bdo-card-title">Basket Return</div><div class="bdo-card-sub">Only tracks basket, not carton</div></div>
                </div>
                <form method="post" enctype="multipart/form-data" class="bdo-card-body" id="bdoBasketReturnForm">
                    <input type="hidden" name="bdo_driver_action" value="basket_return">
                    <input type="hidden" name="bdo_driver_nonce" value="__FORM_NONCE__">
                    <input type="hidden" id="bdo_br_debtor_code" name="br_debtor_code" value="">
                    <input type="hidden" id="bdo_br_debtor_name" name="br_debtor_name" value="">

                    <div class="bdo-field">
                        <label>Customer</label>
                        <div class="bdo-search-wrap">
                            <input type="text" id="bdoBrCustomerInput" class="bdo-input" placeholder="Search customer..." readonly autocomplete="off">
                            <button type="button" class="bdo-clear" id="bdoBrCustomerClear">&times;</button>
                        </div>
                    </div>

                    <div class="bdo-field">
                        <label>Basket Return Qty</label>
                        <input type="number" class="bdo-input" name="br_basket_qty" id="bdoBrQty" min="1" max="9999" step="1" placeholder="Basket qty" required>
                    </div>

                    <div class="bdo-proof-box">
                        <strong>Photo proof optional</strong>
                        <p>Take a photo of the returned baskets when available.</p>
                        <input type="file" class="bdo-file" name="basket_return_proof" accept="image/jpeg,image/png,image/webp" capture="environment">
                        <div class="bdo-file-hint">JPG, PNG, or WebP only. Max 4 MB.</div>
                    </div>

                    <div style="height:12px"></div>
                    <button type="submit" class="bdo-btn">Save Basket Return</button>
                </form>
                <div class="bdo-card-body" id="bdoBasketReceiptMount" style="display:none"></div>
            </div>
        </div>
    </div>

    <div class="bdo-bottom-nav">
        <button type="button" class="bdo-nav active" data-open-panel="home"><i>H</i><span>Home</span></button>
        <button type="button" class="bdo-nav" data-open-panel="deliveries"><i>DO</i><span>Deliveries</span></button>
        <button type="button" class="bdo-nav" data-open-panel="return"><i>BR</i><span>Return</span></button>
    </div>

    <div class="bdo-picker-modal" id="bdoPickerModal" aria-hidden="true">
        <div class="bdo-picker-backdrop" id="bdoPickerBackdrop"></div>
        <div class="bdo-picker-sheet">
            <div class="bdo-picker-head">
                <div class="bdo-picker-title">Select Customer</div>
                <button type="button" class="bdo-picker-close" id="bdoPickerClose">&times;</button>
            </div>
            <div class="bdo-picker-body">
                <input type="text" class="bdo-input" id="bdoPickerSearch" placeholder="Type to search..." autocomplete="off">
                <div class="bdo-picker-results" id="bdoPickerResults"></div>
            </div>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
(function(){
    const root = document.getElementById('bdo-driver-app');
    if (!root || root.dataset.ready === '1') return;
    root.dataset.ready = '1';

    const cfg = {
        ajaxUrl: root.dataset.ajaxUrl,
        debtorNonce: root.dataset.debtorNonce
    };

    let activeJobs = JSON.parse(root.dataset.activeJobs || '[]');
    let deliveredJobs = JSON.parse(root.dataset.deliveredJobs || '[]');
    let returnRows = JSON.parse(root.dataset.returnRows || '[]');
    let basketReceipts = JSON.parse(root.dataset.basketReceipts || '{}');
    let selectedBasketReceiptId = root.dataset.selectedBasketReceiptId || '';
    let lastDeliveredCustomer = JSON.parse(root.dataset.lastDeliveredCustomer || 'null');
    let stats = JSON.parse(root.dataset.stats || '{}');
    let activeJob = activeJobs.length ? activeJobs[0] : null;
    let currentPanel = 'home';
    let deliveryFilter = ['received','delivered','returns'].includes(root.dataset.initialFilter || '') ? root.dataset.initialFilter : 'received';
    let submittingForm = false;
    let brCustomerDefaultDismissed = false;
    let brJsPdfPromise = null;
    const receiptLogoUrl = 'https://website.ipohserver.com/excellentvege/wp-content/uploads/2026/05/Untitled-design-15.png';
    const doCompanyName = 'EXCELLENT VEGE SDN. BHD.';
    const doCompanyAddr = 'No. 45, 47, Complex Pasar Borong, 3rd Miles, Jalan Ipoh, 51200 Kuala Lumpur';
    const doCompanyTel = '017-4373 752 / 016-963 752 / 012-3013 752';

    const $ = id => document.getElementById(id);
    const esc = s => String(s ?? '').replace(/[&<>'"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#039;','"':'&quot;'}[c]));
    const whole = n => { const x = Number(n); return (!Number.isFinite(x) || x < 0) ? 0 : Math.round(x); };
    const fmt = n => String(whole(n));
    const toast = (icon, title, text='') => window.Swal
        ? Swal.fire({toast:true, position:'top-end', icon, title, text, showConfirmButton:false, timer:2300})
        : alert(title + (text ? '\n' + text : ''));
    const confirmModal = async (opts) => {
        if (!window.Swal) {
            return confirm(o�p<өu���������pN4
N?�pts.text || opts.title || 'Continue?');
        }

        const result = await Swal.fire({
            icon: opts.icon || 'question',
            title: opts.title || 'Confirm',
            text: opts.text || '',
            showCancelButton: true,
            confirmButtonText: opts.confirmButtonText || 'Confirm',
            cancelButtonText: opts.cancelButtonText || 'Cancel',
            confirmButtonColor: opts.confirmButtonColor || '#0B4A2D',
            cancelButtonColor: '#64748b',
            reverseButtons: true
        });

        return result.isConfirmed;
    };

    function setStats(s) {
        $('bdoStatReceived').textContent = s.received || 0;
        $('bdoStatDelivered').textContent = s.delivered || 0;
        $('bdoStatReturn').textContent = s.returns || 0;
    }

    function updateStatHighlight() {
        document.querySelectorAll('[data-stat-filter]').forEach(btn => {
            btn.classList.toggle('active', currentPanel === 'deliveries' && btn.dataset.statFilter === deliveryFilter);
        });
    }

    function openPanel(name) {
        currentPanel = name;
        document.querySelectorAll('#bdo-driver-app .bdo-panel').forEach(p => p.classList.toggle('active', p.dataset.panel === name));
        document.querySelectorAll('#bdo-driver-app .bdo-nav').forEach(b => b.classList.toggle('active', b.dataset.openPanel === name));
        updateStatHighlight();
        if (name === 'return') applyLastDeliveredCustomerDefault();
        window.scrollTo({top:0, behavior:'smooth'});
    }

function statusLabel(job) {
    const st = String(job?.deliveryStatus || '').toUpperCase();
    if (st === 'DELIVERED') return 'DELIVERED';
    if (st === 'HIDDEN') return 'HIDDEN';
    return 'OUT FOR DELIVERY';
}

    function packInfo(line) {
        const packType = String(line?.packType || '').toUpperCase();
        const basket = whole(line?.basket || 0);
        const carton = whole(line?.carton || 0);

        if (packType === 'CARTON' || carton > 0) {
            return {label:'Carton', qty:carton || whole(line?.qty || 0)};
        }

        return {label:'Basket', qty:basket || whole(line?.qty || 0)};
    }

    function summaryMetrics(summary) {
        const chips = [
            `<div class="bdo-metric"><strong>${fmt(summary?.items)}</strong><span>Items</span></div>`
        ];

        if (whole(summary?.baskets) > 0) {
            chips.push(`<div class="bdo-metric"><strong>${fmt(summary.baskets)}</strong><span>Baskets</span></div>`);
        }

        if (whole(summary?.cartons) > 0) {
            chips.push(`<div class="bdo-metric"><strong>${fmt(summary.cartons)}</strong><span>Cartons</span></div>`);
        }

        if (whole(summary?.kg) > 0) {
            chips.push(`<div class="bdo-metric"><strong>${fmt(summary.kg)}</strong><span>KG</span></div>`);
        }

        return chips.join('');
    }

    function renderItems(job) {
        const lines = job?.summary?.lines || [];
        if (!lines.length) return '<div class="bdo-empty">No item detail found.</div>';
        return `<div class="bdo-items">${lines.map(l => {
            const pack = packInfo(l);
            return `
                <div class="bdo-item">
                    <div class="bdo-item-name">${esc(l.itemName || 'Item')}</div>
                    <div class="bdo-item-grid">
                        <div><small>${esc(pack.label)}</small><strong>${fmt(pack.qty)}</strong></div>
                        <div><small>KG</small><strong>${fmt(l.kg)}</strong></div>
                    </div>
                </div>`;
        }).join('')}</div>`;
    }

    function receiptFileName(receipt) {
        const ref = String(receipt?.sourceRef || receipt?.id || 'basket-return').replace(/[^A-Za-z0-9_-]/g, '-');
        return `Basket-Return-${ref}.pdf`;
    }

    function doFileName(job) {
        const ref = String(job?.docNo || job?.id || 'DO').replace(/[^A-Za-z0-9_-]/g, '-');
        return `Delivery-Order-${ref}.pdf`;
    }

    function receiptHtml(receipt, includeActions=true) {
        if (!receipt || !receipt.id) return '';

        return `
            <div class="bdo-receipt-card" data-bdo-receipt-id="${esc(receipt.id)}">
                <div class="bdo-receipt-paper">
                    <div class="bdo-receipt-head">
                        <div>
                            <img class="bdo-receipt-logo" src="${receiptLogoUrl}" alt="Company logo">
                        </div>
                        <div>
                            <div class="bdo-receipt-title">BASKET RETURN</div>
                            <div class="bdo-receipt-no">${esc(receipt.sourceRef || ('BR-' + receipt.id))}</div>
                        </div>
                    </div>
                    <div class="bdo-receipt-info">
                        <div class="bdo-receipt-field"><span>Customer</span><strong>${esc(receipt.customerName || 'Customer')}</strong></div>
                        <div class="bdo-receipt-field"><span>Driver</span><strong>${esc(receipt.driverName || '')}</strong></div>
                    </div>
                    <div class="bdo-receipt-qty">${fmt(receipt.qty)} BASKETS</div>
                    <div class="bdo-receipt-proof">
                        ${receipt.proofUrl ? `Image Proof<img src="${esc(receipt.proofUrl)}" alt="Basket return proof">` : 'No image proof uploaded'}
                    </div>
                </div>
                ${includeActions ? `
                    <div class="bdo-receipt-actions">
                        <button type="button" class="bdo-btn-soft" data-print-basket-receipt="${esc(receipt.id)}">Print</button>
                        <button type="button" class="bdo-btn" data-share-basket-receipt="${esc(receipt.id)}">Share PDF</button>
                    </div>` : ''}
            </div>`;
    }

    function renderSelectedBasketReceipt() {
        const mount = $('bdoBasketReceiptMount');
        if (!mount) return;

        const receipt = selectedBasketReceiptId ? basketReceipts[selectedBasketReceiptId] : null;
        if (!receipt) {
            mount.style.display = 'none';
            mount.innerHTML = '';
            return;
        }

        mount.style.display = '';
        mount.innerHTML = receiptHtml(receipt, true);
    }

    function activeCardHtml(job, compact=false) {
        if (!job || !job.id) {
            return '<div class="bdo-empty">No delivery task yet. It will appear after staff assigns a DO for today or an earlier delivery date.</div>';
        }

        const st = String(job.deliveryStatus || '').toUpperCase();
        const canDeliver = st !== 'DELIVERED';
        const docText = job.docNo || ('Job #' + job.id);
        const location = String(job.location || '').trim();
        const locationText = location && location.toUpperCase() !== 'HQ' ? `Location: ${esc(location)}` : '';
        const addressText = job.address ? `Address: ${esc(job.address)}` : '';

        let html = `
            <div class="bdo-active-mini">
                <span class="bdo-status-chip">${esc(statusLabel(job))}</span>
                <div class="bdo-job-main">
                    <h3>${esc(job.customerName || 'Customer')}</h3>
                    <p>${esc(docText)}</p>
                    ${locationText || addressText ? `<div class="bdo-job-note">${locationText}${locationText && addressText ? '<br>' : ''}${addressText}</div>` : ''}
                </div>
                <div class="bdo-job-metrics">
                    ${summaryMetrics(job.summary)}
                </div>
                ${compact ? '' : renderItems(job)}`;

        if (compact) {
            html += `<div style="height:10px"></div><button type="button" class="bdo-btn" data-select-delivery="${job.id}">Open Delivery</button>`;
        } else if (canDeliver) {
            html += `
                <div style="height:12px"></div>
                <form method="post" enctype="multipart/form-data" class="bdo-proof-box bdo-action-form" id="bdoCompleteDeliveryForm">
                    <input type="hidden" name="bdo_driver_action" value="complete_delivery">
                    <input type="hidden" name="bdo_driver_nonce" value="__FORM_NONCE__">
                    <input type="hidden" name="job_id" value="${job.id}">
                    <strong>Proof of delivery optional</strong>
                    <p>Take photo after vegetables are dropped, or continue without photo if needed.</p>
                    <input type="file" class="bdo-file" name="delivery_proof" accept="image/jpeg,image/png,image/webp" capture="environment">
                    <div class="bdo-file-hint">JPG, PNG, or WebP only. Max 4 MB.</div>
                    <div style="height:12px"></div>
                    <button type="submit" class="bdo-btn" data-bdo-loading-text="Saving...">Mark as Delivered</button>
                </form>`;
        } else {
            html += '<div class="bdo-alert ok" style="margin:0;">No driver action needed.</div>';
        }

        html += '</div>';
        return html;
    }

    function deliveryListCard(job) {
        const st = String(job.deliveryStatus || '').toUpperCase();
        const isDelivered = st === 'DELIVERED';
        const docText = job.docNo || ('Job #' + job.id);
        const subText = job.address ? `${docText} | ${job.address}` : docText;
        return `
            <div class="bdo-delivery-row">
                <div class="bdo-delivery-top">
                    <div>
                        <div class="bdo-delivery-title">${esc(job.customerName || 'Customer')}</div>
                        <div class="bdo-delivery-sub">${esc(subText)}</div>
                    </div>
                    <span class="bdo-status-chip">${esc(statusLabel(job))}</span>
                </div>
                <div class="bdo-delivery-metrics">
                    ${summaryMetrics(job.summary)}
                </div>
                ${isDelivered ? `
                    <div class="bdo-two">
                        <button type="button" class="bdo-btn-soft" data-download-do="${esc(job.id)}">Download DO</button>
                        <button type="button" class="bdo-btn" data-share-do="${esc(job.id)}">Share WhatsApp</button>
                    </div>` : `<button type="button" class="bdo-btn" data-select-delivery="${job.id}">Take POD</button>`}
            </div>`;
    }

    function returnListCard(row) {
        return `
            <div class="bdo-delivery-row">
                <div class="bdo-delivery-top">
                    <div>
                        <div class="bdo-delivery-title">${esc(row.customerName || 'Customer')}</div>
                        <div class="bdo-delivery-sub">Basket return record</div>
                    </div>
                    <span class="bdo-status-chip">RETURN</span>
                </div>
                <div class="bdo-delivery-metrics" style="grid-template-columns:1fr">
                    <span>${fmt(row.qty)} Baskets</span>
                </div>
                <div class="bdo-two">
                    <button type="button" class="bdo-btn-soft" data-print-basket-receipt="${esc(row.id)}">Print</button>
                    <button type="button" class="bdo-btn" data-share-basket-receipt="${esc(row.id)}">Share PDF</button>
                </div>
            </div>`;
    }

    function renderDeliveryList() {
        const mount = $('bdoDeliveryListMount');
        if (!mount) return;

        updateStatHighlight();

        let title = 'Out for delivery';
        const today = new Date().toISOString().split('T')[0];
        let rows = activeJobs.filter(j => {
            const st = String(j.deliveryStatus || '').toUpperCase();
            return st !== 'DELIVERED';
        });        let renderer = deliveryListCard;

        if (deliveryFilter === 'delivered') {
            title = 'Delivered today';
            rows = deliveredJobs;
        } else if (deliveryFilter === 'returns') {
            title = 'Basket returns today';
            rows = returnRows;
            renderer = returnListCard;
        }

        if (!rows.length) {
            mount.innerHTML = `<div class="bdo-empty">No record found. ${esc(title)}.</div>`;
            return;
        }

        mount.innerHTML = `<div style="font-size:13px;color:#617067;font-weight:900;margin-bottom:10px">${esc(title)}</div>` + rows.map(renderer).join('');
    }

    function renderActive() {
        $('bdoHomeActiveMount').innerHTML = activeCardHtml(activeJob, true);
        $('bdoActiveMount').innerHTML = activeCardHtml(activeJob, false);
        renderDeliveryList();
        renderSelectedBasketReceipt();
    }

    function loadJsPdf() {
        if (window.jspdf && window.jspdf.jsPDF) {
            return Promise.resolve(window.jspdf.jsPDF);
        }

        if (brJsPdfPromise) {
            return brJsPdfPromise;
        }

        brJsPdfPromise = new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
            script.onload = () => window.jspdf && window.jspdf.jsPDF ? resolve(window.jspdf.jsPDF) : reject(new Error('PDF library did not load.'));
            script.onerror = () => reject(new Error('PDF library could not be loaded.'));
            document.head.appendChild(script);
        });

        return brJsPdfPromise;
    }

    function loadProofImage(url) {
        if (!url) return Promise.resolve(null);

        return new Promise(resolve => {
            const img = new Image();
            img.crossOrigin = 'anonymous';
            img.onload = () => resolve(img);
            img.onerror = () => resolve(null);
            img.src = url;
        });
    }

    function drawText(ctx, text, x, y, size=18, color='#111', weight='400', align='left') {
        ctx.fillStyle = color;
        ctx.font = `${weight} ${size}px Arial, sans-serif`;
        ctx.textAlign = align;
        ctx.textBaseline = 'top';
        ctx.fillText(String(text || ''), x, y);
    }

    function drawWrappedText(ctx, text, x, y, maxWidth, lineHeight, size=18, color='#111', weight='400') {
        const words = String(text || '').split(/\s+/).filter(Boolean);
        let line = '';

        words.forEach(word => {
            const testLine = line ? `${line} ${word}` : word;
            if (ctx.measureText(testLine).width > maxWidth && line) {
                drawText(ctx, line, x, y, size, color, weight);
                line = word;
                y += lineHeight;
            } else {
                line = testLine;
            }
        });

        if (line) drawText(ctx, line, x, y, size, color, weight);
        return y + lineHeight;
    }

    function drawCanvasImageContained(ctx, image, x, y, maxW, maxH) {
        if (!image) return;

        const ratio = Math.min(maxW / image.width, maxH / image.height);
        const imgW = image.width * ratio;
        const imgH = image.height * ratio;
        ctx.drawImage(image, x + (maxW - imgW) / 2, y + (maxH - imgH) / 2, imgW, imgH);
    }

    function doCanvasText(ctx, text, x, y, size, color='#111111', weight='400', align='left') {
        ctx.fillStyle = color;
        ctx.font = `${weight} ${size}px Arial, Helvetica, sans-serif`;
        ctx.textAlign = align;
        ctx.textBaseline = 'alphabetic';
        ctx.fillText(String(text || ''), x, y);
    }

    function doCanvasLine(ctx, x1, y1, x2, y2, color='#333333', width=1) {
        ctx.strokeStyle = color;
        ctx.lineWidth = width;
        ctx.beginPath();
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.stroke();
    }

    function doCanvasRect(ctx, x, y, width, height, color='#333333', lineWidth=1) {
        ctx.strokeStyle = color;
        ctx.lineWidth = lineWidth;
        ctx.strokeRect(x, y, width, height);
    }

    function doCanvasFillRect(ctx, x, y, width, height, color) {
        ctx.fillStyle = color;�pN4P�"���������p��
N?�
        ctx.fillRect(x, y, width, height);
    }

    function doCanvasWrap(ctx, text, x, y, maxWidth, lineHeight, size=18, color='#111111', weight='400', maxLines=2) {
        let words = String(text || '').split(/\s+/).filter(Boolean);
        let line = '';
        let lines = [];

        ctx.fillStyle = color;
        ctx.font = `${weight} ${size}px Arial, Helvetica, sans-serif`;
        ctx.textAlign = 'left';
        ctx.textBaseline = 'alphabetic';

        words.forEach(word => {
            const test = line ? `${line} ${word}` : word;
            if (ctx.measureText(test).width > maxWidth && line !== '') {
                lines.push(line);
                line = word;
            } else {
                line = test;
            }
        });

        if (line) lines.push(line);
        if (maxLines && lines.length > maxLines) {
            lines = lines.slice(0, maxLines);
            while (lines[lines.length - 1] && ctx.measureText(lines[lines.length - 1] + '...').width > maxWidth) {
                lines[lines.length - 1] = lines[lines.length - 1].slice(0, -1);
            }
            lines[lines.length - 1] += '...';
        }

        lines.forEach((value, idx) => ctx.fillText(value, x, y + (idx * lineHeight)));
    }

    function doCanvasCheckbox(ctx, x, y, checked) {
        doCanvasRect(ctx, x, y, 14, 14, '#222222', 1.5);
        if (checked) {
            doCanvasText(ctx, '\u2713', x + 1, y + 13, 21, '#111111', '700');
        }
    }

    function deliveryPdfData(job) {
        const lines = Array.isArray(job?.summary?.lines) ? job.summary.lines : [];
        return {
            companyName: doCompanyName,
            companyAddr: doCompanyAddr,
            companyTel: doCompanyTel,
            docNo: job?.docNo || '',
            customerCode: job?.customerCode || '',
            customerName: job?.customerName || 'Customer',
            displayDate: job?.displayDate || '',
            remark: '',
            lines: lines.map(line => {
                const packType = String(line?.packType || '').toUpperCase();
                const isCtn = packType === 'CARTON' || packType === 'CTN' || whole(line?.carton) > 0;
                const isBsk = packType === 'BASKET' || packType === 'BSK' || (!isCtn && whole(line?.basket) > 0);
                return {
                    qty: fmt(isCtn ? line?.carton : (isBsk ? line?.basket : line?.qty)),
                    kg: fmt(line?.unitKg || line?.kg),
                    description: line?.itemName || line?.itemCode || '',
                    isCtn,
                    isBsk,
                    totalKg: fmt(line?.totalKg || line?.kg)
                };
            }),
            totalCtn: fmt(job?.summary?.cartons),
            totalBsk: fmt(job?.summary?.baskets),
            totalKg: fmt(job?.summary?.kg)
        };
    }

    function makeDeliveryOrderCanvas(job, logoImage=null) {
        const data = deliveryPdfData(job);
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        const lines = Array.isArray(data.lines) ? data.lines : [];
        const pageX = 106;
        const pageY = 58;
        const pageW = 1028;
        const pageH = 1638;
        const tableX = 160;
        const tableY = 360;
        const rowH = 47;
        const col = [tableX, tableX + 105, tableX + 210, tableX + 680, tableX + 830, tableX + 930];
        const minRows = Math.max(14, lines.length);

        canvas.width = 1240;
        canvas.height = 1754;
        ctx.imageSmoothingEnabled = true;
        ctx.imageSmoothingQuality = 'high';

        doCanvasFillRect(ctx, 0, 0, canvas.width, canvas.height, '#ffffff');
        doCanvasFillRect(ctx, pageX, pageY, pageW, pageH, '#ffffff');
        doCanvasRect(ctx, pageX, pageY, pageW, pageH, '#d1d5db', 1);

        if (!logoImage) {
            doCanvasText(ctx, 'Vege', pageX + 86, pageY + 154, 44, '#164f38', '700');
        } else {
            drawCanvasImageContained(ctx, logoImage, pageX + 44, pageY + 74, 180, 118);
        }
        doCanvasText(ctx, data.companyName, pageX + 240, pageY + 125, 38, '#0f172a', '900');
        doCanvasText(ctx, data.companyAddr, pageX + 242, pageY + 157, 13, '#111111', '400');
        doCanvasText(ctx, 'H/P: ' + data.companyTel, pageX + 242, pageY + 181, 13, '#111111', '700');

        doCanvasFillRect(ctx, pageX + 785, pageY + 36, 230, 36, '#444444');
        doCanvasText(ctx, 'DELIVERY ORDER', pageX + 900, pageY + 62, 18, '#ffffff', '700', 'center');
        doCanvasText(ctx, 'No', pageX + 805, pageY + 190, 20, '#111111', '400');
        doCanvasText(ctx, data.docNo, pageX + 835, pageY + 190, 27, '#ef4444', '700');

        doCanvasText(ctx, 'Customer', pageX + 63, pageY + 250, 18, '#111111', '700');
        doCanvasText(ctx, data.customerName + (data.customerCode ? ' (' + data.customerCode + ')' : ''), pageX + 170, pageY + 250, 18, '#111111', '400');
        doCanvasLine(ctx, pageX + 150, pageY + 260, pageX + 650, pageY + 260, '#666666', 1);
        doCanvasText(ctx, 'Date', pageX + 770, pageY + 250, 18, '#111111', '700');
        doCanvasText(ctx, data.displayDate, pageX + 835, pageY + 250, 18, '#111111', '400');
        doCanvasLine(ctx, pageX + 830, pageY + 260, pageX + 980, pageY + 260, '#666666', 1);

        doCanvasRect(ctx, tableX, tableY, col[5] - col[0], rowH * (minRows + 1), '#333333', 1.2);
        for (let i = 1; i < col.length - 1; i++) doCanvasLine(ctx, col[i], tableY, col[i], tableY + rowH * (minRows + 1), '#333333', 1);
        for (let i = 1; i <= minRows + 1; i++) doCanvasLine(ctx, tableX, tableY + rowH * i, col[5], tableY + rowH * i, '#333333', 1);

        doCanvasText(ctx, '\u6570\u91cf', tableX + 55, tableY + 20, 15, '#111111', '700', 'center');
        doCanvasText(ctx, 'Quantity', tableX + 55, tableY + 39, 13, '#111111', '700', 'center');
        doCanvasText(ctx, '\u516c\u65a4', col[1] + 55, tableY + 20, 15, '#111111', '700', 'center');
        doCanvasText(ctx, 'Kg', col[1] + 55, tableY + 39, 13, '#111111', '700', 'center');
        doCanvasText(ctx, '\u8d27\u7269\u540d\u79f0', col[2] + 235, tableY + 20, 15, '#111111', '700', 'center');
        doCanvasText(ctx, 'Description', col[2] + 235, tableY + 39, 13, '#111111', '700', 'center');
        doCanvasText(ctx, '\u7bb1 / \u7bee', col[3] + 75, tableY + 20, 15, '#111111', '700', 'center');
        doCanvasText(ctx, 'Box / Basket', col[3] + 75, tableY + 39, 13, '#111111', '700', 'center');
        doCanvasText(ctx, '\u603b\u516c\u65a4', col[4] + 55, tableY + 20, 15, '#111111', '700', 'center');
        doCanvasText(ctx, 'Total Kg', col[4] + 55, tableY + 39, 13, '#111111', '700', 'center');

        for (let i = 0; i < minRows; i++) {
            const y = tableY + rowH * (i + 1);
            const item = lines[i] || {};
            doCanvasText(ctx, item.qty || '', tableX + 55, y + 30, 18, '#111111', '400', 'center');
            doCanvasText(ctx, item.kg || '', col[1] + 55, y + 30, 18, '#111111', '400', 'center');
            doCanvasWrap(ctx, item.description || '', col[2] + 10, y + 22, 450, 18, 18, '#111111', '400', 2);
            doCanvasCheckbox(ctx, col[3] + 25, y + 16, !!item.isCtn);
            doCanvasText(ctx, 'Ctn', col[3] + 43, y + 29, 14, '#111111', '400');
            doCanvasCheckbox(ctx, col[3] + 86, y + 16, !!item.isBsk);
            doCanvasText(ctx, 'Bsk', col[3] + 104, y + 29, 14, '#111111', '400');
            doCanvasText(ctx, item.totalKg || '', col[4] + 55, y + 30, 18, '#111111', '400', 'center');
        }

        const afterTableY = tableY + rowH * (minRows + 1) + 35;
        doCanvasText(ctx, 'We Do The EXCELLENT Way', tableX, afterTableY + 25, 23, '#111111', '700');

        const totalX = pageX + 705;
        const totalY = afterTableY;
        const totalRows = [
            ['\u603b\u7bb1', 'Total Ctn', data.totalCtn || ''],
            ['\u603b\u7bee', 'Total Bsk', data.totalBsk || ''],
            ['\u603b\u516c\u65a4', 'Total Kg', data.totalKg || '']
        ];
        totalRows.forEach((row, idx) => {
            doCanvasText(ctx, row[0], totalX, totalY + 18 + idx * 48, 18, '#111111', '700', 'right');
            doCanvasText(ctx, row[1], totalX, totalY + 38 + idx * 48, 16, '#111111', '400', 'right');
            doCanvasFillRect(ctx, totalX + 20, totalY + 4 + idx * 48, 100, 37, 'rgba(255,255,255,0.25)');
            doCanvasRect(ctx, totalX + 20, totalY + 4 + idx * 48, 100, 37, '#333333', 1);
            doCanvasText(ctx, row[2], totalX + 70, totalY + 30 + idx * 48, 18, '#111111', '700', 'center');
        });

        doCanvasLine(ctx, tableX, pageY + pageH - 140, tableX + 260, pageY + pageH - 140, '#555555', 1);
        doCanvasLine(ctx, pageX + pageW - 420, pageY + pageH - 140, pageX + pageW - 160, pageY + pageH - 140, '#555555', 1);
        doCanvasText(ctx, '\u7ecf\u624b\u4eba Issued by', tableX, pageY + pageH - 110, 17, '#111111', '400');
        doCanvasText(ctx, '\u6536\u8d27\u4eba Received by', pageX + pageW - 420, pageY + pageH - 110, 17, '#111111', '400');

        return canvas;
    }

    function makeDeliveryProofCanvas(job, proofImage=null) {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        const pageX = 106;
        const pageY = 58;
        const pageW = 1028;
        const pageH = 1638;

        canvas.width = 1240;
        canvas.height = 1754;
        doCanvasFillRect(ctx, 0, 0, canvas.width, canvas.height, '#ffffff');
        doCanvasFillRect(ctx, pageX, pageY, pageW, pageH, '#ffffff');
        doCanvasRect(ctx, pageX, pageY, pageW, pageH, '#d1d5db', 1);

        doCanvasText(ctx, 'Proof of Delivery', pageX + 58, pageY + 105, 36, '#111111', '700');
        doCanvasText(ctx, doCompanyName, pageX + 58, pageY + 142, 18, '#111111', '400');
        doCanvasText(ctx, 'DO No: ' + (job?.docNo || ''), pageX + pageW - 60, pageY + 100, 18, '#111111', '700', 'right');
        doCanvasText(ctx, 'Customer: ' + (job?.customerName || ''), pageX + pageW - 60, pageY + 132, 18, '#111111', '400', 'right');
        doCanvasText(ctx, 'Date: ' + (job?.displayDate || ''), pageX + pageW - 60, pageY + 164, 18, '#111111', '400', 'right');
        doCanvasLine(ctx, pageX + 58, pageY + 190, pageX + pageW - 58, pageY + 190, '#333333', 3);
        doCanvasRect(ctx, pageX + 58, pageY + 240, pageW - 116, 1210, '#333333', 1.5);
        if (proofImage) {
            drawCanvasImageContained(ctx, proofImage, pageX + 90, pageY + 280, pageW - 180, 1120);
        } else {
            doCanvasText(ctx, 'No proof of delivery image uploaded yet.', pageX + pageW / 2, pageY + 850, 24, '#555555', '700', 'center');
        }
        doCanvasLine(ctx, pageX + 58, pageY + pageH - 120, pageX + 410, pageY + pageH - 120, '#555555', 1);
        doCanvasLine(ctx, pageX + pageW - 410, pageY + pageH - 120, pageX + pageW - 58, pageY + pageH - 120, '#555555', 1);
        doCanvasText(ctx, 'Driver / Issued by', pageX + 58, pageY + pageH - 90, 17, '#111111', '400');
        doCanvasText(ctx, 'Customer / Received by', pageX + pageW - 410, pageY + pageH - 90, 17, '#111111', '400');

        return canvas;
    }

    function buildDeliveryOrderPdf(job) {
        return Promise.all([loadJsPdf(), loadProofImage(receiptLogoUrl), loadProofImage(job?.proofUrl || '')])
            .then(([jsPDF, logoImage, proofImage]) => {
                const pdf = new jsPDF({orientation:'portrait', unit:'mm', format:'a5'});
                pdf.addImage(makeDeliveryOrderCanvas(job, logoImage).toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, 148, 210);
                pdf.addPage();
                pdf.addImage(makeDeliveryProofCanvas(job, proofImage).toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, 148, 210);
                return pdf.output('blob');
            });
    }

    function makeReceiptCanvas(receipt, proofImage=null, logoImage=null) {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = 1240;
        canvas.height = 1754;

        ctx.fillStyle = '#ffffff';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.strokeStyle = '#111111';
        ctx.lineWidth = 2;
        ctx.strokeRect(82, 70, 1076, 1614);

        if (logoImage) {
            drawCanvasImageContained(ctx, logoImage, 138, 112, 360, 128);
        } else {
            drawText(ctx, 'BASKET RETURN', 140, 130, 30, '#111', '900');
        }
        drawText(ctx, 'BASKET RETURN', 1100, 130, 24, '#111', '900', 'right');
        drawText(ctx, receipt.sourceRef || ('BR-' + receipt.id), 1100, 172, 22, '#111', '900', 'right');

        ctx.strokeStyle = '#111111';
        ctx.lineWidth = 4;
        ctx.beginPath();
        ctx.moveTo(140, 278);
        ctx.lineTo(1100, 278);
        ctx.stroke();

        let y = 350;
        drawText(ctx, 'Customer', 160, y, 22, '#555', '700');
        drawWrappedText(ctx, receipt.customerName || 'Customer', 160, y + 34, 410, 30, 24, '#111', '800');
        drawText(ctx, 'Driver', 660, y, 22, '#555', '700');
        drawWrappedText(ctx, receipt.driverName || '', 660, y + 34, 410, 30, 24, '#111', '800');
        y += 105;
        ctx.strokeStyle = '#e5e7eb';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(160, y);
        ctx.lineTo(1080, y);
        ctx.stroke();

        drawText(ctx, `${fmt(receipt.qty)} BASKETS`, 620, y + 54, 74, '#111', '900', 'center');
        y += 210;

        ctx.strokeStyle = '#cbd5e1';
        ctx.setLineDash([12, 10]);
        ctx.strokeRect(160, y, 920, 560);
        ctx.setLineDash([]);

        if (proofImage) {
            drawText(ctx, 'Image Proof', 620, y + 28, 22, '#334155', '800', 'center');
            drawCanvasImageContained(ctx, proofImage, 210, y + 82, 820, 420);
        } else {
            drawText(ctx, 'No image proof uploaded', 620, y + 255, 28, '#64748b', '800', 'center');
        }

        return canvas;
    }

    function buildBasketReceiptPdf(receipt) {
        return Promise.all([loadJsPdf(), loadProofImage(receipt.proofUrl), loadProofImage(receiptLogoUrl)])
            .then(([jsPDF, proofImage, logoImage]) => {
                const pdf = new jsPDF({orientation:'portrait', unit:'mm', format:'a5'});
                const canvas = makeReceiptCanvas(receipt, proofImage, logoImage);
                pdf.addImage(canvas.toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, 148, 210);
                return pdf.output('blob');
            });
    }

    function downloadBlob(blob, fileName) {
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = fileName;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        setTimeout(() => URL.revokeObjectURL(url), 1000);
    }

    function printBasketReceipt(id) {
        const receipt = basketReceipts[String(id)];
        if (!receipt) {
            toast('error', 'Receipt not found');
            return;
        }

        buildBasketReceiptPdf(receipt)
            .then(blob => {
                const fileName = receiptFileName(receipt);
                const url = URL.createObjectURL(blob);
                const opened = window.open(url, '_blank', 'noopener');

                if (!opened) {
                    downloadBlob(blob, fileName);
                    toast('info', 'PDF downloaded', 'Open the downloaded PDF to print or share.');
                }

                setTimeout(() => URL.revokeObjectURL(url), 60000);
            })
            .catch(() => {
                toast('error', 'Unable to prepare PDF', 'Please try again.');
            });
    }

    function shareBasketReceipt(id, button) {
        const receipt = basketReceipts[String(id)];
        if (!receipt) {
            toast('error', 'Receipt not found');
            return;
        }

        if (!navigator.share) {
            toast('error', 'Sharing not supported', 'Print or download the PDF, �p��r9$���������p��
N5y����then attach it in WhatsApp.');
            return;
        }

        const originalText = button ? button.textContent : '';
        if (button) {
            button.disabled = true;
            button.textContent = 'Preparing PDF...';
        }

        buildBasketReceiptPdf(receipt)
            .then(blob => {
                const fileName = receiptFileName(receipt);
                const file = new File([blob], fileName, {type:'application/pdf'});
                if (!navigator.canShare || !navigator.canShare({files:[file]})) {
                    downloadBlob(blob, fileName);
                    toast('info', 'PDF downloaded', 'Attach the downloaded PDF in WhatsApp.');
                    return null;
                }

                return navigator.share({
                    title: fileName.replace(/\.pdf$/i, ''),
                    text: 'Basket Return PDF',
                    files: [file]
                });
            })
            .catch(error => {
                if (error && error.name === 'AbortError') return;
                toast('error', 'Unable to prepare PDF', 'Please print or save PDF, then share it in WhatsApp.');
            })
            .finally(() => {
                if (button) {
                    button.disabled = false;
                    button.textContent = originalText || 'Share PDF';
                }
            });
    }

    function findDeliveredJob(id) {
        return deliveredJobs.find(job => String(job.id) === String(id)) || null;
    }

    function downloadDeliveryOrder(id, button) {
        const job = findDeliveredJob(id);
        if (!job) {
            toast('error', 'Delivery order not found');
            return;
        }

        const originalText = button ? button.textContent : '';
        if (button) {
            button.disabled = true;
            button.textContent = 'Preparing PDF...';
        }

        buildDeliveryOrderPdf(job)
            .then(blob => {
                downloadBlob(blob, doFileName(job));
            })
            .catch(() => {
                toast('error', 'Unable to prepare DO PDF', 'Please try again.');
            })
            .finally(() => {
                if (button) {
                    button.disabled = false;
                    button.textContent = originalText || 'Download DO';
                }
            });
    }

    function shareDeliveryOrder(id, button) {
        const job = findDeliveredJob(id);
        if (!job) {
            toast('error', 'Delivery order not found');
            return;
        }

        if (!navigator.share) {
            toast('error', 'Sharing not supported', 'Download the DO PDF, then attach it in WhatsApp.');
            return;
        }

        const originalText = button ? button.textContent : '';
        if (button) {
            button.disabled = true;
            button.textContent = 'Preparing PDF...';
        }

        buildDeliveryOrderPdf(job)
            .then(blob => {
                const fileName = doFileName(job);
                const file = new File([blob], fileName, {type:'application/pdf'});
                if (!navigator.canShare || !navigator.canShare({files:[file]})) {
                    downloadBlob(blob, fileName);
                    toast('info', 'PDF downloaded', 'Attach the downloaded DO PDF in WhatsApp.');
                    return null;
                }

                return navigator.share({
                    title: fileName.replace(/\.pdf$/i, ''),
                    text: 'Delivery Order PDF',
                    files: [file]
                });
            })
            .catch(error => {
                if (error && error.name === 'AbortError') return;
                toast('error', 'Unable to share DO PDF', 'Download the DO PDF, then share it in WhatsApp.');
            })
            .finally(() => {
                if (button) {
                    button.disabled = false;
                    button.textContent = originalText || 'Share WhatsApp';
                }
            });
    }

    document.addEventListener('click', function(e) {
        const panelBtn = e.target.closest('[data-open-panel]');
        if (panelBtn && root.contains(panelBtn)) {
            e.preventDefault();
            if (panelBtn.dataset.setFilter) deliveryFilter = panelBtn.dataset.setFilter;
            openPanel(panelBtn.dataset.openPanel);
            renderDeliveryList();
            return;
        }

        const statBtn = e.target.closest('[data-stat-filter]');
        if (statBtn && root.contains(statBtn)) {
            deliveryFilter = statBtn.dataset.statFilter || 'assigned';
            openPanel(deliveryFilter === 'returns' ? 'deliveries' : 'deliveries');
            renderDeliveryList();
            return;
        }

        const selectBtn = e.target.closest('[data-select-delivery]');
        if (selectBtn && root.contains(selectBtn)) {
            const id = Number(selectBtn.dataset.selectDelivery);
            const found = activeJobs.find(j => Number(j.id) === id);
            if (found) {
                activeJob = found;
                renderActive();
                openPanel('active');
            }
            return;
        }

        const printBtn = e.target.closest('[data-print-basket-receipt]');
        if (printBtn && root.contains(printBtn)) {
            e.preventDefault();
            printBasketReceipt(printBtn.dataset.printBasketReceipt);
            return;
        }

        const shareBtn = e.target.closest('[data-share-basket-receipt]');
        if (shareBtn && root.contains(shareBtn)) {
            e.preventDefault();
            shareBasketReceipt(shareBtn.dataset.shareBasketReceipt, shareBtn);
            return;
        }

        const downloadDoBtn = e.target.closest('[data-download-do]');
        if (downloadDoBtn && root.contains(downloadDoBtn)) {
            e.preventDefault();
            downloadDeliveryOrder(downloadDoBtn.dataset.downloadDo, downloadDoBtn);
            return;
        }

        const shareDoBtn = e.target.closest('[data-share-do]');
        if (shareDoBtn && root.contains(shareDoBtn)) {
            e.preventDefault();
            shareDeliveryOrder(shareDoBtn.dataset.shareDo, shareDoBtn);
            return;
        }
    });

    document.addEventListener('submit', async function(e) {
        if (!root.contains(e.target)) return;

        if (e.target && e.target.id === 'bdoBasketReturnForm') {
            if (!$('bdo_br_debtor_code').value.trim()) {
                e.preventDefault();
                toast('error', 'Select customer');
                return;
            }

            if (whole($('bdoBrQty').value) <= 0) {
                e.preventDefault();
                toast('error', 'Basket qty must be more than 0');
                return;
            }
        }

        if (e.target && e.target.id === 'bdoCompleteDeliveryForm' && e.target.dataset.bdoConfirmed !== '1') {
            const file = e.target.querySelector('input[type="file"]');
            if (!file || !file.files || !file.files.length) {
                e.preventDefault();

                const ok = await confirmModal({
                    icon: 'question',
                    title: 'No proof photo',
                    text: 'Continue marking this delivery as delivered without a photo?',
                    confirmButtonText: 'Mark delivered'
                });

                if (!ok) {
                    return;
                }

                e.target.dataset.bdoConfirmed = '1';
                e.target.requestSubmit();
                return;
            }
        }

        if (e.defaultPrevented) return;

        if (e.target && e.target.matches('form')) {
            if (submittingForm) {
                e.preventDefault();
                return;
            }

            submittingForm = true;
            const submitButton = e.target.querySelector('button[type="submit"]');
            if (submitButton) {
                submitButton.dataset.originalText = submitButton.textContent;
                submitButton.textContent = submitButton.dataset.bdoLoadingText || 'Saving...';
                submitButton.disabled = true;
            }
        }
    });

    async function searchDebtors(q) {
        const url = `${cfg.ajaxUrl}?action=ac_cs_debtor_search&nonce=${encodeURIComponent(cfg.debtorNonce)}&q=${encodeURIComponent(q)}`;
        const res = await fetch(url, {credentials:'same-origin'});
        const data = await res.json();
        if (!data.success) throw new Error(data.data?.error || 'Search failed');
        return (data.data?.items || []).map(it => {
            const name = it.name || it.debtorName || '';
            const code = it.code || it.debtorCode || '';
            return {label:name || code, raw:{name, code}};
        });
    }

    const picker = {items:[], timer:null};
    function pickerNote(msg) { $('bdoPickerResults').innerHTML = `<div class="bdo-picker-note">${esc(msg)}</div>`; }
    function renderPickerItems(items) {
        if (!items.length) return pickerNote('No result found');
        $('bdoPickerResults').innerHTML = items.map((it, idx) => `<button type="button" class="bdo-picker-item" data-idx="${idx}"><span class="bdo-picker-main">${esc(it.label)}</span></button>`).join('');
    }
    function openPicker() {
        picker.items = [];
        $('bdoPickerSearch').value = '';
        $('bdoPickerModal').classList.add('active');
        pickerNote('Type to search');
        setTimeout(() => $('bdoPickerSearch').focus(), 80);
    }
    function closePicker() {
        $('bdoPickerModal').classList.remove('active');
        $('bdoPickerSearch').value = '';
        $('bdoPickerResults').innerHTML = '';
        picker.items = [];
    }
    function setBrCustomer(c) {
        $('bdoBrCustomerInput').value = c.name || c.code || '';
        $('bdo_br_debtor_code').value = c.code || '';
        $('bdo_br_debtor_name').value = c.name || '';
        $('bdoBrCustomerClear').classList.toggle('show', !!$('bdoBrCustomerInput').value.trim());
    }
    function applyLastDeliveredCustomerDefault() {
        if (brCustomerDefaultDismissed || !lastDeliveredCustomer) return;
        const hasCustomer = $('bdo_br_debtor_code').value.trim() || $('bdo_br_debtor_name').value.trim();
        if (hasCustomer || (!lastDeliveredCustomer.code && !lastDeliveredCustomer.name)) return;
        setBrCustomer(lastDeliveredCustomer);
    }
    function clearBrCustomer() {
        brCustomerDefaultDismissed = true;
        $('bdoBrCustomerInput').value = '';
        $('bdo_br_debtor_code').value = '';
        $('bdo_br_debtor_name').value = '';
        $('bdoBrCustomerClear').classList.remove('show');
    }

    $('bdoBrCustomerInput')?.addEventListener('click', openPicker);
    $('bdoBrCustomerClear')?.addEventListener('click', clearBrCustomer);
    $('bdoPickerClose')?.addEventListener('click', closePicker);
    $('bdoPickerBackdrop')?.addEventListener('click', closePicker);
    $('bdoPickerSearch')?.addEventListener('input', e => {
        clearTimeout(picker.timer);
        const q = String(e.target.value || '').trim();
        if (q.length < 1) {
            pickerNote('Type to search');
            return;
        }
        picker.timer = setTimeout(async () => {
            pickerNote('Searching...');
            try {
                picker.items = await searchDebtors(q);
                renderPickerItems(picker.items);
            } catch (err) {
                pickerNote('Failed to load');
            }
        }, 220);
    });
    $('bdoPickerResults')?.addEventListener('click', e => {
        const btn = e.target.closest('[data-idx]');
        if (!btn) return;
        const item = picker.items[Number(btn.dataset.idx)];
        if (item) {
            setBrCustomer(item.raw);
            closePicker();
        }
    });
    applyLastDeliveredCustomerDefault();
    setStats(stats);
    renderActive();
    const initialTab = root.dataset.initialTab || 'home';
    if (['home','deliveries','active','return'].includes(initialTab)) openPanel(initialTab);
})();
</script>
HTML;

$replacements = [
    '__AJAX_URL__'            => esc_url($ajax_url),
    '__DEBTOR_NONCE__'        => esc_attr($debtor_nonce),
    '__ACTIVE_JOBS_JSON__'    => esc_attr(wp_json_encode($active_jobs) ?: '[]'),
    '__DELIVERED_JOBS_JSON__' => esc_attr(wp_json_encode($delivered_jobs) ?: '[]'),
    '__RETURN_ROWS_JSON__'    => esc_attr(wp_json_encode($return_rows) ?: '[]'),
    '__BASKET_RECEIPTS_JSON__' => esc_attr(wp_json_encode($basket_receipts) ?: '{}'),
    '__SELECTED_BASKET_RECEIPT_ID__' => esc_attr($selected_basket_receipt ? (string) $selected_basket_receipt['id'] : ''),
    '__LAST_DELIVERED_CUSTOMER_JSON__' => esc_attr(wp_json_encode($last_delivered_customer) ?: 'null'),
    '__STATS_JSON__'          => esc_attr(wp_json_encode($stats) ?: '{}'),
    '__INITIAL_TAB__'         => esc_attr($initial_tab),
    '__INITIAL_FILTER__'      => esc_attr($initial_filter),
    '__LOGOUT_URL__'          => esc_url(wp_logout_url(wp_login_url())),
    '__DRIVER_NAME__'         => esc_html($driver_name),
    '__GREETING__'            => esc_html($greeting),
    '__ALERT_HTML__'          => $alert_html,
    '__FORM_NONCE__'          => esc_attr($form_nonce),
];

echo strtr($html, $replacements);�p���$�R���������w��
N?�<?php
if (!defined('ABSPATH')) exit;

/*
 * VegeBasketDO staff Delivery Order list - MySQL-only version.
 *
 * Keeps the original staff-list style and action buttons:
 * Print | Edit | View | Delete
 * Hidden-row toggle is available only for recovery users.
 *
 * Data source:
 * WordPress MySQL tables: {$wpdb->prefix}ac_do + {$wpdb->prefix}ac_do_items
 * ac_jobs is only the bridge queue/history and is not used as the list source.
 *
 * Page URLs:
 * Edit: /edit-delivery-order/?docNo=DO-0001&docKey=123
 * View: /view-delivery-order/?docNo=DO-0001&docKey=123
 */

if (!is_user_logged_in()) {
    echo '<div class="wst-dod-alert wst-dod-alert-error">Please log in to view Delivery Order records.</div>';
    return;
}

if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
    echo '<div class="wst-dod-alert wst-dod-alert-error">You do not have permission to view Delivery Order records.</div>';
    return;
}

global $wpdb;

if (!$wpdb) {
    echo '<div class="wst-dod-alert wst-dod-alert-error">WordPress database connection is not available.</div>';
    return;
}

$edit_page_url = home_url('/edit-delivery-order/');
$view_page_url = home_url('/view-delivery-order/');
$show_technical_errors = current_user_can('manage_options') && defined('WP_DEBUG') && WP_DEBUG;

if (!function_exists('wst_dod_log_error')) {
    function wst_dod_log_error($message) {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('[VegeBasketDO DO List] ' . $message);
        }
    }
}

if (!function_exists('wst_dod_valid_date')) {
    function wst_dod_valid_date($value, $fallback) {
        $value = trim((string)$value);
        if ($value === '') return $fallback;

        $dt = DateTime::createFromFormat('Y-m-d', $value, wp_timezone());
        if (!$dt || $dt->format('Y-m-d') !== $value) return $fallback;

        return $value;
    }
}

if (!function_exists('wst_dod_date')) {
    function wst_dod_date($v) {
        if ($v instanceof DateTime) return $v->format('Y-m-d');

        if (is_string($v) && $v !== '') {
            $ts = strtotime($v);
            if ($ts) return date('Y-m-d', $ts);
            return $v;
        }

        return '';
    }
}

if (!function_exists('wst_dod_datetime')) {
    function wst_dod_datetime($v) {
        if ($v instanceof DateTime) return $v->format('Y-m-d H:i:s');

        if (is_string($v) && $v !== '') {
            $v = trim($v);
            if ($v === '' || $v === '0000-00-00 00:00:00') return '';
            $ts = strtotime($v);
            if ($ts) return date('Y-m-d H:i:s', $ts);
            return $v;
        }

        return '';
    }
}

if (!function_exists('wst_dod_fmt_qty')) {
    function wst_dod_fmt_qty($v, $decimals = 2) {
        $n = (float)$v;

        if (abs($n - round($n)) < 0.00001) {
            return number_format_i18n($n, 0);
        }

        return number_format_i18n($n, $decimals);
    }
}

if (!function_exists('wst_dod_fmt_weight')) {
    function wst_dod_fmt_weight($v) {
        return number_format_i18n((float)$v, 2);
    }
}

if (!function_exists('wst_dod_read_json_array')) {
    function wst_dod_read_json_array($json) {
        $data = json_decode((string)$json, true);
        return is_array($data) ? $data : array();
    }
}

if (!function_exists('wst_dod_pick_payload_value')) {
    function wst_dod_pick_payload_value($payload, $keys, $fallback = '') {
        if (!is_array($payload)) return $fallback;

        foreach ((array)$keys as $key) {
            if (isset($payload[$key]) && trim((string)$payload[$key]) !== '') {
                return trim((string)$payload[$key]);
            }
        }

        return $fallback;
    }
}

if (!function_exists('wst_dod_pick_payload_any')) {
    function wst_dod_pick_payload_any($payload, $keys, $fallback = null) {
        if (!is_array($payload)) return $fallback;

        foreach ((array)$keys as $key) {
            if (array_key_exists($key, $payload) && $payload[$key] !== '' && $payload[$key] !== null) {
                return $payload[$key];
            }
        }

        return $fallback;
    }
}

if (!function_exists('wst_dod_doc_key_from_data')) {
    function wst_dod_doc_key_from_data($data) {
        if (!is_array($data)) return 0;

        foreach (array('docKey', 'DocKey', 'dockey', 'doc_key', 'sourceDocKey') as $key) {
            if (isset($data[$key]) && is_numeric($data[$key])) {
                return (int)$data[$key];
            }
        }

        return 0;
    }
}

if (!function_exists('wst_dod_doc_no_from_data')) {
    function wst_dod_doc_no_from_data($data) {
        if (!is_array($data)) return '';

        foreach (array('docNo', 'DocNo', 'docno', 'doc_no', 'sourceDocNo', 'oldDocNo', 'originalDocNo') as $key) {
            if (!empty($data[$key])) {
                return strtoupper(trim((string)$data[$key]));
            }
        }

        return '';
    }
}

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

        $labels = array(
            'PENDING' => 'Pending AutoCount',
            'PROCESSING' => 'Processing AutoCount',
            'SUCCESS' => 'Created',
            'FAILED' => 'AutoCount Failed',
            'FAILED_FINAL' => 'AutoCount Failed',
            'PENDING_DELIVERY' => 'Pending Delivery',
            'ASSIGNED' => 'Assigned',
            'SCHEDULED' => 'Scheduled',
            'DRIVER_ACKNOWLEDGED' => 'Driver Received',
            'RECEIVED' => 'Driver Received',
            'OUT_FOR_DELIVERY' => 'Out for Delivery',
            'DELIVERED' => 'Delivered',
            'NEEDS_STAFF_EDIT' => 'Needs Staff Edit',
            'EDIT_PENDING_AUTOCOUNT' => 'Edit Pending',
            'EDITED_IN_AUTOCOUNT' => 'Edited',
            'CANCELLED' => 'Cancelled',
            'ACTIVE' => 'Active',
            'HIDDEN' => 'Hidden',
            'UNASSIGNED' => 'Unassigned',
        );

        return $labels[$value] ?? ($value !== '' ? ucwords(strtolower(str_replace('_', ' ', $value))) : '-');
    }
}

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

        if (in_array($value, array('DELIVERED', 'SUCCESS', 'EDITED_IN_AUTOCOUNT', 'ACTIVE'), true)) {
            return 'wst-dod-badge-good';
        }

        if (in_array($value, array('FAILED', 'FAILED_FINAL', 'CANCELLED', 'HIDDEN'), true)) {
            return 'wst-dod-badge-danger';
        }

        if (in_array($value, array('PENDING', 'PROCESSING', 'EDIT_PENDING_AUTOCOUNT', 'NEEDS_STAFF_EDIT', 'SCHEDULED'), true)) {
            return 'wst-dod-badge-warn';
        }

        return 'wst-dod-badge-info';
    }
}

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

        $help = array(
            'PENDING' => 'Order is waiting for AutoCount bridge processing.',
            'PROCESSING' => 'AutoCount bridge is currently processing this order.',
            'SUCCESS' => 'Order was created successfully in AutoCount.',
            'FAILED' => 'AutoCount bridge failed to create or update this order.',
            'FAILED_FINAL' => 'AutoCount bridge failed after all retries.',
            'PENDING_DELIVERY' => 'Order exists but has not been assigned to a driver yet.',
            'ASSIGNED' => 'Order has been assigned to a driver.',
            'SCHEDULED' => 'This delivery order is scheduled for a future date.',
            'DRIVER_ACKNOWLEDGED' => 'Driver confirmed receiving the delivery list or goods.',
            'RECEIVED' => 'Driver confirmed receiving the delivery list or goods.',
            'OUT_FOR_DELIVERY' => 'Driver is currently delivering this order.',
            'DELIVERED' => 'Driver marked this order as delivered.',
            'NEEDS_STAFF_EDIT' => 'Driver reported not enough item. Staff should edit and reprint this DO.',
            'EDIT_PENDING_AUTOCOUNT' => 'Staff edited this order and the AutoCount update is still pending.',
            'EDITED_IN_AUTOCOUNT' => 'The edited order was updated successfully in AutoCount.',
            'CANCELLED' => 'This delivery order was cancelled.',
            'ACTIVE' => 'This delivery order is active in AutoCount.',
            'HIDDEN' => 'This row is hidden from normal staff.',
        );

        return $help[$value] ?? 'Current delivery order status.';
    }
}

if (!function_exists('wst_dod_wp_table_exists')) {
    function wst_dod_wp_table_exists($table_name) {
        global $wpdb;
        if (!$wpdb) return false;

        return $wpdb->get_var(
            $wpdb->prepare('SHOW TABLES LIKE %s', $table_name)
        ) === $table_name;
    }
}

if (!function_exists('wst_dod_wp_table_columns')) {
    function wst_dod_wp_table_columns($table_name) {
        global $wpdb;

        static $cache = array();
        if (!$wpdb) return array();

        $refresh = false;
        if (substr($table_name, -9) === '__refresh') {
            $refresh = true;
            $table_name = substr($table_name, 0, -9);
        }

        if (!$refresh && isset($cache[$table_name])) {
            return $cache[$table_name];
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table_name);
        $cols = $wpdb->get_col("SHOW COLUMNS FROM `{$safe_table}`", 0);

        $cache[$table_name] = is_array($cols) ? array_flip($cols) : array();

        return $cache[$table_name];
    }
}

if (!function_exists('wst_dod_is_recovery_user')) {
    function wst_dod_is_recovery_user() {
        $user = wp_get_current_user();
        $login = strtolower(trim((string)($user->user_login ?? '')));

        return in_array($login, array('user01', 'webstation'), true);
    }
}

if (!function_exists('wst_dod_job_soft_delete_available')) {
    function wst_dod_job_soft_delete_available() {
        global $wpdb;
        if (!$wpdb) return false;

        $table = $wpdb->prefix . 'ac_do';
        if (!wst_dod_wp_table_exists($table)) return false;

        $cols = wst_dod_wp_table_columns($table);

        return isset($cols['hidden_from_staff_list']);
    }
}

if (!function_exists('wst_dod_redirect_with_notice')) {
    function wst_dod_redirect_with_notice($type, $message) {
        $request_uri = isset($_SERVER['REQUEST_URI'])
            ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI']))
            : '/';

        $redirect_url = home_url($request_uri);

        $redirect_url = remove_query_arg(
            array('wst_dod_notice_type', 'wst_dod_notice', 'wst_dod_row_action', 'job_id', 'do_id', 'wst_dod_row_nonce'),
            $redirect_url
        );

        $redirect_url = add_query_arg(
            array(
                'wst_dod_notice_type' => sanitize_key($type),
                'wst_dod_notice' => (string)$message,
            ),
            $redirect_url
        );

        wp_safe_redirect($redirect_url);
        exit;
    }
}

if (!function_exists('wst_dod_notice_from_query')) {
    function wst_dod_notice_from_query() {
        $type = isset($_GET['wst_dod_notice_type'])
            ? sanitize_key(wp_unslash($_GET['wst_dod_notice_type']))
            : '';

        $message = isset($_GET['wst_dod_notice'])
            ? rawurldecode((string)wp_unslash($_GET['wst_dod_notice']))
            : '';

        $message = trim($message);

        if ($message === '') {
            return '';
        }

        $class = $type === 'error'
            ? 'wst-dod-alert-error'
            : ($type === 'warning' ? 'wst-dod-alert-warning' : 'wst-dod-alert-success');

        return '<div class="wst-dod-alert ' . esc_attr($class) . '">' . esc_html($message) . '</div>';
    }
}

if (!function_exists('wst_dod_get_job_label')) {
    function wst_dod_get_job_label($job_id) {
        global $wpdb;

        $do_id = (int)$job_id;
        if (!$wpdb || $do_id <= 0) return 'DO-' . $do_id;

        $table = $wpdb->prefix . 'ac_do';
        if (!wst_dod_wp_table_exists($table)) return 'DO-' . $do_id;

        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT local_doc_no, autocount_doc_no
                 FROM `{$table}`
                 WHERE id = %d
                 LIMIT 1",
                $do_id
            ),
            ARRAY_A
        );

        if (!$row) return 'DO-' . $do_id;

        $doc_label = strtoupper(trim((string)($row['local_doc_no'] ?? '')));
        if ($doc_label === '') {
            $doc_label = strtoupper(trim((string)($row['autocount_doc_no'] ?? '')));
        }

        return $doc_label !== '' ? $doc_label : 'DO-' . $do_id;
    }
}

if (!function_exists('wst_dod_handle_soft_delete_action')) {
    function wst_dod_handle_soft_delete_action() {
        global $wpdb;

        if (!$wpdb || strtoupper((string)($_SERVER['REQUEST_METHOD'] ?? '')) !== 'POST') {
            return '';
        }

        $posted_action = isset($_POST['wst_dod_row_action'])
            ? sanitize_key(wp_unslash($_POST['wst_dod_row_action']))
            : '';

        if ($posted_action === '') {
            return '';
        }

        if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
            wst_dod_redirect_with_notice('error', 'You do not have permission to update this row.');
        }

        $job_id = isset($_POST['do_id']) ? absint($_POST['do_id']) : (isset($_POST['job_id']) ? absint($_POST['job_id']) : 0);

        if ($job_id <= 0) {
            wst_dod_redirect_with_notice('error', 'This row cannot be updated because it has no local Delivery Order record.');
        }

        if (
            !isset($_POST['wst_dod_row_nonce'])
            || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['wst_dod_row_nonce'])), 'wst_dod_row_action_' . $job_id)
        ) {
            wst_dod_redirect_with_notice('error', 'Security check failed. Please refresh and try again.');
        }

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

        if (!wst_dod_wp_table_exists($table)) {
            wst_dod_redirect_with_notice('error', 'Local Delivery Order table is not available.');
        }

        if (!wst_dod_job_soft_delete_available()) {
            wst_dod_redirect_with_notice('error', 'Soft delete columns are not available on the local Delivery Order table. Please add the hidden_from_staff_list column first.');
        }

        $cols = wst_dod_wp_table_columns($table);
        $job_label = wst_dod_get_job_label($job_id);

        if ($posted_action === 'delete') {
            $update = array('hidden_from_staff_list' => 1);
            $formats = array('%d');

            if (isset($cols['hidden_reason'])) {
                $update['hidden_reason'] = 'Hidden from Delivery Order Records';
                $formats[] = '%s';
            }

            if (isset($cols['hidden_at'])) {
                $update['hidden_at'] = current_time('mysql');
                $formats[] = '%s';
            }

            if (isset($cols['hidden_by'])) {
                $update['hidden_by'] = get_current_user_id();
                $formats[] = '%d';
            }

            $ok = $wpdb->update(
                $table,
                $update,
                array('id' => $job_id),
                $formats,
                array('%d')
            );

            if ($ok === false) {
                wst_dod_log_error('Soft delete failed for local DO ' . $job_id . ': ' . $wpdb->last_error);
                wst_dod_redirect_with_notice('error', 'Could not delete this row. Please try again.');
            }

            wst_dod_redirect_with_notice('success', $job_label . ' is deleted.');
        }

        if ($posted_action === 'activate') {
            if (!wst_dod_is_recovery_user()) {
                wst_dod_redirect_with_notice('error', 'Only User01 or webstation can activate deleted rows.')�w��ցf����������x�
N?�;
            }

            $update = array('hidden_from_staff_list' => 0);
            $formats = array('%d');

            if (isset($cols['hidden_reason'])) {
                $update['hidden_reason'] = null;
                $formats[] = '%s';
            }

            if (isset($cols['hidden_at'])) {
                $update['hidden_at'] = null;
                $formats[] = '%s';
            }

            if (isset($cols['hidden_by'])) {
                $update['hidden_by'] = null;
                $formats[] = '%d';
            }

            $ok = $wpdb->update(
                $table,
                $update,
                array('id' => $job_id),
                $formats,
                array('%d')
            );

            if ($ok === false) {
                wst_dod_log_error('Activate failed for local DO ' . $job_id . ': ' . $wpdb->last_error);
                wst_dod_redirect_with_notice('error', 'Could not activate this row. Please try again.');
            }

            wst_dod_redirect_with_notice('success', $job_label . ' is restored.');
        }

        wst_dod_redirect_with_notice('error', 'Unknown row action.');
    }
}

if (!function_exists('wst_dod_get_proof_image_by_doc')) {
    function wst_dod_get_proof_image_by_doc($docNo, $docKey) {
        global $wpdb;

        if (!$wpdb) return '';

        $table = $wpdb->prefix . 'ac_do_proof_images';
        if (!wst_dod_wp_table_exists($table)) return '';

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $cols = wst_dod_wp_table_columns($table);

        $orWhere = array();
        $args = array();

        $docNo = trim((string)$docNo);
        $docKey = (int)$docKey;

        if ($docNo !== '' && isset($cols['doc_no'])) {
            $orWhere[] = 'doc_no = %s';
            $args[] = $docNo;
        }

        if ($docKey > 0 && isset($cols['doc_key'])) {
            $orWhere[] = 'doc_key = %d';
            $args[] = $docKey;
        }

        if (empty($orWhere) || !isset($cols['image_url'])) return '';

        $whereSql = '(' . implode(' OR ', $orWhere) . ')';

        if (isset($cols['proof_type'])) {
            $whereSql .= ' AND proof_type = %s';
            $args[] = 'DELIVERY_PROOF';
        }

        if (isset($cols['deleted_at'])) {
            $whereSql .= ' AND deleted_at IS NULL';
        }

        $orderCol = isset($cols['id']) ? 'id' : (isset($cols['captured_at']) ? 'captured_at' : 'image_url');

        $sql = "
            SELECT image_url
            FROM `{$safe_table}`
            WHERE {$whereSql}
            ORDER BY `{$orderCol}` DESC
            LIMIT 1
        ";

        $url = $wpdb->get_var($wpdb->prepare($sql, $args));

        return $url ? esc_url_raw((string)$url) : '';
    }
}

if (!function_exists('wst_dod_driver_label_from_job')) {
    function wst_dod_driver_label_from_job($job, $payload) {
        $driver_id = (int)($job['assigned_driver_id'] ?? 0);

        if ($driver_id > 0) {
            $user = get_userdata($driver_id);
            if ($user) {
                $display = trim((string)$user->display_name);
                return $display !== '' ? $display : (string)$user->user_login;
            }
        }

        foreach (array('assignedDriverName', 'driverName', 'driver_name', 'assignedDriverLogin', 'driverLogin', 'driver_login', 'assignedDriver', 'assigned_driver', 'driver') as $key) {
            if (!empty($payload[$key])) {
                return trim((string)$payload[$key]);
            }
        }

        if (!empty($job['assigned_driver'])) {
            return trim((string)$job['assigned_driver']);
        }

        return '';
    }
}

if (!function_exists('wst_dod_is_goods_receive_row')) {
    function wst_dod_is_goods_receive_row($row) {
        $doc_values = array(
            $row['local_doc_no'] ?? '',
            $row['autocount_doc_no'] ?? '',
        );

        foreach ($doc_values as $doc_no) {
            $doc_no = strtoupper(trim((string)$doc_no));
            if ($doc_no !== '' && (strpos($doc_no, 'WPGR') === 0 || strpos($doc_no, 'GRN') === 0)) {
                return true;
            }
        }

        return false;
    }
}

if (!function_exists('wst_dod_calc_status')) {
    function wst_dod_calc_status($syncStatus, $deliveryStatus, $docDate, $hidden = false, $isGoodsReceive = false) {
        $syncStatus = strtoupper(trim((string)$syncStatus));
        $deliveryStatus = strtoupper(trim((string)$deliveryStatus));
        $docDate = trim((string)$docDate);

        if ($hidden) return 'HIDDEN';

        if ($isGoodsReceive) {
            return $syncStatus !== '' ? $syncStatus : 'ACTIVE';
        }

        if ($deliveryStatus === 'DELIVERED') return 'DELIVERED';
        if ($deliveryStatus === 'CANCELLED') return 'CANCELLED';

        $today = current_time('Y-m-d');
        if ($docDate !== '' && $docDate > $today) return 'SCHEDULED';

        return 'OUT_FOR_DELIVERY';
    }
}

if (!function_exists('wst_dod_item_from_payload_line')) {
    function wst_dod_item_from_payload_line($line) {
        $line = is_array($line) ? $line : array();

        return array(
            'itemCode' => wst_dod_pick_payload_value($line, array('itemCode', 'ItemCode', 'item_code', 'code'), ''),
            'name' => wst_dod_pick_payload_value($line, array('description', 'Description', 'description1', 'itemName', 'item_name', 'name'), ''),
            'qty' => (float)wst_dod_pick_payload_any($line, array('qty', 'Qty', 'quantity'), 0),
            'basket' => (float)wst_dod_pick_payload_any($line, array('basketQty', 'basket_qty', 'basket', 'Basket', 'bsk', 'UDF_BASKET'), 0),
            'carton' => (float)wst_dod_pick_payload_any($line, array('cartonQty', 'carton_qty', 'carton', 'Carton', 'ctn', 'UDF_CARTON'), 0),
            'weightKg' => (float)wst_dod_pick_payload_any($line, array('kg', 'weight', 'weightKg', 'WeightKG', 'weight_kg', 'UDF_WEIGHTKG'), 0),
        );
    }
}

if (!function_exists('wst_dod_can_staff_edit_row')) {
    function wst_dod_can_staff_edit_row($row) {
        if (!empty($row['isGoodsReceive'])) return false;
        if (trim((string)($row['docNo'] ?? '')) === '') return false;

        return true;
    }
}

if (!function_exists('wst_dod_staff_edit_disabled_reason')) {
    function wst_dod_staff_edit_disabled_reason($row) {
        if (trim((string)($row['docNo'] ?? '')) === '') {
            return 'This order is missing a document number.';
        }

        return 'This order cannot be edited because it is a Goods Receive record.';
    }
}

if (!function_exists('wst_dod_load_mysql_rows')) {
    function wst_dod_load_mysql_rows($customer = '', $status = 'ALL', $dateFrom = '', $dateTo = '', $limit = 25, $includeHidden = false) {
        global $wpdb;

        $doTable = $wpdb->prefix . 'ac_do';
        $itemTable = $wpdb->prefix . 'ac_do_items';

        if (!wst_dod_wp_table_exists($doTable)) {
            return array('rows' => array(), 'error' => 'Local Delivery Order table is not available: ' . $doTable);
        }
        if (!wst_dod_wp_table_exists($itemTable)) {
            return array('rows' => array(), 'error' => 'Local Delivery Order item table is not available: ' . $itemTable);
        }

        $safeDoTable = preg_replace('/[^A-Za-z0-9_]/', '', $doTable);
        $safeItemTable = preg_replace('/[^A-Za-z0-9_]/', '', $itemTable);
        $doCols = wst_dod_wp_table_columns($doTable);

        $where = array('1=1');
        $params = array();

        if (isset($doCols['deleted_at'])) {
            $where[] = 'deleted_at IS NULL';
        }

        if (!$includeHidden && isset($doCols['hidden_from_staff_list'])) {
            $where[] = 'hidden_from_staff_list = 0';
        }

        if ($customer !== '') {
            $like = '%' . $wpdb->esc_like($customer) . '%';
            $customerParts = array('local_doc_no LIKE %s', 'debtor_code LIKE %s', 'debtor_name LIKE %s');
            $params[] = $like;
            $params[] = $like;
            $params[] = $like;

            if (isset($doCols['autocount_doc_no'])) {
                $customerParts[] = 'autocount_doc_no LIKE %s';
                $params[] = $like;
            }

            $where[] = '(' . implode(' OR ', $customerParts) . ')';
        }

        if ($dateFrom !== '') {
            $where[] = 'doc_date >= %s';
            $params[] = $dateFrom;
        }
        if ($dateTo !== '') {
            $where[] = 'doc_date <= %s';
            $params[] = $dateTo;
        }

        $sql = "SELECT *
                FROM `{$safeDoTable}`
                WHERE " . implode(' AND ', $where) . "
                ORDER BY doc_date DESC, updated_at DESC, id DESC
                LIMIT 1000";

        if (!empty($params)) {
            $sql = $wpdb->prepare($sql, $params);
        }

        $doRows = $wpdb->get_results($sql, ARRAY_A);
        if ($wpdb->last_error) {
            wst_dod_log_error('Local DO load failed: ' . $wpdb->last_error);
            return array('rows' => array(), 'error' => $wpdb->last_error);
        }

        $ids = array();
        foreach ((array)$doRows as $row) {
            $id = (int)($row['id'] ?? 0);
            if ($id > 0) $ids[] = $id;
        }

        $itemsByDo = array();
        if (!empty($ids)) {
            $placeholders = implode(',', array_fill(0, count($ids), '%d'));
            $itemSql = "SELECT * FROM `{$safeItemTable}` WHERE do_id IN ({$placeholders}) ORDER BY do_id ASC, line_no ASC, id ASC";
            $itemRows = $wpdb->get_results($wpdb->prepare($itemSql, $ids), ARRAY_A);

            if ($wpdb->last_error) {
                wst_dod_log_error('Local DO item load failed: ' . $wpdb->last_error);
                return array('rows' => array(), 'error' => $wpdb->last_error);
            }

            foreach ((array)$itemRows as $item) {
                $doId = (int)($item['do_id'] ?? 0);
                if ($doId <= 0) continue;

                $itemsByDo[$doId][] = array(
                    'itemCode' => (string)($item['item_code'] ?? ''),
                    'name' => (string)($item['description'] ?? ''),
                    'qty' => (float)($item['qty'] ?? 0),
                    'basket' => (float)($item['basket_qty'] ?? 0),
                    'carton' => (float)($item['carton_qty'] ?? 0),
                    'weightKg' => (float)($item['weight_kg'] ?? 0),
                );
            }
        }

        $out = array();
        $wantedStatus = strtoupper(trim((string)$status));

        foreach ((array)$doRows as $do) {
            $doId = (int)($do['id'] ?? 0);
            if ($doId <= 0) continue;

            $docDate = wst_dod_date($do['doc_date'] ?? '');
            $hidden = !empty($do['hidden_from_staff_list']);
            $syncStatus = strtoupper(trim((string)($do['sync_status'] ?? '')));
            $deliveryStatus = strtoupper(trim((string)($do['delivery_status'] ?? '')));
            $isGoodsReceive = wst_dod_is_goods_receive_row($do);
            $displayStatus = wst_dod_calc_status($syncStatus, $deliveryStatus, $docDate, $hidden, $isGoodsReceive);

            if ($wantedStatus !== 'ALL' && $displayStatus !== $wantedStatus) continue;

            $driver = '';
            $driverId = (int)($do['assigned_driver_id'] ?? 0);
            if ($driverId > 0) {
                $driverUser = get_userdata($driverId);
                if ($driverUser) {
                    $driver = trim((string)$driverUser->display_name);
                    if ($driver === '') $driver = trim((string)$driverUser->user_login);
                }
            }
            if ($driver === '') $driver = $isGoodsReceive ? '-' : 'UNASSIGNED';

            $items = $itemsByDo[$doId] ?? array();
            $totalBasket = 0.0;
            $totalCarton = 0.0;
            foreach ($items as $line) {
                $totalBasket += (float)($line['basket'] ?? 0);
                $totalCarton += (float)($line['carton'] ?? 0);
            }

            $docNo = strtoupper(trim((string)($do['local_doc_no'] ?? '')));
            $autoDocNo = strtoupper(trim((string)($do['autocount_doc_no'] ?? '')));
            $docKey = (int)($do['autocount_doc_key'] ?? 0);

            $out[] = array(
                'docKey' => $docKey,
                'docNo' => $docNo !== '' ? $docNo : $autoDocNo,
                'autoCountDocNo' => $autoDocNo,
                'docDate' => $docDate,
                'debtorCode' => (string)($do['debtor_code'] ?? ''),
                'debtorName' => (string)($do['debtor_name'] ?? ''),
                'autoCountStatus' => $syncStatus,
                'displayStatus' => $displayStatus,
                'driver' => $driver,
                'isGoodsReceive' => $isGoodsReceive,
                'documentTypeLabel' => $isGoodsReceive ? 'Goods Receive' : 'Delivery Order',
                'partyLabel' => $isGoodsReceive ? 'Supplier' : 'Customer',
                'jobId' => $doId,
                'doId' => $doId,
                'jobSubtype' => '',
                'syncStatus' => $syncStatus,
                'deliveryStatus' => $deliveryStatus,
                'jobError' => (string)($do['last_sync_error'] ?? ''),
                'createdAt' => wst_dod_datetime($do['created_at'] ?? ''),
                'lastModified' => wst_dod_datetime($do['updated_at'] ?? ''),
                'totalBasket' => $totalBasket,
                'totalCarton' => $totalCarton,
                'items' => $items,
                'proofImage' => wst_dod_get_proof_image_by_doc(($autoDocNo !== '' ? $autoDocNo : $docNo), $docKey),
                'hasAuthoritativeJob' => true,
                'isPendingJob' => false,
                'hiddenFromStaffList' => (int)($do['hidden_from_staff_list'] ?? 0),
                'hiddenReason' => (string)($do['hidden_reason'] ?? ''),
                'hiddenAt' => (string)($do['hidden_at'] ?? ''),
                'hiddenBy' => (int)($do['hidden_by'] ?? 0),
            );
        }

        $limit = (int)$limit;
        if ($limit > 0) {
            $out = array_slice($out, 0, $limit);
        }

        return array('rows' => $out, 'error' => '');
    }
}

$isRecoveryUser = wst_dod_is_recovery_user();
wst_dod_handle_soft_delete_action();
$wst_dod_action_notice = wst_dod_notice_from_query();

$customer = isset($_GET['customer']) ? trim(sanitize_text_field(wp_unslash($_GET['customer']))) : '';
$status = isset($_GET['status']) ? strtoupper(trim(sanitize_text_field(wp_unslash($_GET['status'])))) : 'ALL';
$limit_input = isset($_GET['limit']) ? (int)$_GET['limit'] : 25;
$allowed_limits = array(25, 50, 100);
$limit = in_array($limit_input, $allowed_limits, true) ? $limit_input : 25;

$status_options = array(
    'ALL' => 'All',
    'SCHEDULED' => 'Scheduled',
    'OUT_FOR_DELIVERY' => 'Out for Delivery',
    'DELIVERED' => 'Delivered',
);

if ($isRecoveryUser) {
    $status_options['HIDDEN'] = 'Hidden';
}

if (!isset($status_options[$status])) {
    $status = 'ALL';
}

$todayObj = new DateTime('now', wp_timezone());
$defaultDateTo = $todayObj->format('Y-m-d');

$fromObj = clone $todayObj;
$fromObj->modify('-1 month');
$defaultDateFrom = $fromObj->format('Y-m-d');

$dateFromRaw = isset($_GET['dateFrom']) ? wp_unslash($_GET['dateFrom']) : '';
$dateToRaw = isset($_GET['dateTo']) ? wp_unslash($_GET['dateTo']) : '';
$dateFrom = wst_dod_valid_date(sanitize_text_field($dateFromRaw), $defaultDateFrom);
$dateTo = wst_dod_valid_date(sanitize_text_field($dateToRaw), $defaultDateTo);

/*
 * Hidden / inactive row visibility:
 * - Normal users never see hidden rows and never see this toggle.
 * - Recovery users User01 / webstation see hidden rows by default.
 * - Recovery users can toggle hidden rows off using show_hidden=0.
 */
$showHiddenRows = false;
if ($isRecoveryUser) {
    $showHiddenRaw = isset($_GET['show_hidden'])
        ? sanitize_text_field(wp_unslash($_GET['show_hidden']))
        : '1';

    $showHiddenRows = �x�z5���������x<�
N?� ($showHiddenRaw !== '0');
}

$loadResult = wst_dod_load_mysql_rows($customer, $status, $dateFrom, $dateTo, $limit, $showHiddenRows);
$rows = $loadResult['rows'];
$loadWarning = $loadResult['error'];

$hiddenToggleUrl = '';
$hiddenToggleLabel = '';
if ($isRecoveryUser) {
    $hiddenToggleUrl = add_query_arg(
        array(
            'customer' => $customer,
            'status' => $status,
            'dateFrom' => $dateFrom,
            'dateTo' => $dateTo,
            'limit' => $limit,
            'show_hidden' => $showHiddenRows ? '0' : '1',
        ),
        get_permalink()
    );

    $hiddenToggleLabel = $showHiddenRows ? 'Hide Hidden' : 'Show Hidden';
}
?>

<div class="wst-dod-wrap">
    <?php echo $wst_dod_action_notice; ?>

    <?php if ($loadWarning !== ''): ?>
        <div class="wst-dod-alert wst-dod-alert-error">
            Failed to load Delivery Order records.
            <?php if ($show_technical_errors): ?>
                <?php echo esc_html($loadWarning); ?>
            <?php endif; ?>
        </div>
    <?php endif; ?>

    <div class="wst-dod-filter-card">
        <form method="get" class="wst-dod-form">
            <?php if ($isRecoveryUser): ?>
                <input type="hidden" name="show_hidden" value="<?php echo esc_attr($showHiddenRows ? '1' : '0'); ?>">
            <?php endif; ?>

            <div class="wst-dod-field wst-dod-search-field">
                <label class="wst-dod-label" for="wstDodCustomer">Customer / Supplier / Doc No</label>
                <input id="wstDodCustomer" name="customer" class="wst-dod-input" type="search" value="<?php echo esc_attr($customer); ?>" placeholder="Search customer, supplier, DO or GRN no..." autocomplete="off">
            </div>

            <div class="wst-dod-filter-grid">
                <div class="wst-dod-field">
                    <label class="wst-dod-label" for="wstDodStatus">Status</label>
                    <select id="wstDodStatus" name="status" class="wst-dod-input">
                        <?php foreach ($status_options as $status_value => $status_label): ?>
                            <option value="<?php echo esc_attr($status_value); ?>" <?php selected($status, $status_value); ?>>
                                <?php echo esc_html($status_label); ?>
                            </option>
                        <?php endforeach; ?>
                    </select>
                </div>

                <div class="wst-dod-field">
                    <label class="wst-dod-label" for="wstDodDateFrom">From</label>
                    <input id="wstDodDateFrom" name="dateFrom" class="wst-dod-input" type="date" value="<?php echo esc_attr($dateFrom); ?>">
                </div>

                <div class="wst-dod-field">
                    <label class="wst-dod-label" for="wstDodDateTo">To</label>
                    <input id="wstDodDateTo" name="dateTo" class="wst-dod-input" type="date" value="<?php echo esc_attr($dateTo); ?>">
                </div>

                <div class="wst-dod-field">
                    <label class="wst-dod-label" for="wstDodLimit">Rows</label>
                    <select id="wstDodLimit" name="limit" class="wst-dod-input">
                        <?php foreach ($allowed_limits as $allowed_limit): ?>
                            <option value="<?php echo esc_attr($allowed_limit); ?>" <?php selected($limit, $allowed_limit); ?>>
                                <?php echo esc_html($allowed_limit); ?>
                            </option>
                        <?php endforeach; ?>
                    </select>
                </div>
            </div>

            <button type="submit" class="wst-dod-btn wst-dod-btn-primary">Search</button>
        </form>
    </div>

    <div class="wst-dod-summary">
        <span>Showing <?php echo esc_html(number_format_i18n(count($rows))); ?> rows</span>

        <?php if ($isRecoveryUser): ?>
            <a class="wst-dod-hidden-toggle" href="<?php echo esc_url($hiddenToggleUrl); ?>">
                <?php echo esc_html($hiddenToggleLabel); ?>
            </a>
        <?php endif; ?>
    </div>

    <div class="wst-dod-table-card">
        <div class="wst-dod-table-scroll">
            <table class="wst-dod-table">
                <thead>
                    <tr>
                        <th class="wst-dod-col-date">Date</th>
                        <th class="wst-dod-col-doc">Doc No</th>
                        <th class="wst-dod-col-customer">Customer / Supplier</th>
                        <th class="wst-dod-col-driver">Driver</th>
                        <th class="wst-dod-col-status">Status</th>
                        <th class="wst-dod-col-summary">Bsk / Ctn</th>
                        <th class="wst-dod-col-items">Items</th>
                        <th class="wst-dod-col-action">Actions</th>
                    </tr>
                </thead>

                <tbody>
                    <?php if (empty($rows)): ?>
                        <tr>
                            <td colspan="8" class="wst-dod-empty">No matching delivery order records.</td>
                        </tr>
                    <?php else: ?>
                        <?php foreach ($rows as $r): ?>
                            <?php
                            $isHidden = !empty($r['hiddenFromStaffList']);
                            $rowJobId = (int)($r['jobId'] ?? 0);
                            $rowActionNonce = $rowJobId > 0 ? wp_create_nonce('wst_dod_row_action_' . $rowJobId) : '';

                            $view_args = !empty($r['isPendingJob'])
                                ? array('job_id' => (int)$r['jobId'])
                                : array('docNo' => $r['docNo'], 'docKey' => (int)$r['docKey']);

                            $view_url = add_query_arg($view_args, $view_page_url);

                            $edit_url = add_query_arg(
                                array(
                                    'docNo' => (string)($r['docNo'] ?? ''),
                                    'docKey' => (int)($r['docKey'] ?? 0),
                                ),
                                $edit_page_url
                            );

                            $can_edit_row = wst_dod_can_staff_edit_row($r);
                            $edit_disabled_reason = $can_edit_row ? '' : wst_dod_staff_edit_disabled_reason($r);

                            $row_display_status_key = strtoupper(trim((string)($r['displayStatus'] ?? '')));
                            $row_delivery_status_key = strtoupper(trim((string)($r['deliveryStatus'] ?? '')));
                            $is_delivered_row = ($row_display_status_key === 'DELIVERED' || $row_delivery_status_key === 'DELIVERED');

                            // WordPress-first: allow opening any row that has a real local DO record.
                            // docKey is not required when the order was created in WordPress and not yet synced to AutoCount.
                            $can_open_document = !empty($r['isPendingJob']) || ((int)($r['jobId'] ?? 0) > 0 && trim((string)($r['docNo'] ?? '')) !== '');

                            $print_url = add_query_arg(
                                array(
                                    'autoPrint' => '1',
                                    'printPage' => 'do',
                                ),
                                $view_url
                            );

                            $badgeClass = wst_dod_status_class($r['displayStatus']);
                            ?>

                            <tr class="wst-dod-main-row <?php echo $isHidden ? 'wst-dod-row-hidden' : ''; ?>">
                                <td class="wst-dod-date">
                                    <div class="wst-dod-date-main"><?php echo esc_html($r['docDate'] ?: '-'); ?></div>
                                    <div class="wst-dod-date-sub">Created: <?php echo esc_html($r['createdAt'] ?: '-'); ?></div>
                                    <div class="wst-dod-date-sub">Updated: <?php echo esc_html($r['lastModified'] ?: '-'); ?></div>
                                </td>

                                <td class="wst-dod-docno">
                                    <?php if (!empty($r['isPendingJob'])): ?>
                                        <span class="wst-dod-muted">JOB-<?php echo esc_html((int)$r['jobId']); ?></span>
                                    <?php else: ?>
                                        <?php echo esc_html($r['docNo'] ?: '-'); ?>
                                        <div class="wst-dod-date-sub"><?php echo esc_html($r['documentTypeLabel'] ?? 'Delivery Order'); ?></div>
                                    <?php endif; ?>
                                </td>

                                <td class="wst-dod-customer">
                                    <div class="wst-dod-customer-name"><?php echo esc_html($r['debtorName'] ?: '-'); ?></div>
                                    <div class="wst-dod-customer-code"><?php echo esc_html(($r['partyLabel'] ?? 'Customer') . ': ' . ($r['debtorCode'] ?: '-')); ?></div>
                                </td>

                                <td class="wst-dod-driver">
                                    <?php echo esc_html(strtoupper($r['driver'] ?: 'Unassigned')); ?>
                                </td>

                                <td class="wst-dod-status">
                                    <span
                                        class="wst-dod-badge <?php echo esc_attr($badgeClass); ?>"
                                        data-status-help="<?php echo esc_attr(wst_dod_status_help($r['displayStatus'])); ?>"
                                    >
                                        <?php echo esc_html(wst_dod_label_status($r['displayStatus'])); ?>
                                    </span>

                                    <?php if ($isHidden && $isRecoveryUser): ?>
                                        <span
                                            class="wst-dod-badge wst-dod-badge-hidden"
                                            data-status-help="<?php echo esc_attr($r['hiddenReason'] ?: 'This row is hidden from normal staff.'); ?>"
                                        >
                                            Hidden
                                        </span>
                                    <?php endif; ?>
                                </td>

                                <td class="wst-dod-summary-cell">
                                    <div>Bsk <strong><?php echo esc_html(wst_dod_fmt_qty($r['totalBasket'])); ?></strong></div>
                                    <div>Ctn <strong><?php echo esc_html(wst_dod_fmt_qty($r['totalCarton'])); ?></strong></div>
                                </td>

                                <td class="wst-dod-items">
                                    <?php if (empty($r['items'])): ?>
                                        <div class="wst-dod-muted">No item detail.</div>
                                    <?php else: ?>
                                        <?php foreach ($r['items'] as $item): ?>
                                            <div class="wst-dod-item">
                                                <div class="wst-dod-item-name">
                                                    <?php echo esc_html($item['name'] !== '' ? $item['name'] : ($item['itemCode'] ?: '-')); ?>
                                                </div>
                                                <div class="wst-dod-item-meta">
                                                    <?php echo esc_html($item['itemCode'] ?: '-'); ?>
                                                    | Qty <?php echo esc_html(wst_dod_fmt_qty($item['qty'])); ?>
                                                    | Basket <?php echo esc_html(wst_dod_fmt_qty($item['basket'])); ?>
                                                    | Carton <?php echo esc_html(wst_dod_fmt_qty($item['carton'])); ?>
                                                    | KG <?php echo esc_html(wst_dod_fmt_weight($item['weightKg'])); ?>
                                                </div>
                                            </div>
                                        <?php endforeach; ?>
                                    <?php endif; ?>
                                </td>

                                <td class="wst-dod-action">
                                    <?php if ($can_open_document): ?>
                                        <a
                                            class="wst-dod-action-btn wst-dod-action-print"
                                            href="<?php echo esc_url($print_url); ?>"
                                            onclick="return wstDodOpenPrintPopup(this.href);"
                                        >
                                            Print
                                        </a>
                                    <?php else: ?>
                                        <span
                                            class="wst-dod-action-btn wst-dod-action-disabled"
                                            title="Cannot print because this row has no document reference."
                                            aria-label="Cannot print because this row has no document reference."
                                        >Print</span>
                                    <?php endif; ?>

                                    <?php if ($can_edit_row): ?>
                                        <a class="wst-dod-action-btn wst-dod-action-edit" href="<?php echo esc_url($edit_url); ?>">
                                            Edit
                                        </a>
                                    <?php else: ?>
                                        <span
                                            class="wst-dod-action-btn wst-dod-action-disabled"
                                            title="<?php echo esc_attr($edit_disabled_reason); ?>"
                                            aria-label="<?php echo esc_attr($edit_disabled_reason); ?>"
                                        >Edit</span>
                                    <?php endif; ?>

                                    <?php if ($can_open_document): ?>
                                        <a class="wst-dod-action-btn wst-dod-action-view" href="<?php echo esc_url($view_url); ?>">
                                            View
                                        </a>
                                    <?php else: ?>
                                        <span
                                            class="wst-dod-action-btn wst-dod-action-disabled"
                                            title="Cannot view because this row has no document reference."
                                            aria-label="Cannot view because this row has no document reference."
                                        >View</span>
                                    <?php endif; ?>

                                    <?php if (!$is_delivered_row || $isRecoveryUser): ?>
                                        <?php if ($rowJobId > 0): ?>
                                            <form method="post" class="wst-dod-inline-form" onsubmit="return wstDodConfirmSoftAction(this, '<?php echo esc_js($isHidden ? 'activate' : 'delete'); ?>');">
                                                <input type="hidden" name="do_id" value="<?php echo esc_attr($rowJobId); ?>">
                                                <input type="hidden" name="job_id" value="<?php echo esc_attr($rowJobId); ?>">
                                                <input type="hidden" name="wst_dod_row_nonce" value="<?php echo esc_attr($rowActionNonce); ?>">

                                                <?php if ($isHidden && $isRecoveryUser): ?>
                                 �x<�p�\: ���������x<�
N5�����                   <input type="hidden" name="wst_dod_row_action" value="activate">
                                                    <button type="submit" class="wst-dod-action-btn wst-dod-action-active">Active</button>
                                                <?php else: ?>
                                                    <input type="hidden" name="wst_dod_row_action" value="delete">
                                                    <button type="submit" class="wst-dod-action-btn wst-dod-action-delete">Delete</button>
                                                <?php endif; ?>
                                            </form>
                                        <?php else: ?>
                                            <span
                                                class="wst-dod-action-btn wst-dod-action-disabled"
                                                title="Cannot delete because this row has no local Delivery Order record."
                                                aria-label="Cannot delete because this row has no local Delivery Order record."
                                            >Delete</span>
                                        <?php endif; ?>
                                    <?php endif; ?>
                                </td>
                            </tr>
                        <?php endforeach; ?>
                    <?php endif; ?>
                </tbody>
            </table>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
function wstDodConfirmSoftAction(form, actionType) {
    var isActivate = actionType === 'activate';
    var title = isActivate
        ? 'Restore this record?'
        : 'Delete this record?';

    var text = isActivate
        ? 'This record will be shown in the list again.'
        : 'This record will be removed from the list.';

    var confirmText = isActivate ? 'Yes, restore' : 'Yes, delete';
    var confirmColor = isActivate ? '#166534' : '#dc2626';
    var cancelColor = '#64748b';

    if (window.Swal && typeof window.Swal.fire === 'function') {
        window.Swal.fire({
            title: title,
            text: text,
            icon: isActivate ? 'question' : 'warning',
            showCancelButton: true,
            confirmButtonText: confirmText,
            cancelButtonText: 'Cancel',
            confirmButtonColor: confirmColor,
            cancelButtonColor: cancelColor,
            reverseButtons: true,
            focusCancel: true
        }).then(function(result) {
            if (result && result.isConfirmed) {
                form.submit();
            }
        });

        return false;
    }

    if (window.confirm(text)) {
        form.submit();
    }

    return false;
}

function wstDodOpenPrintPopup(url) {
    var width = 920;
    var height = 760;
    var left = Math.max(0, Math.round((window.screen.width - width) / 2));
    var top = Math.max(0, Math.round((window.screen.height - height) / 2));
    var features = [
        'popup=yes',
        'width=' + width,
        'height=' + height,
        'left=' + left,
        'top=' + top,
        'resizable=yes',
        'scrollbars=yes',
        'noopener=yes'
    ].join(',');

    var popup = window.open(url, 'wstDodPrintWindow', features);

    if (!popup) {
        window.open(url, '_blank', 'noopener=yes');
        return false;
    }

    try {
        popup.focus();
    } catch (error) {}

    return false;
}
</script>

<style>
.wst-dod-wrap{
    --dod-green:#166534;
    --dod-green-dark:#14532d;
    --dod-line:#e5e7eb;
    --dod-text:#0f172a;
    --dod-muted:#64748b;
    width:100%;
    max-width:100%;
    margin:0 auto;
    padding:6px;
    box-sizing:border-box;
    font-family:"Segoe UI", Roboto, Arial, sans-serif;
    color:var(--dod-text);
    background:#f4faf5;
}

.wst-dod-alert{
    padding:12px 14px;
    border-radius:8px;
    margin:8px 0;
    font-size:14px;
    font-weight:700;
}

.wst-dod-alert-error{
    border:1px solid #fecaca;
    background:#fff1f2;
    color:#991b1b;
}

.wst-dod-alert-warning{
    border:1px solid #fed7aa;
    background:#fff7ed;
    color:#9a3412;
}

.wst-dod-alert-success{
    border:1px solid #86efac;
    background:#f0fdf4;
    color:#166534;
}

.wst-dod-filter-card,
.wst-dod-table-card{
    background:#fff;
    border:1px solid var(--dod-line);
    border-radius:8px;
    padding:8px;
    margin-bottom:8px;
    box-sizing:border-box;
}

.wst-dod-form{
    display:flex;
    flex-direction:column;
    gap:8px;
}

.wst-dod-filter-grid{
    display:grid;
    grid-template-columns:repeat(4, minmax(0, 1fr));
    gap:7px;
}

.wst-dod-field{
    min-width:0;
    display:flex;
    flex-direction:column;
    gap:4px;
}

.wst-dod-label{
    font-size:13px;
    line-height:1.1;
    font-weight:800;
    color:#334155;
}

.wst-dod-input{
    width:100%;
    min-height:38px;
    border:1px solid #cbd5e1;
    border-radius:6px;
    padding:7px 9px;
    font-size:14px;
    color:var(--dod-text);
    background:#fff;
    box-sizing:border-box;
}

.wst-dod-input:focus{
    outline:none;
    border-color:var(--dod-green);
    box-shadow:0 0 0 3px rgba(22,101,52,.12);
}

.wst-dod-btn{
    min-height:40px;
    border:none;
    border-radius:8px;
    padding:9px 14px;
    font-size:14px;
    font-weight:900;
    cursor:pointer;
}

.wst-dod-btn-primary{
    background:var(--dod-green);
    color:#fff;
}

.wst-dod-btn-primary:hover{
    background:var(--dod-green-dark);
}

.wst-dod-summary{
    display:flex;
    justify-content:space-between;
    align-items:center;
    gap:10px;
    margin:0 0 8px;
    color:#334155;
    font-size:13px;
    font-weight:800;
}

.wst-dod-hidden-toggle{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    min-height:30px;
    padding:7px 12px;
    border:1px solid #cbd5e1;
    border-radius:999px;
    background:#ffffff;
    color:#334155 !important;
    font-size:12px;
    font-weight:900;
    line-height:1;
    text-decoration:none !important;
    white-space:nowrap;
}

.wst-dod-hidden-toggle:hover,
.wst-dod-hidden-toggle:focus{
    background:#f8fafc;
    border-color:#94a3b8;
    color:#0f172a !important;
    text-decoration:none !important;
}

.wst-dod-table-card{
    padding:0;
    overflow:hidden;
}

.wst-dod-table-scroll{
    display:block;
    width:100%;
    max-width:100%;
    overflow-x:auto;
    overflow-y:hidden;
    -webkit-overflow-scrolling:touch;
    scrollbar-width:thin;
    scrollbar-color:#94a3b8 #e5e7eb;
}

.wst-dod-table-scroll::-webkit-scrollbar{
    height:12px;
}

.wst-dod-table-scroll::-webkit-scrollbar-thumb{
    background:#94a3b8;
    border-radius:999px;
}

.wst-dod-table-scroll::-webkit-scrollbar-track{
    background:#e5e7eb;
    border-radius:999px;
}

.wst-dod-table{
    width:100%;
    min-width:1160px;
    border-collapse:collapse;
    table-layout:fixed;
    background:#fff;
}

.wst-dod-table th{
    background:#f8fafc;
    color:#334155;
    font-size:12px;
    font-weight:900;
    text-align:left;
    padding:8px 7px;
    border-bottom:1px solid var(--dod-line);
    white-space:nowrap;
}

.wst-dod-table td{
    padding:8px 7px;
    vertical-align:top;
    color:var(--dod-text);
    font-size:13px;
    line-height:1.25;
}

.wst-dod-table tbody tr{
    box-shadow:inset 0 -1px 0 #edf2f7;
}

.wst-dod-table tbody tr:nth-child(odd){
    background:#ffffff;
}

.wst-dod-table tbody tr:nth-child(even){
    background:#f1f8f3;
}

.wst-dod-table tbody tr:hover{
    background:#e8f5ec;
}

.wst-dod-table tbody tr.wst-dod-row-hidden{
    background:#f8fafc;
    opacity:.78;
}

.wst-dod-table tbody tr.wst-dod-row-hidden:hover{
    background:#eef2f7;
    opacity:1;
}

.wst-dod-col-date{width:12%;}
.wst-dod-col-doc{width:9%;}
.wst-dod-col-customer{width:19%;}
.wst-dod-col-driver{width:10%;}
.wst-dod-col-status{width:10%;}
.wst-dod-col-summary{width:7%;}
.wst-dod-col-items{width:18%;}
.wst-dod-col-action{width:15%;}

.wst-dod-docno{
    font-weight:900;
    white-space:nowrap;
}

.wst-dod-date{
    white-space:normal;
}

.wst-dod-date-main{
    font-weight:900;
    white-space:nowrap;
}

.wst-dod-date-sub{
    margin-top:2px;
    color:#64748b;
    font-size:11px;
    font-weight:800;
    line-height:1.25;
    word-break:break-word;
}

.wst-dod-customer-name{
    font-size:14px;
    font-weight:900;
    line-height:1.15;
    color:#020617;
    word-break:break-word;
}

.wst-dod-customer-code{
    margin-top:2px;
    color:#64748b;
    font-size:11px;
    font-weight:800;
    word-break:break-word;
}

.wst-dod-driver{
    font-weight:800;
    word-break:break-word;
    text-transform:uppercase;
}

.wst-dod-summary-cell{
    white-space:nowrap;
    font-size:12px;
}

.wst-dod-summary-cell strong{
    font-weight:900;
}

.wst-dod-badge{
    position:relative;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    border-radius:999px;
    border:1px solid;
    padding:3px 7px;
    max-width:100%;
    font-size:10px;
    line-height:1.1;
    font-weight:900;
    text-transform:uppercase;
    white-space:normal;
}

.wst-dod-badge:hover::after,
.wst-dod-badge:focus::after{
    content:attr(data-status-help);
    position:absolute;
    left:0;
    top:calc(100% + 7px);
    z-index:5;
    width:220px;
    padding:8px 10px;
    border:1px solid #cbd5e1;
    border-radius:8px;
    background:#0f172a;
    color:#fff;
    font-size:12px;
    font-weight:800;
    line-height:1.35;
    text-transform:none;
    white-space:normal;
    box-shadow:0 12px 24px rgba(15,23,42,.2);
}

.wst-dod-badge-good{
    color:#166534;
    background:#dcfce7;
    border-color:#86efac;
}

.wst-dod-badge-info{
    color:#075985;
    background:#e0f2fe;
    border-color:#7dd3fc;
}

.wst-dod-badge-warn{
    color:#92400e;
    background:#fef3c7;
    border-color:#fbbf24;
}

.wst-dod-badge-danger{
    color:#9f1239;
    background:#ffe4e6;
    border-color:#fda4af;
}

.wst-dod-badge-hidden{
    margin-top:4px;
    color:#475569;
    background:#f1f5f9;
    border-color:#cbd5e1;
}

.wst-dod-action{
    display:flex;
    align-items:flex-start;
    gap:5px;
    flex-wrap:wrap;
    vertical-align:top;
}

.wst-dod-action-btn{
    appearance:none;
    -webkit-appearance:none;
    display:inline-flex !important;
    align-items:center;
    justify-content:center;
    min-height:30px;
    min-width:54px;
    padding:7px 8px;
    border-radius:999px;
    border:1px solid;
    font-size:10.5px;
    font-weight:900;
    line-height:1;
    text-decoration:none !important;
    box-shadow:none !important;
    cursor:pointer;
    flex:0 0 auto;
    transition:background .15s ease, border-color .15s ease, color .15s ease, transform .15s ease;
}

.wst-dod-action-print{
    background:var(--dod-green);
    border-color:var(--dod-green);
    color:#ffffff !important;
}

.wst-dod-action-edit{
    background:#eff6ff;
    border-color:#93c5fd;
    color:#1d4ed8 !important;
}

.wst-dod-action-view{
    background:#ffffff;
    border-color:#cbd5e1;
    color:#334155 !important;
}

.wst-dod-inline-form{
    display:inline-flex;
    margin:0;
    padding:0;
}

.wst-dod-inline-form button{
    font-family:inherit;
}

.wst-dod-action-delete{
    background:#fff1f2;
    border-color:#fda4af;
    color:#9f1239 !important;
}

.wst-dod-action-active{
    background:#f0fdf4;
    border-color:#86efac;
    color:#166534 !important;
}

.wst-dod-action-disabled{
    background:#f8fafc;
    border-color:#e2e8f0;
    color:#94a3b8 !important;
    cursor:not-allowed;
}

.wst-dod-action-btn:hover{
    filter:none;
    transform:translateY(-1px);
}

.wst-dod-action-print:hover{
    background:var(--dod-green-dark);
    border-color:var(--dod-green-dark);
}

.wst-dod-action-edit:hover{
    background:#dbeafe;
    border-color:#60a5fa;
}

.wst-dod-action-view:hover{
    background:#f8fafc;
    border-color:#94a3b8;
}

.wst-dod-action-delete:hover{
    background:#ffe4e6;
    border-color:#fb7185;
}

.wst-dod-action-active:hover{
    background:#dcfce7;
    border-color:#4ade80;
}

.wst-dod-item{
    padding:0 0 6px;
    margin-bottom:6px;
}

.wst-dod-item:last-child{
    border-bottom:0;
    margin-bottom:0;
    padding-bottom:0;
}

.wst-dod-item-name{
    font-size:13px;
    font-weight:900;
    line-height:1.2;
    color:#020617;
    text-transform:uppercase;
}

.wst-dod-item-meta{
    margin-top:2px;
    color:#64748b;
    font-size:11px;
    font-weight:800;
    word-break:break-word;
}

.wst-dod-muted,
.wst-dod-empty{
    color:var(--dod-muted);
    font-weight:800;
}

.wst-dod-empty{
    text-align:center;
    padding:22px 12px !important;
}

@media (max-width:760px){
    .wst-dod-wrap{
        padding:6px;
    }

    .wst-dod-filter-grid{
        grid-template-columns:1fr 1fr;
    }

    .wst-dod-summary{
        align-items:flex-start;
        flex-direction:column;
    }

    .wst-dod-table{
        min-width:1160px;
    }
}

@media (max-width:480px){
    .wst-dod-filter-grid{
        grid-template-columns:1fr;
    }

    .wst-dod-table{
        min-width:1120px;
    }
}
</style>�x<�`�	w!���������{a�
N?�"<?php
/**
 * VegeBasketDO staff-only Delivery Order edit page.
 *
 * Drop this snippet on /edit-delivery-order/.
 * Open with: /edit-delivery-order/?docNo=DO-000074&docKey=409
 */

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

if (!is_user_logged_in()) {
    echo '<div class="wst-doe-alert wst-doe-alert-error">Please log in to edit Delivery Orders.</div>';
    return;
}

$wst_doe_user  = wp_get_current_user();
$wst_doe_roles = is_array($wst_doe_user->roles ?? null) ? $wst_doe_user->roles : array();
$wst_doe_staff = current_user_can('manage_options') || in_array('editor', $wst_doe_roles, true);

if (!$wst_doe_staff) {
    echo '<div class="wst-doe-alert wst-doe-alert-error">You do not have permission to edit Delivery Orders.</div>';
    return;
}

// The edit page is WordPress-first. AutoCount/MSSQL is optional here and is used only for item validation when available.
global $wpdb;

if (!defined('WST_DOE_MAX_LINES')) {
    define('WST_DOE_MAX_LINES', 80);
}

if (!defined('WST_DOE_MAX_UNIT_QTY')) {
    define('WST_DOE_MAX_UNIT_QTY', 9999);
}

if (!defined('WST_DOE_MAX_KG_PER_UNIT')) {
    define('WST_DOE_MAX_KG_PER_UNIT', 9999);
}

if (!defined('WST_DOE_MAX_TOTAL_KG')) {
    define('WST_DOE_MAX_TOTAL_KG', 999999);
}

if (!function_exists('wst_doe_table_exists')) {
    function wst_doe_table_exists($table_name) {
        global $wpdb;
        return $wpdb && $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table_name)) === $table_name;
    }
}

if (!function_exists('wst_doe_table_columns')) {
    function wst_doe_table_columns($table_name) {
        global $wpdb;
        static $cache = array();

        if (isset($cache[$table_name])) {
            return $cache[$table_name];
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', (string) $table_name);
        $cols = $wpdb ? $wpdb->get_col("SHOW COLUMNS FROM `{$safe_table}`", 0) : array();
        $cache[$table_name] = is_array($cols) ? array_flip($cols) : array();

        return $cache[$table_name];
    }
}

if (!function_exists('wst_doe_json_array')) {
    function wst_doe_json_array($json) {
        $decoded = json_decode((string) $json, true);
        return is_array($decoded) ? $decoded : array();
    }
}

if (!function_exists('wst_doe_pick')) {
    function wst_doe_pick($arr, $keys, $fallback = '') {
        if (!is_array($arr)) {
            return $fallback;
        }

        foreach ($keys as $key) {
            if (isset($arr[$key]) && $arr[$key] !== '' && $arr[$key] !== null) {
                return $arr[$key];
            }
        }

        return $fallback;
    }
}

if (!function_exists('wst_doe_clean_doc_no')) {
    function wst_doe_clean_doc_no($value) {
        $value = strtoupper(trim(sanitize_text_field((string) $value)));
        return preg_match('/^[A-Z0-9][A-Z0-9\-\/]{1,49}$/', $value) ? $value : '';
    }
}

if (!function_exists('wst_doe_valid_date')) {
    function wst_doe_valid_date($value, $fallback = '') {
        $value = trim((string) $value);
        $dt = DateTime::createFromFormat('Y-m-d', $value, wp_timezone());
        return ($dt && $dt->format('Y-m-d') === $value) ? $value : $fallback;
    }
}

if (!function_exists('wst_doe_float')) {
    function wst_doe_float($value) {
        $value = is_string($value) ? str_replace(',', '', $value) : $value;
        return is_numeric($value) ? (float) $value : 0.0;
    }
}

if (!function_exists('wst_doe_sql_errors')) {
    function wst_doe_sql_errors() {
        if (!function_exists('sqlsrv_errors')) {
            return 'Unknown SQL Server error.';
        }

        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
        if (empty($errors)) {
            return 'Unknown SQL Server error.';
        }

        $out = array();
        foreach ($errors as $error) {
            $out[] = '[' . ($error['code'] ?? '') . '] ' . ($error['message'] ?? '');
        }

        return implode(' | ', $out);
    }
}

if (!function_exists('wst_doe_doc_match')) {
    function wst_doe_doc_match($payload, $result, $doc_no, $doc_key) {
        $found_no = strtoupper(trim((string) wst_doe_pick($result, array('docNo', 'DocNo', 'doc_no'), '')));
        if ($found_no === '') {
            $found_no = strtoupper(trim((string) wst_doe_pick($payload, array('docNo', 'DocNo', 'sourceDocNo', 'oldDocNo', 'originalDocNo'), '')));
        }

        $found_key = (int) wst_doe_pick($result, array('docKey', 'DocKey', 'doc_key'), 0);
        if ($found_key <= 0) {
            $found_key = (int) wst_doe_pick($payload, array('docKey', 'DocKey', 'doc_key'), 0);
        }

        if ($doc_no !== '' && $doc_key > 0) {
            return $found_no === $doc_no && $found_key === $doc_key;
        }

        return ($doc_no !== '' && $found_no === $doc_no) || ($doc_key > 0 && $found_key === $doc_key);
    }
}

if (!function_exists('wst_doe_job_matches_doc')) {
    function wst_doe_job_matches_doc($row, $doc_no, $doc_key) {
        $payload = wst_doe_json_array($row['payload'] ?? '');
        $result = wst_doe_json_array($row['result'] ?? '');
        return wst_doe_doc_match($payload, $result, $doc_no, $doc_key);
    }
}

if (!function_exists('wst_doe_jobs_ref_columns_ready')) {
    function wst_doe_jobs_ref_columns_ready($cols) {
        foreach (array('source_doc_no', 'source_doc_key', 'delivery_status', 'job_subtype', 'status') as $col) {
            if (!isset($cols[$col])) {
                return false;
            }
        }

        return true;
    }
}

if (!function_exists('wst_doe_editable_statuses')) {
    function wst_doe_editable_statuses() {
        /*
         * Editing is intentionally not locked by delivery status anymore.
         * Keep this function for backward compatibility with older code paths.
         */
        return array();
    }
}

if (!function_exists('wst_doe_get_local_do_by_identity')) {
    function wst_doe_get_local_do_by_identity($doc_no, $doc_key = 0, $job_id = 0) {
        global $wpdb;
        $table = $wpdb->prefix . 'ac_do';
        if (!wst_doe_table_exists($table)) {
            return null;
        }
        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $where = array();
        $args = array();
        if ($job_id > 0) {
            $where[] = 'source_job_id = %d';
            $args[] = (int) $job_id;
        }
        if ($doc_no !== '') {
            $where[] = 'local_doc_no = %s';
            $args[] = $doc_no;
            $where[] = 'autocount_doc_no = %s';
            $args[] = $doc_no;
        }
        if ($doc_key > 0) {
            $where[] = 'autocount_doc_key = %d';
            $args[] = (int) $doc_key;
        }
        if (empty($where)) {
            return null;
        }
        $deleted_sql = isset(wst_doe_table_columns($table)['deleted_at']) ? ' AND deleted_at IS NULL' : '';
        return $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM `{$safe_table}` WHERE (" . implode(' OR ', $where) . ") {$deleted_sql} ORDER BY id DESC LIMIT 1",
            $args
        ), ARRAY_A);
    }
}

if (!function_exists('wst_doe_synthetic_job_from_local_do')) {
    function wst_doe_synthetic_job_from_local_do($do_row) {
        if (!$do_row || !is_array($do_row)) {
            return null;
        }
        $doc_no = trim((string) ($do_row['local_doc_no'] ?? ''));
        if ($doc_no === '') {
            $doc_no = trim((string) ($do_row['autocount_doc_no'] ?? ''));
        }
        $payload = array(
            'docNo' => $doc_no,
            'DocNo' => $doc_no,
            'docKey' => (int) ($do_row['autocount_doc_key'] ?? 0),
            'docDate' => (string) ($do_row['doc_date'] ?? ''),
            'debtorCode' => (string) ($do_row['debtor_code'] ?? ''),
            'debtorName' => (string) ($do_row['debtor_name'] ?? ''),
            'salesAgent' => (string) ($do_row['sales_agent'] ?? ''),
            'location' => (string) ($do_row['location'] ?? 'HQ'),
            'assignedDriverId' => (int) ($do_row['assigned_driver_id'] ?? 0),
        );
        return array(
            'id' => (int) ($do_row['source_job_id'] ?? 0),
            'payload' => wp_json_encode($payload),
            'result' => '',
            'status' => (string) ($do_row['sync_status'] ?? ''),
            'created_by' => (int) ($do_row['created_by'] ?? 0),
            'updated_at' => (string) ($do_row['updated_at'] ?? ''),
            'job_subtype' => '',
            'delivery_status' => (string) ($do_row['delivery_status'] ?? ''),
            'assigned_driver_id' => (int) ($do_row['assigned_driver_id'] ?? 0),
            'error_message' => (string) ($do_row['last_sync_error'] ?? ''),
            'source_doc_no' => $doc_no,
            'source_doc_key' => (int) ($do_row['autocount_doc_key'] ?? 0),
            '_payload' => $payload,
            '_result' => array(),
            '_local_do' => $do_row,
        );
    }
}

if (!function_exists('wst_doe_get_related_job')) {
    function wst_doe_get_related_job($doc_no, $doc_key, $job_id = 0) {
        global $wpdb;

        // WordPress-only: when there is no AutoCount docKey, the local DO is the authority.
        if ($doc_key <= 0) {
            $local = wst_doe_get_local_do_by_identity($doc_no, $doc_key, $job_id);
            if ($local) {
                return wst_doe_synthetic_job_from_local_do($local);
            }
        }

        $table = $wpdb->prefix . 'ac_jobs';
        if (!wst_doe_table_exists($table)) {
            $local = wst_doe_get_local_do_by_identity($doc_no, $doc_key, $job_id);
            $synthetic = wst_doe_synthetic_job_from_local_do($local);
            return $synthetic ?: new WP_Error('local_do_missing', 'Delivery Order was not found in the local DO table.');
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $cols = wst_doe_table_columns($table);
        if (!wst_doe_jobs_ref_columns_ready($cols)) {
            return new WP_Error('jobs_ref_columns_missing', 'AutoCount job reference columns are missing. Editing is blocked until the bridge schema is upgraded.');
        }

        $select = array('id', 'payload', 'result', 'status', 'created_by', 'updated_at');
        foreach (array('job_subtype', 'delivery_status', 'assigned_driver_id', 'error_message') as $col) {
            if (isset($cols[$col])) {
                $select[] = $col;
            }
        }
        $select[] = 'source_doc_no';
        $select[] = 'source_doc_key';

        // WordPress-only: look up by job_id directly.
        if ($job_id > 0 && $doc_key <= 0) {
            $rows = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT " . implode(',', array_map(function($col) { return "`{$col}`"; }, $select)) . "
                     FROM `{$safe_table}`
                     WHERE job_type = %s
                       AND id = %d
                     LIMIT 1",
                    'DELIVERY_ORDER',
                    $job_id
                ),
                ARRAY_A
            );
        } else {
            $rows = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT " . implode(',', array_map(function($col) { return "`{$col}`"; }, $select)) . "
                     FROM `{$safe_table}`
                     WHERE job_type = %s
                       AND source_doc_no = %s
                       AND source_doc_key = %d
                     ORDER BY id DESC
                     LIMIT 25",
                    'DELIVERY_ORDER',
                    $doc_no,
                    (int) $doc_key
                ),
                ARRAY_A
            );
        }

        if (empty($rows)) {
            $local = wst_doe_get_local_do_by_identity($doc_no, $doc_key, $job_id);
            $synthetic = wst_doe_synthetic_job_from_local_do($local);
            return $synthetic ?: new WP_Error('local_do_missing', 'Delivery Order was not found in the local DO table.');
        }

        $update_statuses = array('EDIT_PENDING_AUTOCOUNT', 'EDITED_IN_AUTOCOUNT');
        foreach ((array) $rows as $row) {
            $delivery_status = strtoupper(trim((string) ($row['delivery_status'] ?? '')));
            $job_subtype = strtoupper(trim((string) ($row['job_subtype'] ?? '')));

            if (!in_array($job_subtype, array('UPDATE', 'EDIT'), true) || !in_array($delivery_status, $update_statuses, true)) {
                $row['_payload'] = wst_doe_json_array($row['payload'] ?? '');
                $row['_result'] = wst_doe_json_array($row['result'] ?? '');
                return $row;
            }
        }

        return new WP_Error('authoritative_lifecycle_missing', 'Only edit-job records were found for this Delivery Order. Editing is blocked because the delivery lifecycle status cannot be confirmed.');
    }
}

if (!function_exists('wst_doe_get_pending_update_job_id')) {
    function wst_doe_get_pending_update_job_id($doc_no, $doc_key) {
        global $wpdb;

        $table = $wpdb->prefix . 'ac_jobs';
        if (!wst_doe_table_exists($table)) {
            return 0;
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $cols = wst_doe_table_columns($table);
        if (!wst_doe_jobs_ref_columns_ready($cols)) {
            return -1;
        }

        $rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT id, payload, result
                 FROM `{$safe_table}`
                 WHERE job_type = %s
                   AND job_subtype IN ('UPDATE', 'EDIT')
                   AND status IN ('PENDING', 'PROCESSING', 'RETRY')
                   AND source_doc_no = %s
                   AND source_doc_key = %d
                 ORDER BY id DESC
                 LIMIT 50",
                'DELIVERY_ORDER',
                $doc_no,
                (int) $doc_key
            ),
            ARRAY_A
        );

        foreach ((array) $rows as $row) {
            return absint($row['id'] ?? 0);
        }

        return 0;
    }
}

if (!function_exists('wst_doe_saved_job_matches_current_do')) {
    function wst_doe_saved_job_matches_current_do($job_id, $doc_no, $doc_key) {
        global $wpdb;

        $job_id = absint($job_id);
        if ($job_id <= 0) {
            return false;
        }

        $table = $wpdb->prefix . 'ac_jobs';
        if (!wst_doe_table_exists($table)) {
            return false;
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $cols = wst_doe_table_columns($table);
        if (!wst_doe_jobs_ref_columns_ready($cols)) {
            return false;
        }

        // WordPress-only: match by job_id directly (in-place update, no subtype check).
        if ($doc_key <= 0) {
            $row = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT id
                     FROM `{$safe_table}`
                     WHERE id = %d
                       AND job_type = %s
                       AND source_doc_no = %s
                     LIMIT 1",
                    $job_id,
                    'DELIVERY_ORDER',
                    $doc_no
                ),
                ARRAY_A
            );
            return !empty($row);
        }

        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT id, payload, result, created_by, source_doc_no, source_doc_key
                 FROM `{$safe_table}`
                 WHERE id = %d
                   AND job_type = %s
                   AND job_subtype IN ('UPDATE', 'EDIT')
                   AND source_doc_no = %s
                   AND source_doc_key = %d
                 LIMIT 1",
                $job_id,
                'DELIVERY_ORDER',
                $doc_no,
                (int) $doc_key
            ),
            ARRAY_A
        );

        if (!$row || absint($row['created_by'] ?? 0) !== get_current_user_id()) {
            return false;
        }

        return true;
    }
}

if (!function_exists('wst_do�{a�o ��"���������{��
N?�#e_driver_label')) {
    function wst_doe_driver_label($driver_id, $fallback = '') {
        $driver_id = absint($driver_id);
        if ($driver_id > 0) {
            $user = get_userdata($driver_id);
            if ($user) {
                $display = trim((string) $user->display_name);
                return $display !== '' ? $display : (string) $user->user_login;
            }
        }

        return trim((string) $fallback);
    }
}

if (!function_exists('wst_doe_is_driver_user')) {
    function wst_doe_is_driver_user($driver_id) {
        $driver_id = absint($driver_id);
        if ($driver_id <= 0) {
            return false;
        }

        $user = get_userdata($driver_id);
        return $user && is_array($user->roles) && in_array('driver', $user->roles, true);
    }
}

if (!function_exists('wst_doe_edit_locked_statuses')) {
    function wst_doe_edit_locked_statuses() {
        /*
         * Editing is intentionally not locked by delivery status anymore.
         * Keep this function for backward compatibility with older code paths.
         */
        return array();
    }
}

if (!function_exists('wst_doe_is_edit_locked_status')) {
    function wst_doe_is_edit_locked_status($delivery_status) {
        return false;
    }
}

if (!function_exists('wst_doe_load_order')) {
    function wst_doe_load_order($conn, $doc_no, $doc_key) {
        global $wpdb;

        $do = wst_doe_get_local_do_by_identity($doc_no, $doc_key, 0);
        if (!$do) {
            return new WP_Error('do_not_found', 'Delivery Order was not found in the local DO table.');
        }

        $items_table = $wpdb->prefix . 'ac_do_items';
        if (!wst_doe_table_exists($items_table)) {
            return new WP_Error('do_items_missing', 'Delivery Order item table was not found.');
        }
        $safe_items_table = preg_replace('/[^A-Za-z0-9_]/', '', $items_table);
        $item_rows = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM `{$safe_items_table}` WHERE do_id = %d ORDER BY line_no ASC, id ASC",
            (int) ($do['id'] ?? 0)
        ), ARRAY_A);

        $lines = array();
        foreach ((array) $item_rows as $line) {
            $basket = wst_doe_float($line['basket_qty'] ?? 0);
            $carton = wst_doe_float($line['carton_qty'] ?? 0);
            $unit_qty = wst_doe_float($line['unit_qty'] ?? 0);
            $pack_type = strtoupper(trim((string) ($line['pack_type'] ?? '')));
            if ($pack_type !== 'CARTON' && $pack_type !== 'BASKET') {
                $pack_type = $carton > 0 ? 'CARTON' : 'BASKET';
            }
            if ($unit_qty <= 0) {
                $unit_qty = $pack_type === 'CARTON' ? $carton : $basket;
            }
            if ($unit_qty <= 0) {
                $unit_qty = 1;
            }
            $total_kg = wst_doe_float($line['total_weight_kg'] ?? 0);
            if ($total_kg <= 0) {
                $total_kg = wst_doe_float($line['qty'] ?? 0);
            }
            $kg_per_unit = wst_doe_float($line['weight_kg'] ?? 0);
            if ($kg_per_unit <= 0 && $total_kg > 0 && $unit_qty > 0) {
                $kg_per_unit = $total_kg / $unit_qty;
            }

            $lines[] = array(
                'itemCode' => trim((string) ($line['item_code'] ?? '')),
                'itemName' => trim((string) ($line['description'] ?? $line['item_code'] ?? '')),
                'uom' => trim((string) ($line['uom'] ?? 'KG')) ?: 'KG',
                'unitPrice' => wst_doe_float($line['unit_price'] ?? 0),
                'packType' => $pack_type,
                'unitQty' => $unit_qty,
                'kg' => $kg_per_unit,
                'totalKg' => $total_kg > 0 ? $total_kg : ($unit_qty * $kg_per_unit),
                'location' => trim((string) ($line['location'] ?? ($do['location'] ?? 'HQ'))),
            );
        }

        return array(
            'doId' => (int) ($do['id'] ?? 0),
            'docKey' => (int) ($do['autocount_doc_key'] ?? 0),
            'docNo' => trim((string) ($do['local_doc_no'] ?: ($do['autocount_doc_no'] ?? $doc_no))),
            'docDate' => wst_doe_valid_date((string) ($do['doc_date'] ?? ''), current_time('Y-m-d')),
            'debtorCode' => trim((string) ($do['debtor_code'] ?? '')),
            'debtorName' => trim((string) ($do['debtor_name'] ?? '')),
            'salesAgent' => trim((string) ($do['sales_agent'] ?? '')),
            'location' => trim((string) ($do['location'] ?? 'HQ')),
            'lines' => $lines,
            '_localDo' => $do,
        );
    }
}

if (!function_exists('wst_doe_load_order_from_job')) {
    function wst_doe_load_order_from_job($job_id, $doc_no) {
        global $wpdb;

        $job_row = $wpdb->get_row($wpdb->prepare(
            "SELECT id, payload, source_doc_no, assigned_driver_id, delivery_status, status
             FROM {$wpdb->prefix}ac_jobs
             WHERE id = %d AND job_type = 'DELIVERY_ORDER'
             LIMIT 1",
            $job_id
        ), ARRAY_A);

        if (!$job_row) {
            return new WP_Error('job_not_found', 'WordPress delivery order job was not found.');
        }

        $payload = wst_doe_json_array($job_row['payload'] ?? '');
        if (empty($payload)) {
            return new WP_Error('job_empty', 'WordPress delivery order job payload is empty.');
        }

        $lines = array();
        foreach ((array) ($payload['lines'] ?? array()) as $line) {
            $item_code = trim((string) ($line['itemCode'] ?? ''));
            if ($item_code === '') {
                continue;
            }
            $basket = wst_doe_float($line['basketQty'] ?? 0);
            $carton = wst_doe_float($line['cartonQty'] ?? 0);
            $unit_qty = wst_doe_float($line['unitQty'] ?? 0);
            $kg = wst_doe_float($line['kg'] ?? 0);
            $total_kg = wst_doe_float($line['totalKg'] ?? 0);
            $pack_type = $carton > 0 ? 'CARTON' : 'BASKET';
            if ($unit_qty <= 0) {
                $unit_qty = 1;
            }
            if ($total_kg <= 0 && $unit_qty > 0 && $kg > 0) {
                $total_kg = round($unit_qty * $kg, 4);
            }

            $lines[] = array(
                'itemCode' => $item_code,
                'itemName' => trim((string) ($line['itemName'] ?? $line['description'] ?? $item_code)),
                'uom' => trim((string) ($line['uom'] ?? 'KG')),
                'unitPrice' => wst_doe_float($line['unitPrice'] ?? 0),
                'packType' => $pack_type,
                'unitQty' => $unit_qty,
                'kg' => $kg,
                'totalKg' => $total_kg,
                'location' => trim((string) ($line['location'] ?? 'HQ')),
            );
        }

        if (empty($lines)) {
            return new WP_Error('job_no_lines', 'WordPress delivery order job has no item lines.');
        }

        $doc_date = wst_doe_valid_date((string) ($payload['docDate'] ?? ''), current_time('Y-m-d'));

        return array(
            'docKey' => 0,
            'docNo' => $doc_no,
            'docDate' => $doc_date,
            'debtorCode' => trim((string) ($payload['debtorCode'] ?? $payload['customerCode'] ?? '')),
            'debtorName' => trim((string) ($payload['debtorName'] ?? $payload['customerName'] ?? '')),
            'salesAgent' => trim((string) ($payload['salesAgent'] ?? '')),
            'lines' => $lines,
        );
    }
}

if (!function_exists('wst_doe_original_line_map')) {
    function wst_doe_original_line_map($order) {
        $map = array();
        foreach ((array) ($order['lines'] ?? array()) as $line) {
            $code = strtoupper(trim((string) ($line['itemCode'] ?? '')));
            if ($code !== '' && !isset($map[$code])) {
                $map[$code] = $line;
            }
        }
        return $map;
    }
}

if (!function_exists('wst_doe_active_masterdata_environment')) {
    function wst_doe_active_masterdata_environment() {
        $env = strtolower(trim((string) get_option('ac_bridge_active_environment', '')));

        if ($env === '') {
            $status = get_option('ac_bridge_sync_status', array());
            if (is_array($status)) {
                $env = strtolower(trim((string) ($status['environment'] ?? '')));
            }
        }

        if ($env === 'production') {
            $env = '';
        }

        return sanitize_key($env);
    }
}

if (!function_exists('wst_doe_masterdata_table')) {
    function wst_doe_masterdata_table($base) {
        global $wpdb;

        $base = preg_replace('/[^A-Za-z0-9_]/', '', (string) $base);
        $env = wst_doe_active_masterdata_environment();

        $preferred = $env !== ''
            ? $wpdb->prefix . $base . '_' . $env
            : $wpdb->prefix . $base;

        if (wst_doe_table_exists($preferred)) {
            return $preferred;
        }

        $fallback = $wpdb->prefix . $base;
        if (wst_doe_table_exists($fallback)) {
            return $fallback;
        }

        $local_fallback = $wpdb->prefix . $base . '_local';
        if (wst_doe_table_exists($local_fallback)) {
            return $local_fallback;
        }

        return $preferred;
    }
}

if (!function_exists('wst_doe_validate_item')) {
    function wst_doe_validate_item($conn, $item_code) {
        static $cache = array();

        $item_code = trim((string) $item_code);
        if ($item_code === '') {
            return new WP_Error('item_required', 'Item code is required.');
        }

        $cache_key = strtoupper($item_code);
        if (isset($cache[$cache_key])) {
            return $cache[$cache_key];
        }

        // WordPress-first: look up item in the synced AutoCount masterdata cache table.
        global $wpdb;
        $items_table = wst_doe_masterdata_table('acs_items');

        if ($wpdb && $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $items_table)) === $items_table) {
            $row = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT item_code, description, base_uom
                     FROM `{$items_table}`
                     WHERE item_code = %s
                       AND is_active = 1
                     LIMIT 1",
                    $item_code
                ),
                ARRAY_A
            );

            if ($row) {
                $result = array(
                    'itemCode' => trim((string) ($row['item_code'] ?? $item_code)),
                    'description' => trim((string) ($row['description'] ?? $item_code)),
                    'uom' => trim((string) ($row['base_uom'] ?? 'KG')) ?: 'KG',
                    'defaultPrice' => 0.0,
                );
                $cache[$cache_key] = $result;
                return $result;
            }
        }

        // Legacy fallback: direct AutoCount SQL Server lookup when the WordPress cache is unavailable.
        if ($conn && function_exists('sqlsrv_query')) {
            $sql = "
                SELECT TOP 1
                    i.ItemCode,
                    ISNULL(i.Description, '') AS Description,
                    ISNULL(i.BaseUOM, '') AS BaseUOM,
                    ISNULL(iu.Price, 0) AS Price
                FROM Item i
                INNER JOIN ItemUOM iu
                    ON iu.ItemCode = i.ItemCode
                    AND iu.UOM = i.BaseUOM
                WHERE i.ItemCode = ?
                  AND i.IsActive = 'T'
            ";
            $stmt = sqlsrv_query($conn, $sql, array($item_code), array('QueryTimeout' => 10));
            if ($stmt === false) {
                return new WP_Error('item_lookup_failed', wst_doe_sql_errors());
            }

            $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
            sqlsrv_free_stmt($stmt);

            if ($row) {
                $result = array(
                    'itemCode' => trim((string) ($row['ItemCode'] ?? $item_code)),
                    'description' => trim((string) ($row['Description'] ?? $item_code)),
                    'uom' => trim((string) ($row['BaseUOM'] ?? 'KG')) ?: 'KG',
                    'defaultPrice' => wst_doe_float($row['Price'] ?? 0),
                );
                $cache[$cache_key] = $result;
                return $result;
            }
        }

        return new WP_Error('invalid_item', 'Invalid item or inactive item: ' . $item_code);
    }
}

if (!function_exists('wst_doe_update_local_do_from_payload')) {
    function wst_doe_update_local_do_from_payload($payload, $assigned_driver_id, $line_payloads) {
        global $wpdb;
        $doc_no = wst_doe_clean_doc_no($payload['docNo'] ?? '');
        $doc_key = absint($payload['docKey'] ?? 0);
        $do = wst_doe_get_local_do_by_identity($doc_no, $doc_key, 0);
        if (!$do) {
            return new WP_Error('local_do_missing', 'Delivery Order was not found in the local DO table.');
        }

        $do_table = $wpdb->prefix . 'ac_do';
        $items_table = $wpdb->prefix . 'ac_do_items';
        if (!wst_doe_table_exists($do_table) || !wst_doe_table_exists($items_table)) {
            return new WP_Error('local_do_schema_missing', 'Local DO tables are missing.');
        }
        $do_cols = wst_doe_table_columns($do_table);
        $item_cols = wst_doe_table_columns($items_table);
        $do_id = (int) ($do['id'] ?? 0);
        $now = current_time('mysql');

        $header_update = array();
        if (isset($do_cols['doc_date'])) $header_update['doc_date'] = wst_doe_valid_date((string) ($payload['docDate'] ?? ''), current_time('Y-m-d'));
        if (isset($do_cols['debtor_code'])) $header_update['debtor_code'] = sanitize_text_field((string) ($payload['debtorCode'] ?? ''));
        if (isset($do_cols['debtor_name'])) $header_update['debtor_name'] = sanitize_text_field((string) ($payload['debtorName'] ?? ''));
        if (isset($do_cols['sales_agent'])) $header_update['sales_agent'] = sanitize_text_field((string) ($payload['salesAgent'] ?? ''));
        if (isset($do_cols['location'])) $header_update['location'] = sanitize_text_field((string) ($payload['location'] ?? 'HQ'));
        if (isset($do_cols['remark'])) $header_update['remark'] = sanitize_textarea_field((string) ($payload['remark'] ?? 'Staff edited existing Delivery Order.'));
        if (isset($do_cols['assigned_driver_id'])) $header_update['assigned_driver_id'] = absint($assigned_driver_id);
        if (isset($do_cols['updated_by'])) $header_update['updated_by'] = get_current_user_id();
        if (isset($do_cols['updated_at'])) $header_update['updated_at'] = $now;
        // Preserve delivery_status; staff edits must not revert Delivered / Out for Delivery / scheduled date logic.

        $wpdb->query('START TRANSACTION');
        if (!empty($header_update)) {
            $ok = $wpdb->update($do_table, $header_update, array('id' => $do_id));
            if ($ok === false) {
                $wpdb->query('ROLLBACK');
                return new WP_Error('local_do_update_failed', 'Failed to update the local Delivery Order header.');
            }
        }

        $safe_items_table = preg_replace('/[^A-Za-z0-9_]/', '', $items_table);
        $deleted = $wpdb->query($wpdb->prepare("DELETE FROM `{$safe_items_table}` WHERE do_id = %d", $do_id));
        if ($deleted === false) {
            $wpdb->query('ROLLBACK');
            return new WP_Error('local_do_items_delete_failed', 'Failed to replace local Delivery Order lines.');
        }

        $line_no = 1;
        foreach ((array) $line_payloads as $line) {
            $pack_type = strtoupper(trim((string) ($line['packType'] ?? 'BASKET')));
            $unit_qty = wst_doe_float($line['unitQty'] ?? 0);
            $total_kg = wst_doe_float($line['totalKg'] ?? $line['qty'] ?? 0);
            $unit_price = wst_doe_float($line['unitPrice'] ?? 0);
            $insert = array(
                'do_id' => $do_id,
                'line_no' => $line_no,
                'seq' => $line_no * 16,
                'item_code' => sanitize_text_field((string�{��kQp�#���������{�m
N?�@) ($line['itemCode'] ?? '')),
                'description' => sanitize_text_field((string) ($line['description'] ?? $line['itemName'] ?? $line['itemCode'] ?? '')),
                'uom' => sanitize_text_field((string) ($line['uom'] ?? 'KG')),
                'location' => sanitize_text_field((string) ($line['location'] ?? ($payload['location'] ?? 'HQ'))),
                'qty' => $total_kg,
                'unit_price' => $unit_price,
                'sub_total' => round($unit_price * $total_kg, 6),
                'tax_code' => sanitize_text_field((string) ($line['taxCode'] ?? 'SR-0')),
                'tax_rate' => wst_doe_float($line['taxRate'] ?? 0),
                'total_amount' => round($unit_price * $total_kg, 6),
                'pack_type' => $pack_type,
                'unit_qty' => $unit_qty,
                'basket_qty' => $pack_type === 'BASKET' ? $unit_qty : null,
                'carton_qty' => $pack_type === 'CARTON' ? $unit_qty : null,
                'weight_kg' => wst_doe_float($line['kg'] ?? 0),
                'total_weight_kg' => $total_kg,
                'meta' => wp_json_encode($line),
            );
            $insert = array_intersect_key($insert, $item_cols);
            $ok = $wpdb->insert($items_table, $insert);
            if (!$ok) {
                $wpdb->query('ROLLBACK');
                return new WP_Error('local_do_item_insert_failed', 'Failed to save local Delivery Order line #' . $line_no . '.');
            }
            $line_no++;
        }

        $wpdb->query('COMMIT');
        return true;
    }
}

if (!function_exists('wst_doe_upsert_wp_only_job')) {
    function wst_doe_upsert_wp_only_job($payload, $client_request_id, $assigned_driver_id, $original_job_id) {
        global $wpdb;

        $table = $wpdb->prefix . 'ac_jobs';
        $cols = wst_doe_table_exists($table) ? wst_doe_table_columns($table) : array();
        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $now = current_time('mysql');
        $doc_no = wst_doe_clean_doc_no($payload['docNo'] ?? '');

        if ($doc_no === '') {
            return new WP_Error('missing_doc_no', 'Delivery Order update requires a DocNo.');
        }

        $client_request_id = substr(preg_replace('/[^a-zA-Z0-9\-_:.]/', '', (string) $client_request_id), 0, 64);
        if ($client_request_id === '') {
            $client_request_id = 'do-wp-only-' . wp_generate_uuid4();
        }

        $payload['_meta'] = array(
            'requestedBy' => get_current_user_id(),
            'requestedAt' => $now,
            'source' => 'wp-ui-edit',
            'ip' => isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '',
        );

        if ($assigned_driver_id > 0) {
            $payload['_assignment'] = array(
                'assignedDriverId' => $assigned_driver_id,
                'assignedAt' => $now,
                'assignedBy' => get_current_user_id(),
            );
        }

        $payload_json = wp_json_encode($payload);

        // If we already have a WP-only job for this docNo, replace its payload with the edited version.
        if ($original_job_id > 0) {
            $existing = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT id, status, delivery_status FROM `{$safe_table}` WHERE id = %d AND job_type = %s LIMIT 1",
                    $original_job_id,
                    'DELIVERY_ORDER'
                ),
                ARRAY_A
            );

            if ($existing) {
                $previous_status = strtoupper(trim((string) ($existing['delivery_status'] ?? '')));
                $update = array(
                    'payload' => $payload_json,
                    'client_request_id' => $client_request_id,
                    'updated_at' => $now,
                );
                $formats = array('%s', '%s', '%s');

                if (isset($cols['status'])) {
                    if (in_array(strtoupper($existing['status'] ?? ''), array('SUCCESS', 'EDITED_IN_AUTOCOUNT', 'FAILED_FINAL', 'CANCELLED'), true)) {
                        $update['status'] = 'PENDING';
                        $formats[] = '%s';
                    }
                }
                if (isset($cols['delivery_status'])) {
                    if ($previous_status === '') {
                        $update['delivery_status'] = $assigned_driver_id > 0 ? 'OUT_FOR_DELIVERY' : 'PENDING_DELIVERY';
                        $formats[] = '%s';
                    }
                    // else: keep the existing delivery_status unchanged.
                }
                if (isset($cols['assigned_driver_id']) && $assigned_driver_id > 0) {
                    $update['assigned_driver_id'] = $assigned_driver_id;
                    $formats[] = '%d';
                }
                if (isset($cols['assigned_at']) && $assigned_driver_id > 0) {
                    $update['assigned_at'] = $now;
                    $formats[] = '%s';
                }
                if (isset($cols['assigned_by']) && $assigned_driver_id > 0) {
                    $update['assigned_by'] = get_current_user_id();
                    $formats[] = '%d';
                }
                if (isset($cols['source_doc_no'])) {
                    $update['source_doc_no'] = $doc_no;
                    $formats[] = '%s';
                }
                if (isset($cols['source_doc_key'])) {
                    $update['source_doc_key'] = 0;
                    $formats[] = '%d';
                }

                $ok = $wpdb->update($table, $update, array('id' => $original_job_id), $formats, array('%d'));
                return $ok !== false ? (int) $original_job_id : new WP_Error('queue_failed', 'Failed to update the pending WordPress-only Delivery Order job.');
            }
        }

        // Search for any existing pending WP-only job by docNo.
        if (!empty($cols)) {
            $existing_id = $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT id FROM `{$safe_table}`
                     WHERE job_type = %s
                       AND source_doc_no = %s
                       AND source_doc_key = %d
                       AND job_subtype IN ('CREATE', 'IMPORT', 'UPDATE', 'EDIT')
                       AND status IN ('PENDING', 'PROCESSING', 'RETRY')
                     ORDER BY id DESC
                     LIMIT 1",
                    'DELIVERY_ORDER',
                    $doc_no,
                    0
                )
            );

            if ($existing_id) {
                $ok = $wpdb->update(
                    $table,
                    array(
                        'payload' => $payload_json,
                        'client_request_id' => $client_request_id,
                        'updated_at' => $now,
                    ),
                    array('id' => $existing_id),
                    array('%s', '%s', '%s'),
                    array('%d')
                );

                if ($ok !== false) {
                    return (int) $existing_id;
                }
            }
        }

        // No existing job to overwrite: insert a new pending DELIVERY_ORDER job.
        $insert = array(
            'client_request_id' => $client_request_id,
            'job_type' => 'DELIVERY_ORDER',
            'job_subtype' => 'CREATE',
            'priority' => 5,
            'payload' => $payload_json,
            'status' => 'PENDING',
            'created_by' => get_current_user_id(),
            'source' => 'wp-ui-edit',
            'max_retries' => 3,
            'source_doc_no' => $doc_no,
            'source_doc_key' => 0,
        );
        $formats = array('%s', '%s', '%s', '%d', '%s', '%s', '%d', '%s', '%d', '%s', '%d');

        if (isset($cols['assigned_driver_id']) && $assigned_driver_id > 0) {
            $insert['assigned_driver_id'] = $assigned_driver_id;
            $formats[] = '%d';
        }
        if (isset($cols['assigned_at']) && $assigned_driver_id > 0) {
            $insert['assigned_at'] = $now;
            $formats[] = '%s';
        }
        if (isset($cols['assigned_by']) && $assigned_driver_id > 0) {
            $insert['assigned_by'] = get_current_user_id();
            $formats[] = '%d';
        }
        if (isset($cols['delivery_status'])) {
            $insert['delivery_status'] = $assigned_driver_id > 0 ? 'OUT_FOR_DELIVERY' : 'PENDING_DELIVERY';
            $formats[] = '%s';
        }
        if (isset($cols['created_at'])) {
            $insert['created_at'] = $now;
            $formats[] = '%s';
        }
        if (isset($cols['updated_at'])) {
            $insert['updated_at'] = $now;
            $formats[] = '%s';
        }

        $ok = $wpdb->insert($table, $insert, $formats);
        if (!$ok) {
            return new WP_Error('queue_failed', 'Failed to create the WordPress-only Delivery Order job.');
        }

        return (int) $wpdb->insert_id;
    }
}

if (!function_exists('wst_doe_queue_update_job')) {
    function wst_doe_queue_update_job($payload, $client_request_id, $assigned_driver_id, $original_job_id) {
        global $wpdb;

        $doc_no = wst_doe_clean_doc_no($payload['docNo'] ?? '');
        $doc_key = absint($payload['docKey'] ?? 0);

        // WordPress-only: no AutoCount docKey yet. Find or create a pending DELIVERY_ORDER job
        // and replace its payload with the edited version so the bridge sends the latest copy.
        if ($doc_key <= 0) {
            return wst_doe_upsert_wp_only_job($payload, $client_request_id, $assigned_driver_id, $original_job_id);
        }

        $table = $wpdb->prefix . 'ac_jobs';
        if (!wst_doe_table_exists($table)) {
            return new WP_Error('jobs_table_missing', 'AutoCount jobs table was not found.');
        }

        $cols = wst_doe_table_columns($table);
        if (!wst_doe_jobs_ref_columns_ready($cols) || !isset($cols['pending_update_key'])) {
            return new WP_Error('jobs_ref_columns_missing', 'AutoCount job reference columns are missing. Editing is blocked until the bridge schema is upgraded.');
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $now = current_time('mysql');
        if ($doc_no === '' || $doc_key <= 0) {
            return new WP_Error('missing_do_identity', 'Delivery Order update requires DocNo and DocKey.');
        }

        if (class_exists('AutoCount_Bridge_Core') && method_exists('AutoCount_Bridge_Core', 'enqueue_protected_delivery_order_update_job')) {
            $queued = AutoCount_Bridge_Core::enqueue_protected_delivery_order_update_job(
                $payload,
                5,
                $client_request_id,
                'wp-ui-edit',
                array('assigned_driver_id' => $assigned_driver_id)
            );

            if (is_array($queued) && !empty($queued['success'])) {
                return (int)($queued['id'] ?? 0);
            }

            return new WP_Error(
                'queue_failed',
                is_array($queued) && !empty($queued['message'])
                    ? (string)$queued['message']
                    : 'Failed to queue protected AutoCount update job.'
            );
        }

        $pending_update_key = 'DELIVERY_ORDER_UPDATE:' . $doc_no . ':' . $doc_key;
        $wpdb->query('START TRANSACTION');

        $authoritative_job = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT id, delivery_status, assigned_driver_id
                 FROM `{$safe_table}`
                 WHERE job_type = %s
                   AND source_doc_no = %s
                   AND source_doc_key = %d
                   AND (
                       job_subtype IS NULL
                       OR job_subtype NOT IN ('UPDATE', 'EDIT')
                       OR delivery_status NOT IN ('EDIT_PENDING_AUTOCOUNT', 'EDITED_IN_AUTOCOUNT')
                   )
                 ORDER BY id DESC
                 LIMIT 1
                 FOR UPDATE",
                'DELIVERY_ORDER',
                $doc_no,
                $doc_key
            ),
            ARRAY_A
        );

        if (!$authoritative_job) {
            $wpdb->query('ROLLBACK');
            return new WP_Error('authoritative_job_missing', 'No authoritative delivery status was found. Editing is blocked.');
        }

        /*
         * Do not block edit queueing by delivery status.
         * Any Delivery Order with an authoritative lifecycle job can be edited.
         */
        $current_delivery_status = strtoupper(trim((string) ($authoritative_job['delivery_status'] ?? '')));

        $pending_job_id = wst_doe_get_pending_update_job_id($doc_no, $doc_key);
        if ($pending_job_id !== 0) {
            $wpdb->query('ROLLBACK');
            if ($pending_job_id < 0) {
                return new WP_Error('jobs_ref_columns_missing', 'AutoCount job reference columns are missing. Editing is blocked until the bridge schema is upgraded.');
            }

            return new WP_Error('pending_update_exists', 'AutoCount update job #' . $pending_job_id . ' is already pending for this Delivery Order. Wait for it to finish before queueing another edit.');
        }

        $client_request_id = substr(preg_replace('/[^a-zA-Z0-9\-_:.]/', '', (string) $client_request_id), 0, 64);
        if ($client_request_id === '') {
            $client_request_id = 'do-update-' . wp_generate_uuid4();
        }

        $payload['_meta'] = array(
            'requestedBy' => get_current_user_id(),
            'requestedAt' => $now,
            'source' => 'wp-ui-edit',
            'ip' => isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '',
        );

        if ($assigned_driver_id > 0) {
            $payload['_assignment'] = array(
                'assignedDriverId' => $assigned_driver_id,
                'assignedAt' => $now,
                'assignedBy' => get_current_user_id(),
            );
        }

        $insert = array(
            'client_request_id' => $client_request_id,
            'job_type' => 'DELIVERY_ORDER',
            'job_subtype' => 'UPDATE',
            'priority' => 5,
            'payload' => wp_json_encode($payload),
            'status' => 'PENDING',
            'created_by' => get_current_user_id(),
            'source' => 'wp-ui-edit',
            'max_retries' => 3,
            'source_doc_no' => $doc_no,
            'source_doc_key' => $doc_key,
            'pending_update_key' => $pending_update_key,
        );
        $formats = array('%s', '%s', '%s', '%d', '%s', '%s', '%d', '%s', '%d', '%s', '%d', '%s');

        if (isset($cols['assigned_driver_id']) && $assigned_driver_id > 0) {
            $insert['assigned_driver_id'] = $assigned_driver_id;
            $formats[] = '%d';
        }
        if (isset($cols['assigned_at']) && $assigned_driver_id > 0) {
            $insert['assigned_at'] = $now;
            $formats[] = '%s';
        }
        if (isset($cols['assigned_by']) && $assigned_driver_id > 0) {
            $insert['assigned_by'] = get_current_user_id();
            $formats[] = '%d';
        }
        if (isset($cols['delivery_status'])) {
            $insert['delivery_status'] = 'EDIT_PENDING_AUTOCOUNT';
            $formats[] = '%s';
        }
        if (isset($cols['created_at'])) {
            $insert['created_at'] = $now;
            $formats[] = '%s';
        }
        if (isset($cols['updated_at'])) {
            $insert['updated_at'] = $now;
            $formats[] = '%s';
        }

        $ok = $wpdb->insert($table, $insert, $formats);
        if (!$ok) {
            $existing = $wpdb->get_var($wpdb->prepare("SELECT id FROM `{$safe_table}` WHERE client_request_id = %s LIMIT 1", $client_request_id));
            if ($existing) {
                $wpdb->query('COMMIT');
                return (int) $existing;
            }

            $existing_pending = $wpdb->get_var($wpd�{�m���@���������|(�
N?�Ab->prepare("SELECT id FROM `{$safe_table}` WHERE pending_update_key = %s LIMIT 1", $pending_update_key));
            if ($existing_pending) {
                $wpdb->query('ROLLBACK');
                return new WP_Error('pending_update_exists', 'AutoCount update job #' . (int) $existing_pending . ' is already pending for this Delivery Order. Wait for it to finish before queueing another edit.');
            }

            $wpdb->query('ROLLBACK');
            return new WP_Error('queue_failed', 'Failed to queue AutoCount update job.');
        }

        $new_job_id = (int) $wpdb->insert_id;

        $wpdb->query('COMMIT');

        return $new_job_id;
    }
}

$wst_doe_doc_no = isset($_GET['docNo']) ? wst_doe_clean_doc_no(wp_unslash($_GET['docNo'])) : '';
$wst_doe_doc_key = isset($_GET['docKey']) ? absint($_GET['docKey']) : 0;
$wst_doe_job_id = isset($_GET['job_id']) ? absint($_GET['job_id']) : 0;
$wst_doe_error = '';
$wst_doe_saved_job_id = isset($_GET['wst_doe_saved']) ? absint($_GET['wst_doe_saved']) : 0;
$wst_doe_list_url = home_url('/delivery-order-records/');

// WordPress-only edit: canonical source is ac_do/ac_do_items.
if ($wst_doe_doc_no === '' && $wst_doe_job_id > 0) {
    $wst_doe_job_row = $wpdb->get_row($wpdb->prepare(
        "SELECT id, payload, source_doc_no, assigned_driver_id, delivery_status, status
         FROM {$wpdb->prefix}ac_jobs
         WHERE id = %d AND job_type = 'DELIVERY_ORDER'
         LIMIT 1",
        $wst_doe_job_id
    ), ARRAY_A);

    if ($wst_doe_job_row) {
        $wst_doe_payload_tmp = wst_doe_json_array($wst_doe_job_row['payload'] ?? '');
        $wst_doe_doc_no = wst_doe_clean_doc_no($wst_doe_job_row['source_doc_no'] ?? $wst_doe_payload_tmp['docNo'] ?? '');
        $wst_doe_doc_key = 0;
    }
}

if ($wst_doe_doc_no === '' && $wst_doe_job_id <= 0) {
    echo '<div class="wst-doe-alert wst-doe-alert-error">Invalid Delivery Order link. DocNo is required.</div>';
    return;
}

$wst_doe_conn = function_exists('get_mssql') ? get_mssql() : null;

$wst_doe_order = wst_doe_load_order($wst_doe_conn, $wst_doe_doc_no, $wst_doe_doc_key);
if (is_wp_error($wst_doe_order)) {
    echo '<div class="wst-doe-alert wst-doe-alert-error">' . esc_html($wst_doe_order->get_error_message()) . '</div>';
    return;
}
$wst_doe_doc_no = wst_doe_clean_doc_no($wst_doe_order['docNo'] ?? $wst_doe_doc_no);
$wst_doe_doc_key = absint($wst_doe_order['docKey'] ?? $wst_doe_doc_key);

$wst_doe_job = wst_doe_get_related_job($wst_doe_doc_no, $wst_doe_doc_key, $wst_doe_job_id);
if (is_wp_error($wst_doe_job)) {
    echo '<div class="wst-doe-alert wst-doe-alert-error">' . esc_html($wst_doe_job->get_error_message()) . '</div>';
    return;
}

$wst_doe_saved_job_id = wst_doe_saved_job_matches_current_do($wst_doe_saved_job_id, $wst_doe_doc_no, $wst_doe_doc_key) ? $wst_doe_saved_job_id : 0;
$wst_doe_original_job_id = absint($wst_doe_job['id'] ?? 0);
$wst_doe_is_wp_only = ($wst_doe_doc_key <= 0);
$wst_doe_result = isset($wst_doe_job['_result']) && is_array($wst_doe_job['_result']) ? $wst_doe_job['_result'] : array();
$wst_doe_real_doc_key = (int) wst_doe_pick($wst_doe_result, array('docKey', 'DocKey', 'doc_key'), 0);
$wst_doe_doc_key_for_form = $wst_doe_doc_key;
if ($wst_doe_real_doc_key > 0) {
    if ((int) $wst_doe_doc_key !== $wst_doe_real_doc_key) {
        $wpdb->update(
            $wpdb->prefix . 'ac_jobs',
            array('source_doc_key' => $wst_doe_real_doc_key),
            array('id' => $wst_doe_original_job_id),
            array('%d'),
            array('%d')
        );
    }
    $wst_doe_doc_key = $wst_doe_real_doc_key;
    $wst_doe_doc_key_for_form = $wst_doe_real_doc_key;
    $wst_doe_order['docKey'] = $wst_doe_real_doc_key;
}
$wst_doe_driver_id = absint($wst_doe_job['assigned_driver_id'] ?? 0);
$wst_doe_payload = isset($wst_doe_job['_payload']) && is_array($wst_doe_job['_payload']) ? $wst_doe_job['_payload'] : array();
$wst_doe_driver_fallback = wst_doe_pick($wst_doe_payload, array('assignedDriverName', 'driverName', 'assignedDriverLogin', 'driverLogin'), '');
$wst_doe_driver_label = wst_doe_driver_label($wst_doe_driver_id, $wst_doe_driver_fallback);
$wst_doe_delivery_status = strtoupper(trim((string) ($wst_doe_job['delivery_status'] ?? '')));

/*
 * Do not block this edit page by delivery status.
 * Staff can open and queue an edit for any Delivery Order status.
 */

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['wst_doe_action'])) {
    $posted_action = sanitize_key(wp_unslash($_POST['wst_doe_action']));

    if ($posted_action !== 'queue_update') {
        $wst_doe_error = 'Invalid action.';
    } elseif (!isset($_POST['wst_doe_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['wst_doe_nonce'])), 'wst_doe_queue_update')) {
        $wst_doe_error = 'Security check failed. Please reload and try again.';
    } else {
        $posted_type = isset($_POST['wst_doe_type']) ? strtoupper(sanitize_text_field(wp_unslash($_POST['wst_doe_type']))) : '';
        $posted_subtype = isset($_POST['wst_doe_subtype']) ? strtoupper(sanitize_text_field(wp_unslash($_POST['wst_doe_subtype']))) : '';
        $posted_doc_no = isset($_POST['wst_doe_doc_no']) ? wst_doe_clean_doc_no(wp_unslash($_POST['wst_doe_doc_no'])) : '';
        $posted_doc_key = isset($_POST['wst_doe_doc_key']) ? absint($_POST['wst_doe_doc_key']) : 0;
        $posted_job_id = isset($_POST['wst_doe_job_id']) ? absint($_POST['wst_doe_job_id']) : 0;
        $posted_driver_id = isset($_POST['wst_doe_driver_id']) ? absint($_POST['wst_doe_driver_id']) : $wst_doe_driver_id;
        $posted_debtor_code = sanitize_text_field((string) ($wst_doe_order['debtorCode'] ?? ''));
        $posted_debtor_name = sanitize_text_field((string) ($wst_doe_order['debtorName'] ?? ''));
        $posted_sales_agent = sanitize_text_field((string) ($wst_doe_order['salesAgent'] ?? ''));
        $posted_doc_date = isset($_POST['wst_doe_doc_date']) ? wst_doe_valid_date((string) wp_unslash($_POST['wst_doe_doc_date']), current_time('Y-m-d')) : wst_doe_valid_date((string) ($wst_doe_order['docDate'] ?? ''), current_time('Y-m-d'));
        $posted_remark = 'Staff edited existing Delivery Order.';

        $valid_subtype = ($posted_doc_key <= 0) ? array('UPDATE', 'CREATE') : array('UPDATE');

        if ($posted_type !== 'DELIVERY_ORDER' || !in_array($posted_subtype, $valid_subtype, true)) {
            $wst_doe_error = 'Invalid update request type.';
        } elseif ($posted_doc_no !== $wst_doe_doc_no || $posted_doc_key !== $wst_doe_doc_key) {
            if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                error_log('WST_DOE identity mismatch. Posted: ' . $posted_doc_no . '/' . $posted_doc_key . ' Loaded: ' . $wst_doe_doc_no . '/' . $wst_doe_doc_key);
            }
            $wst_doe_error = 'Delivery Order identity mismatch. Please reload and try again.';
        } elseif ($posted_debtor_code === '') {
            $wst_doe_error = 'Customer code is required.';
        } elseif (!wst_doe_is_driver_user($posted_driver_id)) {
            if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                error_log('WST_DOE invalid driver for ' . $posted_doc_no . ': user_id=' . $posted_driver_id);
            }
            $wst_doe_error = 'Select a valid driver before queueing the update.';
        } else {
            $posted_lines_raw = isset($_POST['wst_doe_lines']) && is_array($_POST['wst_doe_lines']) ? wp_unslash($_POST['wst_doe_lines']) : array();
            $payload_lines = array();
            $original_lines = wst_doe_original_line_map($wst_doe_order);

            if (count($posted_lines_raw) > WST_DOE_MAX_LINES) {
                $wst_doe_error = 'Too many item lines. Maximum allowed is ' . WST_DOE_MAX_LINES . '.';
            }

            foreach ($posted_lines_raw as $line) {
                if ($wst_doe_error !== '') {
                    break;
                }

                if (!is_array($line)) {
                    continue;
                }

                $item_code = sanitize_text_field($line['item_code'] ?? '');
                $pack_type = strtoupper(sanitize_text_field($line['pack_type'] ?? 'BASKET'));
                $unit_qty = wst_doe_float($line['unit_qty'] ?? 0);
                $kg = wst_doe_float($line['kg'] ?? 0);

                if ($pack_type !== 'BASKET' && $pack_type !== 'CARTON') {
                    $pack_type = 'BASKET';
                }

                $total_kg = round($unit_qty * $kg, 4);

                if ($item_code === '' || $unit_qty <= 0 || $kg <= 0 || $total_kg <= 0) {
                    continue;
                }

                if ($unit_qty > WST_DOE_MAX_UNIT_QTY || $kg > WST_DOE_MAX_KG_PER_UNIT || $total_kg > WST_DOE_MAX_TOTAL_KG) {
                    $wst_doe_error = 'Item line quantity or weight is too large.';
                    break;
                }

                $validated_item = wst_doe_validate_item($wst_doe_conn, $item_code);
                if (is_wp_error($validated_item)) {
                    if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                        error_log('WST_DOE invalid item for ' . $posted_doc_no . ': ' . $item_code . ' — ' . $validated_item->get_error_message());
                    }
                    $wst_doe_error = $validated_item->get_error_message();
                    break;
                }

                $canonical_item_code = (string) ($validated_item['itemCode'] ?? $item_code);
                $canonical_item_name = (string) ($validated_item['description'] ?? $canonical_item_code);
                $original_line = $original_lines[strtoupper($canonical_item_code)] ?? array();
                $uom = trim((string) ($original_line['uom'] ?? ($validated_item['uom'] ?? 'KG'))) ?: 'KG';
                $unit_price = isset($line['unit_price']) && is_numeric($line['unit_price'])
                    ? wst_doe_float($line['unit_price'])
                    : (isset($original_line['unitPrice']) ? wst_doe_float($original_line['unitPrice']) : wst_doe_float($validated_item['defaultPrice'] ?? 0));
                $line_location = trim((string) ($original_line['location'] ?? '')) ?: 'HQ';
                $line_amount = round($unit_price * $total_kg, 2);

                $payload_lines[] = array(
                    'itemCode' => $canonical_item_code,
                    'description' => $canonical_item_name,
                    'itemName' => $canonical_item_name,
                    'ItemName' => $canonical_item_name,
                    'itemDesc' => $canonical_item_name,
                    'uom' => $uom,
                    'unitPrice' => $unit_price,
                    'amount' => $line_amount,
                    'taxCode' => 'SR-0',
                    'taxRate' => 0,
                    'packType' => $pack_type,
                    'qty' => $total_kg,
                    'kg' => $kg,
                    'totalKg' => $total_kg,
                    'unitQty' => $unit_qty,
                    'basketQty' => $pack_type === 'BASKET' ? $unit_qty : 0,
                    'cartonQty' => $pack_type === 'CARTON' ? $unit_qty : 0,
                    'location' => $line_location,
                );
            }

            if ($wst_doe_error !== '') {
                // Error already set while validating posted lines.
            } elseif (empty($payload_lines)) {
                $wst_doe_error = 'Add at least one valid item line.';
            } else {
                $client_request_id = 'do-update-' . $posted_doc_no . '-' . gmdate('YmdHis') . '-' . wp_generate_password(6, false, false);

                // WordPress-only job: update the existing job payload in-place, or create one if missing.
                if ($posted_doc_key <= 0) {
                    $updated_payload = array(
                        'docKey' => 0,
                        'docNo' => $posted_doc_no,
                        'sourceDocNo' => $posted_doc_no,
                        'docDate' => $posted_doc_date,
                        'debtorCode' => $posted_debtor_code,
                        'debtorName' => $posted_debtor_name,
                        'salesAgent' => $posted_sales_agent,
                        'currencyCode' => 'MYR',
                        'currencyRate' => 1,
                        'location' => 'HQ',
                        'ref' => '',
                        'refNo2' => '',
                        'remark' => $posted_remark !== '' ? $posted_remark : 'Staff edited existing Delivery Order.',
                        'displayTerm' => 'CASH',
                        'inclusiveTax' => false,
                        'lines' => $payload_lines,
                        'assignedDriverId' => $posted_driver_id,
                        '_meta' => array(
                            'requestedBy' => get_current_user_id(),
                            'requestedAt' => current_time('mysql'),
                            'source' => 'wp-ui-edit',
                            'ip' => isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '',
                        ),
                        '_assignment' => array(
                            'assignedDriverId' => $posted_driver_id,
                            'assignedAt' => current_time('mysql'),
                            'assignedBy' => get_current_user_id(),
                        ),
                    );

                    $local_update = wst_doe_update_local_do_from_payload($updated_payload, $posted_driver_id, $payload_lines);
                    if (is_wp_error($local_update)) {
                        $wst_doe_error = $local_update->get_error_message();
                    }

                    if ($wst_doe_error === '') {
                        $queued = wst_doe_queue_update_job($updated_payload, $client_request_id, $posted_driver_id, $wst_doe_original_job_id);
                        if (is_wp_error($queued)) {
                            if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                                error_log('WST_DOE WP-only queue error for ' . $posted_doc_no . ': ' . $queued->get_error_message());
                            }
                            $wst_doe_error = $queued->get_error_message();
                        } else {
                            $redirect = add_query_arg(
                                array(
                                    'wst_doe_saved' => (int) $queued,
                                    'docNo' => $wst_doe_doc_no,
                                ),
                                $wst_doe_list_url
                            );
                            wp_safe_redirect($redirect);
                            exit;
                        }
                    }
                } else {
                    $payload = array(
                        'type' => 'DELIVERY_ORDER',
                        'subtype' => 'UPDATE',
                        'docNo' => $posted_doc_no,
                        'DocNo' => $posted_doc_no,
                        'docKey' => $posted_doc_key,
                        'DocKey' => $posted_doc_key,
                        'sourceDocNo' => $posted_doc_no,
                        'docDate' => $posted_doc_date,
                        'debtorCode' => $posted_debtor_code,
                        'DebtorCode' => $posted_debtor_code,
                        'debtorName' => $posted_debtor_name,
                        'DebtorName' => $posted_debtor_name,
                        'salesAgent' => $posted_sales_agent,
                        'SalesAgent' => $posted_sales_agent,
                        'location' => 'HQ',
                        'Location' => 'HQ',
                        'remark' => $posted_remark !== '' ? $posted_remark : 'Staff edited existing Delivery Order.',
                        'overwriteLines' => true,
                        'fullReplacement' => true,
                        'protectedStaffEdit' => true,
                        'editSource' => 'STAFF_DO_EDIT',
         �|(��L_A���������|j�
N?�B               'assignedDriverId' => $posted_driver_id,
                        'driverId' => $posted_driver_id,
                        'lines' => $payload_lines,
                        'client_request_id' => $client_request_id,
                    );

                    $local_update = wst_doe_update_local_do_from_payload($payload, $posted_driver_id, $payload_lines);
                    if (is_wp_error($local_update)) {
                        if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                            error_log('WST_DOE local update error for ' . $posted_doc_no . ': ' . $local_update->get_error_message());
                        }
                        $wst_doe_error = $local_update->get_error_message();
                    } else {
                        $queued = wst_doe_queue_update_job($payload, $client_request_id, $posted_driver_id, $wst_doe_original_job_id);
                        if (is_wp_error($queued)) {
                            if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
                                error_log('WST_DOE AC update queue error for ' . $posted_doc_no . ': ' . $queued->get_error_message());
                            }
                            $wst_doe_error = $queued->get_error_message();
                        } else {
                            $redirect = add_query_arg(
                                array(
                                    'wst_doe_saved' => (int) $queued,
                                    'docNo' => $wst_doe_doc_no,
                                ),
                                $wst_doe_list_url
                            );
                            wp_safe_redirect($redirect);
                            exit;
                        }
                    }
                }
            }
        }
    }
}

$wst_doe_drivers = get_users(array(
    'role' => 'driver',
    'orderby' => 'display_name',
    'order' => 'ASC',
));
$wst_doe_driver_picker_items = array_map(function($driver) {
    $driver_login = trim((string) $driver->user_login);
    $driver_label = strtoupper($driver_login);

    return array(
        'id' => (int) $driver->ID,
        'name' => $driver_label,
        'login' => $driver_login,
        'label' => $driver_label,
    );
}, $wst_doe_drivers);
$wst_doe_nonce = wp_create_nonce('wst_doe_queue_update');
$wst_doe_item_nonce = wp_create_nonce('ac_itemcode_suggest');
$wst_doe_ajax_url = admin_url('admin-ajax.php');
?>

<div id="acd-resp-root" class="acd-resp-root"
     data-ajax-url="<?php echo esc_attr($wst_doe_ajax_url); ?>"
     data-item-nonce="<?php echo esc_attr($wst_doe_item_nonce); ?>"
     data-list-url="<?php echo esc_attr($wst_doe_list_url); ?>"
     data-saved-job="<?php echo esc_attr($wst_doe_saved_job_id); ?>"
     data-doc-no="<?php echo esc_attr($wst_doe_doc_no); ?>"
     data-customer-name="<?php echo esc_attr($wst_doe_order['debtorName'] !== '' ? $wst_doe_order['debtorName'] : $wst_doe_order['debtorCode']); ?>"
     data-original-driver-id="<?php echo esc_attr((string) $wst_doe_driver_id); ?>"
     data-original-driver-label="<?php echo esc_attr($wst_doe_driver_label !== '' ? strtoupper($wst_doe_driver_label) : 'NO DRIVER'); ?>"
     data-drivers="<?php echo esc_attr(wp_json_encode($wst_doe_driver_picker_items)); ?>">

    <?php if ($wst_doe_error !== ''): ?>
        <div class="acd-resp-alert acd-resp-alert-error"><?php echo esc_html($wst_doe_error); ?></div>
    <?php endif; ?>

    <form method="post" id="acd_resp_do_form">
        <input type="hidden" name="wst_doe_action" value="queue_update">
        <input type="hidden" name="wst_doe_nonce" value="<?php echo esc_attr($wst_doe_nonce); ?>">
        <input type="hidden" name="wst_doe_type" value="DELIVERY_ORDER">
        <input type="hidden" name="wst_doe_subtype" value="UPDATE">
        <input type="hidden" name="wst_doe_doc_no" value="<?php echo esc_attr($wst_doe_doc_no); ?>">
        <input type="hidden" name="wst_doe_doc_key" value="<?php echo esc_attr($wst_doe_doc_key_for_form); ?>">
        <input type="hidden" name="wst_doe_job_id" value="<?php echo esc_attr($wst_doe_job_id); ?>">
        <input type="hidden" name="wst_doe_doc_date" id="wst_doe_doc_date" value="<?php echo esc_attr($wst_doe_order['docDate']); ?>">
        <input type="hidden" id="acd_resp_do_customer" value="<?php echo esc_attr($wst_doe_order['debtorCode']); ?>">
        <input type="hidden" id="acd_resp_do_customer_name" value="<?php echo esc_attr($wst_doe_order['debtorName']); ?>">
        <input type="hidden" id="acd_resp_do_sales_agent" value="<?php echo esc_attr($wst_doe_order['salesAgent']); ?>">
        <div id="acd_resp_do_hidden_lines"></div>

        <div class="acd-resp-edit-banner">
            <div>
                <span>Edit Delivery Order</span>
                <strong><?php echo esc_html($wst_doe_doc_no); ?></strong>
            </div>
            <div>
                <span><?php echo esc_html($wst_doe_delivery_status !== '' ? str_replace('_', ' ', $wst_doe_delivery_status) : 'Current DO'); ?></span>
                <strong><?php echo esc_html($wst_doe_driver_label !== '' ? strtoupper($wst_doe_driver_label) : 'NO DRIVER'); ?></strong>
            </div>
        </div>

        <?php if ($wst_doe_delivery_status === 'NEEDS_STAFF_EDIT'): ?>
            <div class="acd-resp-alert acd-resp-alert-warning">Driver sent this DO back because item is not enough. Save here to queue AutoCount update and continue the correction workflow.</div>
        <?php endif; ?>

        <div id="acd-resp-delivery-tab" class="acd-resp-tab-pane active" data-tab="delivery">
            <div class="acd-resp-do-grid">
                <div class="acd-resp-card acd-resp-entry-card">
                    <div class="acd-resp-card-header"><h3>Add Item</h3></div>
                    <div class="acd-resp-card-body">
                        <div class="acd-resp-row-2">
                            <div class="acd-resp-field">
                                <label>Date</label>
                                <input type="date" id="acd_resp_do_date" class="acd-resp-input" value="<?php echo esc_attr($wst_doe_order['docDate']); ?>" required>
                            </div>
                            <div class="acd-resp-field">
                                <label>Customer</label>
                                <div class="acd-resp-search-wrap acd-resp-readonly-wrap" id="acdRespDebtorWrapper">
                                    <input type="text" id="acdRespDebtorInput" class="acd-resp-input acd-resp-input-readonly" value="<?php echo esc_attr($wst_doe_order['debtorName'] !== '' ? $wst_doe_order['debtorName'] : $wst_doe_order['debtorCode']); ?>" autocomplete="off" readonly aria-readonly="true">
                                </div>
                            </div>
                        </div>
                        <div class="acd-resp-row-2">
                            <div class="acd-resp-field">
                                <label>Driver</label>
                                <div class="acd-resp-search-wrap">
                                    <input type="text" id="acd_resp_do_driver_name" class="acd-resp-input" value="<?php echo esc_attr($wst_doe_driver_label !== '' ? strtoupper($wst_doe_driver_label) : ''); ?>" placeholder="Select driver..." autocomplete="off" readonly required>
                                    <button type="button" id="acdRespDriverClear" class="acd-resp-field-clear" aria-label="Clear driver">&times;</button>
                                    <input type="hidden" id="acd_resp_do_driver" name="wst_doe_driver_id" value="<?php echo esc_attr((string) $wst_doe_driver_id); ?>">
                                    <input type="hidden" id="acd_resp_do_driver_login" value="">
                                </div>
                            </div>
                            <div class="acd-resp-field">
                                <label>Item Name</label>
                                <div class="acd-resp-search-wrap">
                                    <input type="text" id="acd_resp_do_item_name" class="acd-resp-input" placeholder="Search item..." autocomplete="off" readonly required>
                                    <button type="button" id="acdRespItemClear" class="acd-resp-field-clear" aria-label="Clear item">&times;</button>
                                    <input type="hidden" id="acd_resp_do_item" value="">
                                    <input type="hidden" id="acd_resp_do_item_display" value="">
                                    <input type="hidden" id="acd_resp_do_item_price" value="0">
                                </div>
                            </div>
                        </div>
                        <div class="acd-resp-row-2">
                            <div class="acd-resp-field">
                                <label>Type</label>
                                <div class="acd-resp-type-toggle" id="acd_resp_do_pack_type_toggle">
                                    <button type="button" class="acd-resp-type-btn active" data-pack-type="BASKET">Basket</button>
                                    <button type="button" class="acd-resp-type-btn" data-pack-type="CARTON">Carton</button>
                                </div>
                                <select id="acd_resp_do_pack_type" style="display:none;" required>
                                    <option value="BASKET" selected>Basket</option>
                                    <option value="CARTON">Carton</option>
                                </select>
                            </div>
                            <div class="acd-resp-field">
                                <label>Qty</label>
                                <input type="number" id="acd_resp_do_qty" class="acd-resp-input" value="" step="1" min="0" placeholder="Qty" required>
                            </div>
                        </div>
                        <div class="acd-resp-row-2">
                            <div class="acd-resp-field">
                                <label>Weight (KG)</label>
                                <input type="number" id="acd_resp_do_kg" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Weight (KG)" required>
                            </div>
                            <div class="acd-resp-field">
                                <label>Price</label>
                                <input type="number" id="acd_resp_do_price" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Price">
                            </div>
                        </div>
                        <div class="acd-resp-preview" id="acd_resp_do_line_preview" style="display:none;"></div>
                        <button type="button" id="acd_resp_do_addline" class="acd-resp-btn-primary">Add Item</button>
                    </div>
                </div>
            </div>
            <div class="acd-resp-card acd-resp-items-card">
                <div class="acd-resp-card-header acd-resp-card-header-stack">
                    <div class="acd-resp-lines-head"><h3>Items Detail</h3><span id="acd_resp_do_lines_count_badge" class="acd-resp-lines-badge">0</span></div>
                    <button type="submit" formnovalidate id="acd_resp_do_submit" class="acd-resp-btn-primary acd-resp-save-btn">Queue AutoCount Update</button>
                    <div id="acd_resp_do_success_actions" class="acd-resp-success-actions" style="display:none;" aria-live="polite">
                        <div class="acd-resp-success-text"><span>Update queued</span>: <strong id="acd_resp_do_success_docno"><?php echo esc_html($wst_doe_doc_no); ?></strong></div>
                        <div class="acd-resp-success-btns"><a id="acd_resp_do_status_btn" class="acd-resp-action-btn acd-resp-action-soft" href="<?php echo esc_url($wst_doe_list_url); ?>">View Status / Reprint</a></div>
                    </div>
                </div>
                <div class="acd-resp-card-body">
                    <div class="acd-resp-lines-header"><span>Item</span><span>Type</span><span>Qty</span><span>KG</span><span>Total KG</span><span>Price</span><span>Total Price</span><span aria-label="Action">&#9998;</span></div>
                    <div id="acd_resp_do_lines" class="acd-resp-lines-container"><div class="acd-resp-empty">No items added</div></div>
                </div>
            </div>
        </div>
    </form>

    <div class="acd-resp-picker-modal" id="acd_resp_do_picker_modal" aria-hidden="true">
        <div class="acd-resp-picker-backdrop" id="acd_resp_do_picker_backdrop"></div>
        <div class="acd-resp-picker-sheet">
            <div class="acd-resp-picker-head"><div class="acd-resp-picker-title" id="acd_resp_do_picker_title">Search</div><button type="button" class="acd-resp-picker-close" id="acd_resp_do_picker_close" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>
            <div class="acd-resp-picker-body"><input type="text" id="acd_resp_do_picker_search" class="acd-resp-input acd-resp-picker-search" placeholder="Type to search..." autocomplete="off"><div class="acd-resp-picker-results" id="acd_resp_do_picker_results"></div></div>
        </div>
    </div>
</div>

<script type="application/json" id="acd_resp_existing_lines_json"><?php echo wp_json_encode(array_values(array_map(function($line) { return array('itemCode'=>(string)($line['itemCode'] ?? ''),'itemName'=>(string)($line['itemName'] ?? ''),'packType'=>(string)($line['packType'] ?? 'BASKET'),'qty'=>(float)($line['unitQty'] ?? 0),'kg'=>(float)($line['kg'] ?? 0),'total'=>(float)($line['totalKg'] ?? 0),'price'=>(float)($line['unitPrice'] ?? 0)); }, $wst_doe_order['lines']))); ?></script>
<style>
#acd-resp-root{--acd-bg:#f8fafc;--acd-card-bg:#fff;--acd-border:#dbe4ee;--acd-border-strong:#c4d0dd;--acd-text:#0f172a;--acd-muted:#475569;--acd-green:#166534;--acd-green-light:#dcfce7;--acd-green-soft:#f0fdf4;--acd-green-dark:#14532d;--acd-danger:#dc2626;--acd-radius:.75rem;--acd-shadow:0 .75rem 1.75rem rgba(15,23,42,.08);font-family:'Segoe UI',Roboto,system-ui,sans-serif;color:var(--acd-text);background:var(--acd-bg);font-size:1rem;margin:0;padding:0;max-width:none}#acd-resp-root *{box-sizing:border-box}.acd-resp-alert{padding:.8rem .95rem;border-radius:.75rem;margin:0 0 .8rem;font-size:.92rem;font-weight:800}.acd-resp-alert-error{border:1px solid #fecaca;background:#fff1f2;color:#991b1b}.acd-resp-alert-warning{border:1px solid #fed7aa;background:#fff7ed;color:#9a3412}.acd-resp-edit-banner{display:flex;align-items:stretch;justify-content:space-between;gap:.8rem;background:#fff;border:1px solid var(--acd-border);border-radius:.9rem;box-shadow:var(--acd-shadow);padding:.85rem .95rem;margin:0 0 .9rem}.acd-resp-edit-banner>div{display:flex;flex-direction:column;gap:.15rem}.acd-resp-edit-banner>div:last-child{text-align:right}.acd-resp-edit-banner span{font-size:.78rem;font-weight:900;color:var(--acd-muted);text-transform:uppercase}.acd-resp-edit-banner strong{font-size:1.25rem;line-height:1.1;color:#052e16}.acd-resp-edit-banner small{font-size:.82rem;font-weight:800;color:var(--acd-muted)}
#acd-resp-root .acd-resp-do-grid{display:block}.acd-resp-tab-pane{display:block;padding:0}.acd-resp-card{background:var(--acd-card-bg);border:1px solid var(--acd-border);border-radius:var(--acd-radius);box-shadow:var(--acd-shadow);overflow:hidden}.acd-resp-items-card{margin-top:.9rem}.acd-resp-card-header{padding:.85rem .9rem;border-bottom:1px solid var(--acd-border);background:#fcfdff;display:flex;align-items:center;justify-content:space-between;gap:.6rem}.acd-resp-card-header-stack{flex-direction:column;align-items:stretch}.acd-resp-card-header h3{margin:0;font-size:1rem;font-weight:800}.acd-resp-lines-head{display:flex;align-items:center;justify-content:space-between;width:100%}.acd-resp-lines-badge{display:inline-flex;align-items:center;justify-content:center;min-width:1.8rem;min-height:1.8rem;padding:0 .45rem;border-radius:999px;background:var(--acd-green-soft);border:1px solid #bbf7d0;color:var(--acd-g�|j�.���B���������|��
N?�Creen);font-size:.82rem;font-weight:800}.acd-resp-card-body{padding:.9rem}
@media (min-width:768px){#acd-resp-root .acd-resp-card-body{padding:1rem}#acd-resp-root .acd-resp-entry-card .acd-resp-card-body{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.9rem 1rem;align-items:end}#acd-resp-root .acd-resp-entry-card .acd-resp-field,#acd-resp-root .acd-resp-entry-card .acd-resp-row-2,#acd-resp-root .acd-resp-entry-card .acd-resp-preview,#acd-resp-root .acd-resp-entry-card #acd_resp_do_addline{margin-bottom:0}#acd-resp-root .acd-resp-entry-card .acd-resp-row-2,#acd-resp-root .acd-resp-entry-card .acd-resp-preview,#acd-resp-root .acd-resp-entry-card #acd_resp_do_addline{grid-column:1 / -1}}
.acd-resp-field{margin-bottom:.85rem}.acd-resp-field label{display:block;font-size:.88rem;font-weight:700;color:var(--acd-muted);margin-bottom:.35rem}.acd-resp-input{width:100%;min-height:3rem;padding:.72rem .85rem;border:1px solid var(--acd-border-strong);border-radius:.65rem;background:#fff;font-size:1rem}.acd-resp-input:focus{outline:none;border-color:var(--acd-green);box-shadow:0 0 0 .2rem rgba(22,101,52,.10)}.acd-resp-search-wrap{position:relative}.acd-resp-search-wrap .acd-resp-input{padding-right:3.1rem;cursor:pointer}.acd-resp-readonly-wrap .acd-resp-input-readonly{padding-right:.85rem;cursor:default;background:#f8fafc;color:#334155}.acd-resp-field-clear{position:absolute;top:50%;right:.5rem;transform:translateY(-50%);width:2.15rem;height:2.15rem;border:1px solid var(--acd-border);background:#fff;color:#64748b;border-radius:.5rem;display:none;align-items:center;justify-content:center;font-size:1.2rem;cursor:pointer}.acd-resp-field-clear.show{display:inline-flex}.acd-resp-field-clear:hover{background:var(--acd-green-soft);border-color:#bbf7d0;color:var(--acd-green)}
.acd-resp-type-toggle{display:flex;gap:.55rem}.acd-resp-type-btn{flex:1;min-height:3rem;padding:.7rem .8rem;border:1px solid var(--acd-border-strong);background:#f8fafc;color:#334155;border-radius:.65rem;font-weight:700;font-size:1rem;cursor:pointer}.acd-resp-type-btn:hover{background:#ecfdf3;border-color:#86efac;color:var(--acd-green)}.acd-resp-type-btn.active{background:var(--acd-green-light);border-color:#16a34a;color:var(--acd-green);box-shadow:0 0 0 1px rgba(22,101,52,.05) inset}.acd-resp-row-2{display:grid;grid-template-columns:1fr 1fr;gap:.8rem;margin-bottom:.5rem}@media(max-width:480px){.acd-resp-row-2{grid-template-columns:1fr;gap:0}.acd-resp-edit-banner{flex-direction:column}.acd-resp-edit-banner>div:last-child{text-align:left}}
.acd-resp-preview{background:var(--acd-green-soft);border:1px solid #bbf7d0;border-radius:.65rem;padding:.7rem .8rem;margin:.6rem 0;font-size:.95rem}#acd-resp-root .acd-resp-btn-primary,#acd-resp-root button.acd-resp-btn-primary{width:100%;min-height:3.05rem;padding:.78rem 1rem;border:1px solid var(--acd-green);border-radius:.7rem;background:var(--acd-green);color:#fff;font-weight:800;font-size:1rem;line-height:1.2;font-family:inherit;text-align:center;cursor:pointer;appearance:none;-webkit-appearance:none;box-shadow:none}#acd-resp-root .acd-resp-btn-primary:hover{background:var(--acd-green-dark);border-color:var(--acd-green-dark);color:#fff;box-shadow:0 4px 12px rgba(22,101,52,.14)}#acd-resp-root .acd-resp-btn-primary:disabled{background:#94a3b8;border-color:#94a3b8;cursor:not-allowed}.acd-resp-lines-header{display:none}@media(min-width:768px){.acd-resp-lines-header{display:grid;grid-template-columns:minmax(12rem,2fr) .8fr .6fr .6fr .8fr 1fr 1fr 5.5rem;gap:.45rem;align-items:center;text-align:center;background:#f1f5f9;border:1px solid var(--acd-border);border-radius:.65rem .65rem 0 0;padding:.72rem .8rem;font-size:.85rem;font-weight:800;margin-bottom:.25rem}.acd-resp-lines-header span:first-child{text-align:left}.acd-resp-lines-header,.acd-resp-line{min-width:64rem}.acd-resp-line{display:grid;grid-template-columns:minmax(12rem,2fr) .8fr .6fr .6fr .8fr 1fr 1fr 5.5rem;gap:.45rem;align-items:center;text-align:center;padding:.7rem .8rem;border-right:1px solid #eef2f6;border-left:1px solid #eef2f6;border-bottom:1px solid #eef2f6;font-size:.95rem}.acd-resp-line>div:first-child{text-align:left}.acd-resp-price-cell{padding:.15rem}.acd-resp-price-input{width:100%;min-height:2.2rem;padding:.35rem .45rem;border:1px solid var(--acd-border-strong);border-radius:.5rem;text-align:right;font-size:.9rem}.acd-resp-money-cell{text-align:right;font-variant-numeric:tabular-nums;padding-right:.3rem}}
.acd-resp-mobile-line-item{display:block;padding:.85rem;border:1px solid var(--acd-border);border-radius:.7rem;margin-bottom:.6rem;background:#fff;box-shadow:0 .45rem 1rem rgba(15,23,42,.05)}@media(min-width:768px){.acd-resp-mobile-line-item{display:none}}.acd-resp-mobile-line-top{display:flex;align-items:flex-start;justify-content:space-between;gap:.7rem;margin-bottom:.45rem}.acd-resp-mobile-line-name{font-size:.94rem;font-weight:800;word-break:break-word}.acd-resp-mobile-delete-btn{flex-shrink:0;min-height:2.35rem;padding:.5rem .8rem;background:#fff5f5;color:var(--acd-danger);border:1px solid #fecaca;border-radius:.6rem;font-size:.82rem;font-weight:800;cursor:pointer}.acd-resp-mobile-line-meta{display:grid;grid-template-columns:1fr 1fr;gap:.5rem}.acd-resp-mobile-chip{border:1px solid var(--acd-border);background:#f8fafc;border-radius:.62rem;padding:.5rem .55rem}.acd-resp-mobile-chip-label{display:block;font-size:.67rem;color:var(--acd-muted);margin-bottom:.08rem}.acd-resp-mobile-chip-value{display:block;font-size:.84rem;font-weight:800}.acd-resp-number-cell{text-align:center;font-variant-numeric:tabular-nums}.acd-resp-type-pill{display:inline-flex;padding:.3rem .7rem;border-radius:999px;background:var(--acd-green-soft);border:1px solid #bbf7d0;color:var(--acd-green);font-size:.8rem;font-weight:800}
#acd-resp-root .acd-resp-delete-btn{display:inline-flex;align-items:center;justify-content:center;width:2.4rem;min-width:2.4rem;min-height:2.35rem;padding:.45rem;border:1px solid #fecaca;background:#fff5f5;color:#dc2626;border-radius:.65rem;font-size:1rem;font-weight:700;cursor:pointer}.acd-resp-lines-container{max-height:min(32rem,64vh);overflow:auto;padding:.15rem}.acd-resp-empty{padding:1.2rem;text-align:center;color:var(--acd-muted);font-style:italic}#acd-resp-root .acd-resp-success-actions{margin-top:.75rem;padding:.85rem;border:1px solid #bbf7d0;background:var(--acd-green-soft);border-radius:.75rem}.acd-resp-success-text{font-size:.9rem;font-weight:700;color:var(--acd-green-dark);margin-bottom:.55rem}.acd-resp-success-btns{display:grid;grid-template-columns:1fr;gap:.5rem}#acd-resp-root .acd-resp-action-btn{min-height:2.8rem;padding:.7rem .8rem;border-radius:.65rem;font-size:.92rem;font-weight:800;text-align:center;text-decoration:none;display:inline-flex;align-items:center;justify-content:center}.acd-resp-action-soft{background:#fff;border:1px solid #86efac;color:var(--acd-green)}
.acd-resp-picker-modal{position:fixed;inset:0;z-index:9999;display:none;align-items:center;justify-content:center;padding:.75rem}.acd-resp-picker-modal.active{display:flex}.acd-resp-picker-backdrop{position:absolute;inset:0;background:rgba(15,23,42,.45)}.acd-resp-picker-sheet{position:relative;width:100%;max-width:min(42rem,calc(100vw - 2rem));max-height:86vh;background:#fff;border-radius:.9rem;box-shadow:0 1.4rem 2.4rem rgba(0,0,0,.18);overflow:hidden}.acd-resp-picker-head{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.85rem .95rem;border-bottom:1px solid var(--acd-border)}.acd-resp-picker-title{font-size:1.05rem;font-weight:800}.acd-resp-picker-close{width:2.35rem;height:2.35rem;padding:0;border:1px solid var(--acd-border-strong);background:#fff;color:var(--acd-text);border-radius:.55rem;display:inline-flex;align-items:center;justify-content:center;line-height:1;font-size:1.35rem;cursor:pointer}.acd-resp-picker-body{padding:.85rem .95rem .95rem;display:flex;flex-direction:column;gap:.6rem}.acd-resp-picker-results{max-height:min(24rem,calc(86vh - 9rem));overflow-y:auto}.acd-resp-picker-item{display:block;width:100%;text-align:left;min-height:3rem;padding:.78rem .85rem;border:1px solid var(--acd-border);border-radius:.65rem;background:#fff;margin-bottom:.5rem;cursor:pointer;color:var(--acd-text)}.acd-resp-picker-item:hover{background:var(--acd-green-soft);border-color:#bbf7d0}.acd-resp-picker-item-main{display:block;font-weight:800;color:var(--acd-text)}.acd-resp-picker-item-sub{display:block;font-size:.8rem;color:var(--acd-muted)}.acd-resp-picker-note{padding:.9rem;text-align:center;color:var(--acd-muted);font-weight:800}
.acd-resp-dialog{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;padding:1rem}.acd-resp-dialog-backdrop{position:absolute;inset:0;background:rgba(15,23,42,.62)}.acd-resp-dialog-card{position:relative;width:100%;max-width:34rem;max-height:min(86vh,44rem);display:flex;flex-direction:column;background:#fff;border-radius:.9rem;box-shadow:0 1.4rem 3rem rgba(15,23,42,.28);overflow:hidden}.acd-resp-dialog-head{display:flex;align-items:flex-start;gap:.75rem;padding:1rem 1rem .75rem;border-bottom:1px solid #dbe4ee}.acd-resp-dialog-icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 2.25rem;width:2.25rem;height:2.25rem;border-radius:999px;background:#f0fdf4;color:#166534;font-weight:900}.acd-resp-dialog-icon-warning{background:#fef3c7;color:#92400e}.acd-resp-dialog-icon-error{background:#fee2e2;color:#991b1b}.acd-resp-dialog-title{margin:.15rem 0 0;font-size:1.1rem;line-height:1.25;font-weight:900;color:#0f172a}.acd-resp-dialog-body{padding:1rem;overflow:auto;color:#0f172a;font-size:.95rem;line-height:1.45}.acd-resp-dialog-body p{margin:.4rem 0 .75rem}.acd-resp-dialog-actions{display:flex;justify-content:flex-end;gap:.65rem;padding:.85rem 1rem 1rem;border-top:1px solid #dbe4ee;background:#f8fafc}.acd-resp-dialog-btn{min-height:2.65rem;padding:.65rem 1rem;border-radius:.65rem;border:1px solid #c4d0dd;font-weight:900;cursor:pointer}.acd-resp-dialog-btn-primary{background:#166534;border-color:#166534;color:#fff}.acd-resp-dialog-btn-primary:hover{background:#14532d;border-color:#14532d;color:#fff}.acd-resp-dialog-btn-secondary{background:#fff;color:#334155}.acd-resp-dialog-btn-secondary:hover{background:#f1f5f9;color:#0f172a}@media(max-width:480px){.acd-resp-dialog-actions{flex-direction:column-reverse}.acd-resp-dialog-btn{width:100%}}
.acd-resp-dialog .acd-resp-dialog-btn,
.acd-resp-dialog button.acd-resp-dialog-btn{
    min-height:2.8rem !important;
    padding:.72rem 1rem !important;
    border-radius:.7rem !important;
    font-family:inherit !important;
    font-size:.96rem !important;
    font-weight:900 !important;
    line-height:1.2 !important;
    text-align:center !important;
    text-decoration:none !important;
    opacity:1 !important;
    cursor:pointer !important;
    appearance:none !important;
    -webkit-appearance:none !important;
    box-shadow:none !important;
    transition:background .18s ease,border-color .18s ease,box-shadow .18s ease,color .18s ease !important;
}
.acd-resp-dialog .acd-resp-dialog-btn-primary,
.acd-resp-dialog button.acd-resp-dialog-btn-primary{
    background:#166534 !important;
    border:1px solid #166534 !important;
    color:#fff !important;
}
.acd-resp-dialog .acd-resp-dialog-btn-primary:hover,
.acd-resp-dialog button.acd-resp-dialog-btn-primary:hover{
    background:#14532d !important;
    border-color:#14532d !important;
    color:#fff !important;
    box-shadow:0 4px 12px rgba(22,101,52,.14) !important;
}
.acd-resp-dialog .acd-resp-dialog-btn-primary:focus,
.acd-resp-dialog button.acd-resp-dialog-btn-primary:focus{
    outline:none !important;
    color:#fff !important;
    box-shadow:0 0 0 .2rem rgba(22,101,52,.18) !important;
}
.acd-resp-dialog .acd-resp-dialog-btn-secondary,
.acd-resp-dialog button.acd-resp-dialog-btn-secondary{
    background:#fff !important;
    border:1px solid #c4d0dd !important;
    color:#334155 !important;
}
.acd-resp-dialog .acd-resp-dialog-btn-secondary:hover,
.acd-resp-dialog button.acd-resp-dialog-btn-secondary:hover,
.acd-resp-dialog .acd-resp-dialog-btn-secondary:focus,
.acd-resp-dialog button.acd-resp-dialog-btn-secondary:focus{
    background:#f1f5f9 !important;
    border-color:#94a3b8 !important;
    color:#0f172a !important;
    outline:none !important;
}
</style>
<script>
(function(){
    const root = document.getElementById('acd-resp-root');
    if (!root) return;
    const $ = id => document.getElementById(id);
    const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));
    const AJAX_URL = root.dataset.ajaxUrl || '';
    const ITEM_NONCE = root.dataset.itemNonce || '';
    let DRIVER_ITEMS = [];
    try { DRIVER_ITEMS = JSON.parse(root.dataset.drivers || '[]'); } catch(e) { DRIVER_ITEMS = []; }
    let pickerTimer = null;
    const pickerState = { items: [], defaultItems: [], fetchFn: null, onPick: null };
    const state = { lines: [], editingIndex: -1, isSubmitting: false };
    try { state.lines = JSON.parse($('acd_resp_existing_lines_json')?.textContent || '[]'); } catch(e) { state.lines = []; }

    function fmtQty(n){ const x = Number(n); return (!isFinite(x)) ? '0' : String(Math.round(x)); }
    function fmtKg(n){ const x = Number(n); return (!isFinite(x)) ? '0.00' : x.toFixed(2); }
    function parseQty(n){ const x = Number(n); return (!isFinite(x) || x < 0) ? 0 : Math.round(x); }
    function parseKg(n){ const x = Number(n); return (!isFinite(x) || x < 0) ? 0 : Number(x.toFixed(2)); }
    function calcTotalKg(qty, kg){ return Number(((parseQty(qty) || 0) * (parseKg(kg) || 0)).toFixed(2)); }
    function toast(icon,title,text=''){ if(window.Swal) Swal.fire({toast:true,position:'center',icon,title,text,showConfirmButton:false,timer:2400,timerProgressBar:true}); }
    function localDialog(opts){
        return new Promise(resolve => {
            const icon = String(opts.icon || 'info').toLowerCase();
            const overlay = document.createElement('div');
            overlay.className = 'acd-resp-dialog';
            overlay.setAttribute('role', 'dialog');
            overlay.setAttribute('aria-modal', 'true');
            overlay.innerHTML = `
                <div class="acd-resp-dialog-backdrop" data-dialog-cancel="1"></div>
                <div class="acd-resp-dialog-card">
                    <div class="acd-resp-dialog-head">
                        <span class="acd-resp-dialog-icon acd-resp-dialog-icon-${esc(icon)}">${icon === 'warning' ? '!' : icon === 'error' ? 'x' : '?'}</span>
                        <h3 class="acd-resp-dialog-title">${esc(opts.title || 'Confirm')}</h3>
                    </div>
                    <div class="acd-resp-dialog-body">${opts.html || esc(opts.text || '')}</div>
                    <div class="acd-resp-dialog-actions">
                        ${opts.showCancel ? `<button type="button" class="acd-resp-dialog-btn acd-resp-dialog-btn-secondary" data-dialog-cancel="1">${esc(opts.cancelText || 'Cancel')}</button>` : ''}
                        <button type="button" class="acd-resp-dialog-btn acd-resp-dialog-btn-primary" data-dialog-confirm="1">${esc(opts.confirmText || 'OK')}</button>
                    </div>
                </div>`;
            const close = value => {
                overlay.remove();
                document.removeEventListener('keydown', onKeydown);
                resolve(value);
            };
            const onKeydown = event => {
                if (event.key === 'Escape') close(false);
            };
            overlay.addEventListener('click', event => {
                if (event.target.closest('[data-dialog-confirm]')) close(true);
                if (event.target.closest('[data-dialog-cancel]')) close(false);
            });
            document.addEventListener('keydown', onKeydown);
            root.appendChild(overlay);
            setTimeout(() => overlay.querySelector(opts.showCancel ? '[data-dialog-cancel]' : '[data-dialog-confirm]')?.focus(), 30);
        });
    }
    function modal(icon,title,html){
        if(window.Swal) { Swal.fire({icon,title,html,confirmButtonText:'OK',confirmButtonColor:�|���"C���������|�A
N?�D'#166534'}); return; }
        localDialog({icon,title,html,confirmText:'OK',showCancel:false});
    }
    function lineSummary(){
        return state.lines.reduce((sum, line) => {
            const type = String(line.packType || '').toUpperCase();
            const qty = parseQty(line.qty || 0);
            const totalKg = Number(line.total || calcTotalKg(line.qty || 0, line.kg || 0)) || 0;
            if (type === 'CARTON') {
                sum.carton += qty;
            } else {
                sum.basket += qty;
            }
            sum.kg += totalKg;
            return sum;
        }, {basket:0, carton:0, kg:0});
    }
    async function confirmQueueUpdate(){
        const selectedDriverId = parseInt($('acd_resp_do_driver')?.value || '0',10) || 0;
        const originalDriverId = parseInt(root.dataset.originalDriverId || '0',10) || 0;
        const selectedDriver = ($('acd_resp_do_driver_name')?.value || '').trim().toUpperCase() || 'NO DRIVER';
        const originalDriver = String(root.dataset.originalDriverLabel || 'NO DRIVER').trim().toUpperCase() || 'NO DRIVER';
        const driverChanged = selectedDriverId > 0 && originalDriverId > 0 && selectedDriverId !== originalDriverId;
        const totals = lineSummary();
        const html = `
            <div style="text-align:left">
                <p><strong>Delivery Order:</strong> ${esc(root.dataset.docNo || '')}</p>
                <p><strong>Customer:</strong> ${esc(root.dataset.customerName || '')}</p>
                <p><strong>Driver:</strong> ${esc(selectedDriver)}${driverChanged ? ` <span style="color:#92400e;font-weight:800">(changed from ${esc(originalDriver)})</span>` : ''}</p>
                <p><strong>Items:</strong> ${esc(state.lines.length)} line(s)</p>
                <p><strong>Summary:</strong> Basket ${esc(fmtQty(totals.basket))} | Carton ${esc(fmtQty(totals.carton))} | Total KG ${esc(fmtKg(totals.kg))}</p>
                <p style="margin-top:.8rem">Queue this update to AutoCount now?</p>
            </div>`;

        if (window.Swal) {
            const result = await Swal.fire({
                icon: driverChanged ? 'warning' : 'question',
                title: 'Queue update to AutoCount',
                html,
                confirmButtonText: 'Yes, queue update',
                confirmButtonColor: '#166534',
                showCancelButton: true,
                cancelButtonText: 'No, keep editing',
                reverseButtons: true,
                focusCancel: driverChanged
            });
            return !!result.isConfirmed;
        }

        return localDialog({
            icon: driverChanged ? 'warning' : 'question',
            title: 'Queue update to AutoCount',
            html,
            confirmText: 'Yes, queue update',
            cancelText: 'No, keep editing',
            showCancel: true
        });
    }

    function updateEntryTotal(){
        const itemCode = ($('acd_resp_do_item').value || '').trim();
        const itemName = ($('acd_resp_do_item_display').value || '').trim() || itemCode;
        const packType = ($('acd_resp_do_pack_type').value || '').trim();
        const qtyRaw = ($('acd_resp_do_qty').value || '').trim();
        const kgRaw = ($('acd_resp_do_kg').value || '').trim();
        const priceRaw = ($('acd_resp_do_price').value || '').trim();
        const qty = parseQty(qtyRaw || '0');
        const kg = parseKg(kgRaw || '0');
        const total = calcTotalKg(qty, kg);
        const defaultItemPrice = parseMoney($('acd_resp_do_item_price').value || '0');
        const enteredPrice = parseMoney(priceRaw);
        const price = enteredPrice > 0 ? enteredPrice : defaultItemPrice;
        const lineTotal = calcTotalPrice({price, total});
        const pv = $('acd_resp_do_line_preview');
        if (!itemCode || qtyRaw === '' || kgRaw === '') { pv.style.display = 'none'; pv.innerHTML = ''; return; }
        pv.style.display = 'block';
        pv.innerHTML = `<div><b>${esc(itemName)}</b></div><div>Type: ${esc(packType)} | Qty: ${fmtQty(qty)} | KG: ${fmtKg(kg)} | Total KG: ${fmtKg(total)} | Price: ${fmtMoney(price)} | Line Total: ${fmtMoney(lineTotal)}</div>`;
    }
    function setPackType(type){
        const nextType = String(type || '').toUpperCase() === 'CARTON' ? 'CARTON' : 'BASKET';
        $('acd_resp_do_pack_type').value = nextType;
        document.querySelectorAll('#acd_resp_do_pack_type_toggle .acd-resp-type-btn').forEach(btn => btn.classList.toggle('active', (btn.dataset.packType || '').toUpperCase() === nextType));
        updateEntryTotal();
    }
    function parseMoney(v){ const s = String(v ?? '').replace(/,/g,'').trim(); const n = parseFloat(s); return (!isFinite(n) || isNaN(n)) ? 0 : Number(n.toFixed(2)); }
    function fmtMoney(n){ const x = Number(n); return (!isFinite(x)) ? '0.00' : x.toLocaleString('en-MY',{minimumFractionDigits:2,maximumFractionDigits:2}); }
    function calcTotalPrice(line){ const totalKg = Number(line.total || calcTotalKg(line.qty || 0, line.kg || 0)) || 0; return Number((parseMoney(line.price || 0) * totalKg).toFixed(2)); }
    function updateLinePrice(idx, value, shouldFormatInput = false){
        if (isNaN(idx) || !state.lines[idx]) return;
        state.lines[idx].price = parseMoney(value);
        const nextTotal = fmtMoney(calcTotalPrice(state.lines[idx]));
        document.querySelectorAll(`[data-total-price-idx="${idx}"]`).forEach(el => { el.textContent = nextTotal; });
        if (shouldFormatInput) {
            document.querySelectorAll(`[data-price-idx="${idx}"]`).forEach(input => { input.value = fmtMoney(state.lines[idx].price); });
        }
        syncHiddenLines();
    }
    function updateClearButtons(){
        $('acdRespDriverClear')?.classList.toggle('show', !!($('acd_resp_do_driver_name')?.value.trim()));
        $('acdRespItemClear')?.classList.toggle('show', !!($('acd_resp_do_item_name')?.value.trim()));
    }
    function syncHiddenLines(){
        const box = $('acd_resp_do_hidden_lines');
        box.innerHTML = state.lines.map((line, idx) => `
            <input type="hidden" name="wst_doe_lines[${idx}][item_name]" value="${esc(line.itemName || line.itemCode || '')}">
            <input type="hidden" name="wst_doe_lines[${idx}][item_code]" value="${esc(line.itemCode || '')}">
            <input type="hidden" name="wst_doe_lines[${idx}][pack_type]" value="${esc(line.packType || 'BASKET')}">
            <input type="hidden" name="wst_doe_lines[${idx}][unit_qty]" value="${esc(line.qty || 0)}">
            <input type="hidden" name="wst_doe_lines[${idx}][kg]" value="${esc(line.kg || 0)}">
            <input type="hidden" name="wst_doe_lines[${idx}][unit_price]" value="${esc(line.price || 0)}">
            <input type="hidden" name="wst_doe_lines[${idx}][total_kg]" value="${esc(line.total || 0)}">`).join('');
    }
    function updateUI(){
        $('acd_resp_do_lines_count_badge').textContent = state.lines.length;
        const container = $('acd_resp_do_lines');
        if (!state.lines.length) { container.innerHTML = '<div class="acd-resp-empty">No items added</div>'; syncHiddenLines(); return; }
        const mobileHtml = state.lines.map((l, idx) => `
            <div class="acd-resp-mobile-line-item" data-idx="${idx}">
                <div class="acd-resp-mobile-line-top"><div class="acd-resp-mobile-line-name">${esc(l.itemName || l.itemCode)}</div><div><button type="button" class="acd-resp-mobile-delete-btn" data-edit-idx="${idx}">Edit</button> <button type="button" class="acd-resp-mobile-delete-btn" data-idx="${idx}">Delete</button></div></div>
                <div class="acd-resp-mobile-line-meta">
                    <div class="acd-resp-mobile-chip"><span class="acd-resp-mobile-chip-label">Type</span><span class="acd-resp-mobile-chip-value"><span class="acd-resp-type-pill">${esc(l.packType)}</span></span></div>
                    <div class="acd-resp-mobile-chip"><span class="acd-resp-mobile-chip-label">Qty</span><span class="acd-resp-mobile-chip-value">${fmtQty(l.qty)}</span></div>
                    <div class="acd-resp-mobile-chip"><span class="acd-resp-mobile-chip-label">KG</span><span class="acd-resp-mobile-chip-value">${fmtKg(l.kg)}</span></div>
                    <div class="acd-resp-mobile-chip"><span class="acd-resp-mobile-chip-label">Total KG</span><span class="acd-resp-mobile-chip-value">${fmtKg(l.total)}</span></div>
                    <div class="acd-resp-mobile-chip"><span class="acd-resp-mobile-chip-label">Price</span><span class="acd-resp-mobile-chip-value">${fmtMoney(l.price || 0)}</span></div>
                    <div class="acd-resp-mobile-chip"><span class="acd-resp-mobile-chip-label">Total Price</span><span class="acd-resp-mobile-chip-value">${fmtMoney(calcTotalPrice(l))}</span></div>
                </div>
            </div>`).join('');
        const desktopHtml = state.lines.map((l, idx) => `<div class="acd-resp-line" data-idx="${idx}">
                <div><strong>${esc(l.itemName || l.itemCode)}</strong></div>
                <div><span class="acd-resp-type-pill">${esc(l.packType)}</span></div>
                <div class="acd-resp-number-cell">${fmtQty(l.qty)}</div>
                <div class="acd-resp-number-cell">${fmtKg(l.kg)}</div>
                <div class="acd-resp-number-cell">${fmtKg(l.total)}</div>
                <div class="acd-resp-price-cell"><input type="number" class="acd-resp-price-input" data-price-idx="${idx}" value="${fmtMoney(l.price || 0)}" min="0" step="0.01" inputmode="decimal" aria-label="Price for ${esc(l.itemName || l.itemCode)}"></div>
                <div class="acd-resp-money-cell" data-total-price-idx="${idx}">${fmtMoney(calcTotalPrice(l))}</div>
                <div><button type="button" class="acd-resp-delete-btn" data-edit-idx="${idx}" title="Edit">&#9998;</button><button type="button" class="acd-resp-delete-btn" data-idx="${idx}" title="Delete">&#128465;</button></div>
            </div>`).join('');
        container.innerHTML = desktopHtml + mobileHtml;
        syncHiddenLines();
    }
    async function searchItemsLive(q){
        const fd = new FormData(); fd.append('action','ac_itemcode_suggest'); fd.append('nonce',ITEM_NONCE); fd.append('term',q);
        const res = await fetch(AJAX_URL,{method:'POST',body:fd,credentials:'same-origin'}); const data = await res.json();
        if (data?.success && data.data?.items) return data.data.items.map(it => ({code:it.code || '', name:(it.desc || it.name || '').trim(), price:parseMoney(it.price ?? it.Price ?? 0)}));
        return [];
    }
    function searchDriversLive(q){
        const query = String(q || '').trim().toLowerCase();
        const rows = !query ? DRIVER_ITEMS : DRIVER_ITEMS.filter(driver => [driver.label,driver.name,driver.login].join(' ').toLowerCase().includes(query));
        return Promise.resolve(rows);
    }
    function renderPickerNote(msg){ $('acd_resp_do_picker_results').innerHTML = `<div class="acd-resp-picker-note">${esc(msg)}</div>`; }
    function renderPickerItems(items){
        if (!items.length) { renderPickerNote('No result found'); return; }
        $('acd_resp_do_picker_results').innerHTML = items.map((it,idx) => `<button type="button" class="acd-resp-picker-item" data-picker-idx="${idx}"><span class="acd-resp-picker-item-main">${esc(it.label || '')}</span>${it.meta ? `<span class="acd-resp-picker-item-sub">${esc(it.meta)}</span>` : ''}</button>`).join('');
    }
    function openPicker(opts){
        pickerState.defaultItems = opts.initialItems || []; pickerState.items = pickerState.defaultItems; pickerState.fetchFn = opts.fetchFn; pickerState.onPick = opts.onPick;
        $('acd_resp_do_picker_title').textContent = opts.title || 'Search'; $('acd_resp_do_picker_search').placeholder = opts.placeholder || 'Type to search...'; $('acd_resp_do_picker_search').value = ''; $('acd_resp_do_picker_modal').classList.add('active');
        pickerState.items.length ? renderPickerItems(pickerState.items) : renderPickerNote('Type to search');
        setTimeout(() => $('acd_resp_do_picker_search').focus(), 80);
    }
    function closePicker(){ $('acd_resp_do_picker_modal').classList.remove('active'); $('acd_resp_do_picker_search').value = ''; $('acd_resp_do_picker_results').innerHTML = ''; pickerState.items = []; pickerState.defaultItems = []; pickerState.fetchFn = null; pickerState.onPick = null; }
    async function runPickerSearch(q){
        const query = (q || '').trim(); clearTimeout(pickerTimer);
        if (query.length < 1) { pickerState.items = pickerState.defaultItems || []; pickerState.items.length ? renderPickerItems(pickerState.items) : renderPickerNote('Type to search'); return; }
        pickerTimer = setTimeout(async () => { renderPickerNote('Searching...'); try { pickerState.items = await pickerState.fetchFn(query) || []; renderPickerItems(pickerState.items); } catch(e) { pickerState.items = []; renderPickerNote('Failed to load'); } }, 220);
    }
    function setDeliveryDriver(picked){ const id = parseInt(picked?.id || 0,10) || 0; const label = String(picked?.login || picked?.label || picked?.name || '').toUpperCase(); $('acd_resp_do_driver_name').value = label; $('acd_resp_do_driver').value = id ? String(id) : ''; $('acd_resp_do_driver_login').value = picked?.login || ''; updateClearButtons(); }
    function clearDriverSelection(){ $('acd_resp_do_driver_name').value=''; $('acd_resp_do_driver').value=''; $('acd_resp_do_driver_login').value=''; updateClearButtons(); }
    function clearItemSelection(){ $('acd_resp_do_item_name').value=''; $('acd_resp_do_item').value=''; $('acd_resp_do_item_display').value=''; $('acd_resp_do_item_price').value='0'; updateEntryTotal(); updateClearButtons(); }
    function clearLineEntry(){ state.editingIndex = -1; $('acd_resp_do_addline').textContent = 'Add Item'; setPackType('BASKET'); $('acd_resp_do_qty').value=''; $('acd_resp_do_kg').value=''; $('acd_resp_do_price').value=''; clearItemSelection(); }
    function loadLineForEdit(idx){
        const line = state.lines[idx];
        if (!line) return;
        state.editingIndex = idx;
        $('acd_resp_do_item_name').value = line.itemName || line.itemCode || '';
        $('acd_resp_do_item').value = line.itemCode || '';
        $('acd_resp_do_item_display').value = line.itemName || line.itemCode || '';
        $('acd_resp_do_item_price').value = fmtMoney(line.price || 0);
        $('acd_resp_do_price').value = (line.price || 0) > 0 ? String(Number(line.price).toFixed(2)) : '';
        $('acd_resp_do_qty').value = fmtQty(line.qty || 0);
        $('acd_resp_do_kg').value = fmtKg(line.kg || 0);
        setPackType(line.packType || 'BASKET');
        $('acd_resp_do_addline').textContent = 'Update Item';
        updateClearButtons();
        updateEntryTotal();
        window.scrollTo({top: root.getBoundingClientRect().top + window.scrollY, behavior: 'smooth'});
    }
    function openDriverPicker(){ const options = DRIVER_ITEMS.map(driver => ({label:String(driver.login || '').toUpperCase(),meta:'',raw:driver})); openPicker({title:'Select Driver',placeholder:'Search driver...',initialItems:options,fetchFn:async q => (await searchDriversLive(q)).map(driver => ({label:String(driver.login || '').toUpperCase(),meta:'',raw:driver})),onPick:picked => { if (!picked) return; setDeliveryDriver(picked); closePicker(); }}); }
    function openItemPicker(){ openPicker({title:'Select Item',placeholder:'Search item...',fetchFn:async q => (await searchItemsLive(q)).map(it => ({label:it.name || it.code,meta:'',raw:{code:it.code,name:it.name || it.code,price:it.price || 0}})),onPick:picked => { if (!picked) return; $('acd_resp_do_item_name').value = picked.name || picked.code || ''; $('acd_resp_do_item').value = picked.code || ''; $('acd_resp_do_item_display').value = picked.name || picked.code || ''; const rawPrice = Number(picked.price || 0); $('acd_resp_do_item_price').value = fmtMoney(rawPrice); $('acd_resp_do_price').value = rawPrice > 0 ? String(rawPrice.toFixed(2)) : ''; updateEntryTotal(); updateClearButtons(); closePicker(); }}); }

    $('acd_resp_do_picker_close').addEventListener('click', closePic�|�A�0vD���������|�A
N�����ker);
    $('acd_resp_do_picker_backdrop').addEventListener('click', closePicker);
    $('acd_resp_do_picker_search').addEventListener('input', function(){ runPickerSearch(this.value); });
    $('acd_resp_do_picker_results').addEventListener('click', e => { const btn = e.target.closest('[data-picker-idx]'); if (!btn) return; const idx = parseInt(btn.dataset.pickerIdx,10); if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) pickerState.onPick(pickerState.items[idx].raw); });
    $('acd_resp_do_driver_name').setAttribute('readonly','readonly');
    $('acd_resp_do_item_name').setAttribute('readonly','readonly');
    $('acd_resp_do_driver_name').addEventListener('click', openDriverPicker);
    $('acd_resp_do_item_name').addEventListener('click', openItemPicker);
    $('acdRespDriverClear')?.addEventListener('click', e => { e.preventDefault(); clearDriverSelection(); });
    $('acdRespItemClear')?.addEventListener('click', e => { e.preventDefault(); clearItemSelection(); });
    $('acd_resp_do_qty').addEventListener('input', updateEntryTotal);
    $('acd_resp_do_kg').addEventListener('input', updateEntryTotal);
    $('acd_resp_do_price').addEventListener('input', updateEntryTotal);
    $('acd_resp_do_date')?.addEventListener('change', () => {
        const hiddenDate = $('wst_doe_doc_date');
        if (hiddenDate) hiddenDate.value = $('acd_resp_do_date').value || '';
    });
    // Ensure hidden date matches visible date before submit, even if unchanged.
    const hiddenDate = $('wst_doe_doc_date');
    const visibleDate = $('acd_resp_do_date');
    if (hiddenDate && visibleDate && !hiddenDate.value && visibleDate.value) {
        hiddenDate.value = visibleDate.value;
    }
    document.querySelectorAll('#acd_resp_do_pack_type_toggle .acd-resp-type-btn').forEach(btn => btn.addEventListener('click', () => setPackType(btn.dataset.packType)));
    $('acd_resp_do_lines').addEventListener('input', e => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, false);
    });
    $('acd_resp_do_lines').addEventListener('change', e => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, true);
    });
    $('acd_resp_do_addline').addEventListener('click', () => {
        const itemCode = ($('acd_resp_do_item').value || '').trim(); const itemName = ($('acd_resp_do_item_display').value || '').trim() || itemCode; const packType = ($('acd_resp_do_pack_type').value || '').trim(); const qtyRaw = ($('acd_resp_do_qty').value || '').trim(); const kgRaw = ($('acd_resp_do_kg').value || '').trim(); const qty = parseQty(qtyRaw || '0'); const kg = parseKg(kgRaw || '0'); const defaultItemPrice = parseMoney($('acd_resp_do_item_price').value || '0'); const enteredPrice = parseMoney($('acd_resp_do_price').value || ''); const price = enteredPrice > 0 ? enteredPrice : defaultItemPrice; const customerCode = ($('acd_resp_do_customer').value || '').trim(); const assignedDriverId = parseInt($('acd_resp_do_driver')?.value || '0',10) || 0;
        if (!customerCode) { toast('error','Customer missing'); return; } if (!assignedDriverId) { toast('error','Select driver'); return; } if (!itemCode) { toast('error','Select an item'); return; } if (qtyRaw === '' || qty <= 0) { toast('error','Qty must be >0'); return; } if (kgRaw === '' || kg <= 0) { toast('error','KG must be >0'); return; }
        const nextLine = {itemCode,itemName,packType,qty,kg,total:calcTotalKg(qty,kg),price};
        if (state.editingIndex >= 0 && state.lines[state.editingIndex]) {
            state.lines[state.editingIndex] = nextLine;
            toast('success','Item updated');
        } else {
            state.lines.push(nextLine);
            toast('success','Item added');
        }
        updateUI(); clearLineEntry();
    });
    $('acd_resp_do_lines').addEventListener('click', e => {
        const editBtn = e.target.closest('[data-edit-idx]');
        if (editBtn) {
            loadLineForEdit(parseInt(editBtn.dataset.editIdx,10));
            return;
        }
        const btn = e.target.closest('.acd-resp-delete-btn,.acd-resp-mobile-delete-btn');
        if (!btn || !btn.dataset.idx) return;
        const idx = parseInt(btn.dataset.idx,10);
        if (!isNaN(idx)) { state.lines.splice(idx,1); updateUI(); toast('info','Item removed'); }
    });
    $('acd_resp_do_form').addEventListener('submit', async e => {
        e.preventDefault();
        if (state.isSubmitting) return;
        if (!parseInt($('acd_resp_do_driver')?.value || '0',10)) { modal('error','Select driver','This update needs one driver for the whole Delivery Order.'); return; }
        if (!state.lines.length) { modal('error','Add at least one item','This update cannot be queued without item lines.'); return; }
        syncHiddenLines();
        const confirmed = await confirmQueueUpdate();
        if (!confirmed) return;
        state.isSubmitting = true;
        $('acd_resp_do_submit').disabled = true;
        $('acd_resp_do_submit').textContent = 'Queueing update...';
        HTMLFormElement.prototype.submit.call($('acd_resp_do_form'));
    });

    updateClearButtons(); setPackType('BASKET'); updateUI();
    const savedJob = Number(root.dataset.savedJob || 0);
    if (savedJob > 0) { $('acd_resp_do_success_actions').style.display = 'block'; if (window.Swal) { Swal.fire({icon:'success',title:'Update queued',html:`AutoCount update job #${savedJob} has been queued for <strong>${esc(root.dataset.docNo || '')}</strong>. Print or reprint after the status is ready.`,confirmButtonText:'Go to list/status',confirmButtonColor:'#166534',showCancelButton:true,cancelButtonText:'Stay here'}).then(result => { if (result.isConfirmed) window.location.href = root.dataset.listUrl || '/delivery-order-records/'; }); } }
})();
</script>�|�Az��E����S.sE�N��
}? infimumsupremum#�P�5�Connection-Test<?php
/**
 * Temporary MSSQL connection test.
 * Paste into a PHP snippet/page.
 * Remove after testing.
 */

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

if (!is_user_logged_in() || !current_user_can('manage_options')) {
    echo 'Admin only.';
    return;
}

echo '<pre style="white-space:pre-wrap;background:#111827;color:#e5e7eb;padding:16px;border-radius:10px;font-size:13px;">';

echo "=== MSSQL Connection Test ===\n\n";

if (!function_exists('sqlsrv_connect')) {
    echo "FAILED: sqlsrv_connect() is not available.\n";
    echo "Meaning: PHP SQLSRV extension is missing or disabled.\n";
    echo '</pre>';
    return;
}

echo "PASS: sqlsrv_connect() function exists.\n";

if (!function_exists('get_mssql')) {
    echo "FAILED: get_mssql() function not found.\n";
    echo "Meaning: Your AutoCount/SQL bridge code is not loaded on this page.\n";
    echo '</pre>';
    return;
}

echo "PASS: get_mssql() function exists.\n\n";

$conn = get_mssql();

if (!$conn) {
    echo "FAILED: get_mssql() returned empty connection.\n\n";

    if (function_exists('sqlsrv_errors')) {
        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);

        if (!empty($errors)) {
            echo "SQLSRV errors:\n";
            foreach ($errors as $err) {
                echo "- SQLSTATE: " . ($err['SQLSTATE'] ?? '') . "\n";
                echo "  Code: " . ($err['code'] ?? '') . "\n";
                echo "  Message: " . ($err['message'] ?? '') . "\n\n";
            }
        } else {
            echo "No SQLSRV error returned.\n";
            echo "Meaning: get_mssql() failed silently or did not expose the SQLSRV error.\n";
        }
    }

    echo '</pre>';
    return;
}

echo "PASS: get_mssql() returned a connection.\n\n";

/**
 * Lightweight test query.
 * This only checks whether SQL Server accepts a simple query.
 * It does not test DO table/columns.
 */
$stmt = sqlsrv_query($conn, "SELECT DB_NAME() AS CurrentDatabase, SUSER_SNAME() AS LoginName, @@SERVERNAME AS ServerName");

if ($stmt === false) {
    echo "FAILED: Connected, but test query failed.\n\n";

    $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
    if (!empty($errors)) {
        echo "SQLSRV errors:\n";
        foreach ($errors as $err) {
            echo "- SQLSTATE: " . ($err['SQLSTATE'] ?? '') . "\n";
            echo "  Code: " . ($err['code'] ?? '') . "\n";
            echo "  Message: " . ($err['message'] ?? '') . "\n\n";
        }
    }

    echo '</pre>';
    return;
}

$row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);

echo "PASS: Simple SQL query succeeded.\n\n";
echo "Connected database: " . ($row['CurrentDatabase'] ?? '-') . "\n";
echo "SQL login/user: " . ($row['LoginName'] ?? '-') . "\n";
echo "SQL server: " . ($row['ServerName'] ?? '-') . "\n";

sqlsrv_free_stmt($stmt);

echo "\nRESULT: Database connection is working.\n";
echo "Meaning: If Delivery Order list still fails, the problem is probably table/column/query permission, not login.\n";

echo '</pre>';[xyz-ips snippet="Connection-Test"]���� ���6�Update-TableNF&1�[xyz-ips snippet="Update-Table"]����)� >�>�purchase-invoice-viewNq&b�[xyz-ips snippet="purchase-invoice-view"]����,�(��8�GRNcreate-staff-workflowNK&��[xyz-ips snippet="GRNcreate-staff-workflow"]����)�0~�9�CreditorDebtorMappingNd&l[[xyz-ips snippet="CreditorDebtorMapping"]����*�8��:�Basket-Summary-UpdatedNQ&��[xyz-ips snippet="Basket-Summary-Updated"]����/�@�;�View-Delivery-Order-UpdatedNX&�B[xyz-ips snippet="View-Delivery-Order-Updated"]����-�H��7�delivery-order-view-printN\&[xyz-ips snippet="delivery-order-view-print"]����9�%P��<�delivery-order-daily-customer-summaryNf&��[xyz-ips snippet="delivery-order-daily-customer-summary"]����/�X�L�=�purchase-invoice-staff-listNO&[I[xyz-ips snippet="purchase-invoice-staff-list"]����,�`��?�wst-operations-dashboardNz&��[xyz-ips snippet="wst-operations-dashboard"]����p
�cS.s4Q
�F�����������_
N1����<?php
/**
 * VegeBasketDO staff-only AutoCount ItemUOM Sync page.
 *
 * Drop this snippet on /sync-item-uoms/.
 * It pulls AutoCount ItemUOM records through get_mssql()
 * and saves/updates them into wp_vege_acs_item_uoms.
 */

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

if (!is_user_logged_in()) {
    echo '<div style="padding:12px;border:1px solid #fecaca;background:#fff1f2;color:#991b1b;border-radius:8px;">Please log in to run ItemUOM sync.</div>';
    return;
}

$wst_user  = wp_get_current_user();
$wst_roles = is_array($wst_user->roles ?? null) ? $wst_user->roles : array();
$wst_staff = current_user_can('manage_options') || in_array('editor', $wst_roles, true);

if (!$wst_staff) {
    echo '<div style="padding:12px;border:1px solid #fecaca;background:#fff1f2;color:#991b1b;border-radius:8px;">You do not have permission to run ItemUOM sync.</div>';
    return;
}

if (!function_exists('get_mssql')) {
    echo '<div style="padding:12px;border:1px solid #fecaca;background:#fff1f2;color:#991b1b;border-radius:8px;">AutoCount connection function get_mssql() is not available.</div>';
    return;
}

global $wpdb;

if (!function_exists('wst_acs_clean_string')) {
    function wst_acs_clean_string($value) {
        if ($value === null) {
            return null;
        }

        $value = trim((string) $value);
        return $value === '' ? null : $value;
    }
}

if (!function_exists('wst_acs_nullable_int')) {
    function wst_acs_nullable_int($value) {
        if ($value === null || $value === '') {
            return null;
        }

        return (int) $value;
    }
}

if (!function_exists('wst_acs_nullable_decimal')) {
    function wst_acs_nullable_decimal($value, $scale = 4) {
        if ($value === null || $value === '') {
            return null;
        }

        return number_format((float) $value, (int) $scale, '.', '');
    }
}

if (!function_exists('wst_acs_sqlsrv_datetime_to_mysql')) {
    function wst_acs_sqlsrv_datetime_to_mysql($value) {
        if ($value === null || $value === '') {
            return null;
        }

        if ($value instanceof DateTimeInterface) {
            return $value->format('Y-m-d H:i:s');
        }

        $timestamp = strtotime((string) $value);

        if ($timestamp === false) {
            return null;
        }

        return date('Y-m-d H:i:s', $timestamp);
    }
}

if (!function_exists('wst_acs_sql_errors')) {
    function wst_acs_sql_errors() {
        if (!function_exists('sqlsrv_errors')) {
            return 'Unknown SQL Server error.';
        }

        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);

        if (empty($errors)) {
            return 'Unknown SQL Server error.';
        }

        $out = array();

        foreach ($errors as $error) {
            $out[] = '[' . ($error['code'] ?? '') . '] ' . ($error['message'] ?? '');
        }

        return implode(' | ', $out);
    }
}

if (!function_exists('wst_acs_table_exists')) {
    function wst_acs_table_exists($table_name) {
        global $wpdb;

        return $wpdb && $wpdb->get_var(
            $wpdb->prepare('SHOW TABLES LIKE %s', $table_name)
        ) === $table_name;
    }
}

if (!function_exists('wst_acs_upsert_item_uom')) {
    function wst_acs_upsert_item_uom($table, array $data) {
        global $wpdb;

        $columns = array_keys($data);

        $column_sql = array();
        $value_sql  = array();
        $values     = array();

        foreach ($columns as $col) {
            $column_sql[] = '`' . str_replace('`', '``', $col) . '`';

            if ($data[$col] === null) {
                $value_sql[] = 'NULL';
            } else {
                $value_sql[] = '%s';
                $values[] = $data[$col];
            }
        }

        $update_sql = array();

        foreach ($columns as $col) {
            if ($col === 'item_code' || $col === 'uom' || $col === 'created_at') {
                continue;
            }

            $safe_col = '`' . str_replace('`', '``', $col) . '`';
            $update_sql[] = "{$safe_col} = VALUES({$safe_col})";
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', (string) $table);

        $sql = "
            INSERT INTO `{$safe_table}`
                (" . implode(', ', $column_sql) . ")
            VALUES
                (" . implode(', ', $value_sql) . ")
            ON DUPLICATE KEY UPDATE
                " . implode(",\n                ", $update_sql) . "
        ";

        if (!empty($values)) {
            $sql = $wpdb->prepare($sql, $values);
        }

        return $wpdb->query($sql) !== false;
    }
}

function wst_acs_run_item_uom_sync() {
    global $wpdb;

    $mysql_table = $wpdb->prefix . 'acs_item_uoms';

    if (!wst_acs_table_exists($mysql_table)) {
        return new WP_Error(
            'mysql_table_missing',
            'MySQL table not found: ' . $mysql_table
        );
    }

    $conn = get_mssql();

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

    $sql = "
        SELECT
            AutoKey,
            ItemCode,
            UOM,
            Rate,
            Shelf,
            Price,
            Cost,
            RealCost,
            MostRecentlyCost,
            MinSalePrice,
            MaxSalePrice,
            MinPurchasePrice,
            MaxPurchasePrice,
            Weight,
            WeightUOM,
            Volume,
            VolumeUOM,
            BarCode,
            LastUpdate,
            Price2,
            Guid,
            Price3,
            Price4,
            Price5,
            Price6,
            Measurement,
            SGEInvoiceUnitCode
        FROM ItemUOM
        ORDER BY ItemCode ASC, UOM ASC
    ";

    $stmt = sqlsrv_query($conn, $sql, array(), array(
        'QueryTimeout' => 120,
    ));

    if ($stmt === false) {
        return new WP_Error(
            'item_uom_query_failed',
            'AutoCount ItemUOM query failed: ' . wst_acs_sql_errors()
        );
    }

    $total   = 0;
    $success = 0;
    $failed  = 0;
    $skipped = 0;
    $errors  = array();

    while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
        $total++;

        $item_code = wst_acs_clean_string($row['ItemCode'] ?? null);
        $uom       = wst_acs_clean_string($row['UOM'] ?? null);

        if ($item_code === null || $uom === null) {
            $skipped++;
            continue;
        }

        $data = array(
            'auto_key'              => wst_acs_nullable_int($row['AutoKey'] ?? null),
            'item_code'             => $item_code,
            'uom'                   => $uom,
            'rate'                  => wst_acs_nullable_decimal($row['Rate'] ?? null, 6),
            'shelf'                 => wst_acs_clean_string($row['Shelf'] ?? null),

            'price'                 => wst_acs_nullable_decimal($row['Price'] ?? null, 4),
            'price2'                => wst_acs_nullable_decimal($row['Price2'] ?? null, 4),
            'price3'                => wst_acs_nullable_decimal($row['Price3'] ?? null, 4),
            'price4'                => wst_acs_nullable_decimal($row['Price4'] ?? null, 4),
            'price5'                => wst_acs_nullable_decimal($row['Price5'] ?? null, 4),
            'price6'                => wst_acs_nullable_decimal($row['Price6'] ?? null, 4),

            'cost'                  => wst_acs_nullable_decimal($row['Cost'] ?? null, 4),
            'real_cost'             => wst_acs_nullable_decimal($row['RealCost'] ?? null, 4),
            'most_recently_cost'    => wst_acs_nullable_decimal($row['MostRecentlyCost'] ?? null, 4),

            'min_sale_price'        => wst_acs_nullable_decimal($row['MinSalePrice'] ?? null, 4),
            'max_sale_price'        => wst_acs_nullable_decimal($row['MaxSalePrice'] ?? null, 4),
            'min_purchase_price'    => wst_acs_nullable_decimal($row['MinPurchasePrice'] ?? null, 4),
            'max_purchase_price'    => wst_acs_nullable_decimal($row['MaxPurchasePrice'] ?? null, 4),

            'weight'                => wst_acs_nullable_decimal($row['Weight'] ?? null, 6),
            'weight_uom'            => wst_acs_clean_string($row['WeightUOM'] ?? null),
            'volume'                => wst_acs_nullable_decimal($row['Volume'] ?? null, 6),
            'volume_uom'            => wst_acs_clean_string($row['VolumeUOM'] ?? null),

            'bar_code'              => wst_acs_clean_string($row['BarCode'] ?? null),
            'guid'                  => wst_acs_clean_string($row['Guid'] ?? null),
            'measurement'           => wst_acs_clean_string($row['Measurement'] ?? null),
            'sg_einvoice_unit_code' => wst_acs_clean_string($row['SGEInvoiceUnitCode'] ?? null),

            'last_update'           => wst_acs_sqlsrv_datetime_to_mysql($row['LastUpdate'] ?? null),
        );

        $ok = wst_acs_upsert_item_uom($mysql_table, $data);

        if ($ok) {
            $success++;
        } else {
            $failed++;
            $errors[] = 'ItemCode ' . $item_code . ' / UOM ' . $uom . ': ' . $wpdb->last_error;
        }
    }

    sqlsrv_free_stmt($stmt);

    return array(
        'total'   => $total,
        'success' => $success,
        'failed'  => $failed,
        'skipped' => $skipped,
        'errors'  => $errors,
        'table'   => $mysql_table,
    );
}

$sync_result = wst_acs_run_item_uom_sync();

?>

<div style="font-family:Segoe UI,Arial,sans-serif;max-width:980px;margin:20px auto;padding:18px;border:1px solid #dbe4ee;border-radius:12px;background:#fff;box-shadow:0 8px 24px rgba(15,23,42,.08);">
    <h2 style="margin-top:0;">AutoCount ItemUOM Sync</h2>

    <?php if (is_wp_error($sync_result)): ?>
        <div style="padding:12px;border:1px solid #fecaca;background:#fff1f2;color:#991b1b;border-radius:8px;">
            <strong>Sync failed.</strong><br>
            <?php echo esc_html($sync_result->get_error_message()); ?>
        </div>
    <?php else: ?>
        <div style="padding:12px;border:1px solid #bbf7d0;background:#f0fdf4;color:#14532d;border-radius:8px;margin-bottom:14px;">
            <strong>Sync completed.</strong>
        </div>

        <table style="width:100%;border-collapse:collapse;">
            <tbody>
                <tr>
                    <td style="padding:8px;border-bottom:1px solid #e5e7eb;"><strong>MySQL Table</strong></td>
                    <td style="padding:8px;border-bottom:1px solid #e5e7eb;"><?php echo esc_html($sync_result['table']); ?></td>
                </tr>
                <tr>
                    <td style="padding:8px;border-bottom:1px solid #e5e7eb;"><strong>Total AutoCount Rows Read</strong></td>
                    <td style="padding:8px;border-bottom:1px solid #e5e7eb;"><?php echo esc_html((string) $sync_result['total']); ?></td>
                </tr>
                <tr>
                    <td style="padding:8px;border-bottom:1px solid #e5e7eb;"><strong>Inserted / Updated</strong></td>
                    <td style="padding:8px;border-bottom:1px solid #e5e7eb;"><?php echo esc_html((string) $sync_result['success']); ?></td>
                </tr>
                <tr>
                    <td style="padding:8px;border-bottom:1px solid #e5e7eb;"><strong>Skipped</strong></td>
                    <td style="padding:8px;border-bottom:1px solid #e5e7eb;"><?php echo esc_html((string) $sync_result['skipped']); ?></td>
                </tr>
                <tr>
                    <td style="padding:8px;border-bottom:1px solid #e5e7eb;"><strong>Failed</strong></td>
                    <td style="padding:8px;border-bottom:1px solid #e5e7eb;"><?php echo esc_html((string) $sync_result['failed']); ?></td>
                </tr>
            </tbody>
        </table>

        <?php if (!empty($sync_result['errors'])): ?>
            <h3>Errors</h3>
            <pre style="white-space:pre-wrap;padding:12px;background:#f8fafc;border:1px solid #e5e7eb;border-radius:8px;"><?php echo esc_html(implode("\n", array_slice($sync_result['errors'], 0, 50))); ?></pre>
        <?php endif; ?>

        <p style="margin-bottom:0;color:#475569;">
            Reloading this page is safe because existing rows are updated by <code>item_code + uom</code>. It will not duplicate ItemUOM rows.
        </p>
    <?php endif; ?>
</div>���_�fIG��������<j�v
N?�H<?php
/**
 * BASKET STAFF RETURN LIST
 *
 * Staff-facing basket summary and movement history page.
 * Basket return receipts use the same A5 visual style as the driver basket receipt.
 */

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

if (!is_user_logged_in()) {
    echo '<div style="padding:14px;border:1px solid #fecaca;background:#fff1f2;border-radius:12px;color:#991b1b;">Please log in to view Basket Summary.</div>';
    return;
}

if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
    echo '<div style="padding:14px;border:1px solid #fecaca;background:#fff1f2;border-radius:12px;color:#991b1b;">You do not have permission to view Basket Summary.</div>';
    return;
}

$rest_nonce         = wp_create_nonce('wp_rest');
$rest_summary_url            = rest_url('ac/v1/basket/summary');
$rest_ledger_url             = rest_url('ac/v1/basket/ledger');
$rest_creditor_summary_url   = rest_url('ac/v1/creditor-basket/summary');
$rest_creditor_ledger_url    = rest_url('ac/v1/creditor-basket/ledger');
$ajax_url                    = admin_url('admin-ajax.php');
$debtor_nonce                = wp_create_nonce('ac_cs_debtor_search');
$creditor_nonce              = wp_create_nonce('ac_cs_creditor_search');
$basket_proof_nonce          = wp_create_nonce('ac_bs_basket_proof');
$receipt_logo_url            = 'https://website.ipohserver.com/excellentvege/wp-content/uploads/2026/05/Untitled-design-15.png';
$show_debtor_code            = false;
$show_creditor_code          = false;
?>

<div id="ac-basket-summary-root"
     class="ac-bs-wrap bs-container"
     data-rest-nonce="<?php echo esc_attr($rest_nonce); ?>"
     data-rest-summary-url="<?php echo esc_attr($rest_summary_url); ?>"
     data-rest-ledger-url="<?php echo esc_attr($rest_ledger_url); ?>"
     data-rest-creditor-summary-url="<?php echo esc_attr($rest_creditor_summary_url); ?>"
     data-rest-creditor-ledger-url="<?php echo esc_attr($rest_creditor_ledger_url); ?>"
     data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
     data-debtor-nonce="<?php echo esc_attr($debtor_nonce); ?>"
     data-creditor-nonce="<?php echo esc_attr($creditor_nonce); ?>"
     data-basket-proof-nonce="<?php echo esc_attr($basket_proof_nonce); ?>"
     data-receipt-logo-url="<?php echo esc_url($receipt_logo_url); ?>"
     data-show-debtor-code="<?php echo $show_debtor_code ? '1' : '0'; ?>"
     data-show-creditor-code="<?php echo $show_creditor_code ? '1' : '0'; ?>">

  <div class="bs-head">
    <h1>Basket Summary</h1>
  </div>

  <div class="bs-card">
    <div class="bs-account-toggle" id="ac_bs_account_toggle" aria-label="Basket account type">
      <button type="button" class="bs-account-toggle-btn active" data-account-mode="customer">Customer Baskets</button>
      <button type="button" class="bs-account-toggle-btn" data-account-mode="creditor">Creditor Baskets</button>
    </div>

    <div class="bs-grid">
      <div class="bs-field">
        <label class="bs-label" id="ac_bs_account_label">Customer</label>
        <div class="bs-search-wrap">
          <input type="text" id="ac_bs_customer_input" class="bs-input" placeholder="Search customer to add..." autocomplete="off" readonly>
          <button type="button" id="ac_bs_customer_clear" class="bs-field-clear" aria-label="Clear customer">x</button>
          <input type="hidden" id="ac_bs_debtor_code" value="">
          <input type="hidden" id="ac_bs_debtor_name" value="">
        </div>
        <div id="ac_bs_selected_customers" class="bs-selected-customers"></div>
        <div id="ac_bs_manage_selected" class="bs-manage-selected" aria-hidden="true">
          <div class="bs-manage-head">
            <div>
              <strong id="ac_bs_manage_title">Selected Customers</strong>
              <span id="ac_bs_manage_count">0 selected</span>
            </div>
            <button type="button" class="bs-mini-btn" id="ac_bs_manage_done">Done</button>
          </div>
          <div class="bs-manage-actions">
            <button type="button" class="bs-mini-btn primary" id="ac_bs_manage_add">Add Customer</button>
            <button type="button" class="bs-mini-btn danger" id="ac_bs_manage_clear">Clear All</button>
          </div>
          <div id="ac_bs_manage_list" class="bs-manage-list"></div>
        </div>
      </div>

      <div class="bs-field">
        <label class="bs-label" for="ac_bs_date_from">Date From</label>
        <input id="ac_bs_date_from" type="date" class="bs-input">
      </div>

      <div class="bs-field">
        <label class="bs-label" for="ac_bs_date_to">Date To</label>
        <input id="ac_bs_date_to" type="date" class="bs-input">
      </div>

      <div class="bs-actions">
        <button id="ac_bs_refresh" class="bs-btn" type="button">Refresh Summary</button>
      </div>
    </div>

    <div id="ac_bs_status" class="bs-status"></div>
  </div>

  <div class="bs-card">
    <div class="bs-totals" id="ac_bs_totals"></div>

    <div class="bs-table-wrap">
      <table class="bs-table">
        <thead>
          <tr>
            <th style="width:60px;">No</th>
            <th id="ac_bs_code_heading" style="width:140px;">Customer Code</th>
            <th id="ac_bs_name_heading">Customer Name</th>
            <th id="ac_bs_in_heading" style="width:120px;">Basket Sent</th>
            <th id="ac_bs_out_heading" style="width:130px;">Basket Returned</th>
            <th style="width:150px;">Outstanding Basket</th>
            <th style="width:130px;">Last Activity</th>
            <th style="width:90px;">Action</th>
          </tr>
        </thead>
        <tbody id="ac_bs_rows_table">
          <tr><td colspan="8" class="bs-empty-cell">No data</td></tr>
        </tbody>
      </table>
    </div>
  </div>

  <div class="bs-ledger-modal" id="ac_bs_ledger_modal" aria-hidden="true">
    <div class="bs-ledger-backdrop" id="ac_bs_ledger_backdrop"></div>
    <div class="bs-ledger-dialog">
      <div class="bs-ledger-head">
        <h2 class="bs-subtitle" id="ac_bs_ledger_title">Basket Movement History</h2>
        <button type="button" class="bs-ledger-close" id="ac_bs_ledger_close" aria-label="Close">x</button>
      </div>

      <div class="bs-ledger-body">
        <div class="bs-ledger-toolbar" id="ac_bs_ledger_toolbar" style="display:none;">
          <div class="bs-ledger-filter">
            <div class="bs-ledger-filter-group">
              <label>From <input type="date" id="ac_bs_ledger_date_from" class="bs-input"></label>
              <label>To <input type="date" id="ac_bs_ledger_date_to" class="bs-input"></label>
            </div>
            <div class="bs-ledger-filter-group">
              <select id="ac_bs_ledger_movement_filter" class="bs-input"></select>
            </div>
            <div class="bs-ledger-filter-group bs-ledger-action-group">
              <button type="button" id="ac_bs_ledger_select_all" class="bs-mini-btn">Select All</button>
              <button type="button" id="ac_bs_ledger_print" class="bs-view-btn bs-receipt-btn">Print PDF</button>
              <button type="button" id="ac_bs_ledger_share" class="bs-view-btn bs-receipt-btn bs-ledger-share-btn">Share PDF</button>
            </div>
          </div>
        </div>
        <div class="bs-table-wrap">
          <table class="bs-table bs-ledger-table">
            <thead>
              <tr>
                <th style="width:40px;"></th>
                <th style="width:50px;">No</th>
                <th style="width:120px;">Date</th>
                <th style="width:120px;">Movement</th>
                <th style="width:80px;">Qty</th>
                <th style="width:140px;">Source</th>
                <th style="width:130px;">Document No.</th>
                <th>Note</th>
                <th style="width:140px;">Receipt</th>
              </tr>
            </thead>
            <tbody id="ac_bs_ledger_table">
              <tr><td colspan="9" class="bs-empty-cell">Select a customer to view basket movement history.</td></tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>

  <div id="ac_bs_receipt_mount"></div>

  <div class="bs-picker-modal" id="ac_bs_picker_modal" aria-hidden="true">
    <div class="bs-picker-backdrop" id="ac_bs_picker_backdrop"></div>
    <div class="bs-picker-sheet">
      <div class="bs-picker-head">
        <div class="bs-picker-title" id="ac_bs_picker_title">Select Customer</div>
        <button type="button" class="bs-picker-close" id="ac_bs_picker_close" aria-label="Close">x</button>
      </div>

      <div class="bs-picker-body">
        <input type="text" id="ac_bs_picker_search" class="bs-input bs-picker-search" placeholder="Search customer..." autocomplete="off">
        <div class="bs-picker-results" id="ac_bs_picker_results"></div>
      </div>
    </div>
  </div>
</div>

<style>
.bs-container{
  --bs-border:#dbe4ee;
  --bs-border-strong:#c4d0dd;
  --bs-text:#0f172a;
  --bs-muted:#475569;
  --bs-green:#0B4A2D;
  --bs-green-2:#166534;
  --bs-green-3:#16a34a;
  --bs-green-soft:#f0fdf4;
  --bs-bg:#f5faf7;
  --bs-danger:#991b1b;
  max-width:1360px;
  margin:0 auto;
  padding:16px;
  font-family:"Segoe UI",Roboto,Arial,sans-serif;
  background:linear-gradient(180deg,#f4faf5 0%,#eef7f1 100%);
  border-radius:16px;
  color:var(--bs-text);
  box-sizing:border-box;
}
.bs-container *{box-sizing:border-box;}
.bs-head{display:none;}
.bs-card{background:#fff;border:1px solid var(--bs-border);border-radius:16px;box-shadow:0 8px 28px rgba(15,23,42,.06);padding:16px;margin-bottom:14px;box-sizing:border-box;}
.bs-account-toggle{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-bottom:12px;padding:5px;border:1px solid var(--bs-border);border-radius:12px;background:#f8fafc;max-width:520px;}
.bs-account-toggle-btn{min-height:42px;border:1px solid transparent!important;border-radius:9px!important;background:transparent!important;color:#475569!important;font-size:13px!important;font-weight:950!important;cursor:pointer!important;padding:8px 12px!important;}
.bs-account-toggle-btn.active{background:#166534!important;border-color:#166534!important;color:#fff!important;box-shadow:0 5px 14px rgba(22,101,52,.18)!important;}
.bs-account-toggle-btn:focus{outline:none!important;box-shadow:0 0 0 3px rgba(22,101,52,.16)!important;}
.bs-grid{display:grid;grid-template-columns:minmax(260px,1.4fr) minmax(150px,.75fr) minmax(150px,.75fr) auto;gap:12px;align-items:end;}
.bs-label{display:block;font-size:13px;color:var(--bs-muted);margin-bottom:6px;font-weight:800;letter-spacing:.01em;}
.bs-input{width:100%;min-height:46px;font-size:15px;padding:10px 12px;border-radius:12px;border:1px solid var(--bs-border-strong);box-sizing:border-box;background:#fff;color:#111;transition:border-color .16s ease, box-shadow .16s ease, background .16s ease;}
.bs-input:focus,.bs-btn:focus,.bs-view-btn:focus,.bs-mini-btn:focus{outline:none;border-color:var(--bs-green);box-shadow:0 0 0 3px rgba(11,74,45,.12);}
#ac-basket-summary-root #ac_bs_refresh.bs-btn{min-width:170px;min-height:46px;border:0!important;border-radius:12px!important;background:linear-gradient(135deg,#16a34a,#0B4A2D)!important;color:#fff!important;font-size:15px!important;font-weight:900!important;padding:10px 18px!important;cursor:pointer;box-shadow:0 10px 22px rgba(22,101,52,.20)!important;transition:transform .16s ease, box-shadow .16s ease, filter .16s ease;}
#ac-basket-summary-root #ac_bs_refresh.bs-btn:hover{filter:brightness(.96);transform:translateY(-1px);box-shadow:0 14px 28px rgba(22,101,52,.23)!important;}
.bs-status{display:none;margin-top:10px;font-size:14px;color:#334155;}
.bs-totals{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:14px;}
.bs-total-box{border:1px solid #e5e7eb;background:linear-gradient(180deg,#fff,#f8fafc);border-radius:14px;padding:12px;box-shadow:0 3px 12px rgba(15,23,42,.035);}
.bs-total-box.issue{border-color:#fecaca;background:#fff7f7;}
.bs-total-label{font-size:12px;color:#64748b;font-weight:800;letter-spacing:.01em;}
.bs-total-value{font-size:24px;font-weight:950;color:#0f172a;margin-top:4px;line-height:1.05;}
.bs-total-sub{font-size:12px;color:#64748b;font-weight:750;margin-top:5px;line-height:1.25;}
.bs-table-wrap{width:100%;overflow:auto;border:1px solid #e5e7eb;border-radius:14px;background:#fff;box-shadow:inset 0 1px 0 rgba(255,255,255,.7);}
.bs-table{width:100%;min-width:980px;border-collapse:separate;border-spacing:0;background:#fff;}
.bs-table thead th{position:sticky;top:0;z-index:1;background:#f8fafc;color:#334155;font-size:13px;font-weight:950;text-align:left;padding:12px 12px;border-bottom:1px solid #e5e7eb;white-space:nowrap;letter-spacing:.01em;}
.bs-table tbody td{padding:11px 12px;font-size:14px;line-height:1.25;color:#0f172a;border-bottom:1px solid #edf2f7;vertical-align:top;}
.bs-table tbody tr:nth-child(odd){background:#ffffff;}
.bs-table tbody tr:nth-child(even){background:#f6fbf7;}
.bs-table tbody tr:hover{background:#edf8f1;}
.bs-empty-cell{color:#64748b;text-align:center;padding:22px!important;font-weight:800;}
.bs-view-btn{-webkit-appearance:none!important;appearance:none!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;min-height:36px!important;border:1px solid #16a34a!important;border-radius:10px!important;background:#f0fdf4!important;color:#166534!important;font-size:12px!important;font-weight:900!important;line-height:1.15!important;padding:7px 11px!important;text-shadow:none!important;box-shadow:none!important;cursor:pointer!important;transition:background .16s ease, color .16s ease, border-color .16s ease, box-shadow .16s ease, transform .16s ease;}
.bs-view-btn:hover,.bs-view-btn:focus{border-color:#166534!important;background:#166534!important;color:#fff!important;text-decoration:none!important;box-shadow:0 0 0 3px rgba(22,101,52,.16)!important;transform:translateY(-1px);}
.bs-receipt-btn{min-width:120px!important;border:0!important;background:#166534!important;color:#fff!important;white-space:normal!important;text-align:center!important;box-shadow:0 7px 16px rgba(22,101,52,.18)!important;}
.bs-ledger-action-group{justify-content:flex-end;}
.bs-ledger-share-btn{background:#128C7E!important;color:#fff!important;border:0!important;box-shadow:0 7px 16px rgba(18,140,126,.18)!important;}
.bs-row-selected,.bs-row-selected td{background:#ecfdf3!important;}
.bs-chip{display:inline-flex;align-items:center;justify-content:center;border-radius:999px;padding:5px 11px;font-size:12px;font-weight:900;border:1px solid;white-space:nowrap;}
.bs-chip.ok{color:#166534;background:#dcfce7;border-color:#86efac;}
.bs-chip.warn{color:#9a3412;background:#ffedd5;border-color:#fdba74;}
.bs-chip.neg{color:#991b1b;background:#fee2e2;border-color:#fca5a5;}
.bs-type-send{color:#166534;font-weight:900;}
.bs-type-return{color:#9a3412;font-weight:900;}
.bs-search-wrap{position:relative;}
.bs-search-wrap .bs-input{padding-right:2.35rem;cursor:pointer;}
.bs-field-clear{position:absolute;top:50%;right:.45rem;transform:translateY(-50%);width:1.75rem;height:1.75rem;border:1px solid var(--bs-border)!important;background:#fff!important;color:#64748b!important;border-radius:.55rem!important;display:none;align-items:center;justify-content:center;font-size:.9rem;font-weight:900;cursor:pointer;padding:0!important;line-height:1!important;}
.bs-field-clear.show{display:inline-flex;}
.bs-selected-customers{display:flex;flex-wrap:nowrap;align-items:center;gap:6px;margin-top:8px;min-height:30px;overflow:hidden;}
.bs-selected-customers:empty{display:none;}
.bs-selected-chip{display:inline-flex;align-items:center;gap:6px;max-width:170px;min-width:0;border:1px solid #bbf7d0;background:#f0fdf4;color:#166534;border-radius:999px;padding:5px 8px;font-size:12px;font-weight:900;line-height:1.15;}
.bs-selected-chip span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.bs-selected-more{display:inline-flex;align-items:center;flex:0 0 auto;border:1px solid #cbd5e1;background:#f8fafc;color:#334155;border-radius:999px;padding:5px 9px;font-size:12px;fo<j�vi�|pH��������<j��
N?�Int-weight:900;line-height:1.15;}
.bs-manage-toggle{flex:0 0 auto;border:1px solid #166534!important;background:#166534!important;color:#fff!important;border-radius:999px!important;padding:5px 10px!important;font-size:12px!important;font-weight:950!important;line-height:1.15!important;cursor:pointer!important;}
.bs-manage-toggle:hover,.bs-manage-toggle:focus{background:#0f4f2e!important;border-color:#0f4f2e!important;outline:none!important;box-shadow:0 0 0 3px rgba(22,101,52,.16)!important;}
.bs-selected-remove{position:relative;width:18px;height:18px;flex:0 0 18px;border:1px solid #86efac!important;background:#fff!important;color:#166534!important;border-radius:999px!important;display:inline-block!important;padding:0!important;font-size:0!important;line-height:0!important;cursor:pointer!important;vertical-align:middle!important;}
.bs-selected-remove::before,.bs-selected-remove::after{content:"";position:absolute;left:50%;top:50%;width:8px;height:2px;background:currentColor;border-radius:999px;transform-origin:center;}
.bs-selected-remove::before{transform:translate(-50%,-50%) rotate(45deg);}
.bs-selected-remove::after{transform:translate(-50%,-50%) rotate(-45deg);}
.bs-selected-remove:hover,.bs-selected-remove:focus{background:#166534!important;color:#fff!important;border-color:#166534!important;outline:none!important;}
.bs-field{position:relative;}
.bs-manage-selected{position:absolute;z-index:30;left:0;top:calc(100% + 8px);width:min(460px, calc(100vw - 48px));display:none;background:#fff;border:1px solid #cbd5e1;border-radius:14px;box-shadow:0 22px 48px rgba(15,23,42,.20);padding:10px;box-sizing:border-box;}
.bs-manage-selected.active{display:block;}
.bs-manage-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-bottom:8px;border-bottom:1px solid #e5e7eb;}
.bs-manage-head strong{display:block;font-size:13px;color:#0f172a;line-height:1.2;}
.bs-manage-head span{display:block;margin-top:2px;font-size:12px;color:#64748b;font-weight:800;}
.bs-manage-actions{display:flex;gap:8px;margin:10px 0;}
.bs-mini-btn{border:1px solid #cbd5e1!important;background:#fff!important;color:#334155!important;border-radius:10px!important;padding:8px 11px!important;font-size:12px!important;font-weight:950!important;cursor:pointer!important;line-height:1.15!important;}
.bs-mini-btn.primary{background:#166534!important;border-color:#166534!important;color:#fff!important;}
.bs-mini-btn.danger{background:#fff1f2!important;border-color:#fecaca!important;color:#991b1b!important;}
.bs-mini-btn:hover,.bs-mini-btn:focus{filter:brightness(.97);outline:none!important;box-shadow:0 0 0 3px rgba(15,23,42,.08)!important;}
.bs-manage-list{max-height:220px;overflow:auto;display:flex;flex-direction:column;gap:6px;}
.bs-manage-row{display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid #e5e7eb;background:#f8fafc;border-radius:10px;padding:8px 9px;}
.bs-manage-row-name{min-width:0;font-size:13px;font-weight:900;color:#0f172a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.bs-manage-empty{padding:14px;text-align:center;color:#64748b;font-size:13px;font-weight:800;background:#f8fafc;border-radius:10px;}
.bs-ledger-modal,.bs-picker-modal{position:fixed;inset:0;z-index:99990;display:none;align-items:center;justify-content:center;padding:18px;box-sizing:border-box;}
.bs-ledger-modal.active,.bs-picker-modal.active{display:flex;}
.bs-ledger-backdrop,.bs-picker-backdrop{position:absolute;inset:0;background:rgba(15,23,42,.62);backdrop-filter:blur(4px);}
.bs-ledger-dialog{position:relative;width:min(1120px, calc(100vw - 36px));max-height:calc(100dvh - 36px);background:#fff;border-radius:22px;box-shadow:0 28px 80px rgba(15,23,42,.32);display:flex;flex-direction:column;overflow:hidden;border:1px solid rgba(255,255,255,.62);}
.bs-ledger-head{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:18px 20px;border-bottom:1px solid rgba(255,255,255,.14);background:linear-gradient(135deg,#0B4A2D,#166534);color:#fff;}
.bs-ledger-head .bs-subtitle{margin:0;font-size:19px;line-height:1.25;font-weight:950;letter-spacing:-.01em;color:#fff;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;}
.bs-ledger-close{width:38px;height:38px;flex:0 0 38px;border:1px solid rgba(255,255,255,.38)!important;border-radius:12px!important;background:rgba(255,255,255,.12)!important;color:#fff!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;padding:0!important;font-size:18px!important;font-weight:950!important;cursor:pointer;line-height:1!important;}
.bs-ledger-close:hover,.bs-ledger-close:focus{background:#fff!important;color:#0B4A2D!important;}
.bs-ledger-body{padding:14px;background:#f8fafc;overflow:auto;}
.bs-ledger-toolbar{padding:0;margin:0 0 12px;display:flex;align-items:center;gap:10px;}
.bs-ledger-filter{width:100%;display:grid;grid-template-columns:minmax(290px,1fr) minmax(180px,.45fr) auto;align-items:end;gap:10px;padding:12px;border:1px solid #e2e8f0;border-radius:16px;background:#fff;box-shadow:0 6px 18px rgba(15,23,42,.045);}
.bs-ledger-filter-group{display:flex;flex-wrap:wrap;align-items:end;gap:8px;padding:0;}
.bs-ledger-filter label{display:flex;align-items:center;gap:7px;font-size:13px;color:#334155;font-weight:850;white-space:nowrap;padding:0;}
.bs-ledger-filter .bs-input{min-height:38px;padding:7px 9px;font-size:13px;border-radius:10px;}
.bs-ledger-filter label .bs-input{width:150px;}
.bs-ledger-filter select.bs-input{min-width:170px;}
.bs-ledger-filter .bs-mini-btn,.bs-ledger-filter .bs-view-btn{min-height:38px;margin-top:0;}
#ac_bs_ledger_table td input[type=checkbox]{width:18px;height:18px;cursor:pointer;accent-color:#166534;}
.bs-ledger-table th:first-child,.bs-ledger-table td:first-child{text-align:center;}
#ac_bs_ledger_select_all.toggled{background:#166534!important;border-color:#166534!important;color:#fff!important;}
.bs-ledger-toolbar.error-message{color:#991b1b;background:#fee2e2;border:1px solid #fecaca;padding:8px 10px;border-radius:8px;font-size:13px;font-weight:700;margin-bottom:10px;}
.bs-ledger-table{min-width:900px!important;table-layout:fixed;}
.bs-ledger-table th,.bs-ledger-table td{padding:10px 12px!important;font-size:13px!important;line-height:1.28!important;vertical-align:middle!important;word-break:break-word;}
.bs-ledger-loading-cell{padding:42px 18px!important;text-align:center!important;background:#fff!important;}
.bs-ledger-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;color:#334155;font-weight:900;}
.bs-ledger-spinner{width:36px;height:36px;border:4px solid #dbe4ee;border-top-color:#166534;border-radius:50%;animation:bsSpin .8s linear infinite;}
@keyframes bsSpin{to{transform:rotate(360deg);}}
body.bs-ledger-open,body.bs-br-open{overflow:hidden;}
.bs-picker-sheet{position:relative;width:100%;max-width:36rem;background:#fff;border-radius:16px;box-shadow:0 24px 70px rgba(15,23,42,.28);overflow:hidden;border:1px solid rgba(255,255,255,.6);}
.bs-picker-head{display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:14px 16px;border-bottom:1px solid #e5e7eb;background:#f8fafc;}
.bs-picker-title{font-size:.95rem;font-weight:900;}
.bs-picker-close{width:34px;height:34px;border:1px solid #dbe4ee!important;border-radius:10px!important;background:#fff!important;color:#334155!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;padding:0!important;font-size:16px!important;font-weight:900!important;cursor:pointer;line-height:1!important;}
.bs-picker-body{padding:.8rem;display:flex;flex-direction:column;gap:.55rem;}
.bs-picker-results{max-height:18rem;overflow-y:auto;display:flex;flex-direction:column;gap:.4rem;}
.bs-picker-note{text-align:center;padding:.7rem;color:var(--bs-muted);font-size:.82rem;font-weight:800;}
.bs-picker-item{display:block;width:100%;text-align:left;padding:.7rem .75rem;border:1px solid var(--bs-border)!important;border-radius:.65rem!important;background:#fff!important;color:var(--bs-text)!important;cursor:pointer;}
.bs-picker-item:hover,.bs-picker-item:focus{border-color:#166534!important;box-shadow:0 0 0 3px rgba(22,101,52,.10)!important;outline:none!important;}
.bs-picker-item-main{display:block;font-weight:900;font-size:.9rem;color:var(--bs-text);}
.bs-picker-item-sub{display:block;font-size:.72rem;color:var(--bs-muted);margin-top:.12rem;}

/* Driver-style Basket Return Receipt */
.bs-br-overlay{position:fixed;inset:0;z-index:100001;background:rgba(15,23,42,.58);overflow:auto;padding:24px;display:flex;align-items:flex-start;justify-content:center;font-family:Arial,Helvetica,sans-serif;color:#111;backdrop-filter:blur(4px);}
.bs-br-modal{width:min(580px, calc(100vw - 40px));background:#f3f4f6;border-radius:18px;padding:14px;box-shadow:0 28px 80px rgba(0,0,0,.35);}
.bs-br-actions{position:sticky;top:0;z-index:5;display:grid;grid-template-columns:1fr 1fr 1fr;gap:9px;margin:0 0 12px;background:#f3f4f6;padding-bottom:8px;}
.bs-br-actions button{border:0!important;border-radius:14px!important;min-height:46px!important;padding:10px 14px!important;font-size:14px!important;font-weight:950!important;cursor:pointer!important;font-family:inherit!important;}
.bs-br-actions button:disabled{opacity:.7;cursor:wait!important;}
.bs-br-print,.bs-br-share{background:#e9f7ee!important;color:#0B4A2D!important;border:1px solid #cce8d6!important;}
.bs-br-close{background:#0B4A2D!important;color:#fff!important;}
.bs-br-card{border:1px solid #dbe4ee;border-radius:18px;padding:14px;background:#fff;box-shadow:0 8px 22px rgba(10,45,29,.045);}
.bs-br-paper{border:1px solid #d1d5db;background:#fff;padding:16px;color:#111;font-family:Arial,sans-serif;box-sizing:border-box;}
.bs-br-head{display:flex;justify-content:space-between;align-items:center;gap:12px;border-bottom:2px solid #111;padding-bottom:10px;margin-bottom:12px;}
.bs-br-logo{width:176px;height:64px;object-fit:contain;object-position:left center;display:block;}
.bs-br-title{text-align:right;font-size:12px;font-weight:900;letter-spacing:.08em;}
.bs-br-no{text-align:right;font-size:15px;font-weight:900;margin-top:4px;}
.bs-br-info{display:grid;grid-template-columns:1fr 1fr;gap:12px;border-bottom:1px solid #e5e7eb;padding:8px 0 10px;}
.bs-br-field{min-width:0;}
.bs-br-field span{display:block;font-size:12px;font-weight:800;color:#111;margin-bottom:4px;}
.bs-br-field strong{display:block;font-size:15px;font-weight:900;line-height:1.2;word-break:break-word;color:#111;}
.bs-br-qty{font-size:34px;font-weight:950;text-align:center;color:#111;padding:18px 0;}
.bs-br-proof{margin-top:6px;border:1px dashed #cbd5e1;padding:10px;text-align:center;font-size:12px;font-weight:800;color:#111;min-height:58px;}
.bs-br-proof img{display:block;width:100%;max-height:330px;object-fit:contain;margin-top:8px;}
.bs-br-loading{padding:24px;text-align:center;font-weight:900;color:#334155;}
#bsBrPrintArea{display:none!important;}

@media (max-width:1024px){
  .bs-container{max-width:none;margin:0;border-radius:0;padding:.7rem;}
  .bs-card{padding:.75rem;margin-bottom:.65rem;border-radius:14px;box-shadow:0 2px 10px rgba(15,23,42,.04);}
  .bs-head{display:flex;align-items:center;margin-bottom:12px;}
  .bs-head h1{font-size:1.35rem;margin:0;font-weight:950;}
  .bs-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:.55rem;}
  .bs-grid .bs-field:first-child{grid-column:1 / -1;}
  .bs-actions{grid-column:1 / -1;}
  #ac-basket-summary-root #ac_bs_refresh.bs-btn{width:100%;min-height:44px;border-radius:.75rem!important;}
  .bs-table{min-width:880px;}
  .bs-table thead th,.bs-table tbody td{font-size:.82rem;padding:.55rem .65rem;}
  .bs-totals{gap:.55rem;margin-bottom:.65rem;}
  .bs-total-value{font-size:1.2rem;}
}

@media (max-width:760px){
  .bs-container{padding:.5rem;background:#f6faf7;}
  .bs-card{border-radius:14px;padding:.65rem;}
  .bs-grid{grid-template-columns:1fr;gap:.55rem;}
  .bs-grid .bs-field:first-child,.bs-actions{grid-column:auto;}
  .bs-label{font-size:.78rem;margin-bottom:.28rem;}
  .bs-input{min-height:42px;padding:.55rem .65rem;font-size:.92rem;border-radius:10px;}
  .bs-totals{grid-template-columns:repeat(2,minmax(0,1fr));gap:.45rem;margin-bottom:.55rem;}
  .bs-total-box{padding:.62rem;border-radius:12px;}
  .bs-total-label{font-size:.62rem;}
  .bs-total-value{font-size:1rem;}
  .bs-total-sub{font-size:.66rem;}
  .bs-selected-customers{overflow:auto;padding-bottom:2px;}
  .bs-selected-chip{max-width:210px;}
  .bs-manage-selected{position:fixed;left:10px;right:10px;top:auto;bottom:10px;width:auto;max-height:65dvh;overflow:auto;z-index:100000;border-radius:16px;}

  .bs-table-wrap{border:0;overflow:visible;background:transparent;box-shadow:none;}
  .bs-table{display:block;width:100%;min-width:0!important;background:transparent;border-collapse:separate;}
  .bs-table thead{display:none;}
  .bs-table tbody{display:block;width:100%;}
  .bs-table tbody tr{display:block;width:100%;margin:0 0 .62rem;border:1px solid #e2e8f0;border-radius:14px;background:#fff!important;box-shadow:0 5px 18px rgba(15,23,42,.055);overflow:hidden;}
  .bs-table tbody td{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;width:100%;padding:.72rem .78rem!important;border-bottom:1px solid #eef2f7!important;text-align:right;font-size:.86rem!important;line-height:1.28!important;}
  .bs-table tbody td:last-child{border-bottom:0!important;}
  .bs-table tbody td[data-label]::before{content:attr(data-label);flex:0 0 42%;text-align:left;color:#475569;font-weight:950;}
  .bs-table tbody td:not([data-label]){display:block;text-align:center;}
  .bs-table tbody td:not([data-label])::before{content:none;}
  .bs-table .bs-empty-cell{display:block!important;width:100%;text-align:center!important;padding:18px!important;border:0!important;}
  .bs-view-btn,.bs-receipt-btn{width:auto!important;min-height:36px!important;}
  .bs-chip{padding:4px 9px;}

  .bs-ledger-modal{padding:0;align-items:stretch;justify-content:stretch;}
  .bs-ledger-dialog{width:100%;max-width:none;height:100dvh;max-height:100dvh;border-radius:0;border:0;}
  .bs-ledger-head{padding:14px 12px;align-items:flex-start;}
  .bs-ledger-head .bs-subtitle{font-size:16px;-webkit-line-clamp:3;}
  .bs-ledger-close{width:36px;height:36px;flex-basis:36px;border-radius:10px!important;}
  .bs-ledger-body{padding:10px;overflow:auto;}
  .bs-ledger-toolbar{margin-bottom:10px;}
  .bs-ledger-filter{grid-template-columns:1fr;gap:9px;padding:10px;border-radius:14px;}
  .bs-ledger-filter-group{display:grid;grid-template-columns:1fr;gap:8px;width:100%;}
  .bs-ledger-filter-group:last-child{grid-template-columns:repeat(3,minmax(0,1fr));}
  .bs-ledger-filter label{display:grid;grid-template-columns:42px 1fr;align-items:center;width:100%;}
  .bs-ledger-filter label .bs-input,.bs-ledger-filter select.bs-input{width:100%;min-width:0;}
  .bs-ledger-filter .bs-mini-btn,.bs-ledger-filter .bs-view-btn{width:100%!important;min-width:0!important;min-height:42px!important;}
  .bs-ledger-table tbody td:first-child{justify-content:space-between;text-align:right;}
  .bs-ledger-table tbody td:first-child input{margin-left:auto;}

  .bs-picker-modal{padding:0;align-items:flex-end;}
  .bs-picker-sheet{max-width:none;width:100%;border-radius:18px 18px 0 0;}
  .bs-picker-results{max-height:55dvh;}
  .bs-br-overlay{padding:10px;align-items:flex-start;}
  .bs-br-modal{width:100%;border-radius:14px;padding:10px;}
  .bs-br-info,.bs-br-actions{grid-template-columns:1fr;}
  .bs-br-actions{position:relative;top:auto;}
  .bs-br-logo{width:140px;height:52px;}
  .bs-br-qty{font-size:28px;}
}

@media (max-width:420px){
  .bs-totals{grid-template-columns:1fr;}
  .bs-ledger-filter-group:last-child{grid-template-columns:1fr;}
  .bs-table tbody td[data-label]::before{flex-basis:46%;}
}

@media print{
  html,body{background:#fff!important;width:148mm;min-height:0!important;height:auto!important;overflow:hidden!important;}
  body > *:not(#bsBrPrintArea){display:none!important;}
  body *{visibility:hidden!important;}
  #bsBrPrintArea,#bsBrPrintArea *{v<j����&�I��������<k8�
N?�Jisibility:visible!important;}
  #bsBrPrintArea{display:block!important;position:absolute!important;left:0!important;top:0!important;width:100%!important;max-height:190mm!important;overflow:hidden!important;page-break-after:avoid!important;break-after:avoid!important;}
  #bsBrPrintArea .bs-br-card{border:0!important;box-shadow:none!important;margin:0!important;padding:0!important;page-break-inside:avoid!important;break-inside:avoid!important;}
  #bsBrPrintArea .bs-br-paper{height:188mm!important;overflow:hidden!important;page-break-inside:avoid!important;break-inside:avoid!important;}
  .bs-br-actions{display:none!important;}
  @page{size:A5 portrait;margin:6mm;}
}
</style>

<script>
(function(){
  const wrap = document.getElementById('ac-basket-summary-root');
  if (!wrap || wrap.dataset.init === '1') return;
  wrap.dataset.init = '1';

  const REST_NONCE                 = wrap.dataset.restNonce || '';
  const REST_SUMMARY_URL           = wrap.dataset.restSummaryUrl || '';
  const REST_LEDGER_URL            = wrap.dataset.restLedgerUrl || '';
  const REST_CREDITOR_SUMMARY_URL  = wrap.dataset.restCreditorSummaryUrl || '';
  const REST_CREDITOR_LEDGER_URL   = wrap.dataset.restCreditorLedgerUrl || '';
  const AJAX_URL                   = wrap.dataset.ajaxUrl || '';
  const DEBTOR_NONCE               = wrap.dataset.debtorNonce || '';
  const CREDITOR_NONCE             = wrap.dataset.creditorNonce || '';
  const SHOW_DEBTOR_CODE           = wrap.dataset.showDebtorCode === '1';
  const SHOW_CREDITOR_CODE         = wrap.dataset.showCreditorCode === '1';
  const RECEIPT_LOGO_URL           = wrap.dataset.receiptLogoUrl || '';

  const $ = id => wrap.querySelector('#' + id);
  const pickerState = { items: [], fetchFn: null, onPick: null };
  const selectedDebtors = [];
  const ledgerCache = {};
  let pickerTimer = null;
  let currentLedgerRows = [];
  let currentLedgerCustomer = { code: '', name: '' };
  let currentReceiptForShare = null;
  let brJsPdfPromise = null;
  let loadSummarySeq = 0;
  let accountMode = 'customer';

  function modeConfig(){
    const creditor = accountMode === 'creditor';
    return {
      creditor,
      singular: creditor ? 'Creditor' : 'Customer',
      plural: creditor ? 'Creditors' : 'Customers',
      singularLower: creditor ? 'creditor' : 'customer',
      pluralLower: creditor ? 'creditors' : 'customers',
      summaryUrl: creditor ? REST_CREDITOR_SUMMARY_URL : REST_SUMMARY_URL,
      ledgerUrl: creditor ? REST_CREDITOR_LEDGER_URL : REST_LEDGER_URL,
      queryCode: creditor ? 'creditorCode' : 'debtorCode',
      ajaxAction: creditor ? 'ac_cs_creditor_search' : 'ac_cs_debtor_search',
      nonce: creditor ? CREDITOR_NONCE : DEBTOR_NONCE,
      showCode: creditor ? SHOW_CREDITOR_CODE : SHOW_DEBTOR_CODE,
      inboundLabel: creditor ? 'Basket Received' : 'Basket Sent',
      outboundLabel: 'Basket Returned',
      selectedLabel: creditor ? 'Selected Creditors' : 'Selected Customers',
      activeLabel: creditor ? 'Creditors Active' : 'Customers Active',
      outstandingLabel: creditor ? 'Creditors With Outstanding' : 'Customers With Outstanding'
    };
  }

  function isReturnTxn(row){
    const type = getRowTxnType(row);
    return type === 'RETURN' || type === 'RETURN_TO_CREDITOR' || type === 'ADJUSTMENT_OUT';
  }

  function fmtQty(n){
    const x = Number(n);
    return Number.isFinite(x) ? Math.round(x) : '0';
  }

  function esc(s){
    if (s === null || s === undefined) return '';
    return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));
  }

  function showError(title, message){
    if (window.Swal && typeof Swal.fire === 'function') {
      Swal.fire({ icon: 'error', title: title || 'Error', text: message || 'Something went wrong' });
    } else {
      alert((title || 'Error') + '\n' + (message || 'Something went wrong'));
    }
  }

  function showInfo(title, message){
    if (window.Swal && typeof Swal.fire === 'function') {
      Swal.fire({ icon:'info', title:title || 'Info', text:message || '' });
    } else {
      alert((title || 'Info') + '\n' + (message || ''));
    }
  }

  function setLedgerButtonBusy(button, text){
    if (!button) return '';
    const originalText = button.textContent || '';
    button.disabled = true;
    button.textContent = text || 'Preparing...';
    return originalText;
  }

  function restoreLedgerButton(button, originalText, fallbackText){
    if (!button) return;
    button.disabled = false;
    button.textContent = originalText || fallbackText || button.textContent;
  }

  function chipClass(outstanding){
    const v = Number(outstanding) || 0;
    if (v < 0) return 'neg';
    if (v > 0) return 'warn';
    return 'ok';
  }

  function outstandingMeaning(outstanding){
    const v = Number(outstanding) || 0;
    const cfg = modeConfig();
    if (v < 0) {
      return cfg.creditor
        ? 'Over-return: returned ' + fmtQty(Math.abs(v)) + ' more baskets than received'
        : 'Over-return: returned ' + fmtQty(Math.abs(v)) + ' more than sent';
    }
    if (v > 0) {
      return cfg.creditor
        ? 'Outstanding: WST still holds ' + fmtQty(v) + ' creditor basket'
        : 'Outstanding: customer still has ' + fmtQty(v) + ' basket';
    }
    return cfg.creditor
      ? 'Balanced: received and returned creditor baskets match'
      : 'Balanced: sent and returned baskets match';
  }

  function dateSortValue(value){
    if (!value) return 0;
    const parsed = Date.parse(String(value).replace(' ', 'T'));
    return Number.isNaN(parsed) ? 0 : parsed;
  }

  function compareText(a, b){
    return String(a || '').localeCompare(String(b || ''), undefined, { sensitivity:'base', numeric:true });
  }

  function compareSummaryByLastActivity(a, b){
    const dateDiff = dateSortValue(b.lastTxnDate || b.last_txn_date) - dateSortValue(a.lastTxnDate || a.last_txn_date);
    if (dateDiff !== 0) return dateDiff;

    const nameDiff = compareText(a.debtorName || a.debtor_name, b.debtorName || b.debtor_name);
    if (nameDiff !== 0) return nameDiff;

    return compareText(a.debtorCode || a.debtor_code, b.debtorCode || b.debtor_code);
  }

  function compareLedgerByLastActivity(a, b){
    const dateDiff = dateSortValue(b.txnDate || b.txn_date || b.date) - dateSortValue(a.txnDate || a.txn_date || a.date);
    if (dateDiff !== 0) return dateDiff;

    return compareText(b.id || b.ledgerId || b.ledger_id, a.id || a.ledgerId || a.ledger_id);
  }

  function pick(row, keys, fallback=''){
    if (!row) return fallback;
    for (const key of keys) {
      if (row[key] !== undefined && row[key] !== null && row[key] !== '') return row[key];
    }
    return fallback;
  }

  function normalizeSummaryRow(row){
    const cfg = modeConfig();
    const debtorCode = String(cfg.creditor
      ? pick(row, ['creditorCode', 'creditor_code', 'supplierCode', 'supplier_code'], '')
      : pick(row, ['debtorCode', 'debtor_code', 'customerCode', 'customer_code'], '')
    ).trim();
    const debtorName = String(cfg.creditor
      ? pick(row, ['creditorName', 'creditor_name', 'supplierName', 'supplier_name'], '')
      : pick(row, ['debtorName', 'debtor_name', 'customerName', 'customer_name'], '')
    ).trim();

    return {
      ...row,
      debtorCode,
      debtorName,
      accountType: cfg.creditor ? 'creditor' : 'customer',
      sendQty: Number(cfg.creditor
        ? pick(row, ['receiveQty', 'receive_qty', 'basketReceived', 'basket_received', 'sendQty', 'send_qty'], 0)
        : pick(row, ['sendQty', 'send_qty', 'basketSent', 'basket_sent'], 0)
      ) || 0,
      returnQty: Number(pick(row, ['returnQty', 'return_qty', 'basketReturned', 'basket_returned'], 0)) || 0,
      outstandingQty: Number(pick(row, ['outstandingQty', 'outstanding_qty', 'outstandingBasket', 'outstanding_basket'], 0)) || 0,
      lastTxnDate: String(pick(row, ['lastTxnDate', 'last_txn_date', 'lastActivity', 'last_activity'], '')).trim()
    };
  }

  function normalizeLedgerRow(row){
    const cfg = modeConfig();
    const debtorCode = String(cfg.creditor
      ? pick(row, ['creditorCode', 'creditor_code', 'supplierCode', 'supplier_code'], '')
      : pick(row, ['debtorCode', 'debtor_code', 'customerCode', 'customer_code'], '')
    ).trim();
    const debtorName = String(cfg.creditor
      ? pick(row, ['creditorName', 'creditor_name', 'supplierName', 'supplier_name'], '')
      : pick(row, ['debtorName', 'debtor_name', 'customerName', 'customer_name'], '')
    ).trim();

    return {
      ...row,
      id: pick(row, ['id', 'ledgerId', 'ledger_id'], ''),
      debtorCode,
      debtorName,
      accountType: cfg.creditor ? 'creditor' : 'customer',
      txnDate: String(pick(row, ['txnDate', 'txn_date', 'date'], '')).trim(),
      txnType: String(pick(row, ['txnType', 'txn_type', 'type'], '')).trim(),
      qty: Number(pick(row, ['qty', 'quantity'], 0)) || 0,
      sourceType: String(pick(row, ['sourceType', 'source_type'], '')).trim(),
      sourceRef: String(pick(row, ['sourceRef', 'source_ref', 'refNo', 'ref_no', 'docNo', 'doc_no'], '')).trim(),
      remark: String(pick(row, ['remark', 'note'], '')).trim(),
      proofImage: String(pick(row, ['proofImage', 'proof_image', 'imageUrl', 'image_url'], '')).trim()
    };
  }

  function dedupeSummaryRowsByDebtor(rows){
    const byCode = new Map();
    rows.forEach(row => {
      const key = debtorKey(row.debtorCode || row.debtorName);
      if (!key) return;
      const existing = byCode.get(key);
      if (!existing || dateSortValue(row.lastTxnDate) > dateSortValue(existing.lastTxnDate)) {
        byCode.set(key, row);
      }
    });
    return Array.from(byCode.values());
  }

  function debtorKey(value){
    return String(value || '').trim().toUpperCase();
  }

  function selectedDebtorCodes(){
    return selectedDebtors.map(d => debtorKey(d.code)).filter(Boolean);
  }

  function syncCustomerFilterInputs(){
    const cfg = modeConfig();
    const first = selectedDebtors[0] || { code: '', name: '' };
    $('ac_bs_debtor_code').value = first.code || '';
    $('ac_bs_debtor_name').value = first.name || '';

    const input = $('ac_bs_customer_input');
    if (!selectedDebtors.length) {
      input.value = '';
    } else if (selectedDebtors.length === 1) {
      input.value = selectedDebtors[0].name || selectedDebtors[0].code || '';
    } else {
      input.value = selectedDebtors.length + ' ' + cfg.pluralLower + ' selected';
    }
  }

  function renderSelectedCustomers(){
    const cfg = modeConfig();
    const mount = $('ac_bs_selected_customers');
    if (!mount) return;

    if (!selectedDebtors.length) {
      mount.innerHTML = '';
      renderManageSelectedCustomers();
      return;
    }

    const visibleDebtors = selectedDebtors.slice(0, 2);
    const hiddenCount = Math.max(0, selectedDebtors.length - visibleDebtors.length);
    const chips = visibleDebtors.map(d => {
      const label = d.name || d.code || cfg.singular;
      const meta = cfg.showCode && d.code ? ' (' + d.code + ')' : '';
      return `<span class="bs-selected-chip" title="${esc(label + meta)}">
        <span>${esc(label + meta)}</span>
        <button type="button" class="bs-selected-remove" data-remove-selected-customer="${esc(d.code)}" aria-label="Remove ${esc(label)}"></button>
      </span>`;
    });

    if (hiddenCount > 0) {
      chips.push('<span class="bs-selected-more">+' + fmtQty(hiddenCount) + ' more</span>');
    }

    chips.push('<button type="button" class="bs-manage-toggle" data-toggle-selected-customers>Manage</button>');
    mount.innerHTML = chips.join('');
    renderManageSelectedCustomers();
  }

  function renderManageSelectedCustomers(){
    const cfg = modeConfig();
    const list = $('ac_bs_manage_list');
    const count = $('ac_bs_manage_count');
    if (count) count.textContent = selectedDebtors.length + ' selected';
    if (!list) return;

    if (!selectedDebtors.length) {
      list.innerHTML = '<div class="bs-manage-empty">No ' + esc(cfg.singularLower) + ' selected.</div>';
      return;
    }

    list.innerHTML = selectedDebtors.map(d => {
      const label = d.name || d.code || cfg.singular;
      const meta = cfg.showCode && d.code ? ' (' + d.code + ')' : '';
      return `<div class="bs-manage-row">
        <div class="bs-manage-row-name" title="${esc(label + meta)}">${esc(label + meta)}</div>
        <button type="button" class="bs-selected-remove" data-remove-selected-customer="${esc(d.code)}" aria-label="Remove ${esc(label)}"></button>
      </div>`;
    }).join('');
  }

  function openSelectedCustomerManager(){
    const panel = $('ac_bs_manage_selected');
    if (!panel) return;
    renderManageSelectedCustomers();
    panel.classList.add('active');
    panel.setAttribute('aria-hidden', 'false');
  }

  function closeSelectedCustomerManager(){
    const panel = $('ac_bs_manage_selected');
    if (!panel) return;
    panel.classList.remove('active');
    panel.setAttribute('aria-hidden', 'true');
  }

  function toggleSelectedCustomerManager(){
    const panel = $('ac_bs_manage_selected');
    if (!panel) return;
    if (panel.classList.contains('active')) {
      closeSelectedCustomerManager();
    } else {
      openSelectedCustomerManager();
    }
  }

  function updateCustomerSelectionUi(){
    syncCustomerFilterInputs();
    renderSelectedCustomers();
    updateCustomerClearButton();
  }

  function addSelectedCustomer(customer){
    if (!customer) return false;
    const code = String(customer.code || '').trim();
    const name = String(customer.name || '').trim();
    if (!code && !name) return false;

    const key = debtorKey(code || name);
    if (selectedDebtors.some(d => debtorKey(d.code || d.name) === key)) return false;

    selectedDebtors.push({ code, name });
    updateCustomerSelectionUi();
    return true;
  }

  function removeSelectedCustomer(code){
    const key = debtorKey(code);
    const idx = selectedDebtors.findIndex(d => debtorKey(d.code) === key);
    if (idx < 0) return;
    selectedDebtors.splice(idx, 1);
    updateCustomerSelectionUi();
    loadSummary();
  }

  function filterRowsBySelectedCustomers(rows){
    const selectedCodes = selectedDebtorCodes();
    if (!selectedCodes.length) return rows;
    const selectedSet = new Set(selectedCodes);
    return rows.filter(r => selectedSet.has(debtorKey(r.debtorCode || r.debtor_code)));
  }

  async function apiGet(url){
    const res = await fetch(url, {
      method:'GET',
      credentials:'same-origin',
      headers: { 'Accept':'application/json', 'X-WP-Nonce': REST_NONCE },
      cache:'no-store'
    });

    if (!res.ok) {
      let errMsg = 'HTTP ' + res.status;
      try {
        const errData = await res.json();
        errMsg = errData.message || errData.error || errMsg;
      } catch(e) {}
      throw new Error(errMsg);
    }

    return await res.json();
  }

  function buildSummaryUrl(selectedDebtor=null){
    const cfg = modeConfig();
    const url = new URL(cfg.summaryUrl, window.location.origin);
    const dateFrom = ($('ac_bs_date_from').value || '').trim();
    const dateTo   = ($('ac_bs_date_to').value || '').trim();
    const debtorCode = selectedDebtor
      ? (selectedDebtor.code || '').trim()
      : (selectedDebtors.length === 1 ? (selectedDebtors[0].code || '').trim() : '');
    const debtorName = selectedDebtor
      ? (selectedDebtor.name || '').trim()
      : (selectedDebtors.length === 1 ? (selectedDebtors[0].name || '').trim() : '');

    if (debtorCode) url.searchParams.set(cfg.queryCode, debtorCode);
    if (!debtorCode && debtorName) url.searchParams.set('q', debtorName);
    if (dateFrom) url.searchParams.set('dateFrom', dateFrom);
    if (dateTo) url.searchParams.set('dateTo', dateTo);
    url.searchParams.set('limit', '500');
    return url.toString();
  }

  function buildLedgerUrl(debtorCode){
    const cfg = modeConfig();
    const url = new URL(cfg.ledgerUrl, <k8��fknJ��������<kz�
N?�awindow.location.origin);
    const dateFrom = ($('ac_bs_date_from').value || '').trim();
    const dateTo   = ($('ac_bs_date_to').value || '').trim();

    url.searchParams.set(cfg.queryCode, debtorCode);
    if (dateFrom) url.searchParams.set('dateFrom', dateFrom);
    if (dateTo) url.searchParams.set('dateTo', dateTo);
    url.searchParams.set('limit', '200');
    return url.toString();
  }

  function dateRangeError(){
    const dateFrom = ($('ac_bs_date_from').value || '').trim();
    const dateTo = ($('ac_bs_date_to').value || '').trim();
    if (dateFrom && dateTo && dateFrom > dateTo) {
      return 'Date From cannot be later than Date To.';
    }
    return '';
  }

  function renderTotals(rows){
    const cfg = modeConfig();
    const totalsEl = $('ac_bs_totals');
    if (!rows.length) {
      if (totalsEl) totalsEl.style.display = 'none';
      return;
    }

    if (totalsEl) totalsEl.style.display = 'grid';

    const totals = rows.reduce((acc, r) => ({
      send: acc.send + Number(r.sendQty || 0),
      returned: acc.returned + Number(r.returnQty || 0),
      outstanding: acc.outstanding + Number(r.outstandingQty || 0),
      positiveOutstanding: acc.positiveOutstanding + Math.max(0, Number(r.outstandingQty || 0)),
      overReturn: acc.overReturn + Math.abs(Math.min(0, Number(r.outstandingQty || 0)))
    }), { send: 0, returned: 0, outstanding: 0, positiveOutstanding: 0, overReturn: 0 });

    const selectedAccount = selectedDebtors.length > 0;
    const issueRows = rows.filter(r => Number(r.outstandingQty || 0) < 0);

    if (selectedAccount) {
      const latestActivity = rows.reduce((latest, r) => {
        const date = r.lastTxnDate || r.last_txn_date || '';
        return dateSortValue(date) > dateSortValue(latest) ? date : latest;
      }, '');
      const accountOutstanding = totals.positiveOutstanding;
      const outstandingSub = totals.overReturn > 0
        ? 'Excludes ' + fmtQty(totals.overReturn) + ' over-return'
        : (accountOutstanding > 0 ? 'Needs follow-up' : 'Balanced');

      const selectedCountCard = selectedDebtors.length > 1
        ? '<div class="bs-total-box"><div class="bs-total-label">' + esc(cfg.selectedLabel) + '</div><div class="bs-total-value">' + fmtQty(selectedDebtors.length) + '</div><div class="bs-total-sub">Combined basket position</div></div>'
        : '';

      totalsEl.innerHTML =
        selectedCountCard +
        '<div class="bs-total-box"><div class="bs-total-label">' + esc(cfg.inboundLabel) + '</div><div class="bs-total-value">' + fmtQty(totals.send) + '</div></div>' +
        '<div class="bs-total-box"><div class="bs-total-label">' + esc(cfg.outboundLabel) + '</div><div class="bs-total-value">' + fmtQty(totals.returned) + '</div></div>' +
        '<div class="bs-total-box"><div class="bs-total-label">Outstanding Basket</div><div class="bs-total-value">' + fmtQty(accountOutstanding) + '</div><div class="bs-total-sub">' + esc(outstandingSub) + '</div></div>' +
        '<div class="bs-total-box"><div class="bs-total-label">Last Activity</div><div class="bs-total-value">' + esc(latestActivity || '-') + '</div></div>' +
        (totals.overReturn > 0
          ? '<div class="bs-total-box issue"><div class="bs-total-label">Data Issues</div><div class="bs-total-value">' + fmtQty(issueRows.length) + '</div><div class="bs-total-sub">Over-return total: ' + fmtQty(totals.overReturn) + '</div></div>'
          : '');
      return;
    }

    const outstandingRows = rows.filter(r => Number(r.outstandingQty || 0) > 0);
    const highest = outstandingRows.slice().sort((a, b) => {
      const qtyDiff = Number(b.outstandingQty || 0) - Number(a.outstandingQty || 0);
      if (qtyDiff !== 0) return qtyDiff;
      return compareSummaryByLastActivity(a, b);
    })[0] || null;
    const highestName = highest ? (highest.debtorName || highest.debtorCode || '-') : 'None';
    const highestQty = highest ? Number(highest.outstandingQty || 0) : 0;

    totalsEl.innerHTML =
      '<div class="bs-total-box"><div class="bs-total-label">' + esc(cfg.activeLabel) + '</div><div class="bs-total-value">' + fmtQty(rows.length) + '</div><div class="bs-total-sub">Has basket movement in range</div></div>' +
      '<div class="bs-total-box"><div class="bs-total-label">' + esc(cfg.outstandingLabel) + '</div><div class="bs-total-value">' + fmtQty(outstandingRows.length) + '</div><div class="bs-total-sub">Needs follow-up</div></div>' +
      '<div class="bs-total-box"><div class="bs-total-label">Total Outstanding</div><div class="bs-total-value">' + fmtQty(totals.positiveOutstanding) + '</div><div class="bs-total-sub">Excludes over-return rows</div></div>' +
      '<div class="bs-total-box"><div class="bs-total-label">Highest Outstanding</div><div class="bs-total-value">' + fmtQty(highestQty) + '</div><div class="bs-total-sub">' + esc(highestName) + '</div></div>' +
      (issueRows.length
        ? '<div class="bs-total-box issue"><div class="bs-total-label">Data Issues</div><div class="bs-total-value">' + fmtQty(issueRows.length) + '</div><div class="bs-total-sub">Over-return total: ' + fmtQty(totals.overReturn) + '</div></div>'
        : '');
  }

  function renderSummary(rows){
    const cfg = modeConfig();
    const sortedRows = rows.slice().sort(compareSummaryByLastActivity);
    wrap._lastRows = sortedRows;
    renderTotals(rows);
    const table = $('ac_bs_rows_table');

    if (!sortedRows.length) {
      table.innerHTML = '<tr><td colspan="8" class="bs-empty-cell">No basket records found.</td></tr>';
      return;
    }

    table.innerHTML = sortedRows.map((r, i) => {
      const code = r.debtorCode || '';
      const name = r.debtorName || '';
      const send = Number(r.sendQty || 0);
      const ret  = Number(r.returnQty || 0);
      const out  = Number(r.outstandingQty || 0);
      const lastDate = r.lastTxnDate || '-';
      const cls = chipClass(out);
      const outTitle = outstandingMeaning(out);

      return `<tr data-summary-row="1" data-debtor-code="${esc(code)}">
        <td data-label="No">${i+1}</td>
        <td data-label="${esc(cfg.singular)} Code">${esc(code)}</td>
        <td data-label="${esc(cfg.singular)} Name">${esc(name)}</td>
        <td data-label="${esc(cfg.inboundLabel)}">${fmtQty(send)}</td>
        <td data-label="${esc(cfg.outboundLabel)}">${fmtQty(ret)}</td>
        <td data-label="Outstanding Basket"><span class="bs-chip ${cls}" title="${esc(outTitle)}" aria-label="${esc(outTitle)}">${fmtQty(out)}</span></td>
        <td data-label="Last Activity">${esc(lastDate)}</td>
        <td data-label="Action"><button class="bs-view-btn" type="button" data-debtor-code="${esc(code)}" data-debtor-name="${esc(name)}">View</button></td>
      </tr>`;
    }).join('');
  }

  function setSummaryLoading(isLoading, message=''){
    const table = $('ac_bs_rows_table');
    const status = $('ac_bs_status');
    if (status) {
      status.style.display = message ? 'block' : 'none';
      status.textContent = message || '';
    }
    if (isLoading && table) {
      table.innerHTML = '<tr><td colspan="8" class="bs-empty-cell">Loading basket summary...</td></tr>';
    }
  }

  function getRowTxnType(row){
    return String(row?.txnType || row?.txn_type || '').toUpperCase();
  }

  function getRowSourceType(row){
    return String(row?.sourceType || row?.source_type || '').toUpperCase();
  }

  function movementLabel(row){
    const txnType = getRowTxnType(row);
    const sourceType = getRowSourceType(row);

    if (txnType === 'RECEIVE_FROM_CREDITOR') return 'Received From Creditor';
    if (txnType === 'RETURN_TO_CREDITOR') return 'Returned To Creditor';
    if (txnType === 'ADJUSTMENT_IN') return 'Adjustment In';
    if (txnType === 'ADJUSTMENT_OUT') return 'Adjustment Out';

    if (txnType === 'RETURN' && (sourceType === 'GOODS_RECEIVE_NOTE' || sourceType === 'GOODS_RECEIVED_NOTE')) {
      return 'GRN Customer Offset';
    }
    if (txnType === 'RETURN') return 'Basket Returned';
    return 'Sent Out';
  }

  function sourceTypeLabel(row){
    const sourceType = getRowSourceType(row);
    if (sourceType === 'DELIVERY_ORDER') return 'Delivery Order';
    if (sourceType === 'BASKET_RETURN') return 'Customer Basket Return';
    if (sourceType === 'CREDITOR_BASKET_RETURN') return 'Creditor Basket Return';
    if (sourceType === 'GOODS_RECEIVE_NOTE' || sourceType === 'GOODS_RECEIVED_NOTE') return 'Goods Receive';
    if (sourceType === 'MANUAL_ADJUSTMENT') return 'Manual Adjustment';
    return row.sourceType || row.source_type || '-';
  }

  function getRowProofImage(row){
    if (!row) return '';
    const possible = [
      row.proofImage, row.proof_image, row.proofImageUrl, row.proof_image_url,
      row.imageUrl, row.image_url, row.proofUrl, row.proof_url,
      row.returnProofImage, row.return_proof_image, row.basketProofImage,
      row.basket_proof_image, row.attachmentUrl, row.attachment_url
    ];

    for (const v of possible) {
      if (v && String(v).trim() !== '') return String(v).trim();
    }

    if (Array.isArray(row.proofImages) && row.proofImages.length) {
      const first = row.proofImages[0];
      return first.imageUrl || first.image_url || first.url || '';
    }

    if (Array.isArray(row.images) && row.images.length) {
      const first = row.images[0];
      return first.imageUrl || first.image_url || first.url || '';
    }

    return '';
  }

  async function fetchBasketProofImage(row){
    const existing = getRowProofImage(row);
    if (existing) return existing;

    const cfg = modeConfig();
    if (cfg.creditor) {
      return '';
    }

    const nonce = wrap.dataset.basketProofNonce || '';
    if (!AJAX_URL || !nonce || !row) return '';

    const fd = new FormData();
    fd.append('action', 'ac_bs_get_basket_proof');
    fd.append('nonce', nonce);
    fd.append('ledgerId', row.id || row.ledgerId || row.ledger_id || row.basketLedgerId || row.basket_ledger_id || row.returnLedgerId || row.return_ledger_id || '');
    fd.append('sourceRef', row.sourceRef || row.source_ref || row.refNo || row.ref_no || row.docNo || row.doc_no || '');
    fd.append('debtorCode', row.debtorCode || row.debtor_code || currentLedgerCustomer.code || '');
    fd.append('debtorName', row.debtorName || row.debtor_name || currentLedgerCustomer.name || '');
    fd.append('txnDate', row.txnDate || row.txn_date || row.date || '');

    try {
      const res = await fetch(AJAX_URL, { method:'POST', credentials:'same-origin', body:fd, cache:'no-store' });
      const data = await res.json();
      if (data && data.success && data.data && data.data.imageUrl) return data.data.imageUrl;
    } catch(e) {
      console.error('Basket proof AJAX failed:', e);
    }

    return '';
  }

  function receiptRef(row){
    return row?.sourceRef || row?.source_ref || row?.refNo || row?.ref_no || row?.docNo || row?.doc_no || row?.id || 'basket-return';
  }

  function receiptFileName(receipt){
    const ref = String(receipt?.ref || receipt?.id || 'basket-return').replace(/[^A-Za-z0-9_-]/g, '-');
    return `Basket-Return-${ref}.pdf`;
  }

  function getRowDriverName(row){
    const possible = [
      row?.driverLogin, row?.driver_login,
      row?.assignedDriverLogin, row?.assigned_driver_login,
      row?.driverName, row?.driver_name,
      row?.createdByLogin, row?.created_by_login,
      row?.createdByUserLogin, row?.created_by_user_login,
      row?.userLogin, row?.user_login,
      row?.createdByName, row?.created_by_name,
      row?.userName, row?.user_name,
      row?.vehiclePlate, row?.vehicle_plate
    ];

    for (const value of possible) {
      const s = String(value || '').trim();
      if (s) return s.toUpperCase();
    }

    return '';
  }

  function basketReceiptCardHtml(receipt){
    const proofHtml = receipt.proofImage
      ? `Image Proof<img src="${esc(receipt.proofImage)}" alt="Basket return proof" decoding="sync">`
      : 'No image proof uploaded';

    return `
      <div class="bs-br-card">
        <div class="bs-br-paper">
          <div class="bs-br-head">
            <div><img class="bs-br-logo" src="${esc(RECEIPT_LOGO_URL)}" alt="Company logo"></div>
            <div>
              <div class="bs-br-title">${esc(receipt.title || 'BASKET RETURN')}</div>
              <div class="bs-br-no">${esc(receipt.ref)}</div>
            </div>
          </div>
          <div class="bs-br-info">
            <div class="bs-br-field"><span>${esc(receipt.accountLabel || 'Customer')}</span><strong>${esc(receipt.customerName || receipt.accountLabel || 'Customer')}</strong></div>
            <div class="bs-br-field"><span>Driver</span><strong>${esc(receipt.driverName || '')}</strong></div>
          </div>
          <div class="bs-br-qty">${esc(receipt.qty)} BASKETS</div>
          <div class="bs-br-proof">${proofHtml}</div>
        </div>
      </div>`;
  }

  function basketReceiptHtml(row, proofImage){
    const cfg = modeConfig();
    const receipt = {
      ref: receiptRef(row),
      title: cfg.creditor ? 'CREDITOR BASKET RETURN' : 'CUSTOMER BASKET RETURN',
      accountLabel: cfg.singular,
      customerName: row?.debtorName || row?.debtor_name || currentLedgerCustomer.name || currentLedgerCustomer.code || cfg.singular,
      driverName: getRowDriverName(row),
      qty: fmtQty(row?.qty || 0),
      proofImage: proofImage || ''
    };

    return `
      <div class="bs-br-overlay" id="acBsBasketReceiptOverlay">
        <div class="bs-br-modal" role="dialog" aria-modal="true" aria-label="Basket Return Receipt">
          <div class="bs-br-actions">
            <button type="button" class="bs-br-print" data-print-current-basket-receipt="1">Print / Save PDF</button>
            <button type="button" class="bs-br-share" data-share-current-basket-receipt="1">Share PDF</button>
            <button type="button" class="bs-br-close" onclick="document.getElementById('ac_bs_receipt_mount').innerHTML='';document.body.classList.remove('bs-br-open')">Close</button>
          </div>
          ${basketReceiptCardHtml(receipt)}
        </div>
      </div>`;
  }

  function basketReceiptLoadingHtml(){
    return `
      <div class="bs-br-overlay" id="acBsBasketReceiptOverlay">
        <div class="bs-br-modal" role="dialog" aria-modal="true" aria-label="Loading Basket Return Receipt">
          <div class="bs-br-actions">
            <button type="button" class="bs-br-close" onclick="document.getElementById('ac_bs_receipt_mount').innerHTML='';document.body.classList.remove('bs-br-open')">Close</button>
          </div>
          <div class="bs-br-card"><div class="bs-br-paper"><div class="bs-br-loading">Loading basket receipt...</div></div></div>
        </div>
      </div>`;
  }

  async function openBasketReceiptByIndex(idx){
    const row = currentLedgerRows[Number(idx)];
    const cfg = modeConfig();
    if (!row || !isReturnTxn(row)) {
      showError('Receipt not available', 'Basket receipt is only available for returned basket movement.');
      return;
    }

    const mount = $('ac_bs_receipt_mount');
    if (!mount) return;

    document.body.classList.add('bs-br-open');
    mount.innerHTML = basketReceiptLoadingHtml();
    const proofImage = await fetchBasketProofImage(row);
    currentReceiptForShare = {
      id: row.id || row.ledgerId || row.ledger_id || idx,
      ref: receiptRef(row),
      title: cfg.creditor ? 'CREDITOR BASKET RETURN' : 'CUSTOMER BASKET RETURN',
      accountLabel: cfg.singular,
      customerName: row?.debtorName || row?.debtor_name || currentLedgerCustomer.name || currentLedgerCustomer.code || cfg.singular,
      driverName: getRowDriverName(row),
      qty: fmtQty(row?.qty || 0),
      proofUrl: proofImage || ''
    };
    mount.innerHTML = basketReceiptHtml(row, proofImage);
  }

  function loadJsPdf(){
    if (window.jspdf && window.jspdf.jsPDF) return Promise.resolve(window.jspdf.jsPDF);
    if (brJsPdfPromise) return brJsPdfPromise;

    brJsPdfPromise = new Promise((resolve, reject) => {
      const scr<kz���K����������<�
N?�L<?php
/**
 * RESPONSIVE GOODS RECEIVE NOTE (GRN) Staff Workflow
 * Single-form page template for creating Goods Receive Notes.
 * - Desktop: entry card grid + full-width items table
 * - Tablet/mobile: stacked cards with scrollable table
 * - Modal pickers for creditors and items
 * - Lines grouped by creditor (no driver).
 * - Duplicate merge rule: creditor + item + type + KG + price equal.
 */

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

if (!is_user_logged_in()) {
    echo '<div style="padding:1rem;border:1px solid #fecaca;background:#fff1f2;border-radius:.75rem;color:#991b1b;text-align:center;">
            Please log in to continue.
          </div>';
    return;
}

$current_user = wp_get_current_user();
$current_roles = is_array($current_user->roles ?? null) ? $current_user->roles : [];
$can_create_grn = current_user_can('manage_options') || in_array('editor', $current_roles, true);
if (!$can_create_grn) {
    echo '<div style="padding:1rem;border:1px solid #fecaca;background:#fff1f2;border-radius:.75rem;color:#991b1b;text-align:center;">
            You do not have permission to create Goods Receive Notes.
          </div>';
    return;
}

// -------------------------------------------------------------------
// Shared REST & AJAX data
// -------------------------------------------------------------------
$rest_nonce          = wp_create_nonce('wp_rest');
$ajax_url            = admin_url('admin-ajax.php');
$creditor_nonce      = wp_create_nonce('ac_cs_creditor_search');
$item_suggest_nonce  = wp_create_nonce('ac_itemcode_suggest');
$default_location    = 'HQ';
$today_date          = current_time('Y-m-d');

$REST_JOB_POST       = rest_url('ac/v1/job');
$REST_JOB_BASE       = rest_url('ac/v1/job/');

$show_creditor_code  = false;
$show_item_code      = false;
?>

<!-- Load SweetAlert2 for all alerts/modals -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.all.min.js" integrity="sha256-M8nwhwekb+0Pt6bKB+xvEYGbQ8lR8g7VHrvS3dRsUBk=" crossorigin="anonymous"></script>

<div id="acd-resp-root" class="acd-resp-root"
     data-rest-nonce="<?php echo esc_attr($rest_nonce); ?>"
     data-rest-job-post="<?php echo esc_attr($REST_JOB_POST); ?>"
     data-rest-job-base="<?php echo esc_attr($REST_JOB_BASE); ?>"
     data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
     data-creditor-nonce="<?php echo esc_attr($creditor_nonce); ?>"
     data-item-nonce="<?php echo esc_attr($item_suggest_nonce); ?>"
     data-show-creditor-code="<?php echo $show_creditor_code ? '1' : '0'; ?>"
     data-show-item-code="<?php echo $show_item_code ? '1' : '0'; ?>"
     data-default-location="<?php echo esc_attr($default_location); ?>"
     data-today="<?php echo esc_attr($today_date); ?>"
     data-grn-mode="compat-v1"
     data-requested-doc-prefix="GRN">

    <!-- ==================== GRN FORM ==================== -->
    <div id="acd-resp-grn-tab" class="acd-resp-tab-pane active">
        <div class="acd-resp-do-grid">
            <!-- Add Item Card -->
            <div class="acd-resp-card acd-resp-entry-card">
                <div class="acd-resp-card-header">
                    <h3>Add Item</h3>
                </div>
                <div class="acd-resp-card-body">
                    <div class="acd-resp-field">
                        <label>Date</label>
                        <input type="date" id="acd_resp_grn_date" class="acd-resp-input" value="<?php echo esc_attr($today_date); ?>">
                    </div>

                    <div class="acd-resp-field">
                        <label>Creditor</label>
                        <div class="acd-resp-search-wrap" id="acdRespCreditorWrapper"
                             data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
                             data-nonce="<?php echo esc_attr($creditor_nonce); ?>">
                            <input type="text" id="acdRespCreditorInput" class="acd-resp-input" placeholder="Search creditor..." autocomplete="off" readonly>
                            <button type="button" id="acdRespCreditorClear" class="acd-resp-field-clear" aria-label="Clear creditor">&times;</button>
                            <input type="hidden" id="acd_resp_grn_creditor" value="">
                            <input type="hidden" id="acd_resp_grn_creditor_name" value="">
                            <input type="hidden" id="acd_resp_grn_location" value="<?php echo esc_attr($default_location); ?>">
                        </div>
                    </div>

                    <div class="acd-resp-field">
                        <label>Item Name</label>
                        <div class="acd-resp-search-wrap">
                            <input type="text" id="acd_resp_grn_item_name" class="acd-resp-input" placeholder="Search item..." autocomplete="off" readonly>
                            <button type="button" id="acdRespItemClear" class="acd-resp-field-clear" aria-label="Clear item">&times;</button>
                            <input type="hidden" id="acd_resp_grn_item" value="">
                            <input type="hidden" id="acd_resp_grn_item_display" value="">
                        </div>
                    </div>

                    <div class="acd-resp-field">
                        <label>Type</label>
                        <div class="acd-resp-type-toggle" id="acd_resp_grn_pack_type_toggle">
                            <button type="button" class="acd-resp-type-btn active" data-pack-type="BASKET">Basket</button>
                            <button type="button" class="acd-resp-type-btn" data-pack-type="CARTON">Carton</button>
                        </div>
                        <select id="acd_resp_grn_pack_type" style="display:none;">
                            <option value="BASKET" selected>Basket</option>
                            <option value="CARTON">Carton</option>
                        </select>
                    </div>

                    <div class="acd-resp-row-2">
                        <div class="acd-resp-field">
                            <label>Qty</label>
                            <input type="number" id="acd_resp_grn_qty" class="acd-resp-input" value="" step="1" min="0" placeholder="Qty">
                        </div>
                        <div class="acd-resp-field">
                            <label>Weight (KG)</label>
                            <input type="number" id="acd_resp_grn_kg" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Weight (KG)">
                        </div>
                    </div>

                    <div class="acd-resp-row-2">
                        <div class="acd-resp-field">
                            <label>Price</label>
                            <input type="number" id="acd_resp_grn_price" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Price">
                        </div>
                    </div>

                    <div class="acd-resp-preview" id="acd_resp_grn_line_preview" style="display:none;"></div>
                    <button type="button" id="acd_resp_grn_addline" class="acd-resp-btn-primary">Add Item</button>
                </div>
            </div>
        </div>

        <!-- Items Detail Card -->
        <div class="acd-resp-card acd-resp-items-card">
            <div class="acd-resp-card-header acd-resp-card-header-stack">
                <div class="acd-resp-lines-head">
                    <h3>Items Detail</h3>
                    <span id="acd_resp_grn_lines_count_badge" class="acd-resp-lines-badge">0</span>
                </div>

                <button type="button" id="acd_resp_grn_submit" class="acd-resp-btn-primary acd-resp-save-btn">Save Goods Receive Note</button>

                <div id="acd_resp_grn_success_actions" class="acd-resp-success-actions" style="display:none;" aria-live="polite">
                    <div class="acd-resp-success-text">
                        <span id="acd_resp_grn_success_label">Saved batch</span>:
                        <strong id="acd_resp_grn_success_docno">-</strong>
                    </div>
                    <div class="acd-resp-success-btns">
                        <button type="button"
                                id="acd_resp_grn_clear_new_btn"
                                class="acd-resp-action-btn acd-resp-action-danger">
                            Clear / New GRN
                        </button>
                    </div>
                </div>
            </div>
            <div class="acd-resp-card-body">
                <div class="acd-resp-lines-header">
                    <span>Item</span>
                    <span>Creditor</span>
                    <span>Type</span>
                    <span>Qty</span>
                    <span>KG</span>
                    <span>Total KG</span>
                    <span>Price</span>
                    <span>Total Price</span>
                    <span aria-label="Action">&#9998;</span>
                </div>
                <div id="acd_resp_grn_lines" class="acd-resp-lines-container">
                    <div class="acd-resp-empty">No items added</div>
                </div>
            </div>
        </div>
    </div>

    <!-- Shared Picker Modal -->
    <div class="acd-resp-picker-modal" id="acd_resp_grn_picker_modal" aria-hidden="true">
        <div class="acd-resp-picker-backdrop" id="acd_resp_grn_picker_backdrop"></div>
        <div class="acd-resp-picker-sheet">
            <div class="acd-resp-picker-head">
                <div class="acd-resp-picker-title" id="acd_resp_grn_picker_title">Search</div>
                <button type="button" class="acd-resp-picker-close" id="acd_resp_grn_picker_close" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="acd-resp-picker-body">
                <input type="text" id="acd_resp_grn_picker_search" class="acd-resp-input acd-resp-picker-search" placeholder="Type to search..." autocomplete="off">
                <div class="acd-resp-picker-results" id="acd_resp_grn_picker_results"></div>
            </div>
        </div>
    </div>
</div>

<style>
/* ----------------------------------------------
   RESPONSIVE STYLES
---------------------------------------------- */
#acd-resp-root {
    --acd-bg: #f8fafc;
    --acd-card-bg: #ffffff;
    --acd-border: #dbe4ee;
    --acd-border-strong: #c4d0dd;
    --acd-text: #0f172a;
    --acd-muted: #475569;
    --acd-green: #166534;
    --acd-green-light: #dcfce7;
    --acd-green-soft: #f0fdf4;
    --acd-green-dark: #14532d;
    --acd-danger: #dc2626;
    --acd-radius: 0.75rem;
    --acd-shadow: 0 0.75rem 1.75rem rgba(15, 23, 42, 0.08);
    font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
    color: var(--acd-text);
    background: var(--acd-bg);
    font-size: 1rem;
    margin: 0;
    padding: 0;
    max-width: none;
}

#acd-resp-root * {
    box-sizing: border-box;
}

#acd-resp-root .acd-resp-do-grid {
    display: block;
}

@media (min-width: 1024px) {
    #acd-resp-root .acd-resp-do-grid {
        display: block;
    }
}

/* Tab Pane */
.acd-resp-tab-pane {
    display: none;
    padding: 0;
}
.acd-resp-tab-pane.active {
    display: block;
}

/* Cards */
.acd-resp-card {
    background: var(--acd-card-bg);
    border: 1px solid var(--acd-border);
    border-radius: var(--acd-radius);
    box-shadow: var(--acd-shadow);
    overflow: hidden;
}
.acd-resp-items-card {
    margin-top: 0.9rem;
}
.acd-resp-card-header {
    padding: 0.85rem 0.9rem;
    border-bottom: 1px solid var(--acd-border);
    background: #fcfdff;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
}
.acd-resp-card-header-stack {
    flex-direction: column;
    align-items: stretch;
}
.acd-resp-card-header h3 {
    margin: 0;
    font-size: 1rem;
    font-weight: 800;
}
.acd-resp-lines-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
}
.acd-resp-lines-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.8rem;
    min-height: 1.8rem;
    padding: 0 0.45rem;
    border-radius: 999px;
    background: var(--acd-green-soft);
    border: 1px solid #bbf7d0;
    color: var(--acd-green);
    font-size: 0.82rem;
    font-weight: 800;
}
.acd-resp-card-body {
    padding: 0.9rem;
}

@media (min-width: 768px) {
    #acd-resp-root .acd-resp-card-body {
        padding: 1rem;
    }

    #acd-resp-root .acd-resp-entry-card .acd-resp-card-body {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.9rem 1rem;
        align-items: end;
    }

    #acd-resp-root .acd-resp-entry-card .acd-resp-field,
    #acd-resp-root .acd-resp-entry-card .acd-resp-row-2,
    #acd-resp-root .acd-resp-entry-card .acd-resp-preview,
    #acd-resp-root .acd-resp-entry-card #acd_resp_grn_addline {
        margin-bottom: 0;
    }

    #acd-resp-root .acd-resp-entry-card .acd-resp-card-body > .acd-resp-field:first-child {
        grid-column: 1 / -1;
    }

    #acd-resp-root .acd-resp-entry-card .acd-resp-row-2,
    #acd-resp-root .acd-resp-entry-card .acd-resp-preview,
    #acd-resp-root .acd-resp-entry-card #acd_resp_grn_addline {
        grid-column: 1 / -1;
    }
}

/* Fields */
.acd-resp-field {
    margin-bottom: 0.85rem;
}
.acd-resp-field label {
    display: block;
    font-size: 0.88rem;
    font-weight: 700;
    color: var(--acd-muted);
    margin-bottom: 0.35rem;
}
.acd-resp-input {
    width: 100%;
    min-height: 3rem;
    padding: 0.72rem 0.85rem;
    border: 1px solid var(--acd-border-strong);
    border-radius: 0.65rem;
    background: #fff;
    font-size: 1rem;
    transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.acd-resp-input:focus {
    outline: none;
    border-color: var(--acd-green);
    box-shadow: 0 0 0 0.2rem rgba(22, 101, 52, 0.10);
}

#acd-resp-root input[type="date"].acd-resp-input,
#acd-resp-root input[type="date"] {
    position: relative;
    cursor: pointer;
}
#acd-resp-root input[type="date"].acd-resp-input::-webkit-calendar-picker-indicator,
#acd-resp-root input[type="date"]::-webkit-calendar-picker-indicator {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}
#acd-resp-root input[type="date"].acd-resp-input::-moz-calendar-picker-indicator,
#acd-resp-root input[type="date"]::-moz-calendar-picker-indicator {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

.acd-resp-search-wrap {
    position: relative;
}
.acd-resp-search-wrap .acd-resp-input {
    padding-right: 3.1rem;
    cursor: pointer;
}
.acd-resp-field-clear {
    position: absolute;
    top: 50%;
    right: 0.5rem;
    transform: translateY(-50%);
    width: 2.15rem;
    height: 2.15rem;
    border: 1px solid var(--acd-border);
    background: #fff;
    color: #64748b;
    border-radius: 0.5rem;
    display: none;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    cursor: pointer;
    transition: all 0.18s ease;
}
.acd-resp-field-clear.show {
    display: inline-flex;
}
.acd-resp-field-clear:hover {
    background: var(--acd-green-soft);
    border-color: #bbf7d0;
    color: var(--acd-green);
}

/* Type Toggle */
.acd-resp-type-toggle {
    display: flex;
    gap: 0.55rem;
}
.acd-resp-type-btn {
    flex: 1;
    min-height: 3rem;
    padding: 0.7rem 0.8rem;
    border: 1px solid var(--acd-border-strong);
    background: #f8fafc;
    color: #334155;
    border-radius: 0.65rem;
    font-weight: 700;
    font-size: 1rem;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}
.acd-resp-type-btn:hover {
    background: #ecfdf3;
    border-color: #86efac;��<��nFL����������
N?�M
    color: var(--acd-green);
}
.acd-resp-type-btn.active {
    background: var(--acd-green-light);
    border-color: #16a34a;
    color: var(--acd-green);
    box-shadow: 0 0 0 1px rgba(22, 101, 52, 0.05) inset;
}
.acd-resp-row-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.8rem;
    margin-bottom: 0.5rem;
}
@media (max-width: 480px) {
    .acd-resp-row-2 {
        grid-template-columns: 1fr;
        gap: 0;
    }
}
.acd-resp-preview {
    background: var(--acd-green-soft);
    border: 1px solid #bbf7d0;
    border-radius: 0.65rem;
    padding: 0.7rem 0.8rem;
    margin: 0.6rem 0;
    font-size: 0.95rem;
}

/* Primary buttons */
#acd-resp-root .acd-resp-btn-primary,
#acd-resp-root button.acd-resp-btn-primary {
    width: 100%;
    min-height: 3.05rem;
    padding: 0.78rem 1rem;
    border: 1px solid var(--acd-green);
    border-radius: 0.7rem;
    background: var(--acd-green);
    color: #ffffff;
    font-weight: 800;
    font-size: 1rem;
    line-height: 1.2;
    font-family: inherit;
    text-align: center;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
    appearance: none;
    -webkit-appearance: none;
    box-shadow: none;
}
#acd-resp-root .acd-resp-btn-primary:hover,
#acd-resp-root button.acd-resp-btn-primary:hover {
    background: var(--acd-green-dark);
    border-color: var(--acd-green-dark);
    color: #ffffff;
    box-shadow: 0 4px 12px rgba(22, 101, 52, 0.14);
}
#acd-resp-root .acd-resp-btn-primary:focus,
#acd-resp-root button.acd-resp-btn-primary:focus {
    outline: none;
    box-shadow: 0 0 0 0.2rem rgba(22, 101, 52, 0.12);
}
#acd-resp-root .acd-resp-btn-primary:disabled,
#acd-resp-root button.acd-resp-btn-primary:disabled {
    background: #94a3b8;
    border-color: #94a3b8;
    color: #ffffff;
    cursor: not-allowed;
    opacity: 1;
    box-shadow: none;
}
#acd-resp-root .acd-resp-save-btn {
    width: 100%;
}

/* Item details table */
.acd-resp-lines-header {
    display: grid;
    grid-template-columns: minmax(12rem, 2fr) 1.4fr 0.8fr 0.55fr 0.55fr 0.75fr 0.85fr 0.9fr 3rem;
    gap: 0.45rem;
    align-items: center;
    text-align: center;
    background: #f1f5f9;
    border: 1px solid var(--acd-border);
    border-radius: 0.65rem 0.65rem 0 0;
    padding: 0.72rem 0.8rem;
    font-size: 0.85rem;
    font-weight: 800;
    margin-bottom: 0.25rem;
}
.acd-resp-lines-header span:first-child {
    text-align: left;
}
.acd-resp-line {
    display: grid;
    grid-template-columns: minmax(12rem, 2fr) 1.4fr 0.8fr 0.55fr 0.55fr 0.75fr 0.85fr 0.9fr 3rem;
    gap: 0.45rem;
    align-items: center;
    text-align: center;
    padding: 0.7rem 0.8rem;
    border-right: 1px solid #eef2f6;
    border-left: 1px solid #eef2f6;
    border-bottom: 1px solid #eef2f6;
    font-size: 0.95rem;
}
.acd-resp-line > div:first-child {
    text-align: left;
}
.acd-resp-price-input {
    width: 100%;
    min-height: 2.35rem;
    padding: 0.45rem 0.55rem;
    border: 1px solid var(--acd-border-strong);
    border-radius: 0.55rem;
    background: #fff;
    color: var(--acd-text);
    font: inherit;
    font-weight: 700;
    text-align: center;
}
.acd-resp-price-input:focus {
    outline: none;
    border-color: var(--acd-green);
    box-shadow: 0 0 0 0.18rem rgba(22, 101, 52, 0.10);
}
.acd-resp-money-cell,
.acd-resp-number-cell {
    font-variant-numeric: tabular-nums;
}
.acd-resp-money-cell,
.acd-resp-price-cell,
.acd-resp-number-cell {
    text-align: center;
}
.acd-resp-type-pill {
    display: inline-flex;
    padding: 0.3rem 0.7rem;
    border-radius: 999px;
    background: var(--acd-green-soft);
    border: 1px solid #bbf7d0;
    color: var(--acd-green);
    font-size: 0.8rem;
    font-weight: 800;
}

/* Delete button */
#acd-resp-root .acd-resp-delete-btn,
#acd-resp-root button.acd-resp-delete-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.4rem;
    min-width: 2.4rem;
    min-height: 2.35rem;
    padding: 0.45rem;
    border: 1px solid #fecaca;
    background: #fff5f5;
    color: #dc2626;
    border-radius: 0.65rem;
    font-size: 1rem;
    font-weight: 700;
    line-height: 1.2;
    font-family: inherit;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
    appearance: none;
    -webkit-appearance: none;
    box-shadow: none;
}
#acd-resp-root .acd-resp-delete-btn:hover,
#acd-resp-root button.acd-resp-delete-btn:hover {
    background: #fee2e2;
    border-color: #fca5a5;
    color: #b91c1c;
}
#acd-resp-root .acd-resp-delete-btn:focus,
#acd-resp-root button.acd-resp-delete-btn:focus {
    outline: none;
    box-shadow: 0 0 0 0.2rem rgba(220, 38, 38, 0.12);
}

.acd-resp-lines-container {
    max-height: min(32rem, 64vh);
    overflow: auto;
    padding: 0.15rem;
}
.acd-resp-lines-header,
.acd-resp-line {
    min-width: 64rem;
}
.acd-resp-empty {
    padding: 1.2rem;
    text-align: center;
    color: var(--acd-muted);
    font-style: italic;
}

/* Success actions panel */
#acd-resp-root .acd-resp-success-actions {
    margin-top: 0.75rem;
    padding: 0.85rem;
    border: 1px solid #bbf7d0;
    background: var(--acd-green-soft);
    border-radius: 0.75rem;
}
#acd-resp-root .acd-resp-success-text {
    font-size: 0.9rem;
    font-weight: 700;
    color: var(--acd-green-dark);
    margin-bottom: 0.55rem;
}
#acd-resp-root .acd-resp-success-btns {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.5rem;
}
@media (min-width: 768px) {
    #acd-resp-root .acd-resp-success-btns {
        grid-template-columns: repeat(2, 1fr);
    }
}
#acd-resp-root .acd-resp-action-btn,
#acd-resp-root a.acd-resp-action-btn,
#acd-resp-root button.acd-resp-action-btn {
    min-height: 2.8rem;
    padding: 0.7rem 0.8rem;
    border-radius: 0.65rem;
    font-size: 0.92rem;
    font-weight: 800;
    line-height: 1.2;
    font-family: inherit;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    appearance: none;
    -webkit-appearance: none;
    box-shadow: none;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}
#acd-resp-root .acd-resp-action-green {
    background: var(--acd-green);
    border: 1px solid var(--acd-green);
    color: #ffffff;
}
#acd-resp-root .acd-resp-action-green:hover {
    background: var(--acd-green-dark);
    border-color: var(--acd-green-dark);
    color: #ffffff;
}
#acd-resp-root .acd-resp-action-soft {
    background: #ffffff;
    border: 1px solid #86efac;
    color: var(--acd-green);
}
#acd-resp-root .acd-resp-action-soft:hover {
    background: #dcfce7;
    border-color: #22c55e;
    color: var(--acd-green-dark);
}
#acd-resp-root .acd-resp-action-danger {
    background: #fff5f5;
    border: 1px solid #fecaca;
    color: var(--acd-danger);
}
#acd-resp-root .acd-resp-action-danger:hover {
    background: #fee2e2;
    border-color: #fca5a5;
    color: #b91c1c;
}

/* Picker Modal */
.acd-resp-picker-modal {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 0.75rem;
}
.acd-resp-picker-modal.active {
    display: flex;
}
.acd-resp-picker-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
}
.acd-resp-picker-sheet {
    position: relative;
    width: 100%;
    max-width: 42rem;
    background: #fff;
    border-radius: 0.9rem;
    box-shadow: 0 1.4rem 2.4rem rgba(0, 0, 0, 0.18);
    overflow: hidden;
}
.acd-resp-picker-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.85rem 0.95rem;
    border-bottom: 1px solid var(--acd-border);
}
.acd-resp-picker-title {
    font-size: 1.05rem;
    font-weight: 800;
}
.acd-resp-picker-close {
    flex: 0 0 auto;
    width: 2.35rem;
    height: 2.35rem;
    padding: 0;
    border: 1px solid var(--acd-border-strong);
    background: #fff;
    color: var(--acd-text);
    border-radius: 0.55rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    font-size: 1.35rem;
    font-weight: 500;
    font-family: Arial, sans-serif;
    cursor: pointer;
    transition: all 0.18s ease;
    appearance: none;
    -webkit-appearance: none;
}
.acd-resp-picker-close span {
    display: block;
    line-height: 1;
    transform: translateY(-1px);
}
.acd-resp-picker-close:hover {
    background: var(--acd-green-soft);
    border-color: #bbf7d0;
    color: var(--acd-green);
}
.acd-resp-picker-body {
    padding: 0.85rem 0.95rem 0.95rem;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.acd-resp-picker-results {
    max-height: min(24rem, calc(86vh - 9rem));
    overflow-y: auto;
}
.acd-resp-picker-title,
.acd-resp-picker-search,
.acd-resp-picker-results,
.acd-resp-picker-item,
.acd-resp-picker-item-main {
    color: var(--acd-text);
}
.acd-resp-picker-note,
.acd-resp-picker-item-sub {
    color: var(--acd-muted);
}
.acd-resp-picker-item {
    display: block;
    width: 100%;
    text-align: left;
    min-height: 3rem;
    padding: 0.78rem 0.85rem;
    border: 1px solid var(--acd-border);
    border-radius: 0.65rem;
    background: #fff;
    margin-bottom: 0.5rem;
    cursor: pointer;
    transition: all 0.18s ease;
}
.acd-resp-picker-item:hover {
    background: var(--acd-green-soft);
    border-color: #bbf7d0;
}
.acd-resp-picker-item-main {
    font-weight: 800;
}
.acd-resp-picker-item-sub {
    font-size: 0.8rem;
    color: var(--acd-muted);
}

#acd-resp-root .acd-resp-picker-modal {
    align-items: center !important;
    justify-content: center !important;
    padding: 0.75rem !important;
}
#acd-resp-root .acd-resp-picker-sheet {
    width: 100% !important;
    max-width: min(42rem, calc(100vw - 2rem)) !important;
    border-radius: 0.9rem !important;
    max-height: 86vh !important;
    overflow: hidden !important;
}
</style>

<script>
(function(){
    // --------------------------------------------------------------
    // GOODS RECEIVE NOTE MODULE
    // --------------------------------------------------------------
    const root = document.getElementById('acd-resp-root');
    const grnContainer = document.getElementById('acd-resp-grn-tab');
    if (!grnContainer || grnContainer.dataset.grnInit) return;
    grnContainer.dataset.grnInit = '1';

    const REST_NONCE    = root.dataset.restNonce;
    const REST_JOB_POST = root.dataset.restJobPost;
    const REST_JOB_BASE = root.dataset.restJobBase;
    const GRN_MODE      = root.dataset.grnMode || 'compat-v1';
    const REQUESTED_DOC_PREFIX = root.dataset.requestedDocPrefix || 'GRN';
    const AJAX_URL      = root.dataset.ajaxUrl;
    const CREDITOR_NONCE = root.dataset.creditorNonce;
    const ITEM_NONCE    = root.dataset.itemNonce;

    const DROPDOWN_META = {
        showCreditorCode: root.dataset.showCreditorCode === '1',
        showItemCode: root.dataset.showItemCode === '1'
    };

    const state = {
        lines: [],
        jobFinished: false,
        isSubmitting: false,
        savedPendingClear: false,
    };
    const pickerState = { items: [], defaultItems: [], fetchFn: null, onPick: null };
    let pickerTimer = null;

    function $(id) { return document.getElementById(id); }
    function submitIdleText() { return 'Save Goods Receive Note'; }
    function submitDoneText() { return 'Saved - Ready for Next Batch'; }
    function submitProgressText() { return 'Queuing...'; }
    function successToastText(count = 1) { return count === 1 ? 'Goods Receive Note queued' : `${count} Goods Receive Notes queued`; }

    function escapeHtml(s) {
        if (!s) return '';
        return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));
    }

    function fmtQty(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0' : String(Math.round(x));
    }

    function fmtKg(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0.00' : x.toFixed(2);
    }

    function fmtMoney(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0.00' : x.toFixed(2);
    }

    function parseQty(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : Math.round(x);
    }

    function parseKg(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : Number(x.toFixed(2));
    }

    function parseMoney(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : x;
    }

    function roundMoney(n) {
        return Number(parseMoney(n).toFixed(2));
    }

    function calcTotalKg(qty, kg) {
        return Number(((parseQty(qty) || 0) * (parseKg(kg) || 0)).toFixed(2));
    }

    function kgKey(n) {
        return fmtKg(parseKg(n));
    }

    function moneyKey(n) {
        return fmtMoney(parseMoney(n));
    }

    function calcTotalPrice(line) {
        return roundMoney(parseMoney(line?.price || 0) * (parseFloat(line?.total) || 0));
    }

    function normalizeBatchId(value) {
        return String(value || '').replace(/[^a-zA-Z0-9\-_.:]/g, '').slice(0, 80);
    }
    function makeBulkBatchId() {
        return normalizeBatchId(`GRNBULK-${Date.now()}-${Math.random().toString(36).slice(2)}`);
    }

    function extractReturnedDocNo(response) {
        return response?.localDocNo
            || response?.local_doc_no
            || response?.sourceDocNo
            || response?.source_doc_no
            || response?.docNo
            || response?.doc_no
            || '';
    }

    function buildGrnCompatMeta(group, bulkBatchId, groupIndex) {
        return {
            mode: GRN_MODE,
            schemaVersion: 'wpgrn-local-v1',
            legacyQueueCompatible: true,
            sourceType: 'GOODS_RECEIVE_NOTE',
            sourceSystem: 'WORDPRESS',
            requestedDocPrefix: REQUESTED_DOC_PREFIX,
            requestedDocNoMode: 'SERVER_GENERATED',
            requestedDocNoFormat: `${REQUESTED_DOC_PREFIX}{yyMM}/0000`,
            localDocNo: '',
            localGrnId: null,
            bulkBatchId,
            groupIndex,
            creditorCode: group?.creditorCode || ''
        };
    }

    function showToast(icon, title, text='') {
        if (window.Swal) {
            Swal.fire({ toast: true, position: 'center', icon, title, text, showConfirmButton: false, timer: 2600, timerProgressBar: true });
        } else {
            alert(title + (text ? '\n' + text : ''));
        }
    }
    function showModal(icon, title, html) {
        if (window.Swal) {
            Swal.fire({ icon, title, html, confirmButtonText: 'OK' });
        } else {
            alert(title + '\n' + html);
        }
    }

    function savedJobListHtml(savedJobs) {
        if (!savedJobs.length) return '<p>No Goods Receive Notes were queued.</p>';
        const rows = savedJobs.map(job => {
            const creditor = escapeHtml(job.creditorName || job.creditorCode || '-');
            const docNo = escapeHtml(job.docNo || 'Queued');
            const jobId = escapeHtml(job.jobId || '-');
            return `<li><strong>${docNo}</strong> | ${creditor} | Job #${jobId}</li>`;
        }).join('');
        return `<ul style="text-align:left;margin:.75rem 0 0;padding-left:1.25rem;">${rows}</ul>`;
    }

    function showBulkSuccessModal(result) {
        const count = result?.count || 0;
        const label = count === 1 ? '1 Goods Receive Note' : `${count} Goods Receive Notes`;
        if (window.Swal) {
            Swal.fire({
                icon: 'success',
                title: 'Goods Receive Notes Queued',
                html: `<p>${escapeHtml(����M�����������H
N?�Nlabel)} queued for AutoCount.</p><p>GRN numbers are still generating.</p>`,
                confirmButtonText: 'OK'
            });
        } else {
            alert(label + ' queued for AutoCount. GRN numbers are still generating.');
        }
    }

    function showBulkPartialFailureModal(result) {
        const savedJobs = result?.savedJobs || [];
        const errorMessage = result?.errorMessage || 'Submit failed';
        const savedCount = savedJobs.length;
        const title = savedCount
            ? `${savedCount} GRN${savedCount === 1 ? '' : 's'} already queued`
            : 'Goods Receive Note submit failed';
        const html = `
            <p>${escapeHtml(errorMessage)}</p>
            ${savedCount ? '<p><strong>Do not resubmit these queued GRNs.</strong> They were removed from the form, so retry will submit only the remaining unsaved items.</p>' : ''}
            ${savedJobListHtml(savedJobs)}
        `;
        showModal(savedCount ? 'warning' : 'error', title, html);
    }

    function updateEntryTotal() {
        const itemCode = ($('acd_resp_grn_item').value || '').trim();
        const itemName = ($('acd_resp_grn_item_display').value || '').trim() || itemCode;
        const packType = ($('acd_resp_grn_pack_type').value || '').trim();
        const qtyRaw = ($('acd_resp_grn_qty').value || '').trim();
        const kgRaw = ($('acd_resp_grn_kg').value || '').trim();
        const priceRaw = ($('acd_resp_grn_price').value || '').trim();
        const qty = parseQty(qtyRaw || '0');
        const kg = parseKg(kgRaw || '0');
        const price = parseMoney(priceRaw || '0');
        const total = calcTotalKg(qty, kg);
        const totalPrice = roundMoney(price * total);
        const pv = $('acd_resp_grn_line_preview');
        if (!itemCode || qtyRaw === '' || kgRaw === '') {
            pv.style.display = 'none';
            pv.innerHTML = '';
            return;
        }
        pv.style.display = 'block';
        pv.innerHTML = `<div><b>${escapeHtml(itemName)}</b></div>
                        <div>Type: ${escapeHtml(packType)} | Qty: ${fmtQty(qty)} | KG: ${fmtKg(kg)} | Total KG: ${fmtKg(total)} | Price: ${fmtMoney(price)} | Total: ${fmtMoney(totalPrice)}</div>`;
    }

    function setPackType(type) {
        const nextType = String(type || '').toUpperCase() === 'BASKET' ? 'BASKET' : 'CARTON';
        $('acd_resp_grn_pack_type').value = nextType;
        document.querySelectorAll('#acd_resp_grn_pack_type_toggle .acd-resp-type-btn').forEach(btn => {
            btn.classList.toggle('active', (btn.dataset.packType || '').toUpperCase() === nextType);
        });
        updateEntryTotal();
    }

    function updateUI() {
        const lines = state.lines;
        const badge = document.getElementById('acd_resp_grn_lines_count_badge');
        if (badge) badge.innerText = lines.length;

        const container = $('acd_resp_grn_lines');
        if (!lines.length) {
            container.innerHTML = '<div class="acd-resp-empty">No items added</div>';
            return;
        }

        container.innerHTML = lines.map((l, idx) => `
            <div class="acd-resp-line" data-idx="${idx}">
                <div><strong>${escapeHtml(l.itemName || l.itemCode)}</strong></div>
                <div><strong>${escapeHtml(l.creditorName || l.creditorCode)}</strong></div>
                <div><span class="acd-resp-type-pill">${l.packType}</span></div>
                <div class="acd-resp-number-cell">${fmtQty(l.qty)}</div>
                <div class="acd-resp-number-cell">${fmtKg(l.kg)}</div>
                <div class="acd-resp-number-cell">${fmtKg(l.total)}</div>
                <div class="acd-resp-price-cell"><input type="number" class="acd-resp-price-input" data-price-idx="${idx}" value="${fmtMoney(l.price)}" min="0" step="0.01" inputmode="decimal" aria-label="Price for ${escapeHtml(l.itemName || l.itemCode)}"></div>
                <div class="acd-resp-money-cell" data-total-price-idx="${idx}">${fmtMoney(calcTotalPrice(l))}</div>
                <div><button type="button" class="acd-resp-delete-btn" data-idx="${idx}" aria-label="Delete ${escapeHtml(l.itemName || l.itemCode)}" title="Delete">&#128465;</button></div>
            </div>
        `).join('');
    }

    function updateLinePrice(idx, value, shouldFormatInput = false) {
        if (isNaN(idx) || !state.lines[idx]) return;
        state.lines[idx].price = parseMoney(value);
        const nextTotal = fmtMoney(calcTotalPrice(state.lines[idx]));
        document.querySelectorAll(`[data-total-price-idx="${idx}"]`).forEach(el => {
            el.textContent = nextTotal;
        });
        if (shouldFormatInput) {
            document.querySelectorAll(`[data-price-idx="${idx}"]`).forEach(input => {
                input.value = fmtMoney(state.lines[idx].price);
            });
        }
    }

    async function apiGet(url) {
        const res = await fetch(url, { method: 'GET', credentials: 'same-origin', headers: { 'Accept': 'application/json', 'X-WP-Nonce': REST_NONCE }, cache: 'no-store' });
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const text = await res.text();
        return text ? JSON.parse(text) : null;
    }
    async function apiPost(url, body) {
        const res = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': REST_NONCE }, body: JSON.stringify(body) });
        let data = null;
        const text = await res.text();
        try { data = text ? JSON.parse(text) : null; } catch (e) { data = { raw: text }; }
        if (!res.ok) {
            const message = data?.message || data?.error || `HTTP ${res.status}`;
            const err = new Error(message);
            err.status = res.status;
            err.data = data;
            throw err;
        }
        return data;
    }

    function buildPayloadLine(l, location) {
        const displayName = String(l.itemName || l.itemCode || '').trim();
        const isBasket = (l.packType === 'BASKET');
        const count = l.qty;
        const weightPerUnit = l.kg;
        const totalWeight = l.total;
        const unitPrice = roundMoney(l.price || 0);
        const amount = roundMoney(unitPrice * totalWeight);

        return {
            itemCode: l.itemCode,
            description: displayName,
            itemName: displayName,
            ItemName: displayName,
            itemDesc: displayName,
            uom: 'KG',
            unitPrice,
            amount,
            taxCode: 'SR-0',
            taxRate: 0,
            packType: l.packType,
            qty: totalWeight,
            kg: weightPerUnit,
            totalKg: totalWeight,
            unitQty: count,
            basketQty: isBasket ? count : null,
            cartonQty: !isBasket ? count : null,
            location
        };
    }

    function groupKey(creditorCode) {
        return `${creditorCode}`;
    }

    function groupLinesByCreditor(lines) {
        const groups = new Map();
        lines.forEach(line => {
            const key = groupKey(line.creditorCode);
            if (!groups.has(key)) {
                groups.set(key, {
                    key,
                    creditorCode: line.creditorCode,
                    creditorName: line.creditorName,
                    lines: []
                });
            }
            groups.get(key).lines.push(line);
        });
        return Array.from(groups.values());
    }

    function removeSavedGroupsFromForm(savedJobs) {
        const savedKeys = new Set(savedJobs.map(job => job.groupKey).filter(Boolean));
        if (!savedKeys.size) return;
        state.lines = state.lines.filter(line => !savedKeys.has(groupKey(line.creditorCode)));
        updateUI();
    }

    function clearGrnFormAfterSave() {
        clearCreditorSelection();
        clearLineEntry();
        state.lines = [];
        state.jobFinished = false;
        state.savedPendingClear = false;
        const submitBtn = $('acd_resp_grn_submit');
        if (submitBtn) {
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        }
        updateUI();
        updateClearButtons();
    }

    async function searchItemsLive(q) {
        if (!AJAX_URL || !ITEM_NONCE) return [];
        const fd = new FormData();
        fd.append('action', 'ac_itemcode_suggest');
        fd.append('nonce', ITEM_NONCE);
        fd.append('term', q);
        const res = await fetch(AJAX_URL, { method: 'POST', body: fd, credentials: 'same-origin' });
        const data = await res.json();
        if (data?.success && data.data?.items) {
            return data.data.items.map(it => ({
                code: it.code || '',
                name: (it.desc || it.name || '').trim(),
                price: parseMoney(it.price ?? it.Price ?? 0)
            }));
        }
        return [];
    }

    async function searchCreditorsLive(q) {
        const wrapper = $('acdRespCreditorWrapper');
        const url = `${wrapper.dataset.ajaxUrl}?action=ac_cs_creditor_search&nonce=${encodeURIComponent(wrapper.dataset.nonce)}&q=${encodeURIComponent(q)}`;
        const res = await fetch(url, { credentials: 'same-origin' });
        const data = await res.json();
        if (!data.success) throw new Error(data.data?.error || 'Search failed');
        const items = data.data?.items || [];
        return items.map(it => {
            const name = it.name || it.creditorName || '';
            const code = it.code || it.creditorCode || '';
            const meta = [];
            if (DROPDOWN_META.showCreditorCode && code) meta.push(code);
            return { label: name || code, meta: meta.join('  |  '), raw: { name, code } };
        });
    }

    function renderPickerNote(msg) { $('acd_resp_grn_picker_results').innerHTML = `<div class="acd-resp-picker-note">${escapeHtml(msg)}</div>`; }
    function renderPickerItems(items) {
        const box = $('acd_resp_grn_picker_results');
        if (!items.length) { renderPickerNote('No result found'); return; }
        box.innerHTML = items.map((it, idx) => `<button type="button" class="acd-resp-picker-item" data-picker-idx="${idx}"><span class="acd-resp-picker-item-main">${escapeHtml(it.label || '')}</span>${it.meta ? `<span class="acd-resp-picker-item-sub">${escapeHtml(it.meta)}</span>` : ''}</button>`).join('');
    }
    async function runPickerSearch(q) {
        const query = (q || '').trim();
        clearTimeout(pickerTimer);
        if (query.length < 1) {
            pickerState.items = pickerState.defaultItems || [];
            if (pickerState.items.length) {
                renderPickerItems(pickerState.items);
            } else {
                renderPickerNote('Type to search');
            }
            return;
        }
        pickerTimer = setTimeout(async () => {
            renderPickerNote('Searching...');
            try {
                const items = await pickerState.fetchFn(query);
                pickerState.items = items || [];
                renderPickerItems(pickerState.items);
            } catch(e) { pickerState.items = []; renderPickerNote('Failed to load'); }
        }, 220);
    }
    function openPicker(opts) {
        pickerState.defaultItems = opts.initialItems || [];
        pickerState.items = pickerState.defaultItems;
        pickerState.fetchFn = opts.fetchFn;
        pickerState.onPick = opts.onPick;
        $('acd_resp_grn_picker_title').textContent = opts.title || 'Search';
        $('acd_resp_grn_picker_search').placeholder = opts.placeholder || 'Type to search...';
        $('acd_resp_grn_picker_search').value = '';
        $('acd_resp_grn_picker_modal').classList.add('active');
        if (pickerState.items.length) {
            renderPickerItems(pickerState.items);
        } else {
            renderPickerNote('Type to search');
        }
        setTimeout(() => $('acd_resp_grn_picker_search').focus(), 80);
    }
    function closePicker() {
        $('acd_resp_grn_picker_modal').classList.remove('active');
        $('acd_resp_grn_picker_search').value = '';
        $('acd_resp_grn_picker_results').innerHTML = '';
        pickerState.items = []; pickerState.defaultItems = []; pickerState.fetchFn = null; pickerState.onPick = null;
    }
    function updateClearButtons() {
        const creditorHas = !!($('acdRespCreditorInput')?.value.trim());
        const itemHas = !!($('acd_resp_grn_item_name')?.value.trim());
        $('acdRespCreditorClear')?.classList.toggle('show', creditorHas);
        $('acdRespItemClear')?.classList.toggle('show', itemHas);
    }

    function setCreditor(picked) {
        const name = picked?.name || '';
        const code = picked?.code || '';
        $('acdRespCreditorInput').value = name || code || '';
        $('acd_resp_grn_creditor').value = code;
        $('acd_resp_grn_creditor_name').value = name;
        updateClearButtons();
    }

    function clearCreditorSelection() {
        $('acdRespCreditorInput').value = '';
        $('acd_resp_grn_creditor').value = '';
        $('acd_resp_grn_creditor_name').value = '';
        updateClearButtons();
    }

    function clearItemSelection() {
        $('acd_resp_grn_item_name').value = '';
        $('acd_resp_grn_item').value = '';
        $('acd_resp_grn_item_display').value = '';
        updateEntryTotal();
        updateClearButtons();
    }

    function openCreditorPicker() {
        openPicker({
            title: 'Select Creditor',
            placeholder: 'Search creditor...',
            fetchFn: searchCreditorsLive,
            onPick: (picked) => {
                if (!picked) return;
                setCreditor(picked);
                closePicker();
            }
        });
    }

    function openItemPicker() {
        openPicker({
            title: 'Select Item',
            placeholder: 'Search item...',
            fetchFn: async (q) => {
                const items = await searchItemsLive(q);
                return items.map(it => ({
                    label: it.name || it.code,
                    meta: (DROPDOWN_META.showItemCode && it.code) ? it.code : '',
                    raw: { code: it.code, name: it.name || it.code, price: it.price || 0 }
                }));
            },
            onPick: (picked) => {
                if (!picked) return;
                $('acd_resp_grn_item_name').value = picked.name || picked.code || '';
                $('acd_resp_grn_item').value = picked.code || '';
                $('acd_resp_grn_item_display').value = picked.name || picked.code || '';
                updateEntryTotal();
                updateClearButtons();
                closePicker();
            }
        });
    }

    function initPickerModal() {
        $('acd_resp_grn_picker_close').addEventListener('click', closePicker);
        $('acd_resp_grn_picker_backdrop').addEventListener('click', closePicker);
        $('acd_resp_grn_picker_search').addEventListener('input', function() { runPickerSearch(this.value); });
        $('acd_resp_grn_picker_results').addEventListener('click', (e) => {
            const btn = e.target.closest('[data-picker-idx]');
            if (!btn) return;
            const idx = parseInt(btn.dataset.pickerIdx);
            if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) pickerState.onPick(pickerState.items[idx].raw);
        });
    }
    function initPickerTriggers() {
        $('acdRespCreditorInput').setAttribute('readonly', 'readonly');
        $('acd_resp_grn_item_name').setAttribute('readonly', 'readonly');
        $('acdRespCreditorInput').addEventListener('click', openCreditorPicker);
        $('acd_resp_grn_item_name').addEventListener('click', openItemPicker);
        $('acdRespCreditorClear')?.addEventListener('click', (e) => { e.preventDefault(); clearCreditorSelection(); });
        $('acdRespItemClear')?.addEventListener('click', (e) => { e.preventDefault(); clearItemSe���H���:N�����������H
N+�����lection(); });
    }
    function makeClientRequestId(prefix='GRN') { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`; }

    function clearLineEntry() {
        $('acd_resp_grn_qty').value = '';
        $('acd_resp_grn_kg').value = '';
        $('acd_resp_grn_price').value = '';
        $('acd_resp_grn_item_name').value = '';
        $('acd_resp_grn_item').value = '';
        $('acd_resp_grn_item_display').value = '';
        updateEntryTotal();
        updateClearButtons();
    }

    // ---- MERGE LOGIC (same creditor + item + type + KG + price) ----
    function findMergeableLineIndex(nextLine) {
        return state.lines.findIndex(line => {
            return String(line.creditorCode || '') === String(nextLine.creditorCode || '')
                && String(line.itemCode || '').toUpperCase() === String(nextLine.itemCode || '').toUpperCase()
                && String(line.packType || '').toUpperCase() === String(nextLine.packType || '').toUpperCase()
                && kgKey(line.kg) === kgKey(nextLine.kg)
                && moneyKey(line.price) === moneyKey(nextLine.price);
        });
    }

    function mergeLine(existingLine, nextLine) {
        const mergedQty = parseQty(existingLine.qty || 0) + parseQty(nextLine.qty || 0);
        const sameKg = parseKg(existingLine.kg || 0);
        existingLine.qty = mergedQty;
        existingLine.kg = sameKg;
        existingLine.total = calcTotalKg(mergedQty, sameKg);
        return existingLine;
    }

    function hideGrnSuccessActions() {
        const box = $('acd_resp_grn_success_actions');
        const docNoEl = $('acd_resp_grn_success_docno');
        if (box) box.style.display = 'none';
        if (docNoEl) docNoEl.textContent = '-';
    }

    function showGrnSuccessActions(data) {
        const box = $('acd_resp_grn_success_actions');
        const docNoEl = $('acd_resp_grn_success_docno');
        const docNo = data?.docNo || data?.batchLabel || '-';
        if (docNoEl) docNoEl.textContent = docNo;
        if (box) box.style.display = 'block';
    }

    function resetGrnForm() {
        clearCreditorSelection();
        clearLineEntry();
        state.lines = [];
        state.jobFinished = false;
        state.savedPendingClear = false;
        const dateField = $('acd_resp_grn_date');
        if (dateField) dateField.value = root.dataset.today || '';
        const submitBtn = $('acd_resp_grn_submit');
        if (submitBtn) {
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        }
        hideGrnSuccessActions();
        updateUI();
        updateClearButtons();
    }

    initPickerModal();
    initPickerTriggers();
    $('acd_resp_grn_qty').addEventListener('input', updateEntryTotal);
    $('acd_resp_grn_kg').addEventListener('input', updateEntryTotal);
    $('acd_resp_grn_price').addEventListener('input', updateEntryTotal);
    $('acd_resp_grn_pack_type').addEventListener('change', () => setPackType($('acd_resp_grn_pack_type').value));
    document.querySelectorAll('#acd_resp_grn_pack_type_toggle .acd-resp-type-btn').forEach(btn => btn.addEventListener('click', () => setPackType(btn.dataset.packType)));
    setPackType('BASKET');
    updateUI();

    $('acd_resp_grn_addline').addEventListener('click', () => {
        const itemCode = ($('acd_resp_grn_item').value || '').trim();
        const itemName = ($('acd_resp_grn_item_display').value || '').trim() || itemCode;
        const packType = ($('acd_resp_grn_pack_type').value || '').trim();
        const qtyRaw = ($('acd_resp_grn_qty').value || '').trim();
        const kgRaw = ($('acd_resp_grn_kg').value || '').trim();
        const priceRaw = ($('acd_resp_grn_price').value || '').trim();
        const qty = parseQty(qtyRaw || '0');
        const kg = parseKg(kgRaw || '0');
        const price = parseMoney(priceRaw || '0');
        const creditorCode = ($('acd_resp_grn_creditor').value || '').trim();
        const creditorName = ($('acd_resp_grn_creditor_name').value || '').trim();

        if (!creditorCode) { showToast('error', 'Select creditor'); return; }
        if (!itemCode) { showToast('error', 'Select an item'); return; }
        if (qty <= 0) { showToast('error', 'Qty must be >0'); return; }
        if (kg <= 0) { showToast('error', 'KG must be >0'); return; }

        const nextLine = {
            creditorCode,
            creditorName,
            itemCode,
            itemName,
            packType,
            qty,
            kg,
            total: calcTotalKg(qty, kg),
            price
        };

        const existingIdx = findMergeableLineIndex(nextLine);
        if (existingIdx >= 0) {
            mergeLine(state.lines[existingIdx], nextLine);
            updateUI();
            clearLineEntry();
            showToast(
                'warning',
                'Same item + KG + price merged',
                `${itemName} ${fmtKg(kg)}KG @ ${fmtMoney(price)} already exists for ${creditorName || creditorCode}. Quantity has been added into the same row.`
            );
            return;
        }

        state.lines.push(nextLine);
        updateUI();
        clearLineEntry();
        showToast('success', 'Item added');
    });

    document.getElementById('acd_resp_grn_lines').addEventListener('click', (e) => {
        const btn = e.target.closest('.acd-resp-delete-btn');
        if (!btn) return;
        const idx = parseInt(btn.dataset.idx);
        if (!isNaN(idx)) {
            state.lines.splice(idx, 1);
            updateUI();
            showToast('info', 'Item removed');
        }
    });
    document.getElementById('acd_resp_grn_lines').addEventListener('input', (e) => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, false);
    });
    document.getElementById('acd_resp_grn_lines').addEventListener('change', (e) => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, true);
    });

    const clearNewBtn = $('acd_resp_grn_clear_new_btn');
    if (clearNewBtn) {
        clearNewBtn.addEventListener('click', () => {
            resetGrnForm();
            showToast('info', 'Ready for new GRN');
        });
    }

    $('acd_resp_grn_submit').addEventListener('click', async () => {
        if (state.isSubmitting) { showToast('warning', 'Already submitting'); return; }

        const submitBtn = $('acd_resp_grn_submit');
        let saveSucceeded = false;
        state.isSubmitting = true;
        state.jobFinished = false;
        submitBtn.disabled = true;
        submitBtn.textContent = submitProgressText();

        const savedJobs = [];

        try {
            const location = ($('acd_resp_grn_location').value || '').trim();
            const docDate = ($('acd_resp_grn_date').value || '').trim();
            if (!state.lines.length) throw new Error('Add at least one item');

            const groups = groupLinesByCreditor(state.lines);
            if (!groups.length) throw new Error('Add at least one valid item');

            groups.forEach((group, groupIdx) => {
                if (!group.creditorCode) throw new Error(`Group ${groupIdx + 1}: creditor missing`);
                if (!group.lines.length) throw new Error(`Group ${groupIdx + 1}: item lines missing`);
                group.lines.forEach((line, lineIdx) => {
                    if (!line.itemCode) throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: item missing`);
                    if (line.qty <= 0 || line.kg <= 0 || line.total <= 0) {
                        throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: quantity and KG must be >0`);
                    }
                });
            });

            const bulkBatchId = makeBulkBatchId();

            for (let i = 0; i < groups.length; i++) {
                const group = groups[i];
                const payloadLines = group.lines.map(line => buildPayloadLine(line, location));
                const payload = {
                    bulkBatchId,
                    creditorCode: group.creditorCode,
                    creditorName: group.creditorName,
                    CreditorCode: group.creditorCode,
                    CreditorName: group.creditorName,
                    location,
                    Location: location,
                    docDate,
                    remark: '',

                    localGrnCompat: buildGrnCompatMeta(group, bulkBatchId, i + 1),

                    localDocNo: '',
                    sourceType: 'GOODS_RECEIVE_NOTE',
                    sourceSystem: 'WORDPRESS',
                    requestedDocPrefix: REQUESTED_DOC_PREFIX,
                    requestedDocNoMode: 'SERVER_GENERATED',

                    lines: payloadLines
                };
                const body = {
                    type: 'GOODS_RECEIVE_NOTE',
                    bulkBatchId,
                    client_request_id: normalizeBatchId(`${bulkBatchId}-${i + 1}`),
                    source: 'wp-ui',
                    payload
                };
                const r = await apiPost(REST_JOB_POST, body);
                const jobId = r.jobId || r.id;
                const returnedDocNo = extractReturnedDocNo(r);
                if (!jobId) throw new Error(`No job ID returned for ${group.creditorName || group.creditorCode}`);
                showToast('info', 'Job queued', `${group.creditorName || group.creditorCode}${returnedDocNo ? ' | ' + returnedDocNo : ' | Job #' + jobId}`);

                savedJobs.push({
                    jobId,
                    groupKey: group.key,
                    docNo: returnedDocNo,
                    creditorCode: group.creditorCode,
                    creditorName: group.creditorName
                });
            }

            showGrnSuccessActions({
                batchLabel: `${savedJobs.length} GRN${savedJobs.length === 1 ? '' : 's'} | ${bulkBatchId}`
            });
            showBulkSuccessModal({ count: savedJobs.length });
            clearGrnFormAfterSave();
            saveSucceeded = true;
            submitBtn.textContent = submitDoneText();
        } catch(err) {
            if (savedJobs.length) {
                removeSavedGroupsFromForm(savedJobs);
            }
            showBulkPartialFailureModal({
                savedJobs,
                errorMessage: err.message
            });
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        } finally {
            state.isSubmitting = false;
            if (!saveSucceeded) {
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            }
        }
    });
})();
</script>���Hv"�O��������O4�s
N?�P<?php
if (!defined('ABSPATH')) exit;

/*
 * WST Excellent Vege — Purchase Invoice staff list.
 *
 * Canonical data source:
 *   {$wpdb->prefix}ac_pi
 *
 * The bridge queue ({$wpdb->prefix}ac_jobs) is not used as the primary list source.
 * Page expected for single-record viewing:
 *   /view-purchase-invoice/?pi_id=123
 */

if (!is_user_logged_in()) {
    echo '<div class="wst-pil-alert wst-pil-alert-error">Please log in to view Purchase Invoice records.</div>';
    return;
}

if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
    echo '<div class="wst-pil-alert wst-pil-alert-error">You do not have permission to view Purchase Invoice records.</div>';
    return;
}

global $wpdb;

if (!$wpdb) {
    echo '<div class="wst-pil-alert wst-pil-alert-error">WordPress database connection is not available.</div>';
    return;
}

if (!function_exists('wst_pil_table_exists')) {
    function wst_pil_table_exists($table_name) {
        global $wpdb;
        return $wpdb && $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table_name)) === $table_name;
    }
}

if (!function_exists('wst_pil_valid_date')) {
    function wst_pil_valid_date($value, $fallback = '') {
        $value = trim((string)$value);
        if ($value === '') return $fallback;

        $dt = DateTime::createFromFormat('Y-m-d', $value, wp_timezone());
        if (!$dt || $dt->format('Y-m-d') !== $value) return $fallback;

        return $value;
    }
}

if (!function_exists('wst_pil_status_label')) {
    function wst_pil_status_label($status) {
        $status = strtoupper(trim((string)$status));

        $labels = array(
            'PENDING' => 'Pending AutoCount',
            'PROCESSING' => 'Processing AutoCount',
            'SUCCESS' => 'Created',
            'SYNCED' => 'Created',
            'FAILED' => 'AutoCount Failed',
            'FAILED_FINAL' => 'AutoCount Failed',
            'CANCELLED' => 'Cancelled',
            'HIDDEN' => 'Hidden',
        );

        return $labels[$status] ?? ($status !== '' ? ucwords(strtolower(str_replace('_', ' ', $status))) : '-');
    }
}

if (!function_exists('wst_pil_status_class')) {
    function wst_pil_status_class($status) {
        $status = strtoupper(trim((string)$status));

        if (in_array($status, array('SUCCESS', 'SYNCED'), true)) return 'wst-pil-badge-good';
        if (in_array($status, array('FAILED', 'FAILED_FINAL', 'CANCELLED'), true)) return 'wst-pil-badge-danger';
        if (in_array($status, array('PENDING', 'PROCESSING'), true)) return 'wst-pil-badge-warn';

        return 'wst-pil-badge-info';
    }
}

if (!function_exists('wst_pil_money')) {
    function wst_pil_money($value) {
        return number_format_i18n((float)$value, 2);
    }
}

if (!function_exists('wst_pil_admin')) {
    function wst_pil_admin() {
        return current_user_can('manage_options');
    }
}

$table_pi = $wpdb->prefix . 'ac_pi';

if (!wst_pil_table_exists($table_pi)) {
    echo '<div class="wst-pil-alert wst-pil-alert-error">Purchase Invoice storage table is missing. Expected table: <code>' . esc_html($table_pi) . '</code>.</div>';
    return;
}

$view_page_url = home_url('/view-purchase-invoice/');
$is_admin = wst_pil_admin();

$today = current_time('Y-m-d');
$default_from = date('Y-m-01', current_time('timestamp'));

$date_from = isset($_GET['date_from'])
    ? wst_pil_valid_date(wp_unslash($_GET['date_from']), $default_from)
    : $default_from;

$date_to = isset($_GET['date_to'])
    ? wst_pil_valid_date(wp_unslash($_GET['date_to']), $today)
    : $today;

$search = isset($_GET['pi_search'])
    ? sanitize_text_field(wp_unslash($_GET['pi_search']))
    : '';

$status = isset($_GET['pi_status'])
    ? strtoupper(sanitize_key(wp_unslash($_GET['pi_status'])))
    : '';

$show_hidden = $is_admin && !empty($_GET['show_hidden']);
$page = isset($_GET['pi_page']) ? max(1, absint($_GET['pi_page'])) : 1;
$per_page = 25;
$offset = ($page - 1) * $per_page;

$allowed_statuses = array('PENDING', 'PROCESSING', 'SUCCESS', 'SYNCED', 'FAILED', 'FAILED_FINAL', 'CANCELLED');
if (!in_array($status, $allowed_statuses, true)) {
    $status = '';
}

$where = array('deleted_at IS NULL');
$params = array();

if (!$show_hidden) {
    $where[] = 'hidden_from_staff_list = 0';
}

if ($date_from !== '') {
    $where[] = 'doc_date >= %s';
    $params[] = $date_from;
}

if ($date_to !== '') {
    $where[] = 'doc_date <= %s';
    $params[] = $date_to;
}

if ($status !== '') {
    if ($status === 'SUCCESS') {
        $where[] = "sync_status IN ('SUCCESS', 'SYNCED')";
    } else {
        $where[] = 'sync_status = %s';
        $params[] = $status;
    }
}

if ($search !== '') {
    $like = '%' . $wpdb->esc_like($search) . '%';
    $where[] = '(
        local_doc_no LIKE %s
        OR creditor_code LIKE %s
        OR creditor_name LIKE %s
        OR supplier_invoice_no LIKE %s
        OR autocount_doc_no LIKE %s
    )';
    array_push($params, $like, $like, $like, $like, $like);
}

$where_sql = implode(' AND ', $where);

$count_sql = "SELECT COUNT(*) FROM `{$table_pi}` WHERE {$where_sql}";
if (!empty($params)) {
    $count_sql = $wpdb->prepare($count_sql, $params);
}
$total_rows = (int)$wpdb->get_var($count_sql);

$list_sql = "
    SELECT
        id,
        local_doc_no,
        doc_date,
        creditor_code,
        creditor_name,
        supplier_invoice_no,
        currency_code,
        sub_total,
        tax_amount,
        total_amount,
        sync_status,
        autocount_doc_no,
        autocount_doc_key,
        last_sync_error,
        last_sync_error_code,
        source_job_id,
        hidden_from_staff_list,
        created_by,
        created_at,
        updated_at
    FROM `{$table_pi}`
    WHERE {$where_sql}
    ORDER BY doc_date DESC, id DESC
    LIMIT %d OFFSET %d
";

$list_params = array_merge($params, array($per_page, $offset));
$list_sql = $wpdb->prepare($list_sql, $list_params);
$rows = $wpdb->get_results($list_sql, ARRAY_A);

if (!is_array($rows)) {
    $rows = array();
}

$user_ids = array_values(array_unique(array_filter(array_map(
    static function($row) {
        return !empty($row['created_by']) ? (int)$row['created_by'] : 0;
    },
    $rows
))));

$user_names = array();
foreach ($user_ids as $user_id) {
    $user = get_userdata($user_id);
    if ($user) {
        $user_names[$user_id] = $user->display_name ?: $user->user_login;
    }
}

$total_pages = max(1, (int)ceil($total_rows / $per_page));
$current_url = home_url(wp_unslash($_SERVER['REQUEST_URI'] ?? '/'));
$current_url = remove_query_arg('pi_page', $current_url);
$clear_url = remove_query_arg(
    array('date_from', 'date_to', 'pi_search', 'pi_status', 'show_hidden', 'pi_page'),
    $current_url
);
?>

<div class="wst-pil-wrap">
    <div class="wst-pil-head">
        <div>
            <h2>Purchase Invoice Records</h2>
            <p>WordPress Purchase Invoice records and AutoCount sync status.</p>
        </div>
        <div class="wst-pil-count"><?php echo esc_html(number_format_i18n($total_rows)); ?> record(s)</div>
    </div>

    <form method="get" class="wst-pil-filter">
        <?php
        foreach ($_GET as $key => $value) {
            if (in_array($key, array('date_from', 'date_to', 'pi_search', 'pi_status', 'show_hidden', 'pi_page'), true)) {
                continue;
            }
            if (!is_scalar($value)) continue;
            echo '<input type="hidden" name="' . esc_attr($key) . '" value="' . esc_attr(wp_unslash($value)) . '">';
        }
        ?>

        <div class="wst-pil-field">
            <label for="wst-pil-date-from">From</label>
            <input id="wst-pil-date-from" type="date" name="date_from" value="<?php echo esc_attr($date_from); ?>">
        </div>

        <div class="wst-pil-field">
            <label for="wst-pil-date-to">To</label>
            <input id="wst-pil-date-to" type="date" name="date_to" value="<?php echo esc_attr($date_to); ?>">
        </div>

        <div class="wst-pil-field wst-pil-field-search">
            <label for="wst-pil-search">Search</label>
            <input
                id="wst-pil-search"
                type="search"
                name="pi_search"
                value="<?php echo esc_attr($search); ?>"
                placeholder="WPPI no., creditor, supplier invoice..."
            >
        </div>

        <div class="wst-pil-field">
            <label for="wst-pil-status">Status</label>
            <select id="wst-pil-status" name="pi_status">
                <option value="">All statuses</option>
                <option value="PENDING" <?php selected($status, 'PENDING'); ?>>Pending AutoCount</option>
                <option value="PROCESSING" <?php selected($status, 'PROCESSING'); ?>>Processing AutoCount</option>
                <option value="SUCCESS" <?php selected($status, 'SUCCESS'); ?>>Created</option>
                <option value="FAILED" <?php selected($status, 'FAILED'); ?>>AutoCount Failed</option>
                <option value="FAILED_FINAL" <?php selected($status, 'FAILED_FINAL'); ?>>Failed Final</option>
                <option value="CANCELLED" <?php selected($status, 'CANCELLED'); ?>>Cancelled</option>
            </select>
        </div>

        <?php if ($is_admin): ?>
            <label class="wst-pil-hidden-toggle">
                <input type="checkbox" name="show_hidden" value="1" <?php checked($show_hidden); ?>>
                Show hidden
            </label>
        <?php endif; ?>

        <div class="wst-pil-filter-actions">
            <button type="submit" class="wst-pil-btn wst-pil-btn-primary">Search</button>
            <a href="<?php echo esc_url($clear_url); ?>" class="wst-pil-btn wst-pil-btn-light">Clear</a>
        </div>
    </form>

    <?php if (empty($rows)): ?>
        <div class="wst-pil-empty">
            No Purchase Invoice records were found for the selected filters.
        </div>
    <?php else: ?>
        <div class="wst-pil-table-scroll">
            <table class="wst-pil-table">
                <thead>
                    <tr>
                        <th>Date</th>
                        <th>WPPI No.</th>
                        <th>Creditor</th>
                        <th>Supplier Invoice</th>
                        <th class="wst-pil-number">Amount</th>
                        <th>Status</th>
                        <th>AutoCount No.</th>
                        <th>Created By</th>
                        <th>Created</th>
                        <th class="wst-pil-actions-col">Action</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($rows as $row): ?>
                        <?php
                        $pi_id = (int)$row['id'];
                        $view_url = add_query_arg('pi_id', $pi_id, $view_page_url);
                        $creator_id = (int)($row['created_by'] ?? 0);
                        $creator_name = $user_names[$creator_id] ?? ($creator_id ? 'User #' . $creator_id : '-');
                        $sync_status = strtoupper(trim((string)$row['sync_status']));
                        $is_hidden = !empty($row['hidden_from_staff_list']);
                        ?>
                        <tr class="<?php echo $is_hidden ? 'wst-pil-row-hidden' : ''; ?>">
                            <td data-label="Date"><?php echo esc_html(mysql2date('d/m/Y', $row['doc_date'])); ?></td>
                            <td data-label="WPPI No.">
                                <a class="wst-pil-doc-link" href="<?php echo esc_url($view_url); ?>">
                                    <?php echo esc_html($row['local_doc_no']); ?>
                                </a>
                                <?php if ($is_hidden): ?>
                                    <span class="wst-pil-mini-badge">Hidden</span>
                                <?php endif; ?>
                            </td>
                            <td data-label="Creditor">
                                <strong><?php echo esc_html($row['creditor_name'] ?: $row['creditor_code']); ?></strong>
                                <?php if ($row['creditor_code'] !== ''): ?>
                                    <small><?php echo esc_html($row['creditor_code']); ?></small>
                                <?php endif; ?>
                            </td>
                            <td data-label="Supplier Invoice">
                                <?php echo esc_html($row['supplier_invoice_no'] ?: '-'); ?>
                            </td>
                            <td data-label="Amount" class="wst-pil-number">
                                <?php echo esc_html(($row['currency_code'] ?: 'MYR') . ' ' . wst_pil_money($row['total_amount'])); ?>
                            </td>
                            <td data-label="Status">
                                <span
                                    class="wst-pil-badge <?php echo esc_attr(wst_pil_status_class($sync_status)); ?>"
                                    <?php if (!empty($row['last_sync_error'])): ?>
                                        title="<?php echo esc_attr($row['last_sync_error']); ?>"
                                    <?php endif; ?>
                                >
                                    <?php echo esc_html(wst_pil_status_label($sync_status)); ?>
                                </span>
                                <?php if (!empty($row['last_sync_error'])): ?>
                                    <small class="wst-pil-error">
                                        <?php echo esc_html(wp_trim_words($row['last_sync_error'], 9, '…')); ?>
                                    </small>
                                <?php endif; ?>
                            </td>
                            <td data-label="AutoCount No."><?php echo esc_html($row['autocount_doc_no'] ?: '-'); ?></td>
                            <td data-label="Created By"><?php echo esc_html($creator_name); ?></td>
                            <td data-label="Created">
                                <?php echo esc_html(mysql2date('d/m/Y H:i', $row['created_at'])); ?>
                            </td>
                            <td data-label="Action" class="wst-pil-actions">
                                <a class="wst-pil-btn wst-pil-btn-view" href="<?php echo esc_url($view_url); ?>">View</a>
                                <a
                                    class="wst-pil-btn wst-pil-btn-print"
                                    href="<?php echo esc_url(add_query_arg(array('pi_id' => $pi_id, 'print' => '1'), $view_page_url)); ?>"
                                    target="_blank"
                                    rel="noopener"
                                >Print</a>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </div>

        <?php if ($total_pages > 1): ?>
            <nav class="wst-pil-pagination" aria-label="Purchase Invoice pagination">
                <?php
                echo wp_kses_post(paginate_links(array(
                    'base' => esc_url_raw(add_query_arg('pi_page', '%#%', $current_url)),
                    'format' => '',
                    'current' => $page,
                    'total' => $total_pages,
                    'prev_text' => '‹ Previous',
                    'next_text' => 'Next ›',
                    'type' => 'list',
                )));
                ?>
            </nav>
        <?php endif; ?>
    <?php endif; ?>
</div>

<style>
.wst-pil-wrap {
    --pil-green: #166534;
    --pil-green-dark: #14532d;
    --pil-green-soft: #f0fdf4;
    --pil-border: #dbe4ee;
    --pil-text: #0f172a;
    --pil-muted: #64748b;
    color: var(--pil-text);
    font-family: "Segoe UI", Roboto, system-ui, sans-serif;
}
.wst-pil-head {
    display: flex;
O4�s��P��������O4�s
N����
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1rem;
}
.wst-pil-head h2 {
    margin: 0;
    font-size: 1.55rem;
}
.wst-pil-head p {
    margin: .3rem 0 0;
    color: var(--pil-muted);
}
.wst-pil-count {
    flex: 0 0 auto;
    padding: .5rem .75rem;
    border: 1px solid #bbf7d0;
    border-radius: 999px;
    background: var(--pil-green-soft);
    color: var(--pil-green);
    font-weight: 800;
}
.wst-pil-filter {
    display: grid;
    grid-template-columns: repeat(2, minmax(9rem, .7fr)) minmax(16rem, 2fr) minmax(12rem, 1fr) auto;
    gap: .75rem;
    align-items: end;
    padding: 1rem;
    margin-bottom: 1rem;
    border: 1px solid var(--pil-border);
    border-radius: .8rem;
    background: #fff;
    box-shadow: 0 .5rem 1.3rem rgba(15, 23, 42, .06);
}
.wst-pil-field label {
    display: block;
    margin-bottom: .3rem;
    color: #475569;
    font-size: .82rem;
    font-weight: 800;
}
.wst-pil-field input,
.wst-pil-field select {
    width: 100%;
    min-height: 2.8rem;
    padding: .62rem .72rem;
    border: 1px solid #cbd5e1;
    border-radius: .58rem;
    background: #fff;
    color: var(--pil-text);
    font: inherit;
}
.wst-pil-hidden-toggle {
    display: flex;
    align-items: center;
    min-height: 2.8rem;
    gap: .4rem;
    font-weight: 700;
    white-space: nowrap;
}
.wst-pil-filter-actions {
    display: flex;
    gap: .5rem;
}
.wst-pil-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 2.55rem;
    padding: .55rem .8rem;
    border: 1px solid transparent;
    border-radius: .55rem;
    font-size: .88rem;
    font-weight: 800;
    line-height: 1;
    text-decoration: none !important;
    cursor: pointer;
}
.wst-pil-btn-primary,
.wst-pil-btn-view {
    border-color: var(--pil-green);
    background: var(--pil-green);
    color: #fff !important;
}
.wst-pil-btn-primary:hover,
.wst-pil-btn-view:hover {
    background: var(--pil-green-dark);
}
.wst-pil-btn-light,
.wst-pil-btn-print {
    border-color: #cbd5e1;
    background: #fff;
    color: #334155 !important;
}
.wst-pil-btn-light:hover,
.wst-pil-btn-print:hover {
    background: #f8fafc;
}
.wst-pil-table-scroll {
    overflow-x: auto;
    border: 1px solid var(--pil-border);
    border-radius: .8rem;
    background: #fff;
    box-shadow: 0 .5rem 1.3rem rgba(15, 23, 42, .06);
}
.wst-pil-table {
    width: 100%;
    min-width: 78rem;
    border-collapse: collapse;
}
.wst-pil-table th,
.wst-pil-table td {
    padding: .75rem .8rem;
    border-bottom: 1px solid #edf2f7;
    vertical-align: middle;
    text-align: left;
}
.wst-pil-table th {
    background: #f8fafc;
    color: #334155;
    font-size: .8rem;
    font-weight: 900;
    white-space: nowrap;
}
.wst-pil-table tbody tr:hover {
    background: #fbfefc;
}
.wst-pil-table small {
    display: block;
    margin-top: .18rem;
    color: var(--pil-muted);
}
.wst-pil-number {
    text-align: right !important;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.wst-pil-actions-col {
    width: 9.5rem;
}
.wst-pil-actions {
    display: flex;
    gap: .4rem;
}
.wst-pil-doc-link {
    color: var(--pil-green);
    font-weight: 900;
    text-decoration: none;
}
.wst-pil-badge,
.wst-pil-mini-badge {
    display: inline-flex;
    align-items: center;
    width: fit-content;
    padding: .3rem .55rem;
    border-radius: 999px;
    font-size: .76rem;
    font-weight: 900;
}
.wst-pil-badge-good {
    border: 1px solid #86efac;
    background: #dcfce7;
    color: #166534;
}
.wst-pil-badge-warn {
    border: 1px solid #fcd34d;
    background: #fef3c7;
    color: #92400e;
}
.wst-pil-badge-danger {
    border: 1px solid #fca5a5;
    background: #fee2e2;
    color: #991b1b;
}
.wst-pil-badge-info,
.wst-pil-mini-badge {
    border: 1px solid #bae6fd;
    background: #e0f2fe;
    color: #075985;
}
.wst-pil-mini-badge {
    margin-left: .35rem;
    padding: .2rem .42rem;
}
.wst-pil-error {
    max-width: 18rem;
    color: #b91c1c !important;
}
.wst-pil-row-hidden {
    opacity: .68;
}
.wst-pil-empty,
.wst-pil-alert {
    padding: 1rem;
    border-radius: .75rem;
}
.wst-pil-empty {
    border: 1px dashed #cbd5e1;
    background: #f8fafc;
    color: #64748b;
    text-align: center;
}
.wst-pil-alert-error {
    border: 1px solid #fecaca;
    background: #fff1f2;
    color: #991b1b;
}
.wst-pil-pagination {
    margin-top: 1rem;
}
.wst-pil-pagination ul {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem;
    padding: 0;
    margin: 0;
    list-style: none;
}
.wst-pil-pagination a,
.wst-pil-pagination span {
    display: inline-flex;
    min-width: 2.4rem;
    min-height: 2.4rem;
    align-items: center;
    justify-content: center;
    padding: .4rem .65rem;
    border: 1px solid #cbd5e1;
    border-radius: .5rem;
    background: #fff;
    color: #334155;
    text-decoration: none;
}
.wst-pil-pagination .current {
    border-color: var(--pil-green);
    background: var(--pil-green);
    color: #fff;
}
@media (max-width: 1080px) {
    .wst-pil-filter {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    .wst-pil-field-search,
    .wst-pil-filter-actions {
        grid-column: 1 / -1;
    }
}
@media (max-width: 720px) {
    .wst-pil-head {
        flex-direction: column;
    }
    .wst-pil-filter {
        grid-template-columns: 1fr;
    }
    .wst-pil-field-search,
    .wst-pil-filter-actions {
        grid-column: auto;
    }
    .wst-pil-filter-actions .wst-pil-btn {
        flex: 1;
    }
    .wst-pil-table-scroll {
        overflow: visible;
        border: 0;
        box-shadow: none;
        background: transparent;
    }
    .wst-pil-table,
    .wst-pil-table tbody,
    .wst-pil-table tr,
    .wst-pil-table td {
        display: block;
        width: 100%;
        min-width: 0;
    }
    .wst-pil-table thead {
        display: none;
    }
    .wst-pil-table tr {
        margin-bottom: .8rem;
        padding: .35rem .8rem;
        border: 1px solid var(--pil-border);
        border-radius: .75rem;
        background: #fff;
        box-shadow: 0 .35rem 1rem rgba(15, 23, 42, .05);
    }
    .wst-pil-table td {
        display: grid;
        grid-template-columns: 8.4rem minmax(0, 1fr);
        gap: .65rem;
        padding: .58rem 0;
        border-bottom: 1px solid #edf2f7;
        text-align: left !important;
    }
    .wst-pil-table td::before {
        content: attr(data-label);
        color: #64748b;
        font-size: .76rem;
        font-weight: 900;
        text-transform: uppercase;
    }
    .wst-pil-table td:last-child {
        border-bottom: 0;
    }
    .wst-pil-actions {
        display: grid !important;
        grid-template-columns: 1fr 1fr;
    }
}
</style>O4�s��{fQ����������}�
N?�R<?php
/**
 * BASKET STAFF RETURN LIST
 *
 * Staff-facing basket summary and movement history page.
 * Basket return receipts use the same A5 visual style as the driver basket receipt.
 */

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

if (!is_user_logged_in()) {
    echo '<div style="padding:14px;border:1px solid #fecaca;background:#fff1f2;border-radius:12px;color:#991b1b;">Please log in to view Basket Summary.</div>';
    return;
}

if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
    echo '<div style="padding:14px;border:1px solid #fecaca;background:#fff1f2;border-radius:12px;color:#991b1b;">You do not have permission to view Basket Summary.</div>';
    return;
}

$rest_nonce         = wp_create_nonce('wp_rest');
$rest_summary_url   = rest_url('ac/v1/basket/summary');
$rest_ledger_url    = rest_url('ac/v1/basket/ledger');
$ajax_url           = admin_url('admin-ajax.php');
$debtor_nonce       = wp_create_nonce('ac_cs_debtor_search');
$basket_proof_nonce = wp_create_nonce('ac_bs_basket_proof');
$receipt_logo_url   = 'https://website.ipohserver.com/excellentvege/wp-content/uploads/2026/05/Untitled-design-15.png';
$show_debtor_code   = false;
?>

<div id="ac-basket-summary-root"
     class="ac-bs-wrap bs-container"
     data-rest-nonce="<?php echo esc_attr($rest_nonce); ?>"
     data-rest-summary-url="<?php echo esc_attr($rest_summary_url); ?>"
     data-rest-ledger-url="<?php echo esc_attr($rest_ledger_url); ?>"
     data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
     data-debtor-nonce="<?php echo esc_attr($debtor_nonce); ?>"
     data-basket-proof-nonce="<?php echo esc_attr($basket_proof_nonce); ?>"
     data-receipt-logo-url="<?php echo esc_url($receipt_logo_url); ?>"
     data-show-debtor-code="<?php echo $show_debtor_code ? '1' : '0'; ?>">

  <div class="bs-head">
    <h1>Basket Summary</h1>
  </div>

  <div class="bs-card">
    <div class="bs-grid">
      <div class="bs-field">
        <label class="bs-label">Customer</label>
        <div class="bs-search-wrap">
          <input type="text" id="ac_bs_customer_input" class="bs-input" placeholder="Search customer to add..." autocomplete="off" readonly>
          <button type="button" id="ac_bs_customer_clear" class="bs-field-clear" aria-label="Clear customer">x</button>
          <input type="hidden" id="ac_bs_debtor_code" value="">
          <input type="hidden" id="ac_bs_debtor_name" value="">
        </div>
        <div id="ac_bs_selected_customers" class="bs-selected-customers"></div>
        <div id="ac_bs_manage_selected" class="bs-manage-selected" aria-hidden="true">
          <div class="bs-manage-head">
            <div>
              <strong>Selected Customers</strong>
              <span id="ac_bs_manage_count">0 selected</span>
            </div>
            <button type="button" class="bs-mini-btn" id="ac_bs_manage_done">Done</button>
          </div>
          <div class="bs-manage-actions">
            <button type="button" class="bs-mini-btn primary" id="ac_bs_manage_add">Add Customer</button>
            <button type="button" class="bs-mini-btn danger" id="ac_bs_manage_clear">Clear All</button>
          </div>
          <div id="ac_bs_manage_list" class="bs-manage-list"></div>
        </div>
      </div>

      <div class="bs-field">
        <label class="bs-label" for="ac_bs_date_from">Date From</label>
        <input id="ac_bs_date_from" type="date" class="bs-input">
      </div>

      <div class="bs-field">
        <label class="bs-label" for="ac_bs_date_to">Date To</label>
        <input id="ac_bs_date_to" type="date" class="bs-input">
      </div>

      <div class="bs-actions">
        <button id="ac_bs_refresh" class="bs-btn" type="button">Refresh Summary</button>
      </div>
    </div>

    <div id="ac_bs_status" class="bs-status"></div>
  </div>

  <div class="bs-card">
    <div class="bs-totals" id="ac_bs_totals"></div>

    <div class="bs-table-wrap">
      <table class="bs-table">
        <thead>
          <tr>
            <th style="width:60px;">No</th>
            <th style="width:140px;">Customer Code</th>
            <th>Customer Name</th>
            <th style="width:120px;">Basket Sent</th>
            <th style="width:130px;">Basket Returned</th>
            <th style="width:150px;">Outstanding Basket</th>
            <th style="width:130px;">Last Activity</th>
            <th style="width:90px;">Action</th>
          </tr>
        </thead>
        <tbody id="ac_bs_rows_table">
          <tr><td colspan="8" class="bs-empty-cell">No data</td></tr>
        </tbody>
      </table>
    </div>
  </div>

  <div class="bs-ledger-modal" id="ac_bs_ledger_modal" aria-hidden="true">
    <div class="bs-ledger-backdrop" id="ac_bs_ledger_backdrop"></div>
    <div class="bs-ledger-dialog">
      <div class="bs-ledger-head">
        <h2 class="bs-subtitle" id="ac_bs_ledger_title">Basket Movement History</h2>
        <button type="button" class="bs-ledger-close" id="ac_bs_ledger_close" aria-label="Close">x</button>
      </div>

      <div class="bs-ledger-body">
        <div class="bs-ledger-toolbar" id="ac_bs_ledger_toolbar" style="display:none;">
          <div class="bs-ledger-filter">
            <div class="bs-ledger-filter-group">
              <label>From <input type="date" id="ac_bs_ledger_date_from" class="bs-input"></label>
              <label>To <input type="date" id="ac_bs_ledger_date_to" class="bs-input"></label>
            </div>
            <div class="bs-ledger-filter-group">
              <select id="ac_bs_ledger_movement_filter" class="bs-input"></select>
            </div>
            <div class="bs-ledger-filter-group bs-ledger-action-group">
              <button type="button" id="ac_bs_ledger_select_all" class="bs-mini-btn">Select All</button>
              <button type="button" id="ac_bs_ledger_print" class="bs-view-btn bs-receipt-btn">Print PDF</button>
              <button type="button" id="ac_bs_ledger_share" class="bs-view-btn bs-receipt-btn bs-ledger-share-btn">Share PDF</button>
            </div>
          </div>
        </div>
        <div class="bs-table-wrap">
          <table class="bs-table bs-ledger-table">
            <thead>
              <tr>
                <th style="width:40px;"></th>
                <th style="width:50px;">No</th>
                <th style="width:120px;">Date</th>
                <th style="width:120px;">Movement</th>
                <th style="width:80px;">Qty</th>
                <th style="width:140px;">From</th>
                <th style="width:130px;">Document No.</th>
                <th>Note</th>
                <th style="width:140px;">Receipt</th>
              </tr>
            </thead>
            <tbody id="ac_bs_ledger_table">
              <tr><td colspan="9" class="bs-empty-cell">Select a customer to view basket movement history.</td></tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>

  <div id="ac_bs_receipt_mount"></div>

  <div class="bs-picker-modal" id="ac_bs_picker_modal" aria-hidden="true">
    <div class="bs-picker-backdrop" id="ac_bs_picker_backdrop"></div>
    <div class="bs-picker-sheet">
      <div class="bs-picker-head">
        <div class="bs-picker-title" id="ac_bs_picker_title">Select Customer</div>
        <button type="button" class="bs-picker-close" id="ac_bs_picker_close" aria-label="Close">x</button>
      </div>

      <div class="bs-picker-body">
        <input type="text" id="ac_bs_picker_search" class="bs-input bs-picker-search" placeholder="Search customer..." autocomplete="off">
        <div class="bs-picker-results" id="ac_bs_picker_results"></div>
      </div>
    </div>
  </div>
</div>

<style>
.bs-container{
  --bs-border:#dbe4ee;
  --bs-border-strong:#c4d0dd;
  --bs-text:#0f172a;
  --bs-muted:#475569;
  --bs-green:#0B4A2D;
  --bs-green-2:#166534;
  --bs-green-3:#16a34a;
  --bs-green-soft:#f0fdf4;
  --bs-bg:#f5faf7;
  --bs-danger:#991b1b;
  max-width:1360px;
  margin:0 auto;
  padding:16px;
  font-family:"Segoe UI",Roboto,Arial,sans-serif;
  background:linear-gradient(180deg,#f4faf5 0%,#eef7f1 100%);
  border-radius:16px;
  color:var(--bs-text);
  box-sizing:border-box;
}
.bs-container *{box-sizing:border-box;}
.bs-head{display:none;}
.bs-card{background:#fff;border:1px solid var(--bs-border);border-radius:16px;box-shadow:0 8px 28px rgba(15,23,42,.06);padding:16px;margin-bottom:14px;box-sizing:border-box;}
.bs-grid{display:grid;grid-template-columns:minmax(260px,1.4fr) minmax(150px,.75fr) minmax(150px,.75fr) auto;gap:12px;align-items:end;}
.bs-label{display:block;font-size:13px;color:var(--bs-muted);margin-bottom:6px;font-weight:800;letter-spacing:.01em;}
.bs-input{width:100%;min-height:46px;font-size:15px;padding:10px 12px;border-radius:12px;border:1px solid var(--bs-border-strong);box-sizing:border-box;background:#fff;color:#111;transition:border-color .16s ease, box-shadow .16s ease, background .16s ease;}
.bs-input:focus,.bs-btn:focus,.bs-view-btn:focus,.bs-mini-btn:focus{outline:none;border-color:var(--bs-green);box-shadow:0 0 0 3px rgba(11,74,45,.12);}
#ac-basket-summary-root #ac_bs_refresh.bs-btn{min-width:170px;min-height:46px;border:0!important;border-radius:12px!important;background:linear-gradient(135deg,#16a34a,#0B4A2D)!important;color:#fff!important;font-size:15px!important;font-weight:900!important;padding:10px 18px!important;cursor:pointer;box-shadow:0 10px 22px rgba(22,101,52,.20)!important;transition:transform .16s ease, box-shadow .16s ease, filter .16s ease;}
#ac-basket-summary-root #ac_bs_refresh.bs-btn:hover{filter:brightness(.96);transform:translateY(-1px);box-shadow:0 14px 28px rgba(22,101,52,.23)!important;}
.bs-status{display:none;margin-top:10px;font-size:14px;color:#334155;}
.bs-totals{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:14px;}
.bs-total-box{border:1px solid #e5e7eb;background:linear-gradient(180deg,#fff,#f8fafc);border-radius:14px;padding:12px;box-shadow:0 3px 12px rgba(15,23,42,.035);}
.bs-total-box.issue{border-color:#fecaca;background:#fff7f7;}
.bs-total-label{font-size:12px;color:#64748b;font-weight:800;letter-spacing:.01em;}
.bs-total-value{font-size:24px;font-weight:950;color:#0f172a;margin-top:4px;line-height:1.05;}
.bs-total-sub{font-size:12px;color:#64748b;font-weight:750;margin-top:5px;line-height:1.25;}
.bs-table-wrap{width:100%;overflow:auto;border:1px solid #e5e7eb;border-radius:14px;background:#fff;box-shadow:inset 0 1px 0 rgba(255,255,255,.7);}
.bs-table{width:100%;min-width:980px;border-collapse:separate;border-spacing:0;background:#fff;}
.bs-table thead th{position:sticky;top:0;z-index:1;background:#f8fafc;color:#334155;font-size:13px;font-weight:950;text-align:left;padding:12px 12px;border-bottom:1px solid #e5e7eb;white-space:nowrap;letter-spacing:.01em;}
.bs-table tbody td{padding:11px 12px;font-size:14px;line-height:1.25;color:#0f172a;border-bottom:1px solid #edf2f7;vertical-align:top;}
.bs-table tbody tr:nth-child(odd){background:#ffffff;}
.bs-table tbody tr:nth-child(even){background:#f6fbf7;}
.bs-table tbody tr:hover{background:#edf8f1;}
.bs-empty-cell{color:#64748b;text-align:center;padding:22px!important;font-weight:800;}
.bs-view-btn{-webkit-appearance:none!important;appearance:none!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;min-height:36px!important;border:1px solid #16a34a!important;border-radius:10px!important;background:#f0fdf4!important;color:#166534!important;font-size:12px!important;font-weight:900!important;line-height:1.15!important;padding:7px 11px!important;text-shadow:none!important;box-shadow:none!important;cursor:pointer!important;transition:background .16s ease, color .16s ease, border-color .16s ease, box-shadow .16s ease, transform .16s ease;}
.bs-view-btn:hover,.bs-view-btn:focus{border-color:#166534!important;background:#166534!important;color:#fff!important;text-decoration:none!important;box-shadow:0 0 0 3px rgba(22,101,52,.16)!important;transform:translateY(-1px);}
.bs-receipt-btn{min-width:120px!important;border:0!important;background:#166534!important;color:#fff!important;white-space:normal!important;text-align:center!important;box-shadow:0 7px 16px rgba(22,101,52,.18)!important;}
.bs-ledger-action-group{justify-content:flex-end;}
.bs-ledger-share-btn{background:#128C7E!important;color:#fff!important;border:0!important;box-shadow:0 7px 16px rgba(18,140,126,.18)!important;}
.bs-row-selected,.bs-row-selected td{background:#ecfdf3!important;}
.bs-chip{display:inline-flex;align-items:center;justify-content:center;border-radius:999px;padding:5px 11px;font-size:12px;font-weight:900;border:1px solid;white-space:nowrap;}
.bs-chip.ok{color:#166534;background:#dcfce7;border-color:#86efac;}
.bs-chip.warn{color:#9a3412;background:#ffedd5;border-color:#fdba74;}
.bs-chip.neg{color:#991b1b;background:#fee2e2;border-color:#fca5a5;}
.bs-type-send{color:#166534;font-weight:900;}
.bs-type-return{color:#9a3412;font-weight:900;}
.bs-search-wrap{position:relative;}
.bs-search-wrap .bs-input{padding-right:2.35rem;cursor:pointer;}
.bs-field-clear{position:absolute;top:50%;right:.45rem;transform:translateY(-50%);width:1.75rem;height:1.75rem;border:1px solid var(--bs-border)!important;background:#fff!important;color:#64748b!important;border-radius:.55rem!important;display:none;align-items:center;justify-content:center;font-size:.9rem;font-weight:900;cursor:pointer;padding:0!important;line-height:1!important;}
.bs-field-clear.show{display:inline-flex;}
.bs-selected-customers{display:flex;flex-wrap:nowrap;align-items:center;gap:6px;margin-top:8px;min-height:30px;overflow:hidden;}
.bs-selected-customers:empty{display:none;}
.bs-selected-chip{display:inline-flex;align-items:center;gap:6px;max-width:170px;min-width:0;border:1px solid #bbf7d0;background:#f0fdf4;color:#166534;border-radius:999px;padding:5px 8px;font-size:12px;font-weight:900;line-height:1.15;}
.bs-selected-chip span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.bs-selected-more{display:inline-flex;align-items:center;flex:0 0 auto;border:1px solid #cbd5e1;background:#f8fafc;color:#334155;border-radius:999px;padding:5px 9px;font-size:12px;font-weight:900;line-height:1.15;}
.bs-manage-toggle{flex:0 0 auto;border:1px solid #166534!important;background:#166534!important;color:#fff!important;border-radius:999px!important;padding:5px 10px!important;font-size:12px!important;font-weight:950!important;line-height:1.15!important;cursor:pointer!important;}
.bs-manage-toggle:hover,.bs-manage-toggle:focus{background:#0f4f2e!important;border-color:#0f4f2e!important;outline:none!important;box-shadow:0 0 0 3px rgba(22,101,52,.16)!important;}
.bs-selected-remove{position:relative;width:18px;height:18px;flex:0 0 18px;border:1px solid #86efac!important;background:#fff!important;color:#166534!important;border-radius:999px!important;display:inline-block!important;padding:0!important;font-size:0!important;line-height:0!important;cursor:pointer!important;vertical-align:middle!important;}
.bs-selected-remove::before,.bs-selected-remove::after{content:"";position:absolute;left:50%;top:50%;width:8px;height:2px;background:currentColor;border-radius:999px;transform-origin:center;}
.bs-selected-remove::before{transform:translate(-50%,-50%) rotate(45deg);}
.bs-selected-remove::after{transform:translate(-50%,-50%) rotate(-45deg);}
.bs-selected-remove:hover,.bs-selected-remove:focus{background:#166534!important;color:#fff!important;border-color:#166534!important;outline:none!important;}
.bs-field{position:relative;}
.bs-manage-selected{position:absolute;z-index:30;left:0;top:calc(100% + 8px);width:min(460px, calc(100vw - 48px));display:none;background:#fff;border:1px solid #cbd5e1;border-radius:14px;box-shadow:0 22px 48px rgba(15,23,42,.20);padding:10px;box-sizing:border-box;}
.bs-manage-selected.active{display:block;}
.bs-manage-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-bottom:8px;border-bottom:1px solid #e5e7eb;}
.bs-manage-head strong{display:block;font-size:13px;color:��}�KV~R������������
N?�S#0f172a;line-height:1.2;}
.bs-manage-head span{display:block;margin-top:2px;font-size:12px;color:#64748b;font-weight:800;}
.bs-manage-actions{display:flex;gap:8px;margin:10px 0;}
.bs-mini-btn{border:1px solid #cbd5e1!important;background:#fff!important;color:#334155!important;border-radius:10px!important;padding:8px 11px!important;font-size:12px!important;font-weight:950!important;cursor:pointer!important;line-height:1.15!important;}
.bs-mini-btn.primary{background:#166534!important;border-color:#166534!important;color:#fff!important;}
.bs-mini-btn.danger{background:#fff1f2!important;border-color:#fecaca!important;color:#991b1b!important;}
.bs-mini-btn:hover,.bs-mini-btn:focus{filter:brightness(.97);outline:none!important;box-shadow:0 0 0 3px rgba(15,23,42,.08)!important;}
.bs-manage-list{max-height:220px;overflow:auto;display:flex;flex-direction:column;gap:6px;}
.bs-manage-row{display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid #e5e7eb;background:#f8fafc;border-radius:10px;padding:8px 9px;}
.bs-manage-row-name{min-width:0;font-size:13px;font-weight:900;color:#0f172a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.bs-manage-empty{padding:14px;text-align:center;color:#64748b;font-size:13px;font-weight:800;background:#f8fafc;border-radius:10px;}
.bs-ledger-modal,.bs-picker-modal{position:fixed;inset:0;z-index:99990;display:none;align-items:center;justify-content:center;padding:18px;box-sizing:border-box;}
.bs-ledger-modal.active,.bs-picker-modal.active{display:flex;}
.bs-ledger-backdrop,.bs-picker-backdrop{position:absolute;inset:0;background:rgba(15,23,42,.62);backdrop-filter:blur(4px);}
.bs-ledger-dialog{position:relative;width:min(1120px, calc(100vw - 36px));max-height:calc(100dvh - 36px);background:#fff;border-radius:22px;box-shadow:0 28px 80px rgba(15,23,42,.32);display:flex;flex-direction:column;overflow:hidden;border:1px solid rgba(255,255,255,.62);}
.bs-ledger-head{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:18px 20px;border-bottom:1px solid rgba(255,255,255,.14);background:linear-gradient(135deg,#0B4A2D,#166534);color:#fff;}
.bs-ledger-head .bs-subtitle{margin:0;font-size:19px;line-height:1.25;font-weight:950;letter-spacing:-.01em;color:#fff;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;}
.bs-ledger-close{width:38px;height:38px;flex:0 0 38px;border:1px solid rgba(255,255,255,.38)!important;border-radius:12px!important;background:rgba(255,255,255,.12)!important;color:#fff!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;padding:0!important;font-size:18px!important;font-weight:950!important;cursor:pointer;line-height:1!important;}
.bs-ledger-close:hover,.bs-ledger-close:focus{background:#fff!important;color:#0B4A2D!important;}
.bs-ledger-body{padding:14px;background:#f8fafc;overflow:auto;}
.bs-ledger-toolbar{padding:0;margin:0 0 12px;display:flex;align-items:center;gap:10px;}
.bs-ledger-filter{width:100%;display:grid;grid-template-columns:minmax(290px,1fr) minmax(180px,.45fr) auto;align-items:end;gap:10px;padding:12px;border:1px solid #e2e8f0;border-radius:16px;background:#fff;box-shadow:0 6px 18px rgba(15,23,42,.045);}
.bs-ledger-filter-group{display:flex;flex-wrap:wrap;align-items:end;gap:8px;padding:0;}
.bs-ledger-filter label{display:flex;align-items:center;gap:7px;font-size:13px;color:#334155;font-weight:850;white-space:nowrap;padding:0;}
.bs-ledger-filter .bs-input{min-height:38px;padding:7px 9px;font-size:13px;border-radius:10px;}
.bs-ledger-filter label .bs-input{width:150px;}
.bs-ledger-filter select.bs-input{min-width:170px;}
.bs-ledger-filter .bs-mini-btn,.bs-ledger-filter .bs-view-btn{min-height:38px;margin-top:0;}
#ac_bs_ledger_table td input[type=checkbox]{width:18px;height:18px;cursor:pointer;accent-color:#166534;}
.bs-ledger-table th:first-child,.bs-ledger-table td:first-child{text-align:center;}
#ac_bs_ledger_select_all.toggled{background:#166534!important;border-color:#166534!important;color:#fff!important;}
.bs-ledger-toolbar.error-message{color:#991b1b;background:#fee2e2;border:1px solid #fecaca;padding:8px 10px;border-radius:8px;font-size:13px;font-weight:700;margin-bottom:10px;}
.bs-ledger-table{min-width:900px!important;table-layout:fixed;}
.bs-ledger-table th,.bs-ledger-table td{padding:10px 12px!important;font-size:13px!important;line-height:1.28!important;vertical-align:middle!important;word-break:break-word;}
.bs-ledger-loading-cell{padding:42px 18px!important;text-align:center!important;background:#fff!important;}
.bs-ledger-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;color:#334155;font-weight:900;}
.bs-ledger-spinner{width:36px;height:36px;border:4px solid #dbe4ee;border-top-color:#166534;border-radius:50%;animation:bsSpin .8s linear infinite;}
@keyframes bsSpin{to{transform:rotate(360deg);}}
body.bs-ledger-open,body.bs-br-open{overflow:hidden;}
.bs-picker-sheet{position:relative;width:100%;max-width:36rem;background:#fff;border-radius:16px;box-shadow:0 24px 70px rgba(15,23,42,.28);overflow:hidden;border:1px solid rgba(255,255,255,.6);}
.bs-picker-head{display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:14px 16px;border-bottom:1px solid #e5e7eb;background:#f8fafc;}
.bs-picker-title{font-size:.95rem;font-weight:900;}
.bs-picker-close{width:34px;height:34px;border:1px solid #dbe4ee!important;border-radius:10px!important;background:#fff!important;color:#334155!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;padding:0!important;font-size:16px!important;font-weight:900!important;cursor:pointer;line-height:1!important;}
.bs-picker-body{padding:.8rem;display:flex;flex-direction:column;gap:.55rem;}
.bs-picker-results{max-height:18rem;overflow-y:auto;display:flex;flex-direction:column;gap:.4rem;}
.bs-picker-note{text-align:center;padding:.7rem;color:var(--bs-muted);font-size:.82rem;font-weight:800;}
.bs-picker-item{display:block;width:100%;text-align:left;padding:.7rem .75rem;border:1px solid var(--bs-border)!important;border-radius:.65rem!important;background:#fff!important;color:var(--bs-text)!important;cursor:pointer;}
.bs-picker-item:hover,.bs-picker-item:focus{border-color:#166534!important;box-shadow:0 0 0 3px rgba(22,101,52,.10)!important;outline:none!important;}
.bs-picker-item-main{display:block;font-weight:900;font-size:.9rem;color:var(--bs-text);}
.bs-picker-item-sub{display:block;font-size:.72rem;color:var(--bs-muted);margin-top:.12rem;}

/* Driver-style Basket Return Receipt */
.bs-br-overlay{position:fixed;inset:0;z-index:100001;background:rgba(15,23,42,.58);overflow:auto;padding:24px;display:flex;align-items:flex-start;justify-content:center;font-family:Arial,Helvetica,sans-serif;color:#111;backdrop-filter:blur(4px);}
.bs-br-modal{width:min(580px, calc(100vw - 40px));background:#f3f4f6;border-radius:18px;padding:14px;box-shadow:0 28px 80px rgba(0,0,0,.35);}
.bs-br-actions{position:sticky;top:0;z-index:5;display:grid;grid-template-columns:1fr 1fr 1fr;gap:9px;margin:0 0 12px;background:#f3f4f6;padding-bottom:8px;}
.bs-br-actions button{border:0!important;border-radius:14px!important;min-height:46px!important;padding:10px 14px!important;font-size:14px!important;font-weight:950!important;cursor:pointer!important;font-family:inherit!important;}
.bs-br-actions button:disabled{opacity:.7;cursor:wait!important;}
.bs-br-print,.bs-br-share{background:#e9f7ee!important;color:#0B4A2D!important;border:1px solid #cce8d6!important;}
.bs-br-close{background:#0B4A2D!important;color:#fff!important;}
.bs-br-card{border:1px solid #dbe4ee;border-radius:18px;padding:14px;background:#fff;box-shadow:0 8px 22px rgba(10,45,29,.045);}
.bs-br-paper{border:1px solid #d1d5db;background:#fff;padding:16px;color:#111;font-family:Arial,sans-serif;box-sizing:border-box;}
.bs-br-head{display:flex;justify-content:space-between;align-items:center;gap:12px;border-bottom:2px solid #111;padding-bottom:10px;margin-bottom:12px;}
.bs-br-logo{width:176px;height:64px;object-fit:contain;object-position:left center;display:block;}
.bs-br-title{text-align:right;font-size:12px;font-weight:900;letter-spacing:.08em;}
.bs-br-no{text-align:right;font-size:15px;font-weight:900;margin-top:4px;}
.bs-br-info{display:grid;grid-template-columns:1fr 1fr;gap:12px;border-bottom:1px solid #e5e7eb;padding:8px 0 10px;}
.bs-br-field{min-width:0;}
.bs-br-field span{display:block;font-size:12px;font-weight:800;color:#111;margin-bottom:4px;}
.bs-br-field strong{display:block;font-size:15px;font-weight:900;line-height:1.2;word-break:break-word;color:#111;}
.bs-br-qty{font-size:34px;font-weight:950;text-align:center;color:#111;padding:18px 0;}
.bs-br-proof{margin-top:6px;border:1px dashed #cbd5e1;padding:10px;text-align:center;font-size:12px;font-weight:800;color:#111;min-height:58px;}
.bs-br-proof img{display:block;width:100%;max-height:330px;object-fit:contain;margin-top:8px;}
.bs-br-loading{padding:24px;text-align:center;font-weight:900;color:#334155;}
#bsBrPrintArea{display:none!important;}

@media (max-width:1024px){
  .bs-container{max-width:none;margin:0;border-radius:0;padding:.7rem;}
  .bs-card{padding:.75rem;margin-bottom:.65rem;border-radius:14px;box-shadow:0 2px 10px rgba(15,23,42,.04);}
  .bs-head{display:flex;align-items:center;margin-bottom:12px;}
  .bs-head h1{font-size:1.35rem;margin:0;font-weight:950;}
  .bs-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:.55rem;}
  .bs-grid .bs-field:first-child{grid-column:1 / -1;}
  .bs-actions{grid-column:1 / -1;}
  #ac-basket-summary-root #ac_bs_refresh.bs-btn{width:100%;min-height:44px;border-radius:.75rem!important;}
  .bs-table{min-width:880px;}
  .bs-table thead th,.bs-table tbody td{font-size:.82rem;padding:.55rem .65rem;}
  .bs-totals{gap:.55rem;margin-bottom:.65rem;}
  .bs-total-value{font-size:1.2rem;}
}

@media (max-width:760px){
  .bs-container{padding:.5rem;background:#f6faf7;}
  .bs-card{border-radius:14px;padding:.65rem;}
  .bs-grid{grid-template-columns:1fr;gap:.55rem;}
  .bs-grid .bs-field:first-child,.bs-actions{grid-column:auto;}
  .bs-label{font-size:.78rem;margin-bottom:.28rem;}
  .bs-input{min-height:42px;padding:.55rem .65rem;font-size:.92rem;border-radius:10px;}
  .bs-totals{grid-template-columns:repeat(2,minmax(0,1fr));gap:.45rem;margin-bottom:.55rem;}
  .bs-total-box{padding:.62rem;border-radius:12px;}
  .bs-total-label{font-size:.62rem;}
  .bs-total-value{font-size:1rem;}
  .bs-total-sub{font-size:.66rem;}
  .bs-selected-customers{overflow:auto;padding-bottom:2px;}
  .bs-selected-chip{max-width:210px;}
  .bs-manage-selected{position:fixed;left:10px;right:10px;top:auto;bottom:10px;width:auto;max-height:65dvh;overflow:auto;z-index:100000;border-radius:16px;}

  .bs-table-wrap{border:0;overflow:visible;background:transparent;box-shadow:none;}
  .bs-table{display:block;width:100%;min-width:0!important;background:transparent;border-collapse:separate;}
  .bs-table thead{display:none;}
  .bs-table tbody{display:block;width:100%;}
  .bs-table tbody tr{display:block;width:100%;margin:0 0 .62rem;border:1px solid #e2e8f0;border-radius:14px;background:#fff!important;box-shadow:0 5px 18px rgba(15,23,42,.055);overflow:hidden;}
  .bs-table tbody td{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;width:100%;padding:.72rem .78rem!important;border-bottom:1px solid #eef2f7!important;text-align:right;font-size:.86rem!important;line-height:1.28!important;}
  .bs-table tbody td:last-child{border-bottom:0!important;}
  .bs-table tbody td[data-label]::before{content:attr(data-label);flex:0 0 42%;text-align:left;color:#475569;font-weight:950;}
  .bs-table tbody td:not([data-label]){display:block;text-align:center;}
  .bs-table tbody td:not([data-label])::before{content:none;}
  .bs-table .bs-empty-cell{display:block!important;width:100%;text-align:center!important;padding:18px!important;border:0!important;}
  .bs-view-btn,.bs-receipt-btn{width:auto!important;min-height:36px!important;}
  .bs-chip{padding:4px 9px;}

  .bs-ledger-modal{padding:0;align-items:stretch;justify-content:stretch;}
  .bs-ledger-dialog{width:100%;max-width:none;height:100dvh;max-height:100dvh;border-radius:0;border:0;}
  .bs-ledger-head{padding:14px 12px;align-items:flex-start;}
  .bs-ledger-head .bs-subtitle{font-size:16px;-webkit-line-clamp:3;}
  .bs-ledger-close{width:36px;height:36px;flex-basis:36px;border-radius:10px!important;}
  .bs-ledger-body{padding:10px;overflow:auto;}
  .bs-ledger-toolbar{margin-bottom:10px;}
  .bs-ledger-filter{grid-template-columns:1fr;gap:9px;padding:10px;border-radius:14px;}
  .bs-ledger-filter-group{display:grid;grid-template-columns:1fr;gap:8px;width:100%;}
  .bs-ledger-filter-group:last-child{grid-template-columns:repeat(3,minmax(0,1fr));}
  .bs-ledger-filter label{display:grid;grid-template-columns:42px 1fr;align-items:center;width:100%;}
  .bs-ledger-filter label .bs-input,.bs-ledger-filter select.bs-input{width:100%;min-width:0;}
  .bs-ledger-filter .bs-mini-btn,.bs-ledger-filter .bs-view-btn{width:100%!important;min-width:0!important;min-height:42px!important;}
  .bs-ledger-table tbody td:first-child{justify-content:space-between;text-align:right;}
  .bs-ledger-table tbody td:first-child input{margin-left:auto;}

  .bs-picker-modal{padding:0;align-items:flex-end;}
  .bs-picker-sheet{max-width:none;width:100%;border-radius:18px 18px 0 0;}
  .bs-picker-results{max-height:55dvh;}
  .bs-br-overlay{padding:10px;align-items:flex-start;}
  .bs-br-modal{width:100%;border-radius:14px;padding:10px;}
  .bs-br-info,.bs-br-actions{grid-template-columns:1fr;}
  .bs-br-actions{position:relative;top:auto;}
  .bs-br-logo{width:140px;height:52px;}
  .bs-br-qty{font-size:28px;}
}

@media (max-width:420px){
  .bs-totals{grid-template-columns:1fr;}
  .bs-ledger-filter-group:last-child{grid-template-columns:1fr;}
  .bs-table tbody td[data-label]::before{flex-basis:46%;}
}

@media print{
  html,body{background:#fff!important;width:148mm;min-height:0!important;height:auto!important;overflow:hidden!important;}
  body > *:not(#bsBrPrintArea){display:none!important;}
  body *{visibility:hidden!important;}
  #bsBrPrintArea,#bsBrPrintArea *{visibility:visible!important;}
  #bsBrPrintArea{display:block!important;position:absolute!important;left:0!important;top:0!important;width:100%!important;max-height:190mm!important;overflow:hidden!important;page-break-after:avoid!important;break-after:avoid!important;}
  #bsBrPrintArea .bs-br-card{border:0!important;box-shadow:none!important;margin:0!important;padding:0!important;page-break-inside:avoid!important;break-inside:avoid!important;}
  #bsBrPrintArea .bs-br-paper{height:188mm!important;overflow:hidden!important;page-break-inside:avoid!important;break-inside:avoid!important;}
  .bs-br-actions{display:none!important;}
  @page{size:A5 portrait;margin:6mm;}
}
</style>

<script>
(function(){
  const wrap = document.getElementById('ac-basket-summary-root');
  if (!wrap || wrap.dataset.init === '1') return;
  wrap.dataset.init = '1';

  const REST_NONCE       = wrap.dataset.restNonce || '';
  const REST_SUMMARY_URL = wrap.dataset.restSummaryUrl || '';
  const REST_LEDGER_URL  = wrap.dataset.restLedgerUrl || '';
  const AJAX_URL         = wrap.dataset.ajaxUrl || '';
  const DEBTOR_NONCE     = wrap.dataset.debtorNonce || '';
  const SHOW_DEBTOR_CODE = wrap.dataset.showDebtorCode === '1';
  const RECEIPT_LOGO_URL = wrap.dataset.receiptLogoUrl || '';

  const $ = id => wrap.querySelector('#' + id);
  const pickerState = { items: [], fetchFn: null, onPick: null };
  const selectedDebtors = [];
  const ledgerCache = {};
  let pickerTimer = null;
  let currentLedgerRows = [];
  let currentLedgerCustomer = { code: '', name: '' };
  let currentReceiptForShare = null;
  let brJsPdfPromise = null;
  let loadSummarySeq = 0;

  function fmtQty(n){
    const x = Number(n);
    return Number.isFinite(x) ? Math.round(x) : '0';
  }

  function esc(s){
    if (s === null || s === undefined) return '';
    return String(s).replace(/[&<����#�S�����������
N?�T>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));
  }

  function showError(title, message){
    if (window.Swal && typeof Swal.fire === 'function') {
      Swal.fire({ icon: 'error', title: title || 'Error', text: message || 'Something went wrong' });
    } else {
      alert((title || 'Error') + '\n' + (message || 'Something went wrong'));
    }
  }

  function showInfo(title, message){
    if (window.Swal && typeof Swal.fire === 'function') {
      Swal.fire({ icon:'info', title:title || 'Info', text:message || '' });
    } else {
      alert((title || 'Info') + '\n' + (message || ''));
    }
  }

  function setLedgerButtonBusy(button, text){
    if (!button) return '';
    const originalText = button.textContent || '';
    button.disabled = true;
    button.textContent = text || 'Preparing...';
    return originalText;
  }

  function restoreLedgerButton(button, originalText, fallbackText){
    if (!button) return;
    button.disabled = false;
    button.textContent = originalText || fallbackText || button.textContent;
  }

  function chipClass(outstanding){
    const v = Number(outstanding) || 0;
    if (v < 0) return 'neg';
    if (v > 0) return 'warn';
    return 'ok';
  }

  function outstandingMeaning(outstanding){
    const v = Number(outstanding) || 0;
    if (v < 0) return 'Over-return: returned ' + fmtQty(Math.abs(v)) + ' more than sent';
    if (v > 0) return 'Outstanding: customer still has ' + fmtQty(v) + ' basket';
    return 'Balanced: sent and returned baskets match';
  }

  function dateSortValue(value){
    if (!value) return 0;
    const parsed = Date.parse(String(value).replace(' ', 'T'));
    return Number.isNaN(parsed) ? 0 : parsed;
  }

  function compareText(a, b){
    return String(a || '').localeCompare(String(b || ''), undefined, { sensitivity:'base', numeric:true });
  }

  function compareSummaryByLastActivity(a, b){
    const dateDiff = dateSortValue(b.lastTxnDate || b.last_txn_date) - dateSortValue(a.lastTxnDate || a.last_txn_date);
    if (dateDiff !== 0) return dateDiff;

    const nameDiff = compareText(a.debtorName || a.debtor_name, b.debtorName || b.debtor_name);
    if (nameDiff !== 0) return nameDiff;

    return compareText(a.debtorCode || a.debtor_code, b.debtorCode || b.debtor_code);
  }

  function compareLedgerByLastActivity(a, b){
    const dateDiff = dateSortValue(b.txnDate || b.txn_date || b.date) - dateSortValue(a.txnDate || a.txn_date || a.date);
    if (dateDiff !== 0) return dateDiff;

    return compareText(b.id || b.ledgerId || b.ledger_id, a.id || a.ledgerId || a.ledger_id);
  }

  function pick(row, keys, fallback=''){
    if (!row) return fallback;
    for (const key of keys) {
      if (row[key] !== undefined && row[key] !== null && row[key] !== '') return row[key];
    }
    return fallback;
  }

  function normalizeSummaryRow(row){
    const debtorCode = String(pick(row, ['debtorCode', 'debtor_code', 'customerCode', 'customer_code'], '')).trim();
    const debtorName = String(pick(row, ['debtorName', 'debtor_name', 'customerName', 'customer_name'], '')).trim();
    return {
      ...row,
      debtorCode,
      debtorName,
      sendQty: Number(pick(row, ['sendQty', 'send_qty', 'basketSent', 'basket_sent'], 0)) || 0,
      returnQty: Number(pick(row, ['returnQty', 'return_qty', 'basketReturned', 'basket_returned'], 0)) || 0,
      outstandingQty: Number(pick(row, ['outstandingQty', 'outstanding_qty', 'outstandingBasket', 'outstanding_basket'], 0)) || 0,
      lastTxnDate: String(pick(row, ['lastTxnDate', 'last_txn_date', 'lastActivity', 'last_activity'], '')).trim()
    };
  }

  function normalizeLedgerRow(row){
    return {
      ...row,
      id: pick(row, ['id', 'ledgerId', 'ledger_id'], ''),
      txnDate: String(pick(row, ['txnDate', 'txn_date', 'date'], '')).trim(),
      txnType: String(pick(row, ['txnType', 'txn_type', 'type'], '')).trim(),
      qty: Number(pick(row, ['qty', 'quantity'], 0)) || 0,
      sourceType: String(pick(row, ['sourceType', 'source_type'], '')).trim(),
      sourceRef: String(pick(row, ['sourceRef', 'source_ref', 'refNo', 'ref_no', 'docNo', 'doc_no'], '')).trim(),
      remark: String(pick(row, ['remark', 'note'], '')).trim()
    };
  }

  function dedupeSummaryRowsByDebtor(rows){
    const byCode = new Map();
    rows.forEach(row => {
      const key = debtorKey(row.debtorCode || row.debtorName);
      if (!key) return;
      const existing = byCode.get(key);
      if (!existing || dateSortValue(row.lastTxnDate) > dateSortValue(existing.lastTxnDate)) {
        byCode.set(key, row);
      }
    });
    return Array.from(byCode.values());
  }

  function debtorKey(value){
    return String(value || '').trim().toUpperCase();
  }

  function selectedDebtorCodes(){
    return selectedDebtors.map(d => debtorKey(d.code)).filter(Boolean);
  }

  function syncCustomerFilterInputs(){
    const first = selectedDebtors[0] || { code: '', name: '' };
    $('ac_bs_debtor_code').value = first.code || '';
    $('ac_bs_debtor_name').value = first.name || '';

    const input = $('ac_bs_customer_input');
    if (!selectedDebtors.length) {
      input.value = '';
    } else if (selectedDebtors.length === 1) {
      input.value = selectedDebtors[0].name || selectedDebtors[0].code || '';
    } else {
      input.value = selectedDebtors.length + ' customers selected';
    }
  }

  function renderSelectedCustomers(){
    const mount = $('ac_bs_selected_customers');
    if (!mount) return;

    if (!selectedDebtors.length) {
      mount.innerHTML = '';
      renderManageSelectedCustomers();
      return;
    }

    const visibleDebtors = selectedDebtors.slice(0, 2);
    const hiddenCount = Math.max(0, selectedDebtors.length - visibleDebtors.length);
    const chips = visibleDebtors.map(d => {
      const label = d.name || d.code || 'Customer';
      const meta = SHOW_DEBTOR_CODE && d.code ? ' (' + d.code + ')' : '';
      return `<span class="bs-selected-chip" title="${esc(label + meta)}">
        <span>${esc(label + meta)}</span>
        <button type="button" class="bs-selected-remove" data-remove-selected-customer="${esc(d.code)}" aria-label="Remove ${esc(label)}"></button>
      </span>`;
    });

    if (hiddenCount > 0) {
      chips.push('<span class="bs-selected-more">+' + fmtQty(hiddenCount) + ' more</span>');
    }

    chips.push('<button type="button" class="bs-manage-toggle" data-toggle-selected-customers>Manage</button>');
    mount.innerHTML = chips.join('');
    renderManageSelectedCustomers();
  }

  function renderManageSelectedCustomers(){
    const list = $('ac_bs_manage_list');
    const count = $('ac_bs_manage_count');
    if (count) count.textContent = selectedDebtors.length + (selectedDebtors.length === 1 ? ' selected' : ' selected');
    if (!list) return;

    if (!selectedDebtors.length) {
      list.innerHTML = '<div class="bs-manage-empty">No customer selected.</div>';
      return;
    }

    list.innerHTML = selectedDebtors.map(d => {
      const label = d.name || d.code || 'Customer';
      const meta = SHOW_DEBTOR_CODE && d.code ? ' (' + d.code + ')' : '';
      return `<div class="bs-manage-row">
        <div class="bs-manage-row-name" title="${esc(label + meta)}">${esc(label + meta)}</div>
        <button type="button" class="bs-selected-remove" data-remove-selected-customer="${esc(d.code)}" aria-label="Remove ${esc(label)}"></button>
      </div>`;
    }).join('');
  }

  function openSelectedCustomerManager(){
    const panel = $('ac_bs_manage_selected');
    if (!panel) return;
    renderManageSelectedCustomers();
    panel.classList.add('active');
    panel.setAttribute('aria-hidden', 'false');
  }

  function closeSelectedCustomerManager(){
    const panel = $('ac_bs_manage_selected');
    if (!panel) return;
    panel.classList.remove('active');
    panel.setAttribute('aria-hidden', 'true');
  }

  function toggleSelectedCustomerManager(){
    const panel = $('ac_bs_manage_selected');
    if (!panel) return;
    if (panel.classList.contains('active')) {
      closeSelectedCustomerManager();
    } else {
      openSelectedCustomerManager();
    }
  }

  function updateCustomerSelectionUi(){
    syncCustomerFilterInputs();
    renderSelectedCustomers();
    updateCustomerClearButton();
  }

  function addSelectedCustomer(customer){
    if (!customer) return false;
    const code = String(customer.code || '').trim();
    const name = String(customer.name || '').trim();
    if (!code && !name) return false;

    const key = debtorKey(code || name);
    if (selectedDebtors.some(d => debtorKey(d.code || d.name) === key)) return false;

    selectedDebtors.push({ code, name });
    updateCustomerSelectionUi();
    return true;
  }

  function removeSelectedCustomer(code){
    const key = debtorKey(code);
    const idx = selectedDebtors.findIndex(d => debtorKey(d.code) === key);
    if (idx < 0) return;
    selectedDebtors.splice(idx, 1);
    updateCustomerSelectionUi();
    loadSummary();
  }

  function filterRowsBySelectedCustomers(rows){
    const selectedCodes = selectedDebtorCodes();
    if (!selectedCodes.length) return rows;
    const selectedSet = new Set(selectedCodes);
    return rows.filter(r => selectedSet.has(debtorKey(r.debtorCode || r.debtor_code)));
  }

  async function apiGet(url){
    const res = await fetch(url, {
      method:'GET',
      credentials:'same-origin',
      headers: { 'Accept':'application/json', 'X-WP-Nonce': REST_NONCE },
      cache:'no-store'
    });

    if (!res.ok) {
      let errMsg = 'HTTP ' + res.status;
      try {
        const errData = await res.json();
        errMsg = errData.message || errData.error || errMsg;
      } catch(e) {}
      throw new Error(errMsg);
    }

    return await res.json();
  }

  function buildSummaryUrl(selectedDebtor=null){
    const url = new URL(REST_SUMMARY_URL, window.location.origin);
    const dateFrom = ($('ac_bs_date_from').value || '').trim();
    const dateTo   = ($('ac_bs_date_to').value || '').trim();
    const debtorCode = selectedDebtor
      ? (selectedDebtor.code || '').trim()
      : (selectedDebtors.length === 1 ? (selectedDebtors[0].code || '').trim() : '');
    const debtorName = selectedDebtor
      ? (selectedDebtor.name || '').trim()
      : (selectedDebtors.length === 1 ? (selectedDebtors[0].name || '').trim() : '');

    if (debtorCode) url.searchParams.set('debtorCode', debtorCode);
    if (!debtorCode && debtorName) url.searchParams.set('q', debtorName);
    if (dateFrom) url.searchParams.set('dateFrom', dateFrom);
    if (dateTo) url.searchParams.set('dateTo', dateTo);
    url.searchParams.set('limit', '500');
    return url.toString();
  }

  function buildLedgerUrl(debtorCode){
    const url = new URL(REST_LEDGER_URL, window.location.origin);
    const dateFrom = ($('ac_bs_date_from').value || '').trim();
    const dateTo   = ($('ac_bs_date_to').value || '').trim();

    url.searchParams.set('debtorCode', debtorCode);
    if (dateFrom) url.searchParams.set('dateFrom', dateFrom);
    if (dateTo) url.searchParams.set('dateTo', dateTo);
    url.searchParams.set('limit', '200');
    return url.toString();
  }

  function dateRangeError(){
    const dateFrom = ($('ac_bs_date_from').value || '').trim();
    const dateTo = ($('ac_bs_date_to').value || '').trim();
    if (dateFrom && dateTo && dateFrom > dateTo) {
      return 'Date From cannot be later than Date To.';
    }
    return '';
  }

  function renderTotals(rows){
    const totalsEl = $('ac_bs_totals');
    if (!rows.length) {
      if (totalsEl) totalsEl.style.display = 'none';
      return;
    }

    if (totalsEl) totalsEl.style.display = 'grid';

    const totals = rows.reduce((acc, r) => ({
      send: acc.send + Number(r.sendQty || 0),
      returned: acc.returned + Number(r.returnQty || 0),
      outstanding: acc.outstanding + Number(r.outstandingQty || 0),
      positiveOutstanding: acc.positiveOutstanding + Math.max(0, Number(r.outstandingQty || 0)),
      overReturn: acc.overReturn + Math.abs(Math.min(0, Number(r.outstandingQty || 0)))
    }), { send: 0, returned: 0, outstanding: 0, positiveOutstanding: 0, overReturn: 0 });

    const selectedCustomer = selectedDebtors.length > 0;
    const issueRows = rows.filter(r => Number(r.outstandingQty || 0) < 0);
    if (selectedCustomer) {
      const latestActivity = rows.reduce((latest, r) => {
        const date = r.lastTxnDate || r.last_txn_date || '';
        return dateSortValue(date) > dateSortValue(latest) ? date : latest;
      }, '');
      const customerOutstanding = totals.positiveOutstanding;
      const outstandingSub = totals.overReturn > 0
        ? 'Excludes ' + fmtQty(totals.overReturn) + ' over-return'
        : (customerOutstanding > 0 ? 'Needs follow-up' : 'Balanced');

      const selectedCountCard = selectedDebtors.length > 1
        ? '<div class="bs-total-box"><div class="bs-total-label">Selected Customers</div><div class="bs-total-value">' + fmtQty(selectedDebtors.length) + '</div><div class="bs-total-sub">Combined basket position</div></div>'
        : '';

      totalsEl.innerHTML =
        selectedCountCard +
        '<div class="bs-total-box"><div class="bs-total-label">Basket Sent</div><div class="bs-total-value">' + fmtQty(totals.send) + '</div></div>' +
        '<div class="bs-total-box"><div class="bs-total-label">Basket Returned</div><div class="bs-total-value">' + fmtQty(totals.returned) + '</div></div>' +
        '<div class="bs-total-box"><div class="bs-total-label">Outstanding Basket</div><div class="bs-total-value">' + fmtQty(customerOutstanding) + '</div><div class="bs-total-sub">' + esc(outstandingSub) + '</div></div>' +
        '<div class="bs-total-box"><div class="bs-total-label">Last Activity</div><div class="bs-total-value">' + esc(latestActivity || '-') + '</div></div>' +
        (totals.overReturn > 0
          ? '<div class="bs-total-box issue"><div class="bs-total-label">Data Issues</div><div class="bs-total-value">' + fmtQty(issueRows.length) + '</div><div class="bs-total-sub">Over-return total: ' + fmtQty(totals.overReturn) + '</div></div>'
          : '');
      return;
    }

    const outstandingRows = rows.filter(r => Number(r.outstandingQty || 0) > 0);
    const highest = outstandingRows.slice().sort((a, b) => {
      const qtyDiff = Number(b.outstandingQty || 0) - Number(a.outstandingQty || 0);
      if (qtyDiff !== 0) return qtyDiff;
      return compareSummaryByLastActivity(a, b);
    })[0] || null;
    const highestName = highest ? (highest.debtorName || highest.debtor_name || highest.debtorCode || highest.debtor_code || '-') : 'None';
    const highestQty = highest ? Number(highest.outstandingQty || 0) : 0;

    totalsEl.innerHTML =
      '<div class="bs-total-box"><div class="bs-total-label">Customers Active</div><div class="bs-total-value">' + fmtQty(rows.length) + '</div><div class="bs-total-sub">Has basket movement in range</div></div>' +
      '<div class="bs-total-box"><div class="bs-total-label">Customers With Outstanding</div><div class="bs-total-value">' + fmtQty(outstandingRows.length) + '</div><div class="bs-total-sub">Needs follow-up</div></div>' +
      '<div class="bs-total-box"><div class="bs-total-label">Total Outstanding</div><div class="bs-total-value">' + fmtQty(totals.positiveOutstanding) + '</div><div class="bs-total-sub">Excludes over-return rows</div></div>' +
      '<div class="bs-total-box"><div class="bs-total-label">Highest Outstanding</div><div class="bs-total-value">' + fmtQty(highestQty) + '</div><div class="bs-total-sub">' + esc(highestName) + '</div></div>' +
      (issueRows.length
        ? '<div class="bs-total-box issue"><div class="bs-total-label">Data Issues</div><div class="bs-total-value">' + fmtQty(issueRows.length) + '</div><div class="bs-total-sub">Over-return total: ' + fmtQty(totals.overReturn) + '</div></div>'
        : '');
  }

  function renderSummary(rows){
    const sortedRows = rows.sl�����5lT����������C�
N?�Uice().sort(compareSummaryByLastActivity);
    wrap._lastRows = sortedRows;
    renderTotals(rows);
    const table = $('ac_bs_rows_table');

    if (!sortedRows.length) {
      table.innerHTML = '<tr><td colspan="8" class="bs-empty-cell">No basket records found.</td></tr>';
      return;
    }

    table.innerHTML = sortedRows.map((r, i) => {
      const code = r.debtorCode || '';
      const name = r.debtorName || '';
      const send = Number(r.sendQty || 0);
      const ret  = Number(r.returnQty || 0);
      const out  = Number(r.outstandingQty || 0);
      const lastDate = r.lastTxnDate || '-';
      const cls = chipClass(out);
      const outTitle = outstandingMeaning(out);

      return `<tr data-summary-row="1" data-debtor-code="${esc(code)}">
        <td data-label="No">${i+1}</td>
        <td data-label="Customer Code">${esc(code)}</td>
        <td data-label="Customer Name">${esc(name)}</td>
        <td data-label="Basket Sent">${fmtQty(send)}</td>
        <td data-label="Basket Returned">${fmtQty(ret)}</td>
        <td data-label="Outstanding Basket"><span class="bs-chip ${cls}" title="${esc(outTitle)}" aria-label="${esc(outTitle)}">${fmtQty(out)}</span></td>
        <td data-label="Last Activity">${esc(lastDate)}</td>
        <td data-label="Action"><button class="bs-view-btn" type="button" data-debtor-code="${esc(code)}" data-debtor-name="${esc(name)}">View</button></td>
      </tr>`;
    }).join('');
  }

  function setSummaryLoading(isLoading, message=''){
    const table = $('ac_bs_rows_table');
    const status = $('ac_bs_status');
    if (status) {
      status.style.display = message ? 'block' : 'none';
      status.textContent = message || '';
    }
    if (isLoading && table) {
      table.innerHTML = '<tr><td colspan="8" class="bs-empty-cell">Loading basket summary...</td></tr>';
    }
  }

  function getRowTxnType(row){
    return String(row?.txnType || row?.txn_type || '').toUpperCase();
  }

  function getRowSourceType(row){
    return String(row?.sourceType || row?.source_type || '').toUpperCase();
  }

  function movementLabel(row){
    const txnType = getRowTxnType(row);
    const sourceType = getRowSourceType(row);
    if (txnType === 'RETURN' && (sourceType === 'GOODS_RECEIVE_NOTE' || sourceType === 'GOODS_RECEIVED_NOTE')) {
      return 'GRN Basket Return';
    }
    if (txnType === 'RETURN') return 'Basket Returned';
    return 'Sent Out';
  }

  function sourceTypeLabel(row){
    const sourceType = getRowSourceType(row);
    if (sourceType === 'DELIVERY_ORDER') return 'Delivery Order';
    if (sourceType === 'BASKET_RETURN') return 'Basket Return';
    if (sourceType === 'GOODS_RECEIVE_NOTE' || sourceType === 'GOODS_RECEIVED_NOTE') return 'Goods Receive';
    return row.sourceType || row.source_type || '-';
  }

  function getRowProofImage(row){
    if (!row) return '';
    const possible = [
      row.proofImage, row.proof_image, row.proofImageUrl, row.proof_image_url,
      row.imageUrl, row.image_url, row.proofUrl, row.proof_url,
      row.returnProofImage, row.return_proof_image, row.basketProofImage,
      row.basket_proof_image, row.attachmentUrl, row.attachment_url
    ];

    for (const v of possible) {
      if (v && String(v).trim() !== '') return String(v).trim();
    }

    if (Array.isArray(row.proofImages) && row.proofImages.length) {
      const first = row.proofImages[0];
      return first.imageUrl || first.image_url || first.url || '';
    }

    if (Array.isArray(row.images) && row.images.length) {
      const first = row.images[0];
      return first.imageUrl || first.image_url || first.url || '';
    }

    return '';
  }

  async function fetchBasketProofImage(row){
    const existing = getRowProofImage(row);
    if (existing) return existing;

    const nonce = wrap.dataset.basketProofNonce || '';
    if (!AJAX_URL || !nonce || !row) return '';

    const fd = new FormData();
    fd.append('action', 'ac_bs_get_basket_proof');
    fd.append('nonce', nonce);
    fd.append('ledgerId', row.id || row.ledgerId || row.ledger_id || row.basketLedgerId || row.basket_ledger_id || row.returnLedgerId || row.return_ledger_id || '');
    fd.append('sourceRef', row.sourceRef || row.source_ref || row.refNo || row.ref_no || row.docNo || row.doc_no || '');
    fd.append('debtorCode', row.debtorCode || row.debtor_code || currentLedgerCustomer.code || '');
    fd.append('debtorName', row.debtorName || row.debtor_name || currentLedgerCustomer.name || '');
    fd.append('txnDate', row.txnDate || row.txn_date || row.date || '');

    try {
      const res = await fetch(AJAX_URL, { method:'POST', credentials:'same-origin', body:fd, cache:'no-store' });
      const data = await res.json();
      if (data && data.success && data.data && data.data.imageUrl) return data.data.imageUrl;
    } catch(e) {
      console.error('Basket proof AJAX failed:', e);
    }

    return '';
  }

  function receiptRef(row){
    return row?.sourceRef || row?.source_ref || row?.refNo || row?.ref_no || row?.docNo || row?.doc_no || row?.id || 'basket-return';
  }

  function receiptFileName(receipt){
    const ref = String(receipt?.ref || receipt?.id || 'basket-return').replace(/[^A-Za-z0-9_-]/g, '-');
    return `Basket-Return-${ref}.pdf`;
  }

  function getRowDriverName(row){
    const possible = [
      row?.driverLogin, row?.driver_login,
      row?.assignedDriverLogin, row?.assigned_driver_login,
      row?.driverName, row?.driver_name,
      row?.createdByLogin, row?.created_by_login,
      row?.createdByUserLogin, row?.created_by_user_login,
      row?.userLogin, row?.user_login,
      row?.createdByName, row?.created_by_name,
      row?.userName, row?.user_name,
      row?.vehiclePlate, row?.vehicle_plate
    ];

    for (const value of possible) {
      const s = String(value || '').trim();
      if (s) return s.toUpperCase();
    }

    return '';
  }

  function basketReceiptCardHtml(receipt){
    const proofHtml = receipt.proofImage
      ? `Image Proof<img src="${esc(receipt.proofImage)}" alt="Basket return proof" decoding="sync">`
      : 'No image proof uploaded';

    return `
      <div class="bs-br-card">
        <div class="bs-br-paper">
          <div class="bs-br-head">
            <div><img class="bs-br-logo" src="${esc(RECEIPT_LOGO_URL)}" alt="Company logo"></div>
            <div>
              <div class="bs-br-title">BASKET RETURN</div>
              <div class="bs-br-no">${esc(receipt.ref)}</div>
            </div>
          </div>
          <div class="bs-br-info">
            <div class="bs-br-field"><span>Customer</span><strong>${esc(receipt.customerName || 'Customer')}</strong></div>
            <div class="bs-br-field"><span>Driver</span><strong>${esc(receipt.driverName || '')}</strong></div>
          </div>
          <div class="bs-br-qty">${esc(receipt.qty)} BASKETS</div>
          <div class="bs-br-proof">${proofHtml}</div>
        </div>
      </div>`;
  }

  function basketReceiptHtml(row, proofImage){
    const receipt = {
      ref: receiptRef(row),
      customerName: row?.debtorName || row?.debtor_name || currentLedgerCustomer.name || currentLedgerCustomer.code || 'Customer',
      driverName: getRowDriverName(row),
      qty: fmtQty(row?.qty || 0),
      proofImage: proofImage || ''
    };

    return `
      <div class="bs-br-overlay" id="acBsBasketReceiptOverlay">
        <div class="bs-br-modal" role="dialog" aria-modal="true" aria-label="Basket Return Receipt">
          <div class="bs-br-actions">
            <button type="button" class="bs-br-print" data-print-current-basket-receipt="1">Print / Save PDF</button>
            <button type="button" class="bs-br-share" data-share-current-basket-receipt="1">Share PDF</button>
            <button type="button" class="bs-br-close" onclick="document.getElementById('ac_bs_receipt_mount').innerHTML='';document.body.classList.remove('bs-br-open')">Close</button>
          </div>
          ${basketReceiptCardHtml(receipt)}
        </div>
      </div>`;
  }

  function basketReceiptLoadingHtml(){
    return `
      <div class="bs-br-overlay" id="acBsBasketReceiptOverlay">
        <div class="bs-br-modal" role="dialog" aria-modal="true" aria-label="Loading Basket Return Receipt">
          <div class="bs-br-actions">
            <button type="button" class="bs-br-close" onclick="document.getElementById('ac_bs_receipt_mount').innerHTML='';document.body.classList.remove('bs-br-open')">Close</button>
          </div>
          <div class="bs-br-card"><div class="bs-br-paper"><div class="bs-br-loading">Loading basket receipt...</div></div></div>
        </div>
      </div>`;
  }

  async function openBasketReceiptByIndex(idx){
    const row = currentLedgerRows[Number(idx)];
    if (!row || getRowTxnType(row) !== 'RETURN') {
      showError('Receipt not available', 'Basket receipt is only available for returned basket movement.');
      return;
    }

    const mount = $('ac_bs_receipt_mount');
    if (!mount) return;

    document.body.classList.add('bs-br-open');
    mount.innerHTML = basketReceiptLoadingHtml();
    const proofImage = await fetchBasketProofImage(row);
    currentReceiptForShare = {
      id: row.id || row.ledgerId || row.ledger_id || idx,
      ref: receiptRef(row),
      customerName: row?.debtorName || row?.debtor_name || currentLedgerCustomer.name || currentLedgerCustomer.code || 'Customer',
      driverName: getRowDriverName(row),
      qty: fmtQty(row?.qty || 0),
      proofUrl: proofImage || ''
    };
    mount.innerHTML = basketReceiptHtml(row, proofImage);
  }

  function loadJsPdf(){
    if (window.jspdf && window.jspdf.jsPDF) return Promise.resolve(window.jspdf.jsPDF);
    if (brJsPdfPromise) return brJsPdfPromise;

    brJsPdfPromise = new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
      script.onload = () => window.jspdf && window.jspdf.jsPDF ? resolve(window.jspdf.jsPDF) : reject(new Error('PDF library did not load.'));
      script.onerror = () => reject(new Error('PDF library could not be loaded.'));
      document.head.appendChild(script);
    });

    return brJsPdfPromise;
  }

  function loadCanvasImage(url){
    if (!url) return Promise.resolve(null);
    return new Promise(resolve => {
      const img = new Image();
      img.crossOrigin = 'anonymous';
      img.onload = () => resolve(img);
      img.onerror = () => resolve(null);
      img.src = url;
    });
  }

  function drawCanvasText(ctx, text, x, y, size=18, color='#111', weight='400', align='left'){
    ctx.fillStyle = color;
    ctx.font = `${weight} ${size}px Arial, sans-serif`;
    ctx.textAlign = align;
    ctx.textBaseline = 'top';
    ctx.fillText(String(text || ''), x, y);
  }

  function drawWrappedCanvasText(ctx, text, x, y, maxWidth, lineHeight, size=18, color='#111', weight='400'){
    const words = String(text || '').split(/\s+/).filter(Boolean);
    let line = '';

    words.forEach(word => {
      const testLine = line ? `${line} ${word}` : word;
      if (ctx.measureText(testLine).width > maxWidth && line) {
        drawCanvasText(ctx, line, x, y, size, color, weight);
        line = word;
        y += lineHeight;
      } else {
        line = testLine;
      }
    });

    if (line) drawCanvasText(ctx, line, x, y, size, color, weight);
    return y + lineHeight;
  }

  function drawCanvasImageContained(ctx, image, x, y, maxW, maxH){
    if (!image) return;
    const ratio = Math.min(maxW / image.width, maxH / image.height);
    const imgW = image.width * ratio;
    const imgH = image.height * ratio;
    ctx.drawImage(image, x + (maxW - imgW) / 2, y + (maxH - imgH) / 2, imgW, imgH);
  }

  function makeReceiptCanvas(receipt, proofImage=null, logoImage=null){
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = 1240;
    canvas.height = 1754;

    ctx.fillStyle = '#ffffff';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.strokeStyle = '#111111';
    ctx.lineWidth = 2;
    ctx.strokeRect(82, 70, 1076, 1614);

    if (logoImage) {
      drawCanvasImageContained(ctx, logoImage, 138, 112, 360, 128);
    } else {
      drawCanvasText(ctx, 'BASKET RETURN', 140, 130, 30, '#111', '900');
    }

    drawCanvasText(ctx, 'BASKET RETURN', 1100, 130, 24, '#111', '900', 'right');
    drawCanvasText(ctx, receipt.ref || ('BR-' + receipt.id), 1100, 172, 22, '#111', '900', 'right');

    ctx.strokeStyle = '#111111';
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.moveTo(140, 278);
    ctx.lineTo(1100, 278);
    ctx.stroke();

    let y = 350;
    drawCanvasText(ctx, 'Customer', 160, y, 22, '#555', '700');
    drawWrappedCanvasText(ctx, receipt.customerName || 'Customer', 160, y + 34, 410, 30, 24, '#111', '800');
    drawCanvasText(ctx, 'Driver', 660, y, 22, '#555', '700');
    drawWrappedCanvasText(ctx, receipt.driverName || '', 660, y + 34, 410, 30, 24, '#111', '800');

    y += 105;
    ctx.strokeStyle = '#e5e7eb';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(160, y);
    ctx.lineTo(1080, y);
    ctx.stroke();

    drawCanvasText(ctx, `${receipt.qty} BASKETS`, 620, y + 54, 74, '#111', '900', 'center');
    y += 210;

    ctx.strokeStyle = '#cbd5e1';
    ctx.setLineDash([12, 10]);
    ctx.strokeRect(160, y, 920, 560);
    ctx.setLineDash([]);

    if (proofImage) {
      drawCanvasText(ctx, 'Image Proof', 620, y + 28, 22, '#334155', '800', 'center');
      drawCanvasImageContained(ctx, proofImage, 210, y + 82, 820, 420);
    } else {
      drawCanvasText(ctx, 'No image proof uploaded', 620, y + 255, 28, '#64748b', '800', 'center');
    }

    return canvas;
  }

  function buildBasketReceiptPdf(receipt){
    return Promise.all([loadJsPdf(), loadCanvasImage(receipt.proofUrl), loadCanvasImage(RECEIPT_LOGO_URL)])
      .then(([jsPDF, proofImage, logoImage]) => {
        const warnings = [];
        if (receipt.proofUrl && !proofImage) warnings.push('Proof image could not be included in the PDF.');
        if (RECEIPT_LOGO_URL && !logoImage) warnings.push('Logo could not be included in the PDF.');
        if (warnings.length && window.Swal && typeof Swal.fire === 'function') {
          Swal.fire({ icon:'warning', title:'PDF image warning', text:warnings.join(' ') });
        }
        const pdf = new jsPDF({orientation:'portrait', unit:'mm', format:'a5'});
        const canvas = makeReceiptCanvas(receipt, proofImage, logoImage);
        pdf.addImage(canvas.toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, 148, 210);
        return pdf.output('blob');
      });
  }

  function downloadBlob(blob, fileName){
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  }

  function preloadReceiptImage(url){
    if (!url) return Promise.resolve();

    return new Promise(resolve => {
      const img = new Image();
      const done = () => resolve();
      img.onload = done;
      img.onerror = done;
      img.src = url;

      if (img.complete) resolve();
      setTimeout(done, 2500);
    });
  }

  function waitForElementImages(el){
    const images = Array.from(el.querySelectorAll('img'));
    if (!images.length) return Promise.resolve();

    return Promise.all(images.map(img => new Promise(resolve => {
      if (img.complete && img.naturalWidth > 0) {
        resolve();
        return;
      }

      const done = () => resolve();
      img.addEventListener('load', done, { once:true });
      img.addEventListener('error', done, { once:true });
      setTimeout(done, 2500);
    }))).then(() => undefined);
  }

  function printCurrentBasketReceipt(){
    const receipt = currentReceiptForShare;��C��Y�(U������������
N?�V
    if (!receipt) {
      showError('Receipt not found', 'Please reopen the basket receipt and try again.');
      return;
    }

    buildBasketReceiptPdf(receipt)
      .then(blob => {
        const fileName = receiptFileName(receipt);
        const url = URL.createObjectURL(blob);
        const opened = window.open(url, '_blank', 'noopener');

        if (!opened) {
          downloadBlob(blob, fileName);
          if (window.Swal && typeof Swal.fire === 'function') {
            Swal.fire({ icon:'info', title:'PDF downloaded', text:'Open the downloaded PDF to print or share.' });
          }
        }

        setTimeout(() => URL.revokeObjectURL(url), 60000);
      })
      .catch(() => {
        showError('Unable to prepare PDF', 'Please try again.');
      });
  }

  function shareCurrentBasketReceipt(button){
    const receipt = currentReceiptForShare;
    if (!receipt) {
      showError('Receipt not found', 'Please reopen the basket receipt and try again.');
      return;
    }

    const originalText = button ? button.textContent : '';
    if (button) {
      button.disabled = true;
      button.textContent = 'Preparing...';
    }

    buildBasketReceiptPdf(receipt)
      .then(blob => {
        const fileName = receiptFileName(receipt);
        const file = new File([blob], fileName, {type:'application/pdf'});

        if (!navigator.share || !navigator.canShare || !navigator.canShare({files:[file]})) {
          downloadBlob(blob, fileName);
          if (window.Swal && typeof Swal.fire === 'function') {
            Swal.fire({ icon:'info', title:'PDF downloaded', text:'Attach the downloaded PDF in WhatsApp.' });
          }
          return null;
        }

        return navigator.share({
          title: fileName.replace(/\.pdf$/i, ''),
          text: 'Basket Return PDF',
          files: [file]
        });
      })
      .catch(error => {
        if (error && error.name === 'AbortError') return;
        showError('Unable to prepare PDF', 'Please print or save PDF, then share it in WhatsApp.');
      })
      .finally(() => {
        if (button) {
          button.disabled = false;
          button.textContent = originalText || 'Share PDF';
        }
      });
  }

  function markSelectedDebtorRow(debtorCode){
    wrap.querySelectorAll('[data-summary-row="1"]').forEach(row => {
      row.classList.toggle('bs-row-selected', (row.dataset.debtorCode || '') === debtorCode);
    });
  }

  async function loadSummary(){
    if (!REST_SUMMARY_URL) {
      showError('Missing configuration', 'Summary endpoint missing.');
      return;
    }

    const rangeError = dateRangeError();
    if (rangeError) {
      showError('Invalid date range', rangeError);
      return;
    }

    const requestSeq = ++loadSummarySeq;
    setSummaryLoading(true, 'Loading basket summary...');

    try {
      let rows = [];
      if (selectedDebtors.length > 1) {
        const selectedResults = await Promise.all(selectedDebtors.map(debtor => apiGet(buildSummaryUrl(debtor))));
        rows = selectedResults.flatMap(data => Array.isArray(data && data.rows) ? data.rows : []);
      } else {
        const data = await apiGet(buildSummaryUrl());
        rows = Array.isArray(data && data.rows) ? data.rows : [];
      }

      if (requestSeq !== loadSummarySeq) return;

      rows = dedupeSummaryRowsByDebtor(filterRowsBySelectedCustomers(rows.map(normalizeSummaryRow)));
      Object.keys(ledgerCache).forEach(k => delete ledgerCache[k]);
      renderSummary(rows);
      setSummaryLoading(false);

      const receiptMount = $('ac_bs_receipt_mount');
      if (receiptMount) receiptMount.innerHTML = '';

      currentLedgerRows = [];
      currentLedgerCustomer = { code: '', name: '' };
      $('ac_bs_ledger_title').textContent = 'Basket Movement History';
      $('ac_bs_ledger_table').innerHTML = '<tr><td colspan="8" class="bs-empty-cell">Select a customer to view basket movement history.</td></tr>';
    } catch(err) {
      if (requestSeq !== loadSummarySeq) return;
      showError('Failed to load summary', err && err.message ? err.message : 'Please try again.');
      setSummaryLoading(false);
      $('ac_bs_rows_table').innerHTML = '<tr><td colspan="8" class="bs-empty-cell">Failed to load summary.</td></tr>';
    }
  }

  function showLedgerLoading(debtorCode, debtorName){
    currentLedgerRows = [];
    currentLedgerCustomer = { code: debtorCode || '', name: debtorName || '' };
    $('ac_bs_ledger_title').textContent = 'Basket Movement History: ' + (debtorName || debtorCode);
    $('ac_bs_ledger_table').innerHTML = `
      <tr>
        <td colspan="9" class="bs-ledger-loading-cell">
          <div class="bs-ledger-loading">
            <div class="bs-ledger-spinner"></div>
            <div>Loading basket movement...</div>
            <div style="font-size:12px;color:#64748b;">${esc(debtorName || debtorCode || 'Customer')}</div>
          </div>
        </td>
      </tr>`;
    const toolbar = $('ac_bs_ledger_toolbar');
    if (toolbar) toolbar.style.display = 'none';
    resetLedgerFilters();
  }

  function resetLedgerFilters(){
    $('ac_bs_ledger_date_from').value = '';
    $('ac_bs_ledger_date_to').value = '';
    const select = $('ac_bs_ledger_movement_filter');
    if (select) {
      select.innerHTML = '<option value="">All Movement</option>';
    }
    const selectAll = $('ac_bs_ledger_select_all');
    if (selectAll) selectAll.classList.remove('toggled');
  }

  function renderLedger(debtorCode, debtorName, rows){
    currentLedgerRows = Array.isArray(rows) ? rows.map(normalizeLedgerRow).sort(compareLedgerByLastActivity) : [];
    currentLedgerCustomer = { code: debtorCode || '', name: debtorName || '' };
    $('ac_bs_ledger_title').textContent = 'Basket Movement History: ' + (debtorName || debtorCode);
    const toolbar = $('ac_bs_ledger_toolbar');
    if (toolbar) toolbar.style.display = currentLedgerRows.length ? 'flex' : 'none';
    populateLedgerMovementFilter(currentLedgerRows);

    const table = $('ac_bs_ledger_table');
    if (!currentLedgerRows.length) {
      table.innerHTML = '<tr><td colspan="9" class="bs-empty-cell">No basket movement found for this customer.</td></tr>';
      return;
    }

    applyLedgerRender(currentLedgerRows);
  }

  function populateLedgerMovementFilter(rows){
    const select = $('ac_bs_ledger_movement_filter');
    if (!select) return;
    const labels = new Set();
    rows.forEach(r => labels.add(movementLabel(r)));
    const current = select.value;
    select.innerHTML = '<option value="">All Movement</option>' + Array.from(labels).sort().map(l => `<option value="${esc(l)}">${esc(l)}</option>`).join('');
    if (current && Array.from(labels).includes(current)) select.value = current;
  }

  function applyLedgerRender(rows){
    const dateFrom = ($('ac_bs_ledger_date_from').value || '').trim();
    const dateTo = ($('ac_bs_ledger_date_to').value || '').trim();
    const movementFilter = ($('ac_bs_ledger_movement_filter').value || '').trim();

    let filtered = rows.filter(r => {
      const okDate = matchDateRange(r.txnDate || r.txn_date || r.date, dateFrom, dateTo);
      const okMovement = !movementFilter || movementLabel(r) === movementFilter;
      return okDate && okMovement;
    });

    const table = $('ac_bs_ledger_table');
    if (!filtered.length) {
      table.innerHTML = '<tr><td colspan="9" class="bs-empty-cell">No basket movement matches the selected filters.</td></tr>';
      return;
    }

    table.innerHTML = filtered.map((r, i) => {
      const txnType = getRowTxnType(r);
      const sourceType = getRowSourceType(r);
      const label = movementLabel(r);
      const typeClass = txnType === 'RETURN' ? 'bs-type-return' : 'bs-type-send';
      const idx = currentLedgerRows.indexOf(r);
      const ledgerIdx = idx >= 0 ? idx : i;
      const canViewReceipt = txnType === 'RETURN' && sourceType === 'BASKET_RETURN';
      const receiptBtn = canViewReceipt
        ? `<button class="bs-view-btn bs-receipt-btn" type="button" data-basket-receipt-idx="${ledgerIdx}">View Basket Receipt</button>`
        : '<span style="color:#94a3b8;">-</span>';

      return `<tr data-ledger-idx="${ledgerIdx}">
        <td data-label="Select"><input type="checkbox" class="bs-ledger-row-check" data-ledger-idx="${ledgerIdx}" aria-label="Select row ${i+1}"></td>
        <td data-label="No">${i+1}</td>
        <td data-label="Date">${esc(r.txnDate || r.txn_date || '-')}</td>
        <td data-label="Movement"><span class="${typeClass}">${esc(label)}</span></td>
        <td data-label="Qty">${fmtQty(r.qty || 0)}</td>
        <td data-label="From">${esc(sourceTypeLabel(r))}</td>
        <td data-label="Document No.">${esc(r.sourceRef || r.source_ref || '-')}</td>
        <td data-label="Note">${esc(r.remark || r.note || '-')}</td>
        <td data-label="Receipt">${receiptBtn}</td>
      </tr>`;
    }).join('');

    const selectAllBtn = $('ac_bs_ledger_select_all');
    if (selectAllBtn) {
      selectAllBtn.classList.remove('toggled');
      selectAllBtn.textContent = 'Select All';
    }
  }

  function matchDateRange(value, from, to){
    if (!value) return true;
    const d = String(value).split(' ')[0];
    if (from && d < from) return false;
    if (to && d > to) return false;
    return true;
  }

  function getSelectedLedgerRows(){
    const selected = [];
    wrap.querySelectorAll('.bs-ledger-row-check:checked').forEach(cb => {
      const idx = parseInt(cb.dataset.ledgerIdx, 10);
      const row = currentLedgerRows[idx];
      if (row) selected.push(row);
    });
    return selected;
  }

  function toggleSelectAllLedgerRows(){
    const checks = Array.from(wrap.querySelectorAll('.bs-ledger-row-check'));
    const anyUnchecked = checks.some(cb => !cb.checked);
    checks.forEach(cb => cb.checked = anyUnchecked);
    const btn = $('ac_bs_ledger_select_all');
    if (btn) btn.classList.toggle('toggled', anyUnchecked);
    if (btn) btn.textContent = anyUnchecked ? 'Deselect All' : 'Select All';
  }

  function formatLedgerPrintDateRange(){
    const from = ($('ac_bs_ledger_date_from').value || '').trim();
    const to = ($('ac_bs_ledger_date_to').value || '').trim();
    if (!from && !to) return '';
    if (from === to) return from;
    if (from && !to) return 'From ' + from;
    if (!from && to) return 'Until ' + to;
    return from + ' - ' + to;
  }

  function ledgerRowDateOnly(row){
    const raw = String(row?.txnDate || row?.txn_date || row?.date || '').trim();
    if (!raw) return '';
    return raw.split(' ')[0];
  }

  function formatSelectedLedgerDateRange(rows){
    const dates = Array.from(new Set((rows || [])
      .map(ledgerRowDateOnly)
      .filter(Boolean)))
      .sort();

    if (!dates.length) return '-';
    if (dates.length === 1) return dates[0];
    return dates[0] + ' - ' + dates[dates.length - 1];
  }

  function getOverallOutstandingBalance(){
    const customerCode = debtorKey(currentLedgerCustomer.code || '');
    const customerName = debtorKey(currentLedgerCustomer.name || '');
    const summaryRows = Array.isArray(wrap._lastRows) ? wrap._lastRows : [];

    const summaryRow = summaryRows.find(row => {
      const rowCode = debtorKey(row.debtorCode || row.debtor_code || '');
      const rowName = debtorKey(row.debtorName || row.debtor_name || '');
      return (customerCode && rowCode === customerCode) || (!customerCode && customerName && rowName === customerName);
    });

    if (summaryRow) {
      const summaryOutstanding = Number(summaryRow.outstandingQty ?? summaryRow.outstanding_qty ?? summaryRow.outstandingBasket ?? summaryRow.outstanding_basket);
      if (Number.isFinite(summaryOutstanding)) return summaryOutstanding;
    }

    return (Array.isArray(currentLedgerRows) ? currentLedgerRows : []).reduce((sum, row) => {
      const qty = Number(row.qty || 0) || 0;
      return sum + (getRowTxnType(row) === 'RETURN' ? -qty : qty);
    }, 0);
  }

  async function buildLedgerStatementPdfBlob(){
    const selected = getSelectedLedgerRows();

    if (!selected.length) {
      showError('No rows selected', 'Please tick at least one row to print or share.');
      return null;
    }

    const customerName = currentLedgerCustomer.name || currentLedgerCustomer.code || 'Customer';
    const customerCode = currentLedgerCustomer.code || '';
    const dateRange = formatSelectedLedgerDateRange(selected);
    const selectedRows = selected.slice().sort(compareLedgerByLastActivity);

    const sendQty = selectedRows
      .filter(r => getRowTxnType(r) !== 'RETURN')
      .reduce((sum, r) => sum + (Number(r.qty) || 0), 0);

    const returnQty = selectedRows
      .filter(r => getRowTxnType(r) === 'RETURN')
      .reduce((sum, r) => sum + (Number(r.qty) || 0), 0);

    const overallOutstandingQty = getOverallOutstandingBalance();
    const generatedAt = new Date().toLocaleString('en-MY', {
      year:'numeric', month:'2-digit', day:'2-digit',
      hour:'2-digit', minute:'2-digit'
    });

    const jsPDF = await loadJsPdf();
    const pdf = new jsPDF({ orientation:'portrait', unit:'mm', format:'a4' });

    if (pdf.setProperties) {
      pdf.setProperties({
        title: 'Basket Movement Statement - ' + customerName,
        subject: 'Basket Movement Statement',
        author: 'Basket Summary'
      });
    }

    const pageW = pdf.internal.pageSize.getWidth();
    const pageH = pdf.internal.pageSize.getHeight();
    const margin = 12;
    const contentW = pageW - margin * 2;
    let y = 0;

    function cleanText(value){
      const text = String(value === null || value === undefined || value === '' ? '-' : value);
      return text.replace(/\s+/g, ' ').trim();
    }

    function fileSafe(value){
      return String(value || 'customer')
        .replace(/[^A-Za-z0-9_-]+/g, '-')
        .replace(/-+/g, '-')
        .replace(/^-|-$/g, '') || 'customer';
    }

    function drawHeader(){
      pdf.setFillColor(11, 74, 45);
      pdf.rect(0, 0, pageW, 34, 'F');

      pdf.setTextColor(255, 255, 255);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(18);
      pdf.text('Basket Movement Statement', margin, 16);

      pdf.setFontSize(9);
      pdf.setFont(undefined, 'normal');
      pdf.text('Generated: ' + generatedAt, margin, 24);

      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(10);
      pdf.text('Excellent Vege Basket Record', pageW - margin, 16, { align:'right' });

      pdf.setFont(undefined, 'normal');
      pdf.text('Selected movements only', pageW - margin, 24, { align:'right' });

      y = 44;

      pdf.setTextColor(15, 23, 42);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(11);
      pdf.text('Customer', margin, y);
      pdf.text('Date Range', pageW - margin - 62, y);

      pdf.setFont(undefined, 'normal');
      pdf.setFontSize(10);

      const customerLines = pdf.splitTextToSize(cleanText(customerName), 88);
      pdf.text(customerLines, margin, y + 6);

      if (customerCode) {
        pdf.setTextColor(71, 85, 105);
        pdf.text('Code: ' + customerCode, margin, y + 6 + customerLines.length * 4.5);
        pdf.setTextColor(15, 23, 42);
      }

      pdf.text(cleanText(dateRange), pageW - margin - 62, y + 6);
      y += Math.max(24, 8 + customerLines.length * 4.5 + (customerCode ? 5 : 0));
    }

    function drawSummaryBox(x, label, value, width){
      pdf.setFillColor(248, 250, 252);
      pdf.setDrawColor(226, 232, 240);
      pdf.roundedRect(x, y, width, 18, 2, 2, 'FD');

      pdf.setTextColor(100, 116, 139);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(7.5);
      pdf.text(label, x + 4, y + 6);

      pdf.setTextColor(15, 23, 42);
      pdf.setFontSize(13);
      pdf.text(String(value), x + 4, y + 14);
    }

    function drawSummary(){
      const gap = 4;
      const boxW = (contentW - gap * 3) / 4;

      drawSummaryBox(margin, 'TOTAL SENT', fmtQty(sendQty), boxW);
      dra����v��sV�����������B
N?�WwSummaryBox(margin + (boxW + gap), 'TOTAL RETURNED', fmtQty(returnQty), boxW);
      drawSummaryBox(margin + (boxW + gap) * 2, 'OUTSTANDING BALANCE', fmtQty(overallOutstandingQty), boxW);
      drawSummaryBox(margin + (boxW + gap) * 3, 'MOVEMENT ROWS', fmtQty(selectedRows.length), boxW);
      y += 26;
    }

    const tableRight = pageW - margin - 10;
    const tableW = tableRight - margin;
    const columns = [
      { title:'Date', x:margin, w:30, key:'date' },
      { title:'Document No.', x:margin + 32, w:68, key:'doc' },
      { title:'Movement', x:margin + 104, w:46, key:'movement' },
      { title:'Qty', x:tableRight - 24, w:20, key:'qty', align:'right' }
    ];

    function drawTableHeader(){
      pdf.setFillColor(22, 101, 52);
      pdf.rect(margin, y, tableW, 8, 'F');

      pdf.setTextColor(255, 255, 255);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(8.5);

      columns.forEach(col => {
        const tx = col.align === 'right' ? col.x + col.w : col.x + 2;
        pdf.text(col.title, tx, y + 5.3, col.align === 'right' ? { align:'right' } : undefined);
      });

      y += 8;
    }

    function addNewPageWithTableHeader(){
      pdf.addPage();
      y = 18;
      drawTableHeader();
    }

    function drawRow(row, index){
      const values = {
        date: cleanText(row.txnDate || row.txn_date || '-'),
        doc: cleanText(row.sourceRef || row.source_ref || '-'),
        movement: cleanText(movementLabel(row)),
        qty: String(fmtQty(row.qty || 0))
      };

      const lineHeight = 4.3;
      const cellLines = columns.map(col => {
        const maxW = col.align === 'right' ? col.w : col.w - 2;
        return pdf.splitTextToSize(values[col.key], maxW);
      });
      const rowHeight = Math.max(8, Math.max(...cellLines.map(lines => lines.length)) * lineHeight + 4);

      if (y + rowHeight > pageH - 18) addNewPageWithTableHeader();

      if (index % 2 === 0) {
        pdf.setFillColor(249, 250, 251);
        pdf.rect(margin, y, tableW, rowHeight, 'F');
      }

      pdf.setDrawColor(226, 232, 240);
      pdf.line(margin, y + rowHeight, margin + tableW, y + rowHeight);
      pdf.setTextColor(15, 23, 42);
      pdf.setFont(undefined, 'normal');
      pdf.setFontSize(8.2);

      columns.forEach((col, colIndex) => {
        const lines = cellLines[colIndex];
        if (col.align === 'right') {
          pdf.text(lines, col.x + col.w, y + 5, { align:'right' });
        } else {
          pdf.text(lines, col.x + 2, y + 5);
        }
      });

      y += rowHeight;
    }

    function drawTotals(){
      if (y + 22 > pageH - 18) {
        pdf.addPage();
        y = 18;
      }

      y += 7;
      pdf.setDrawColor(226, 232, 240);
      pdf.line(margin, y, margin + contentW, y);
      y += 7;

      pdf.setTextColor(15, 23, 42);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(10);
      pdf.text('Statement Totals', margin, y);
      y += 6;

      pdf.setFontSize(9);
      pdf.text('Sent: ' + fmtQty(sendQty), margin, y);
      pdf.text('Returned: ' + fmtQty(returnQty), margin + 42, y);
      pdf.text('Total Outstanding Balance: ' + fmtQty(overallOutstandingQty), margin + 96, y);
      y += 10;
    }

    function drawFooter(){
      const totalPages = pdf.internal.getNumberOfPages();
      for (let page = 1; page <= totalPages; page++) {
        pdf.setPage(page);
        pdf.setDrawColor(226, 232, 240);
        pdf.line(margin, pageH - 12, pageW - margin, pageH - 12);
        pdf.setTextColor(100, 116, 139);
        pdf.setFont(undefined, 'normal');
        pdf.setFontSize(8);
        pdf.text('Basket Movement Statement', margin, pageH - 7);
        pdf.text('Page ' + page + ' of ' + totalPages, pageW - margin, pageH - 7, { align:'right' });
      }
    }

    drawHeader();
    drawSummary();
    drawTableHeader();
    selectedRows.forEach(drawRow);
    drawTotals();
    drawFooter();

    return {
      blob: pdf.output('blob'),
      fileName: 'basket-movement-' + fileSafe(customerCode || customerName) + '.pdf'
    };
  }

  async function printLedgerPdf(button){
    const originalText = setLedgerButtonBusy(button, 'Preparing...');

    try {
      const result = await buildLedgerStatementPdfBlob();
      if (!result) return;
      downloadBlob(result.blob, result.fileName);
    } catch(err) {
      showError('PDF failed', err && err.message ? err.message : 'Could not generate PDF.');
    } finally {
      restoreLedgerButton(button, originalText, 'Print PDF');
    }
  }

  async function shareLedgerPdf(button){
    const originalText = setLedgerButtonBusy(button, 'Preparing...');

    try {
      if (!navigator.share) {
        showInfo('Share not supported', 'This browser cannot open the native share menu. The PDF will be downloaded instead.');
        const fallbackResult = await buildLedgerStatementPdfBlob();
        if (fallbackResult) downloadBlob(fallbackResult.blob, fallbackResult.fileName);
        return;
      }

      const result = await buildLedgerStatementPdfBlob();
      if (!result) return;

      const file = new File([result.blob], result.fileName, { type:'application/pdf' });

      if (!navigator.canShare || !navigator.canShare({ files:[file] })) {
        downloadBlob(result.blob, result.fileName);
        showInfo('PDF downloaded', 'This browser cannot share PDF files directly. Attach the downloaded PDF in WhatsApp.');
        return;
      }

      await navigator.share({
        title: result.fileName.replace(/\.pdf$/i, ''),
        text: 'Basket Movement Statement PDF',
        files: [file]
      });
    } catch(error) {
      if (error && error.name === 'AbortError') return;
      showError('Unable to share PDF', 'Please print or save PDF, then share it in WhatsApp.');
    } finally {
      restoreLedgerButton(button, originalText, 'Share PDF');
    }
  }

  async function loadLedger(debtorCode, debtorName){
    if (!REST_LEDGER_URL) {
      showError('Missing configuration', 'Ledger endpoint missing.');
      return;
    }

    const rangeError = dateRangeError();
    if (rangeError) {
      showError('Invalid date range', rangeError);
      return;
    }

    const cacheKey = debtorCode + '|' + ($('ac_bs_date_from').value || '') + '|' + ($('ac_bs_date_to').value || '');
    showLedgerLoading(debtorCode, debtorName);
    markSelectedDebtorRow(debtorCode);
    openLedgerModal();

    if (ledgerCache[cacheKey]) {
      renderLedger(debtorCode, debtorName, ledgerCache[cacheKey]);
      return;
    }

    try {
      const data = await apiGet(buildLedgerUrl(debtorCode));
      const rows = Array.isArray(data && data.rows) ? data.rows.map(normalizeLedgerRow) : [];
      ledgerCache[cacheKey] = rows;
      renderLedger(debtorCode, debtorName, rows);
    } catch(err) {
      showError('Failed to load movement history', err && err.message ? err.message : 'Please try again.');
      $('ac_bs_ledger_table').innerHTML = '<tr><td colspan="9" class="bs-empty-cell">Failed to load movement history.</td></tr>';
    }
  }

  function updateCustomerClearButton(){
    const hasValue = selectedDebtors.length > 0;
    $('ac_bs_customer_clear')?.classList.toggle('show', hasValue);
  }

  function clearCustomerSelection(){
    selectedDebtors.splice(0, selectedDebtors.length);
    updateCustomerSelectionUi();
    closeSelectedCustomerManager();
    loadSummary();
  }

  async function searchCustomersLive(q){
    if (!AJAX_URL || !DEBTOR_NONCE) return [];

    const url = AJAX_URL + '?action=ac_cs_debtor_search&nonce=' + encodeURIComponent(DEBTOR_NONCE) + '&q=' + encodeURIComponent(q);
    const res = await fetch(url, { credentials:'same-origin', cache:'no-store' });
    const data = await res.json();
    if (!data.success) throw new Error((data.data && data.data.error) ? data.data.error : 'Search failed');

    const items = data.data?.items || [];
    return items.map(it => {
      const name = it.name || it.debtorName || '';
      const code = it.code || it.debtorCode || '';
      return { label: name || code, meta: (SHOW_DEBTOR_CODE && code) ? code : '', raw: { name, code } };
    });
  }

  function renderPickerNote(msg){
    $('ac_bs_picker_results').innerHTML = '<div class="bs-picker-note">' + esc(msg) + '</div>';
  }

  function renderPickerItems(items){
    if (!items.length) {
      renderPickerNote('No result found');
      return;
    }

    $('ac_bs_picker_results').innerHTML = items.map((it, idx) => `<button type="button" class="bs-picker-item" data-picker-idx="${idx}">
      <span class="bs-picker-item-main">${esc(it.label || '')}</span>
      ${it.meta ? '<span class="bs-picker-item-sub">' + esc(it.meta) + '</span>' : ''}
    </button>`).join('');
  }

  function runPickerSearch(q){
    const query = (q || '').trim();
    clearTimeout(pickerTimer);

    if (query.length < 1) {
      pickerState.items = [];
      renderPickerNote('Type to search');
      return;
    }

    pickerTimer = setTimeout(async function(){
      renderPickerNote('Searching...');
      try {
        pickerState.items = await pickerState.fetchFn(query) || [];
        renderPickerItems(pickerState.items);
      } catch(e) {
        pickerState.items = [];
        renderPickerNote('Failed to load');
      }
    }, 220);
  }

  function openPicker(opts){
    pickerState.items = [];
    pickerState.fetchFn = opts.fetchFn;
    pickerState.onPick = opts.onPick;
    $('ac_bs_picker_title').textContent = opts.title || 'Search';
    $('ac_bs_picker_search').placeholder = opts.placeholder || 'Type to search...';
    $('ac_bs_picker_search').value = '';
    $('ac_bs_picker_modal').classList.add('active');
    renderPickerNote('Type to search');
    setTimeout(() => $('ac_bs_picker_search').focus(), 80);
  }

  function closePicker(){
    $('ac_bs_picker_modal').classList.remove('active');
    $('ac_bs_picker_search').value = '';
    $('ac_bs_picker_results').innerHTML = '';
    pickerState.items = [];
    pickerState.fetchFn = null;
    pickerState.onPick = null;
  }

  function openCustomerPicker(){
    openPicker({
      title:'Select Customer',
      placeholder:'Search customer...',
      fetchFn: searchCustomersLive,
      onPick: function(picked){
        if (!picked) return;
        addSelectedCustomer(picked);
        closePicker();
        loadSummary();
      }
    });
  }

  function setDefaultDateRange(){
    const dateFromEl = $('ac_bs_date_from');
    const dateToEl = $('ac_bs_date_to');
    if (!dateFromEl || !dateToEl) return;

    const today = new Date();
    const oneMonthAgo = new Date(today);
    oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);

    function toYmd(d){
      return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
    }

    if (!dateToEl.value) dateToEl.value = toYmd(today);
    if (!dateFromEl.value) dateFromEl.value = toYmd(oneMonthAgo);
  }

  function openLedgerModal(){
    const modal = $('ac_bs_ledger_modal');
    if (!modal) return;
    modal.classList.add('active');
    modal.setAttribute('aria-hidden', 'false');
    document.body.classList.add('bs-ledger-open');
  }

  function closeLedgerModal(){
    const modal = $('ac_bs_ledger_modal');
    if (!modal) return;
    modal.classList.remove('active');
    modal.setAttribute('aria-hidden', 'true');
    document.body.classList.remove('bs-ledger-open');
  }

  $('ac_bs_refresh') && $('ac_bs_refresh').addEventListener('click', e => { e.preventDefault(); loadSummary(); });

  // Auto-refresh summary when date range changes
  const dateFromEl = $('ac_bs_date_from');
  const dateToEl = $('ac_bs_date_to');
  let dateLoadTimer = null;
  function onDateChange() {
    clearTimeout(dateLoadTimer);
    dateLoadTimer = setTimeout(loadSummary, 300);
  }
  if (dateFromEl) dateFromEl.addEventListener('input', onDateChange);
  if (dateToEl) dateToEl.addEventListener('input', onDateChange);

  $('ac_bs_rows_table').addEventListener('click', e => {
    const btn = e.target.closest('[data-debtor-code]');
    if (!btn) return;
    const code = btn.dataset.debtorCode || '';
    const name = btn.dataset.debtorName || '';
    if (code) loadLedger(code, name);
  });
  $('ac_bs_ledger_table').addEventListener('click', e => {
    const receiptBtn = e.target.closest('[data-basket-receipt-idx]');
    if (receiptBtn) {
      e.preventDefault();
      e.stopPropagation();
      openBasketReceiptByIndex(receiptBtn.dataset.basketReceiptIdx);
      return;
    }
  });
  $('ac_bs_ledger_date_from')?.addEventListener('input', () => applyLedgerRender(currentLedgerRows));
  $('ac_bs_ledger_date_to')?.addEventListener('input', () => applyLedgerRender(currentLedgerRows));
  $('ac_bs_ledger_movement_filter')?.addEventListener('change', () => applyLedgerRender(currentLedgerRows));
  $('ac_bs_ledger_select_all')?.addEventListener('click', e => { e.preventDefault(); toggleSelectAllLedgerRows(); });
  $('ac_bs_ledger_print')?.addEventListener('click', e => {
    e.preventDefault();
    printLedgerPdf(e.currentTarget);
  });
  $('ac_bs_ledger_share')?.addEventListener('click', e => {
    e.preventDefault();
    shareLedgerPdf(e.currentTarget);
  });
  $('ac_bs_receipt_mount').addEventListener('click', e => {
    const printBtn = e.target.closest('[data-print-current-basket-receipt]');
    if (printBtn) {
      e.preventDefault();
      printCurrentBasketReceipt();
      return;
    }

    const btn = e.target.closest('[data-share-current-basket-receipt]');
    if (!btn) return;
    e.preventDefault();
    shareCurrentBasketReceipt(btn);
  });
  $('ac_bs_customer_input').addEventListener('click', openCustomerPicker);
  $('ac_bs_customer_clear').addEventListener('click', e => {
    e.preventDefault();
    e.stopPropagation();
    clearCustomerSelection();
  });
  $('ac_bs_selected_customers').addEventListener('click', e => {
    const toggleBtn = e.target.closest('[data-toggle-selected-customers]');
    if (toggleBtn) {
      e.preventDefault();
      e.stopPropagation();
      toggleSelectedCustomerManager();
      return;
    }

    const btn = e.target.closest('[data-remove-selected-customer]');
    if (!btn) return;
    e.preventDefault();
    e.stopPropagation();
    removeSelectedCustomer(btn.dataset.removeSelectedCustomer || '');
  });
  $('ac_bs_manage_list').addEventListener('click', e => {
    const btn = e.target.closest('[data-remove-selected-customer]');
    if (!btn) return;
    e.preventDefault();
    e.stopPropagation();
    removeSelectedCustomer(btn.dataset.removeSelectedCustomer || '');
  });
  $('ac_bs_manage_add').addEventListener('click', e => {
    e.preventDefault();
    openCustomerPicker();
  });
  $('ac_bs_manage_clear').addEventListener('click', e => {
    e.preventDefault();
    clearCustomerSelection();
  });
  $('ac_bs_manage_done').addEventListener('click', e => {
    e.preventDefault();
    closeSelectedCustomerManager();
  });
  document.addEventListener('click', function(e){
    const panel = $('ac_bs_manage_selected');
    const selectedArea = $('ac_bs_selected_customers');
    if (!panel || !panel.classList.contains('active')) return;
    if (panel.contains(e.target) || selectedArea.contains(e.target)) return;
    closeSelectedCustomerManager();
  });
  $('ac_bs_picker_close').addEventListener('click', closePicker);
  $('ac_bs_picker_backdrop').addEventListener('click', closePicker);
  $('ac_bs_picker_search').addEventListener('input', function(){ runPickerSearch(this.value); });
  $('ac_bs_picker_results').addEventListener('click', e => {
    const btn = e.target.closest('[data-picker-idx]');
    if (!btn) return;
    const idx = parseInt(btn.dataset.pickerIdx, 10);
    if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) {
      pickerState.onPick(pickerState.items[idx].raw);
    }
  });
  $('ac_bs_ledger_close').addEventListener('click', closeLedgerModal);
  $('ac_bs_ledger_b���BNU�W�����������B
N����ackdrop').addEventListener('click', closeLedgerModal);
  document.addEventListener('keydown', function(e){
    if (e.key === 'Escape') {
      closeSelectedCustomerManager();
      closeLedgerModal();
      const receiptMount = $('ac_bs_receipt_mount');
      if (receiptMount && receiptMount.innerHTML) {
        receiptMount.innerHTML = '';
        document.body.classList.remove('bs-br-open');
      }
    }
  });

  setDefaultDateRange();
  updateCustomerClearButton();
  loadSummary();
})();
</script>���B<v�yX�����������U
N?�Y<?php
if (!defined('ABSPATH')) exit;

/*
 * Template Name: View Delivery Order
 *
 * VegeBasketDO printable Delivery Order page.
 * A5 landscape Delivery Order layout + optional Proof of Delivery PDF page.
 *
 * Staff list URL:
 * /view-delivery-order/?docNo=DO-000074&docKey=409
 *
 * Legacy fallback URL:
 * /view-delivery-order/?job_id=83
 */

global $wpdb;

if (!is_user_logged_in()) {
    wp_die('Please login to view this delivery order.');
}

if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
    wp_die('You do not have permission to view this delivery order.');
}

if (!function_exists('ac_do_h')) {
    function ac_do_h($value) {
        return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
    }
}

if (!function_exists('ac_do_num')) {
    function ac_do_num($value) {
        if ($value === '' || $value === null) return '';

        $num = (float)$value;

        if (floor($num) == $num) {
            return (string)intval($num);
        }

        return rtrim(rtrim(number_format($num, 2, '.', ''), '0'), '.');
    }
}

if (!function_exists('ac_do_qty3')) {
    function ac_do_qty3($value) {
        if ($value === '' || $value === null) return '';
        return number_format((float)$value, 3, '.', '');
    }
}

if (!function_exists('ac_do_pick')) {
    function ac_do_pick($array, $keys, $default = '') {
        if (!is_array($array)) return $default;

        foreach ($keys as $key) {
            if (isset($array[$key]) && $array[$key] !== '' && $array[$key] !== null) {
                return $array[$key];
            }
        }

        return $default;
    }
}

if (!function_exists('ac_do_date')) {
    function ac_do_date($value) {
        if ($value instanceof DateTime) {
            return $value->format('Y-m-d');
        }

        $value = trim((string)$value);
        if ($value === '') return '';

        $timestamp = strtotime($value);
        return $timestamp ? date('Y-m-d', $timestamp) : $value;
    }
}

if (!function_exists('ac_do_wp_table_exists')) {
    function ac_do_wp_table_exists($table_name) {
        global $wpdb;

        if (!$wpdb) return false;

        return $wpdb->get_var(
            $wpdb->prepare('SHOW TABLES LIKE %s', $table_name)
        ) === $table_name;
    }
}

if (!function_exists('ac_do_wp_table_columns')) {
    function ac_do_wp_table_columns($table_name) {
        global $wpdb;

        static $cache = array();
        if (!$wpdb) return array();

        if (isset($cache[$table_name])) {
            return $cache[$table_name];
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table_name);
        $cols = $wpdb->get_col("SHOW COLUMNS FROM `{$safe_table}`", 0);

        $cache[$table_name] = is_array($cols) ? array_flip($cols) : array();

        return $cache[$table_name];
    }
}

if (!function_exists('ac_do_doc_no_from_data')) {
    function ac_do_doc_no_from_data($data) {
        $doc_no = ac_do_pick($data, array('docNo', 'DocNo', 'docno', 'sourceDocNo', 'oldDocNo', 'originalDocNo'), '');
        return strtoupper(trim((string)$doc_no));
    }
}

if (!function_exists('ac_do_doc_key_from_data')) {
    function ac_do_doc_key_from_data($data) {
        $doc_key = ac_do_pick($data, array('docKey', 'DocKey', 'dockey'), 0);
        return is_numeric($doc_key) ? (int)$doc_key : 0;
    }
}

if (!function_exists('ac_do_customer_phone_from_data')) {
    function ac_do_customer_phone_from_data($data) {
        return trim((string)ac_do_pick($data, array(
            'customerPhone',
            'customerTel',
            'debtorPhone',
            'debtorTel',
            'phone',
            'tel',
            'Phone',
            'Tel',
            'mobile',
            'Mobile'
        ), ''));
    }
}

if (!function_exists('ac_do_load_job_by_ref')) {
    function ac_do_load_job_by_ref($job_id, $doc_no, $doc_key) {
        global $wpdb;

        $table = $wpdb->prefix . 'ac_jobs';
        if (!ac_do_wp_table_exists($table)) return null;

        if ($job_id > 0) {
            return $wpdb->get_row(
                $wpdb->prepare("
                    SELECT *
                    FROM {$table}
                    WHERE id = %d
                      AND job_type = 'DELIVERY_ORDER'
                    LIMIT 1
                ", $job_id),
                ARRAY_A
            );
        }

        $rows = $wpdb->get_results("
            SELECT *
            FROM {$table}
            WHERE job_type = 'DELIVERY_ORDER'
            ORDER BY id DESC
            LIMIT 800
        ", ARRAY_A);

        $doc_no = strtoupper(trim((string)$doc_no));
        $doc_key = (int)$doc_key;

        foreach ((array)$rows as $row) {
            $payload = json_decode((string)($row['payload'] ?? ''), true);
            $result = json_decode((string)($row['result'] ?? ''), true);
            $payload = is_array($payload) ? $payload : array();
            $result = is_array($result) ? $result : array();

            $row_doc_no = ac_do_doc_no_from_data($result);
            if ($row_doc_no === '') {
                $row_doc_no = ac_do_doc_no_from_data($payload);
            }

            $row_doc_key = ac_do_doc_key_from_data($result);
            if ($row_doc_key <= 0) {
                $row_doc_key = ac_do_doc_key_from_data($payload);
            }

            if ($doc_no !== '' && $row_doc_no === $doc_no) {
                return $row;
            }

            if ($doc_key > 0 && $row_doc_key === $doc_key) {
                return $row;
            }
        }

        return null;
    }
}

if (!function_exists('ac_do_load_wp_only_order')) {
    function ac_do_load_wp_only_order($doc_no, $doc_key = 0) {
        global $wpdb;

        $do_table = $wpdb->prefix . 'ac_do';
        $items_table = $wpdb->prefix . 'ac_do_items';

        if (!ac_do_wp_table_exists($do_table) || !ac_do_wp_table_exists($items_table)) {
            return null;
        }

        $safe_do_table = preg_replace('/[^A-Za-z0-9_]/', '', $do_table);
        $safe_items_table = preg_replace('/[^A-Za-z0-9_]/', '', $items_table);
        $doc_no = strtoupper(trim((string)$doc_no));
        $doc_key = (int)$doc_key;

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

        if ($doc_no !== '') {
            $where[] = '(local_doc_no = %s OR autocount_doc_no = %s)';
            $args[] = $doc_no;
            $args[] = $doc_no;
        }

        if ($doc_key > 0) {
            $where[] = 'autocount_doc_key = %d';
            $args[] = $doc_key;
        }

        if (empty($where)) {
            return null;
        }

        $deleted_sql = '';
        $do_cols = ac_do_wp_table_columns($do_table);

        if (isset($do_cols['deleted_at'])) {
            $deleted_sql = ' AND deleted_at IS NULL';
        }

        $do = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM `{$safe_do_table}` WHERE (" . implode(' OR ', $where) . ") {$deleted_sql} ORDER BY id DESC LIMIT 1",
                $args
            ),
            ARRAY_A
        );

        if (!$do) {
            return null;
        }

        $do_id = (int)($do['id'] ?? 0);

        $item_rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT * FROM `{$safe_items_table}` WHERE do_id = %d ORDER BY line_no ASC, id ASC",
                $do_id
            ),
            ARRAY_A
        );

        $lines = array();

        foreach ((array)$item_rows as $line) {
            $basket = (float)($line['basket_qty'] ?? 0);
            $carton = (float)($line['carton_qty'] ?? 0);
            $qty = (float)($line['qty'] ?? 0);
            $weight_kg = (float)($line['weight_kg'] ?? 0);
            $total_weight_kg = (float)($line['total_weight_kg'] ?? $line['qty'] ?? 0);
            $pack_type = $carton > 0 ? 'CTN' : ($basket > 0 ? 'BSK' : '');

            $lines[] = array(
                'qty' => $qty,
                'kg' => $weight_kg,
                'totalKg' => $total_weight_kg,
                'description' => (string)($line['description'] ?? '') !== '' ? (string)$line['description'] : (string)($line['item_code'] ?? ''),
                'packType' => $pack_type,
                'cartonQty' => $carton,
                'basketQty' => $basket,
                'uom' => (string)($line['uom'] ?? 'KG'),
            );
        }

        $doc_date = ac_do_date($do['doc_date'] ?? '');

        return array(
            'docNo' => (string)($do['local_doc_no'] ?? $do['autocount_doc_no'] ?? $doc_no),
            'docKey' => (int)($do['autocount_doc_key'] ?? 0),
            'customerCode' => (string)($do['debtor_code'] ?? ''),
            'customerName' => (string)($do['debtor_name'] ?? ''),
            'customerPhone' => ac_do_customer_phone_from_data($do),
            'docDate' => $doc_date !== '' ? $doc_date : date('Y-m-d'),
            'remark' => (string)($do['remark'] ?? ''),
            'lines' => $lines,
        );
    }
}

if (!function_exists('ac_do_load_autocount_order')) {
    function ac_do_load_autocount_order($doc_no, $doc_key) {
        if (!function_exists('get_mssql')) return null;

        $conn = get_mssql();
        if (!$conn) return null;

        $where = array();
        $params = array();

        $doc_no = trim((string)$doc_no);
        $doc_key = (int)$doc_key;

        if ($doc_key > 0) {
            $where[] = 'DOH.DocKey = ?';
            $params[] = $doc_key;
        }

        if ($doc_no !== '') {
            $where[] = 'DOH.DocNo = ?';
            $params[] = $doc_no;
        }

        if (empty($where)) return null;

        $header_sql = "
            SELECT TOP 1
                DOH.DocKey,
                DOH.DocNo,
                DOH.DocDate,
                DOH.DebtorCode,
                DOH.DebtorName,
                ISNULL(DOH.UDF_SUMBASKET, 0) AS TotalBasket,
                ISNULL(DOH.UDF_SUMCARTON, 0) AS TotalCarton
            FROM dbo.[DO] AS DOH
            WHERE " . implode(' OR ', $where) . "
            ORDER BY DOH.DocKey DESC
        ";

        $stmt = sqlsrv_query($conn, $header_sql, $params);
        if ($stmt === false) return null;

        $header = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
        sqlsrv_free_stmt($stmt);

        if (!$header) return null;

        $detail_sql = "
            SELECT
                ISNULL(DTL.ItemCode, '') AS ItemCode,
                ISNULL(DTL.Description, '') AS Description,
                ISNULL(DTL.Qty, 0) AS Qty,
                ISNULL(DTL.UDF_BASKET, 0) AS Basket,
                ISNULL(DTL.UDF_CARTON, 0) AS Carton,
                ISNULL(DTL.UDF_WEIGHTKG, 0) AS WeightKG
            FROM dbo.DODTL AS DTL
            WHERE DTL.DocKey = ?
              AND ISNULL(DTL.MainItem, 'T') = 'T'
            ORDER BY ISNULL(DTL.Seq, 0) ASC, ISNULL(DTL.ItemCode, '') ASC
        ";

        $detail_stmt = sqlsrv_query($conn, $detail_sql, array((int)$header['DocKey']));
        $lines = array();

        if ($detail_stmt !== false) {
            while ($line = sqlsrv_fetch_array($detail_stmt, SQLSRV_FETCH_ASSOC)) {
                $basket = (float)($line['Basket'] ?? 0);
                $carton = (float)($line['Carton'] ?? 0);
                $qty = (float)($line['Qty'] ?? 0);
                $weight_kg = (float)($line['WeightKG'] ?? 0);
                $pack_type = $carton > 0 ? 'CTN' : ($basket > 0 ? 'BSK' : '');

                $lines[] = array(
                    'qty' => $qty,
                    'kg' => $weight_kg,
                    'totalKg' => $weight_kg > 0 ? $weight_kg : $qty,
                    'description' => $line['Description'] !== '' ? $line['Description'] : $line['ItemCode'],
                    'packType' => $pack_type,
                    'cartonQty' => $carton,
                    'basketQty' => $basket,
                    'uom' => 'KG',
                );
            }

            sqlsrv_free_stmt($detail_stmt);
        }

        return array(
            'docNo' => (string)($header['DocNo'] ?? ''),
            'docKey' => (int)($header['DocKey'] ?? 0),
            'customerCode' => (string)($header['DebtorCode'] ?? ''),
            'customerName' => (string)($header['DebtorName'] ?? ''),
            'customerPhone' => '',
            'docDate' => ac_do_date($header['DocDate'] ?? ''),
            'remark' => '',
            'lines' => $lines,
        );
    }
}

if (!function_exists('ac_do_get_proof_url')) {
    function ac_do_get_proof_url($job_id, $doc_key, $doc_no) {
        global $wpdb;

        $table = $wpdb->prefix . 'ac_do_proof_images';
        if (!ac_do_wp_table_exists($table)) return '';

        $cols = ac_do_wp_table_columns($table);
        $where = array();
        $args = array();

        if ((int)$job_id > 0 && isset($cols['job_id'])) {
            $where[] = 'job_id = %d';
            $args[] = (int)$job_id;
        }

        if ((int)$doc_key > 0 && isset($cols['doc_key'])) {
            $where[] = 'doc_key = %d';
            $args[] = (int)$doc_key;
        }

        $doc_no = trim((string)$doc_no);
        if ($doc_no !== '' && isset($cols['doc_no'])) {
            $where[] = 'doc_no = %s';
            $args[] = $doc_no;
        }

        if (empty($where)) return '';

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $select = array();

        foreach (array('attachment_id', 'image_url') as $col) {
            if (isset($cols[$col])) {
                $select[] = "`{$col}`";
            }
        }

        if (empty($select)) return '';

        $where_sql = '(' . implode(' OR ', $where) . ')';

        if (isset($cols['proof_type'])) {
            $where_sql .= ' AND proof_type = %s';
            $args[] = 'DELIVERY_PROOF';
        }

        if (isset($cols['deleted_at'])) {
            $where_sql .= ' AND deleted_at IS NULL';
        }

        $order_col = isset($cols['captured_at']) ? 'captured_at' : 'id';

        $sql = "
            SELECT " . implode(', ', $select) . "
            FROM `{$safe_table}`
            WHERE {$where_sql}
            ORDER BY `{$order_col}` ASC
            LIMIT 1
        ";

        $proof = $wpdb->get_row($wpdb->prepare($sql, $args), ARRAY_A);
        if (!$proof) return '';

        if (!empty($proof['attachment_id'])) {
            $attachment_url = wp_get_attachment_url((int)$proof['attachment_id']);
            if (!empty($attachment_url)) {
                return $attachment_url;
            }
        }

        return !empty($proof['image_url']) ? esc_url_raw((string)$proof['image_url']) : '';
    }
}

$job_id = isset($_GET['job_id']) ? absint($_GET['job_id']) : 0;
$doc_no_param = isset($_GET['docNo']) ? sanitize_text_field(wp_unslash($_GET['docNo'])) : '';
$doc_key_param = isset($_GET['docKey']) ? absint($_GET['docKey']) : 0;

if ($doc_no_param === '' && isset($_GET['docno'])) {
    $doc_no_param = sanitize_text_field(wp_unslash($_GET['docno']));
}

if ($doc_key_param <= 0 && isset($_GET['dockey'])) {
    $doc_key_param = absint($_GET['dockey']);
}

$job = ac_do_load_job_by_ref($job_id, $doc_no_param, $doc_key_param);
$payload = array();
$result = array();

if ($job) {
    $payload = json_decode((string)($job['payload'] ?? ''), true);
    $result = json_decode((string)($job['result'] ?? ''), true);
    $payload = is_array($payload) ? $payload : array();
    $result = is_array($result) ? $result : array();
}

$doc_no = $doc_no_param !== '' ? strtoupper(trim($doc_no_param)) : ac_do_doc_no_from_data($result);
if ($doc_no === '') {
    $doc_no = ac_do_doc_no_from_data($payload);
}

$doc_key = $doc_key_param > 0 ? $doc_key_param : ac_do_doc_key_from_data($result);
if ($doc_key <= 0) {
    $doc_key = ac_do_doc_key_from_data($payload);
}

$autocount_order = ac_do_load_autocount_order($doc_no, $doc_key);

$wp_only_order = null;
if (!$autocount_order) {
    $wp_onl���U��Y�����������h
N?�Zy_order = ac_do_load_wp_only_order($doc_no, $doc_key);
}

if ($autocount_order) {
    $doc_no = $autocount_order['docNo'];
    $doc_key = $autocount_order['docKey'];
    $customer_code = $autocount_order['customerCode'];
    $customer_name = $autocount_order['customerName'];
    $customer_phone = $autocount_order['customerPhone'];
    $doc_date = $autocount_order['docDate'];
    $remark = $autocount_order['remark'];
    $lines = $autocount_order['lines'];
} elseif ($wp_only_order) {
    $doc_no = $wp_only_order['docNo'];
    $doc_key = $wp_only_order['docKey'];
    $customer_code = $wp_only_order['customerCode'];
    $customer_name = $wp_only_order['customerName'];
    $customer_phone = $wp_only_order['customerPhone'];
    $doc_date = $wp_only_order['docDate'];
    $remark = $wp_only_order['remark'];
    $lines = $wp_only_order['lines'];
} elseif (!empty($payload)) {
    $customer_code = ac_do_pick($payload, array('customerCode', 'debtorCode', 'DebtorCode'), '');
    $customer_name = ac_do_pick($payload, array('customerName', 'debtorName', 'DebtorName'), '');
    $customer_phone = ac_do_customer_phone_from_data($payload);
    if ($customer_phone === '') {
        $customer_phone = ac_do_customer_phone_from_data($result);
    }
    $doc_date = ac_do_pick($payload, array('docDate', 'DocDate'), date('Y-m-d'));
    $remark = ac_do_pick($payload, array('remark', 'Remark'), '');
    $lines = isset($payload['lines']) && is_array($payload['lines']) ? $payload['lines'] : array();
} else {
    wp_die('Delivery order not found.');
}

if ($doc_no === '' && $job) {
    $doc_no = 'DO-' . str_pad((string)$job['id'], 6, '0', STR_PAD_LEFT);
}

$company_name = 'EXCELLENTVEGE SDN. BHD.';
$company_reg  = '(202201001739 (144736-P))';
$company_addr_line_1 = 'NO.39 & 41 JALAN PPC 2, PUSAT PERNIAGAAN CORINA,';
$company_addr_line_2 = '39010 CAMERON HIGHLANDS, PAHANG DARUL MAKMUR.';
$company_tel  = '017-5373752, 012-5227976';
$company_logo_url = '';

$total_ctn = 0;
$total_bsk = 0;
$total_qty = 0;

foreach ((array)$lines as $line) {
    $pack_type = strtoupper((string)ac_do_pick($line, array('packType', 'PackType'), ''));

    if ($pack_type === 'CARTON' || $pack_type === 'CTN') {
        $total_ctn += (float)ac_do_pick($line, array('cartonQty', 'ctnQty', 'qty', 'Qty'), 0);
    } elseif ($pack_type === 'BASKET' || $pack_type === 'BSK') {
        $total_bsk += (float)ac_do_pick($line, array('basketQty', 'bskQty', 'qty', 'Qty'), 0);
    }

    $line_total = ac_do_pick($line, array('totalKg', 'TotalKg', 'UDF_WEIGHTKG', 'kg', 'Kg', 'qty', 'Qty'), 0);
    $total_qty += (float)$line_total;
}

$display_date = $doc_date;
$timestamp = strtotime((string)$doc_date);
if ($timestamp) {
    $display_date = date('d/m/Y', $timestamp);
}

$proof_url = ac_do_get_proof_url((int)($job['id'] ?? 0), $doc_key, $doc_no);
$auto_print = isset($_GET['autoPrint']) && sanitize_text_field(wp_unslash($_GET['autoPrint'])) === '1';
$print_page = isset($_GET['printPage']) ? sanitize_text_field(wp_unslash($_GET['printPage'])) : '';
$auto_print_do_only = $print_page === 'do';
$pdf_file_name = 'Delivery-Order-' . preg_replace('/[^A-Za-z0-9_-]/', '-', $doc_no !== '' ? $doc_no : 'DO') . '.pdf';
$pdf_lines = array();

foreach ((array)$lines as $line) {
    $line_qty = ac_do_pick($line, array('totalKg', 'TotalKg', 'UDF_WEIGHTKG', 'kg', 'Kg', 'qty', 'Qty'), '');
    $description = ac_do_pick($line, array('description', 'Description', 'itemName', 'ItemName', 'itemDescription', 'ItemDescription', 'itemCode', 'ItemCode'), '');
    $uom = strtoupper(trim((string)ac_do_pick($line, array('uom', 'UOM', 'unit', 'Unit'), 'KG')));
    if ($uom === '') $uom = 'KG';

    $pdf_lines[] = array(
        'description' => (string)$description,
        'uom' => $uom,
        'qty' => ac_do_qty3($line_qty),
    );
}

$pdf_payload = array(
    'companyName' => $company_name,
    'companyReg' => $company_reg,
    'companyAddrLine1' => $company_addr_line_1,
    'companyAddrLine2' => $company_addr_line_2,
    'companyTel' => $company_tel,
    'companyLogoUrl' => $company_logo_url,
    'docNo' => $doc_no,
    'customerCode' => $customer_code,
    'customerName' => $customer_name,
    'customerPhone' => $customer_phone,
    'displayDate' => $display_date,
    'remark' => $remark,
    'lines' => $pdf_lines,
    'totalCtn' => ac_do_num($total_ctn),
    'totalBsk' => ac_do_num($total_bsk),
    'totalQty' => ac_do_qty3($total_qty),
    'proofUrl' => $proof_url,
);
?>

<style>
    * {
        box-sizing: border-box;
    }

    .ac-do-page-wrap {
        --ac-do-preview-scale: 1;
        --ac-do-preview-width: 794px;
        --ac-do-preview-height: 559px;
        --ac-do-preview-gap: 22px;
        width: 100%;
        background: #e5e5e5;
        padding: 20px 0 40px;
        font-family: Arial, Helvetica, sans-serif;
        color: #111;
        overflow-x: hidden;
    }

    .ac-do-actions {
        width: min(794px, calc(100vw - 32px));
        margin: 0 auto 14px;
        display: flex;
        justify-content: flex-end;
        gap: 8px;
        text-align: right;
    }

    .ac-do-actions button {
        border: 0;
        background: #0B4A2D;
        color: #fff;
        padding: 10px 18px;
        border-radius: 6px;
        font-size: 14px;
        cursor: pointer;
        font-weight: 700;
    }

    .ac-do-actions button.ac-do-back-btn {
        background: #475569;
        margin-right: auto;
    }

    .ac-do-actions button.ac-do-share-btn {
        background: #128C7E;
    }

    .ac-do-actions button.ac-do-hidden-print-btn {
        display: none !important;
    }

    .ac-do-actions button:disabled {
        cursor: not-allowed;
        opacity: 0.65;
    }

    .ac-do-preview-page {
        width: var(--ac-do-preview-width);
        height: var(--ac-do-preview-height);
        margin: 0 auto;
        position: relative;
    }

    .ac-do-preview-page + .ac-do-preview-page {
        margin-top: var(--ac-do-preview-gap);
    }

    .ac-do-preview-page > .ac-do-paper,
    .ac-do-preview-page > .ac-do-proof-paper {
        position: absolute;
        top: 0;
        left: 0;
        transform: scale(var(--ac-do-preview-scale));
        transform-origin: top left;
    }

    .ac-do-paper,
    .ac-do-proof-paper {
        width: 794px;
        height: 559px;
        min-height: 559px;
        margin: 0 auto;
        background: #fff;
        padding: 42px 58px 34px;
        border: 1px solid #d1d5db;
        position: relative;
        overflow: hidden;
    }

    .ac-do-company-head {
        text-align: center;
        line-height: 1.15;
        margin-bottom: 22px;
    }

    .ac-do-company-head h1 {
        margin: 0 0 4px;
        font-size: 20px;
        line-height: 1.1;
        font-weight: 900;
        letter-spacing: 0.3px;
    }

    .ac-do-company-head .ac-do-reg {
        font-size: 10.5px;
        margin-bottom: 2px;
    }

    .ac-do-company-head .ac-do-addr,
    .ac-do-company-head .ac-do-tel {
        font-size: 11px;
    }

    .ac-do-meta {
        display: grid;
        grid-template-columns: 1fr 240px;
        gap: 24px;
        margin: 0 36px 12px;
        font-size: 12px;
    }

    .ac-do-delivered {
        padding-top: 6px;
        line-height: 1.55;
    }

    .ac-do-delivered-label {
        margin-bottom: 8px;
    }

    .ac-do-customer-name {
        font-weight: 700;
        text-transform: uppercase;
    }

    .ac-do-document-box {
        line-height: 1.55;
    }

    .ac-do-document-title {
        font-size: 18px;
        line-height: 1.1;
        font-weight: 900;
        text-align: left;
        margin-bottom: 9px;
        letter-spacing: 0.2px;
    }

    .ac-do-doc-grid {
        display: grid;
        grid-template-columns: 52px 10px 1fr;
        gap: 0;
        align-items: baseline;
    }

    .ac-do-doc-value {
        font-weight: 700;
    }

    .ac-do-table-wrap {
        margin: 0 24px;
    }

    .ac-do-table {
        width: 100%;
        border-collapse: collapse;
        table-layout: fixed;
        font-size: 11px;
        border-top: 1px solid #111;
        border-bottom: 1px solid #111;
    }

    .ac-do-table thead tr {
        border-bottom: 1px solid #111;
    }

    .ac-do-table th {
        padding: 5px 6px 6px;
        font-size: 11px;
        font-weight: 400;
        text-align: left;
        line-height: 1.1;
    }

    .ac-do-table td {
        padding: 3px 6px;
        height: 22px;
        vertical-align: middle;
        line-height: 1.1;
    }

    .ac-do-item-col {
        width: 68px;
        text-align: right !important;
        padding-right: 14px !important;
    }

    .ac-do-desc-col {
        width: auto;
        text-align: left;
    }

    .ac-do-uom-col {
        width: 85px;
        text-align: center !important;
    }

    .ac-do-qty-col {
        width: 105px;
        text-align: right !important;
    }

    .ac-do-table tbody td {
        font-weight: 400;
    }

    .ac-do-table tbody .ac-do-desc-col {
        text-transform: uppercase;
    }

    .ac-do-total-row {
        display: grid;
        grid-template-columns: 1fr 55px 105px;
        align-items: center;
        margin: 7px 24px 0;
        font-size: 11px;
    }

    .ac-do-total-label {
        grid-column: 2;
        text-align: right;
        padding-right: 8px;
        font-weight: 700;
    }

    .ac-do-total-box {
        grid-column: 3;
        border: 1px solid #111;
        height: 18px;
        display: flex;
        align-items: center;
        justify-content: flex-end;
        padding-right: 8px;
        font-weight: 700;
        background: #f8f8f8;
    }

    .ac-do-remark {
        margin: 8px 24px 0;
        font-size: 10.5px;
    }

    .ac-do-signature-row {
        position: absolute;
        left: 58px;
        right: 58px;
        bottom: 32px;
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 210px;
        font-size: 11px;
        font-weight: 700;
        text-align: center;
    }

    .ac-do-signature {
        border-top: 1px solid #111;
        padding-top: 7px;
        min-height: 24px;
    }

    .ac-do-proof-paper {
        padding: 36px 48px;
    }

    .ac-proof-title-row {
        display: grid;
        grid-template-columns: 1fr auto;
        gap: 20px;
        align-items: start;
        margin-bottom: 16px;
        border-bottom: 2px solid #333;
        padding-bottom: 10px;
    }

    .ac-proof-title h2 {
        margin: 0;
        font-size: 22px;
        line-height: 1.2;
    }

    .ac-proof-title p {
        margin: 4px 0 0;
        font-size: 12px;
    }

    .ac-proof-meta {
        text-align: right;
        font-size: 12px;
        line-height: 1.5;
    }

    .ac-proof-image-box {
        width: 100%;
        height: 360px;
        border: 1px solid #444;
        background: #fff;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 12px;
        overflow: hidden;
    }

    .ac-proof-image-box img {
        max-width: 100%;
        max-height: 334px;
        width: auto;
        height: auto;
        object-fit: contain;
        display: block;
    }

    .ac-proof-empty {
        font-size: 15px;
        font-weight: 700;
        color: #555;
        text-align: center;
    }

    .ac-proof-footer {
        margin-top: 22px;
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 120px;
        font-size: 12px;
    }

    .ac-proof-footer-line {
        border-top: 1px solid #555;
        padding-top: 6px;
        text-align: center;
    }

    @media screen and (max-width: 860px) {
        .ac-do-page-wrap {
            --ac-do-preview-scale: 0.9;
            --ac-do-preview-width: 715px;
            --ac-do-preview-height: 503px;
            --ac-do-preview-gap: 20px;
        }
    }

    @media screen and (max-width: 760px) {
        .ac-do-page-wrap {
            --ac-do-preview-scale: 0.8;
            --ac-do-preview-width: 635px;
            --ac-do-preview-height: 447px;
            --ac-do-preview-gap: 18px;
            padding: 14px 0 28px;
        }

        .ac-do-actions {
            justify-content: center;
            flex-wrap: wrap;
            gap: 8px;
        }
    }

    @media screen and (max-width: 680px) {
        .ac-do-page-wrap {
            --ac-do-preview-scale: 0.72;
            --ac-do-preview-width: 572px;
            --ac-do-preview-height: 403px;
            --ac-do-preview-gap: 16px;
        }
    }

    @media screen and (max-width: 600px) {
        .ac-do-page-wrap {
            --ac-do-preview-scale: 0.62;
            --ac-do-preview-width: 492px;
            --ac-do-preview-height: 347px;
            --ac-do-preview-gap: 14px;
        }
    }

    @media screen and (max-width: 520px) {
        .ac-do-page-wrap {
            --ac-do-preview-scale: 0.52;
            --ac-do-preview-width: 413px;
            --ac-do-preview-height: 291px;
            --ac-do-preview-gap: 12px;
        }

        .ac-do-actions button {
            padding: 9px 12px;
            font-size: 13px;
        }
    }

    @media screen and (max-width: 430px) {
        .ac-do-page-wrap {
            --ac-do-preview-scale: 0.44;
            --ac-do-preview-width: 349px;
            --ac-do-preview-height: 246px;
            --ac-do-preview-gap: 10px;
        }
    }

    @media print {
        html,
        body {
            background: #fff !important;
            margin: 0 !important;
            padding: 0 !important;
            -webkit-print-color-adjust: exact !important;
            print-color-adjust: exact !important;
        }

        #wpadminbar,
        header,
        footer,
        .site-header,
        .site-footer,
        .elementor-location-header,
        .elementor-location-footer,
        .ac-do-actions {
            display: none !important;
        }

        .ac-do-page-wrap {
            width: 100% !important;
            margin: 0 !important;
            padding: 0 !important;
            background: #fff !important;
            overflow: hidden !important;
        }

        .ac-do-preview-page {
            width: 210mm !important;
            height: 148mm !important;
            margin: 0 !important;
            position: static !important;
            overflow: hidden !important;
            page-break-after: always !important;
            break-after: page !important;
        }

        .ac-do-preview-page:last-child {
            page-break-after: auto !important;
            break-after: auto !important;
        }

        .ac-do-paper,
        .ac-do-proof-paper {
            width: 210mm !important;
            height: 148mm !important;
            min-height: 0 !important;
            margin: 0 !important;
            padding: 9mm 13mm 8mm !important;
            border: 0 !important;
            box-shadow: none !important;
            overflow: hidden !important;
            background: #fff !important;
            position: relative !important;
            left: auto !important;
            top: auto !important;
            transform: none !important;
        }

        .ac-do-company-head {
            margin-bottom: 5.5mm !important;
        }

        .ac-do-company-head h1 {
            font-size: 16pt !important;
        }

        .ac-do-company-head .ac-do-reg,
        .ac-do-company-head .ac-do-addr,
        .ac-do-company-head .ac-do-tel {
            font-size: 8.5pt !important;
        }

        .ac-do-meta {
            margin: 0 20mm 3mm !important;
            grid-template-columns: 1fr 55mm !important;
            gap: 8mm !important;
            font-size: 8.5pt !important;
        }

        .ac-do-document-title {
            font-size: 13pt !important;
���hro\?Z�����������
N?�[            margin-bottom: 2mm !important;
        }

        .ac-do-table-wrap {
            margin: 0 8mm !important;
        }

        .ac-do-table {
            font-size: 8.5pt !important;
        }

        .ac-do-table th {
            font-size: 8.5pt !important;
            padding: 1.5mm 1.6mm !important;
        }

        .ac-do-table td {
            height: 5.6mm !important;
            padding: 0.8mm 1.6mm !important;
        }

        .ac-do-total-row {
            margin: 2mm 8mm 0 !important;
            font-size: 8.5pt !important;
        }

        .ac-do-total-box {
            height: 5mm !important;
        }

        .ac-do-signature-row {
            left: 18mm !important;
            right: 18mm !important;
            bottom: 9mm !important;
            gap: 78mm !important;
            font-size: 8.5pt !important;
        }

        .ac-proof-image-box {
            height: 82mm !important;
            min-height: 82mm !important;
            max-height: 82mm !important;
        }

        .ac-proof-image-box img {
            max-width: 100% !important;
            max-height: 78mm !important;
            object-fit: contain !important;
        }

        @page {
            size: 210mm 148mm;
            margin: 0;
        }
    }
</style>

<div class="ac-do-page-wrap">
    <div class="ac-do-actions">
        <button type="button" class="ac-do-back-btn" onclick="acDoGoBack()">Back</button>
        <button type="button" class="ac-do-hidden-print-btn" onclick="acDoPrintDeliveryOrderOnly(this)" aria-hidden="true" tabindex="-1">Print</button>
        <button type="button" onclick="acDoPrintFullPdf(this)">Print / Save PDF</button>
        <button type="button" class="ac-do-share-btn" onclick="acDoSharePdf(this)">Share PDF</button>
    </div>

    <div class="ac-do-preview-page">
        <div class="ac-do-paper">
            <div class="ac-do-company-head">
                <h1><?php echo ac_do_h($company_name); ?></h1>
                <div class="ac-do-reg"><?php echo ac_do_h($company_reg); ?></div>
                <div class="ac-do-addr"><?php echo ac_do_h($company_addr_line_1); ?></div>
                <div class="ac-do-addr"><?php echo ac_do_h($company_addr_line_2); ?></div>
                <div class="ac-do-tel">Tel: <?php echo ac_do_h($company_tel); ?></div>
            </div>

            <div class="ac-do-meta">
                <div class="ac-do-delivered">
                    <div class="ac-do-delivered-label">Delivered to :</div>
                    <div class="ac-do-customer-name"><?php echo ac_do_h($customer_name); ?></div>
                    <div>Tel : <?php echo ac_do_h($customer_phone); ?></div>
                </div>

                <div class="ac-do-document-box">
                    <div class="ac-do-document-title">DELIVERY ORDER</div>

                    <div class="ac-do-doc-grid">
                        <div>No.</div>
                        <div>:</div>
                        <div class="ac-do-doc-value"><?php echo ac_do_h($doc_no); ?></div>

                        <div>Date</div>
                        <div>:</div>
                        <div><?php echo ac_do_h($display_date); ?></div>

                        <div>Page</div>
                        <div>:</div>
                        <div>1 of 1</div>
                    </div>
                </div>
            </div>

            <div class="ac-do-table-wrap">
                <table class="ac-do-table">
                    <thead>
                        <tr>
                            <th class="ac-do-item-col">Item</th>
                            <th class="ac-do-desc-col">Description</th>
                            <th class="ac-do-uom-col">UOM</th>
                            <th class="ac-do-qty-col">Qty</th>
                        </tr>
                    </thead>

                    <tbody>
                        <?php foreach ((array)$lines as $index => $line): ?>
                            <?php
                            $line_qty = ac_do_pick($line, array('totalKg', 'TotalKg', 'UDF_WEIGHTKG', 'kg', 'Kg', 'qty', 'Qty'), '');
                            $description = ac_do_pick($line, array('description', 'Description', 'itemName', 'ItemName', 'itemDescription', 'ItemDescription', 'itemCode', 'ItemCode'), '');
                            $uom = strtoupper(trim((string)ac_do_pick($line, array('uom', 'UOM', 'unit', 'Unit'), 'KG')));
                            if ($uom === '') $uom = 'KG';
                            ?>

                            <tr>
                                <td class="ac-do-item-col"><?php echo (int)$index + 1; ?>.</td>
                                <td class="ac-do-desc-col"><?php echo ac_do_h($description); ?></td>
                                <td class="ac-do-uom-col"><?php echo ac_do_h($uom); ?></td>
                                <td class="ac-do-qty-col"><?php echo ac_do_h(ac_do_qty3($line_qty)); ?></td>
                            </tr>
                        <?php endforeach; ?>

                        <?php
                        $minimum_rows = 6;
                        $remaining_rows = max(0, $minimum_rows - count((array)$lines));

                        for ($i = 0; $i < $remaining_rows; $i++):
                        ?>
                            <tr>
                                <td class="ac-do-item-col">&nbsp;</td>
                                <td class="ac-do-desc-col"></td>
                                <td class="ac-do-uom-col"></td>
                                <td class="ac-do-qty-col"></td>
                            </tr>
                        <?php endfor; ?>
                    </tbody>
                </table>
            </div>

            <div class="ac-do-total-row">
                <div class="ac-do-total-label">Total</div>
                <div class="ac-do-total-box"><?php echo ac_do_h(ac_do_qty3($total_qty)); ?></div>
            </div>

            <?php if ($remark !== ''): ?>
                <div class="ac-do-remark">
                    <strong>Remark:</strong> <?php echo ac_do_h($remark); ?>
                </div>
            <?php endif; ?>

            <div class="ac-do-signature-row">
                <div class="ac-do-signature">Authorised Signature</div>
                <div class="ac-do-signature">Recipient's Chop &amp; Signature</div>
            </div>
        </div>
    </div>

    <div class="ac-do-preview-page">
        <div class="ac-do-proof-paper">
            <div class="ac-proof-title-row">
                <div class="ac-proof-title">
                    <h2>Proof of Delivery</h2>
                    <p><?php echo ac_do_h($company_name); ?></p>
                </div>

                <div class="ac-proof-meta">
                    <strong>DO No:</strong> <?php echo ac_do_h($doc_no); ?><br>
                    <strong>Customer:</strong> <?php echo ac_do_h($customer_name); ?><br>
                    <strong>Date:</strong> <?php echo ac_do_h($display_date); ?>
                </div>
            </div>

            <div class="ac-proof-image-box">
                <?php if ($proof_url !== ''): ?>
                    <img
                        src="<?php echo esc_url($proof_url); ?>"
                        alt="Proof of Delivery"
                        loading="eager"
                        decoding="sync"
                    >
                <?php else: ?>
                    <div class="ac-proof-empty">
                        No proof of delivery image uploaded yet.
                    </div>
                <?php endif; ?>
            </div>

            <div class="ac-proof-footer">
                <div class="ac-proof-footer-line">Driver / Issued by</div>
                <div class="ac-proof-footer-line">Customer / Received by</div>
            </div>
        </div>
    </div>
</div>

<script>
var acDoPdfFileName = <?php echo wp_json_encode($pdf_file_name); ?>;
var acDoPdfData = <?php echo wp_json_encode($pdf_payload); ?>;
var acDoAutoPrint = <?php echo $auto_print ? 'true' : 'false'; ?>;
var acDoAutoPrintDoOnly = <?php echo $auto_print_do_only ? 'true' : 'false'; ?>;
var acDoJsPdfPromise = null;

function acDoGoBack() {
    var fallbackUrl = <?php echo wp_json_encode(home_url('/delivery-order-records/')); ?>;
    var params = new URLSearchParams(window.location.search || '');

    var isStaffPrintPopup = params.get('autoPrint') === '1'
        || window.name === 'wstDodPrintWindow'
        || !!window.opener;

    if (isStaffPrintPopup) {
        window.close();

        setTimeout(function() {
            if (!window.closed) {
                window.location.href = fallbackUrl;
            }
        }, 250);

        return;
    }

    if (window.history.length > 1) {
        window.history.back();
        return;
    }

    window.location.href = fallbackUrl;
}

function acDoWaitImage(img) {
    return new Promise(function(resolve) {
        if (!img) {
            resolve();
            return;
        }

        if (img.complete && img.naturalWidth > 0) {
            resolve();
            return;
        }

        var done = false;

        function finish() {
            if (done) return;
            done = true;
            resolve();
        }

        img.onload = finish;
        img.onerror = finish;

        setTimeout(finish, 4000);
    });
}

function acDoLoadJsPdf() {
    if (window.jspdf && window.jspdf.jsPDF) {
        return Promise.resolve(window.jspdf.jsPDF);
    }

    if (acDoJsPdfPromise) {
        return acDoJsPdfPromise;
    }

    acDoJsPdfPromise = new Promise(function(resolve, reject) {
        var script = document.createElement('script');
        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
        script.async = true;
        script.onload = function() {
            if (window.jspdf && window.jspdf.jsPDF) {
                resolve(window.jspdf.jsPDF);
                return;
            }

            reject(new Error('jsPDF library did not load.'));
        };
        script.onerror = function() {
            reject(new Error('PDF library could not be loaded.'));
        };
        document.head.appendChild(script);
    });

    return acDoJsPdfPromise;
}

function acDoCanvas(width, height) {
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');

    canvas.width = width;
    canvas.height = height;
    ctx.imageSmoothingEnabled = true;
    ctx.imageSmoothingQuality = 'high';

    return { canvas: canvas, ctx: ctx };
}

function acDoText(ctx, text, x, y, size, color, weight, align) {
    ctx.fillStyle = color || '#111111';
    ctx.font = (weight || '400') + ' ' + size + 'px Arial, Helvetica, sans-serif';
    ctx.textAlign = align || 'left';
    ctx.textBaseline = 'alphabetic';
    ctx.fillText(String(text || ''), x, y);
}

function acDoLine(ctx, x1, y1, x2, y2, color, width) {
    ctx.strokeStyle = color || '#333333';
    ctx.lineWidth = width || 1;
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.stroke();
}

function acDoRect(ctx, x, y, width, height, color, lineWidth) {
    ctx.strokeStyle = color || '#333333';
    ctx.lineWidth = lineWidth || 1;
    ctx.strokeRect(x, y, width, height);
}

function acDoFillRect(ctx, x, y, width, height, color) {
    ctx.fillStyle = color;
    ctx.fillRect(x, y, width, height);
}

function acDoWrap(ctx, text, x, y, maxWidth, lineHeight, size, color, weight, maxLines) {
    var words = String(text || '').split(/\s+/);
    var line = '';
    var currentY = y;
    var lines = [];
    var i;
    var test;

    ctx.fillStyle = color || '#111111';
    ctx.font = (weight || '400') + ' ' + size + 'px Arial, Helvetica, sans-serif';
    ctx.textAlign = 'left';
    ctx.textBaseline = 'alphabetic';

    for (i = 0; i < words.length; i++) {
        test = line ? line + ' ' + words[i] : words[i];

        if (ctx.measureText(test).width > maxWidth && line !== '') {
            lines.push(line);
            line = words[i];
        } else {
            line = test;
        }
    }

    if (line) {
        lines.push(line);
    }

    if (maxLines && lines.length > maxLines) {
        lines = lines.slice(0, maxLines);

        while (lines[lines.length - 1] && ctx.measureText(lines[lines.length - 1] + '...').width > maxWidth) {
            lines[lines.length - 1] = lines[lines.length - 1].slice(0, -1);
        }

        lines[lines.length - 1] = lines[lines.length - 1] + '...';
    }

    for (i = 0; i < lines.length; i++) {
        ctx.fillText(lines[i], x, currentY + (i * lineHeight));
    }
}

function acDoLoadCanvasImage(url) {
    return new Promise(function(resolve) {
        if (!url) {
            resolve(null);
            return;
        }

        var img = new Image();
        var done = false;

        function finish(result) {
            if (done) return;
            done = true;
            resolve(result);
        }

        img.crossOrigin = 'anonymous';
        img.onload = function() {
            finish(img);
        };
        img.onerror = function() {
            finish(null);
        };
        img.src = url;

        setTimeout(function() {
            finish(null);
        }, 4000);
    });
}

function acDoDrawContainImage(ctx, img, x, y, maxWidth, maxHeight) {
    var ratio;
    var width;
    var height;

    if (!img || !img.naturalWidth || !img.naturalHeight) {
        return false;
    }

    ratio = Math.min(maxWidth / img.naturalWidth, maxHeight / img.naturalHeight);
    width = img.naturalWidth * ratio;
    height = img.naturalHeight * ratio;

    ctx.drawImage(img, x + ((maxWidth - width) / 2), y + ((maxHeight - height) / 2), width, height);
    return true;
}

function acDoDrawReceiptCanvas() {
    var out = acDoCanvas(1748, 1240);
    var canvas = out.canvas;
    var ctx = out.ctx;
    var data = acDoPdfData || {};
    var lines = Array.isArray(data.lines) ? data.lines : [];
    var pageX = 72;
    var pageY = 58;
    var pageW = 1604;
    var pageH = 1124;

    var tableX = 190;
    var tableY = 500;
    var tableW = 1368;
    var headerH = 54;
    var minRows = Math.max(6, lines.length);
    var rowH = 42;

    if (minRows > 8) {
        rowH = Math.max(27, Math.floor(350 / minRows));
    }

    var tableBottom = tableY + headerH + (rowH * minRows);
    var colItemX = tableX;
    var colDescX = tableX + 110;
    var colUomX = tableX + 865;
    var colQtyX = tableX + 1110;
    var colEndX = tableX + tableW;

    var i;
    var y;
    var item;

    acDoFillRect(ctx, 0, 0, canvas.width, canvas.height, '#ffffff');
    acDoFillRect(ctx, pageX, pageY, pageW, pageH, '#ffffff');

    acDoText(ctx, data.companyName || '', canvas.width / 2, 138, 38, '#111111', '900', 'center');
    acDoText(ctx, data.companyReg || '', canvas.width / 2, 176, 20, '#111111', '400', 'center');
    acDoText(ctx, data.companyAddrLine1 || '', canvas.width / 2, 208, 21, '#111111', '400', 'center');
    acDoText(ctx, data.companyAddrLine2 || '', canvas.width / 2, 238, 21, '#111111', '400', 'center');
    acDoText(ctx, 'Tel: ' + (data.companyTel || ''), canvas.width / 2, 270, 21, '#111111', '400', 'center');

    acDoText(ctx, 'Delivered to :', 480, 336, 22, '#111111', '400');
    acDoText(ctx, String(data.customerName || '').toUpperCase(), 480, 386, 23, '#111111', '700');
    acDoText(ctx, 'Tel :   ' + (data.customerPhone || ''), 480, 426, 22, '#111111', '400');

    acDoText(ctx, 'DELIVERY ORDER', 1230, 318, 30, '#111111', '900');
    acDoText(ctx, 'No.', 1210, 370, 21, '#111111', '700');
    acDoText(ctx, ':', 1272, 370, 21, '#111111', '700');
    acDoText(ctx, data.docNo || '', 1320, 370, 22, '#111111', '700');

    acDoText(ctx, 'Date', 1210, 414, 21, '#111111', '400');
    acDoText(ctx, ':', 1272, 414, 21, '#111111', '400');
    acDoText(ctx, ���@�AS[�����������
N+���data.displayDate || '', 1320, 414, 21, '#111111', '400');

    acDoText(ctx, 'Page', 1210, 452, 21, '#111111', '400');
    acDoText(ctx, ':', 1272, 452, 21, '#111111', '400');
    acDoText(ctx, '1 of 1', 1320, 452, 21, '#111111', '400');

    acDoLine(ctx, tableX, tableY, colEndX, tableY, '#111111', 2);
    acDoLine(ctx, tableX, tableY + headerH, colEndX, tableY + headerH, '#111111', 2);
    acDoLine(ctx, tableX, tableBottom, colEndX, tableBottom, '#111111', 2);

    acDoText(ctx, 'Item', colItemX + 62, tableY + 34, 21, '#111111', '400', 'center');
    acDoText(ctx, 'Description', colDescX + 18, tableY + 34, 21, '#111111', '400');
    acDoText(ctx, 'UOM', colUomX + 92, tableY + 34, 21, '#111111', '400', 'center');
    acDoText(ctx, 'Qty', colQtyX + 220, tableY + 34, 21, '#111111', '400', 'right');

    for (i = 0; i < minRows; i++) {
        y = tableY + headerH + (rowH * i);
        item = lines[i] || {};

        if (item.description || item.qty || item.uom) {
            acDoText(ctx, (i + 1) + '.', colItemX + 72, y + Math.min(30, rowH - 8), 22, '#111111', '400', 'right');
            acDoWrap(ctx, String(item.description || '').toUpperCase(), colDescX + 18, y + Math.min(30, rowH - 8), 720, rowH > 34 ? 21 : 17, rowH > 34 ? 21 : 17, '#111111', '400', rowH > 34 ? 2 : 1);
            acDoText(ctx, item.uom || 'KG', colUomX + 92, y + Math.min(30, rowH - 8), 22, '#111111', '400', 'center');
            acDoText(ctx, item.qty || '', colQtyX + 220, y + Math.min(30, rowH - 8), 22, '#111111', '400', 'right');
        }
    }

    var totalY = tableBottom + 12;
    var totalBoxX = colQtyX + 56;
    var totalBoxW = 228;

    acDoText(ctx, 'Total', totalBoxX - 18, totalY + 30, 22, '#111111', '700', 'right');
    acDoRect(ctx, totalBoxX, totalY, totalBoxW, 42, '#111111', 1.5);
    acDoText(ctx, data.totalQty || '', totalBoxX + totalBoxW - 12, totalY + 29, 23, '#111111', '700', 'right');

    if (data.remark) {
        acDoText(ctx, 'Remark: ' + data.remark, tableX, totalY + 62, 18, '#111111', '700');
    }

    acDoLine(ctx, 230, 1080, 590, 1080, '#111111', 2);
    acDoLine(ctx, 1130, 1080, 1510, 1080, '#111111', 2);
    acDoText(ctx, 'Authorised Signature', 410, 1115, 21, '#111111', '700', 'center');
    acDoText(ctx, "Recipient's Chop & Signature", 1320, 1115, 21, '#111111', '700', 'center');

    return canvas;
}

function acDoDrawProofCanvas(proofImage) {
    var out = acDoCanvas(1748, 1240);
    var canvas = out.canvas;
    var ctx = out.ctx;
    var data = acDoPdfData || {};
    var pageX = 72;
    var pageY = 58;
    var pageW = 1604;
    var pageH = 1124;

    acDoFillRect(ctx, 0, 0, canvas.width, canvas.height, '#ffffff');
    acDoFillRect(ctx, pageX, pageY, pageW, pageH, '#ffffff');

    acDoText(ctx, 'Proof of Delivery', pageX + 70, pageY + 88, 38, '#111111', '700');
    acDoText(ctx, data.companyName || '', pageX + 70, pageY + 126, 21, '#111111', '400');

    acDoText(ctx, 'DO No: ' + (data.docNo || ''), pageX + pageW - 70, pageY + 86, 22, '#111111', '700', 'right');
    acDoText(ctx, 'Customer: ' + (data.customerName || ''), pageX + pageW - 70, pageY + 122, 21, '#111111', '400', 'right');
    acDoText(ctx, 'Date: ' + (data.displayDate || ''), pageX + pageW - 70, pageY + 158, 21, '#111111', '400', 'right');

    acDoLine(ctx, pageX + 70, pageY + 190, pageX + pageW - 70, pageY + 190, '#333333', 3);
    acDoRect(ctx, pageX + 70, pageY + 235, pageW - 140, 760, '#333333', 2);

    if (!acDoDrawContainImage(ctx, proofImage, pageX + 90, pageY + 255, pageW - 180, 720)) {
        var proofMessage = data.proofUrl
            ? 'Proof image could not be loaded for sharing.'
            : 'No proof of delivery image uploaded yet.';

        acDoText(ctx, proofMessage, pageX + pageW / 2, pageY + 630, 28, '#555555', '700', 'center');
    }

    acDoLine(ctx, pageX + 130, pageY + pageH - 110, pageX + 560, pageY + pageH - 110, '#555555', 2);
    acDoLine(ctx, pageX + pageW - 560, pageY + pageH - 110, pageX + pageW - 130, pageY + pageH - 110, '#555555', 2);
    acDoText(ctx, 'Driver / Issued by', pageX + 345, pageY + pageH - 72, 21, '#111111', '400', 'center');
    acDoText(ctx, 'Customer / Received by', pageX + pageW - 345, pageY + pageH - 72, 21, '#111111', '400', 'center');

    return canvas;
}

function acDoApplyPdfPrintHints(pdf) {
    if (!pdf) return;

    try {
        if (typeof pdf.viewerPreferences === 'function') {
            pdf.viewerPreferences({
                PrintScaling: 'None',
                Duplex: 'Simplex'
            });
        }
    } catch (error) {}

    try {
        if (typeof pdf.setDisplayMode === 'function') {
            pdf.setDisplayMode('fullwidth', 'single', 'UseNone');
        }
    } catch (error) {}
}

function acDoBuildPdfBlob(includeProof) {
    includeProof = includeProof === true;

    return acDoLoadJsPdf()
        .then(function(jsPDF) {
            var data = acDoPdfData || {};

            return Promise.all([
                Promise.resolve(jsPDF),
                includeProof ? acDoLoadCanvasImage(data.proofUrl) : Promise.resolve(null)
            ]);
        })
        .then(function(result) {
            var jsPDF = result[0];
            var proofImage = result[1];
            var pdf = new jsPDF({ orientation: 'landscape', unit: 'mm', format: [210, 148], compress: true });
            var pageOne = acDoDrawReceiptCanvas();

            pdf.setProperties({
                title: acDoPdfFileName.replace(/\.pdf$/i, '')
            });

            acDoApplyPdfPrintHints(pdf);

            pdf.addImage(pageOne.toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, 210, 148);

            if (includeProof) {
                pdf.addPage([210, 148], 'landscape');
                pdf.addImage(acDoDrawProofCanvas(proofImage).toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, 210, 148);
            }

            return pdf.output('blob');
        });
}

function acDoDownloadBlob(blob) {
    var url = URL.createObjectURL(blob);
    var link = document.createElement('a');

    link.href = url;
    link.download = acDoPdfFileName;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);

    setTimeout(function() {
        URL.revokeObjectURL(url);
    }, 1000);
}

function acDoPrintPdfBlob(blob) {
    var url = URL.createObjectURL(blob);
    var iframe = document.createElement('iframe');
    var cleanupTimer;

    iframe.style.position = 'fixed';
    iframe.style.right = '0';
    iframe.style.bottom = '0';
    iframe.style.width = '1px';
    iframe.style.height = '1px';
    iframe.style.border = '0';
    iframe.style.opacity = '0';
    iframe.title = acDoPdfFileName.replace(/\.pdf$/i, '');

    function cleanup() {
        if (cleanupTimer) {
            clearTimeout(cleanupTimer);
        }

        setTimeout(function() {
            if (iframe.parentNode) {
                iframe.parentNode.removeChild(iframe);
            }

            URL.revokeObjectURL(url);
        }, 1000);
    }

    iframe.onload = function() {
        cleanupTimer = setTimeout(cleanup, 60000);

        setTimeout(function() {
            try {
                iframe.contentWindow.focus();
                iframe.contentWindow.print();
            } catch (error) {
                acDoDownloadBlob(blob);
                alert('The PDF was downloaded because this browser could not open the printer automatically. Please print the downloaded PDF.');
                cleanup();
            }
        }, 500);
    };

    iframe.src = url;
    document.body.appendChild(iframe);
}

function acDoSetButtonBusy(button, text) {
    if (!button) return '';

    var originalText = button.textContent || '';
    button.disabled = true;
    button.textContent = text || 'Preparing PDF...';

    return originalText;
}

function acDoRestoreButton(button, originalText, fallbackText) {
    if (!button) return;

    button.disabled = false;
    button.textContent = originalText || fallbackText || button.textContent;
}

function acDoWaitForProofImages(includeProof) {
    var images = includeProof ? document.querySelectorAll('.ac-do-proof-paper img') : [];
    var waits = [];

    images.forEach(function(img) {
        waits.push(acDoWaitImage(img));
    });

    return Promise.all(waits);
}

function acDoPrintDeliveryOrderOnly(button) {
    var originalText = acDoSetButtonBusy(button, 'Preparing Print...');

    acDoWaitForProofImages(false)
        .then(function() {
            return acDoBuildPdfBlob(false);
        })
        .then(acDoPrintPdfBlob)
        .catch(function() {
            alert('Unable to prepare the delivery order for printing. Please try again.');
        })
        .finally(function() {
            acDoRestoreButton(button, originalText, 'Print');
        });
}

function acDoPrintFullPdf(button) {
    var originalText = acDoSetButtonBusy(button, 'Preparing PDF...');

    acDoWaitForProofImages(true)
        .then(function() {
            return acDoBuildPdfBlob(true);
        })
        .then(acDoPrintPdfBlob)
        .catch(function() {
            alert('Unable to prepare the full PDF for printing. Please use Share PDF or try again.');
        })
        .finally(function() {
            acDoRestoreButton(button, originalText, 'Print / Save PDF');
        });
}

function acDoSharePdf(button) {
    var originalText;

    if (!navigator.share) {
        alert('This browser does not support the native share interface. Please use Print / Save PDF, then share the saved PDF in WhatsApp.');
        return;
    }

    originalText = acDoSetButtonBusy(button, 'Preparing PDF...');

    acDoWaitForProofImages(true)
        .then(function() {
            return acDoBuildPdfBlob(true);
        })
        .then(function(blob) {
            var file = new File([blob], acDoPdfFileName, { type: 'application/pdf' });
            var shareData = {
                title: acDoPdfFileName.replace(/\.pdf$/i, ''),
                text: 'Delivery Order PDF',
                files: [file]
            };

            if (!navigator.canShare || !navigator.canShare({ files: [file] })) {
                acDoDownloadBlob(blob);
                alert('PDF downloaded. This browser cannot share PDF files directly, so please attach the downloaded PDF in WhatsApp.');
                return null;
            }

            return navigator.share(shareData);
        })
        .catch(function(error) {
            if (error && error.name === 'AbortError') {
                return;
            }

            alert('Unable to prepare the PDF for sharing. Please use Print / Save PDF, then share the saved PDF in WhatsApp.');
        })
        .finally(function() {
            acDoRestoreButton(button, originalText, 'Share PDF');
        });
}

if (acDoAutoPrint) {
    window.addEventListener('load', function() {
        setTimeout(function() {
            acDoPrintDeliveryOrderOnly(null);
        }, 350);
    });
}
</script>����\���������gp
N?�]<?php
if (!defined('ABSPATH')) exit;

/*
 * Template Name: View Delivery Order
 *
 * VegeBasketDO printable Delivery Order page with proof image.
 *
 * Staff list URL:
 * /view-delivery-order/?docNo=DO-000074&docKey=409
 *
 * Legacy fallback URL:
 * /view-delivery-order/?job_id=83
 */

global $wpdb;

if (!is_user_logged_in()) {
    wp_die('Please login to view this delivery order.');
}

if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
    wp_die('You do not have permission to view this delivery order.');
}

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

if (!function_exists('ac_do_h')) {
    function ac_do_h($value) {
        return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
    }
}

if (!function_exists('ac_do_num')) {
    function ac_do_num($value) {
        if ($value === '' || $value === null) return '';

        $num = (float)$value;

        if (floor($num) == $num) {
            return (string)intval($num);
        }

        return rtrim(rtrim(number_format($num, 2, '.', ''), '0'), '.');
    }
}

if (!function_exists('ac_do_pick')) {
    function ac_do_pick($array, $keys, $default = '') {
        if (!is_array($array)) return $default;

        foreach ($keys as $key) {
            if (isset($array[$key]) && $array[$key] !== '' && $array[$key] !== null) {
                return $array[$key];
            }
        }

        return $default;
    }
}


if (!function_exists('ac_do_to_float')) {
    function ac_do_to_float($value) {
        if ($value === '' || $value === null) return 0.0;
        return is_numeric($value) ? (float)$value : 0.0;
    }
}

if (!function_exists('ac_do_line_pack_type')) {
    function ac_do_line_pack_type($line) {
        $pack_type = strtoupper(trim((string)ac_do_pick($line, array('packType', 'PackType', 'pack_type', 'Pack_Type'), '')));

        if ($pack_type === 'CARTON') return 'CTN';
        if ($pack_type === 'BASKET') return 'BSK';

        if ($pack_type === 'CTN' || $pack_type === 'BSK') {
            return $pack_type;
        }

        $carton = ac_do_to_float(ac_do_pick($line, array('cartonQty', 'ctnQty', 'Carton', 'UDF_CARTON', 'carton_qty'), 0));
        $basket = ac_do_to_float(ac_do_pick($line, array('basketQty', 'bskQty', 'Basket', 'UDF_BASKET', 'basket_qty'), 0));

        if ($carton > 0) return 'CTN';
        if ($basket > 0) return 'BSK';

        return '';
    }
}

if (!function_exists('ac_do_line_qty')) {
    function ac_do_line_qty($line) {
        $pack_type = ac_do_line_pack_type($line);

        if ($pack_type === 'CTN') {
            return ac_do_pick($line, array('cartonQty', 'ctnQty', 'Carton', 'UDF_CARTON', 'carton_qty', 'qty', 'Qty'), 0);
        }

        if ($pack_type === 'BSK') {
            return ac_do_pick($line, array('basketQty', 'bskQty', 'Basket', 'UDF_BASKET', 'basket_qty', 'qty', 'Qty'), 0);
        }

        return ac_do_pick($line, array('cartonQty', 'basketQty', 'ctnQty', 'bskQty', 'qty', 'Qty'), 0);
    }
}

if (!function_exists('ac_do_line_weight_kg')) {
    function ac_do_line_weight_kg($line) {
        return ac_do_pick($line, array('kg', 'Kg', 'UDF_WEIGHTKG', 'WeightKG', 'weight_kg'), 0);
    }
}

if (!function_exists('ac_do_line_total_kg')) {
    function ac_do_line_total_kg($line) {
        /*
         * Prefer the saved/calculated total KG from the loader.
         * The loader already normalizes AutoCount Qty / local total_weight_kg into totalKg.
         * This prevents stale per-unit KG values from overriding edited totals.
         */
        $total = ac_do_to_float(ac_do_pick($line, array('totalKg', 'TotalKg', 'total_weight_kg'), 0));
        if ($total != 0.0) {
            return $total;
        }

        $qty = ac_do_to_float(ac_do_line_qty($line));
        $kg = ac_do_to_float(ac_do_line_weight_kg($line));

        if ($qty != 0.0 && $kg != 0.0) {
            return $qty * $kg;
        }

        return 0.0;
    }
}

if (!function_exists('ac_do_date')) {
    function ac_do_date($value) {
        if ($value instanceof DateTime) {
            return $value->format('Y-m-d');
        }

        $value = trim((string)$value);
        if ($value === '') return '';

        $timestamp = strtotime($value);
        return $timestamp ? date('Y-m-d', $timestamp) : $value;
    }
}

if (!function_exists('ac_do_wp_table_exists')) {
    function ac_do_wp_table_exists($table_name) {
        global $wpdb;

        if (!$wpdb) return false;

        return $wpdb->get_var(
            $wpdb->prepare('SHOW TABLES LIKE %s', $table_name)
        ) === $table_name;
    }
}

if (!function_exists('ac_do_wp_table_columns')) {
    function ac_do_wp_table_columns($table_name) {
        global $wpdb;

        static $cache = array();
        if (!$wpdb) return array();

        if (isset($cache[$table_name])) {
            return $cache[$table_name];
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table_name);
        $cols = $wpdb->get_col("SHOW COLUMNS FROM `{$safe_table}`", 0);

        $cache[$table_name] = is_array($cols) ? array_flip($cols) : array();

        return $cache[$table_name];
    }
}

if (!function_exists('ac_do_doc_no_from_data')) {
    function ac_do_doc_no_from_data($data) {
        $doc_no = ac_do_pick($data, array('docNo', 'DocNo', 'docno', 'sourceDocNo', 'oldDocNo', 'originalDocNo'), '');
        return strtoupper(trim((string)$doc_no));
    }
}

if (!function_exists('ac_do_doc_key_from_data')) {
    function ac_do_doc_key_from_data($data) {
        $doc_key = ac_do_pick($data, array('docKey', 'DocKey', 'dockey'), 0);
        return is_numeric($doc_key) ? (int)$doc_key : 0;
    }
}

if (!function_exists('ac_do_load_job_by_ref')) {
    function ac_do_load_job_by_ref($job_id, $doc_no, $doc_key) {
        global $wpdb;

        $table = $wpdb->prefix . 'ac_jobs';
        if (!ac_do_wp_table_exists($table)) return null;

        if ($job_id > 0) {
            return $wpdb->get_row(
                $wpdb->prepare("
                    SELECT *
                    FROM {$table}
                    WHERE id = %d
                      AND job_type = 'DELIVERY_ORDER'
                    LIMIT 1
                ", $job_id),
                ARRAY_A
            );
        }

        $rows = $wpdb->get_results("
            SELECT *
            FROM {$table}
            WHERE job_type = 'DELIVERY_ORDER'
            ORDER BY id DESC
            LIMIT 800
        ", ARRAY_A);

        $doc_no = strtoupper(trim((string)$doc_no));
        $doc_key = (int)$doc_key;

        foreach ((array)$rows as $row) {
            $payload = json_decode((string)($row['payload'] ?? ''), true);
            $result = json_decode((string)($row['result'] ?? ''), true);
            $payload = is_array($payload) ? $payload : array();
            $result = is_array($result) ? $result : array();

            $row_doc_no = ac_do_doc_no_from_data($result);
            if ($row_doc_no === '') {
                $row_doc_no = ac_do_doc_no_from_data($payload);
            }

            $row_doc_key = ac_do_doc_key_from_data($result);
            if ($row_doc_key <= 0) {
                $row_doc_key = ac_do_doc_key_from_data($payload);
            }

            if ($doc_no !== '' && $row_doc_no === $doc_no) {
                return $row;
            }

            if ($doc_key > 0 && $row_doc_key === $doc_key) {
                return $row;
            }
        }

        return null;
    }
}

if (!function_exists('ac_do_load_wp_only_order')) {
    function ac_do_load_wp_only_order($doc_no, $doc_key = 0) {
        global $wpdb;

        $do_table = $wpdb->prefix . 'ac_do';
        $items_table = $wpdb->prefix . 'ac_do_items';

        if (!ac_do_wp_table_exists($do_table) || !ac_do_wp_table_exists($items_table)) {
            return null;
        }

        $safe_do_table = preg_replace('/[^A-Za-z0-9_]/', '', $do_table);
        $safe_items_table = preg_replace('/[^A-Za-z0-9_]/', '', $items_table);
        $doc_no = strtoupper(trim((string)$doc_no));
        $doc_key = (int)$doc_key;

        $where = array();
        $args = array();
        if ($doc_no !== '') {
            $where[] = '(local_doc_no = %s OR autocount_doc_no = %s)';
            $args[] = $doc_no;
            $args[] = $doc_no;
        }
        if ($doc_key > 0) {
            $where[] = 'autocount_doc_key = %d';
            $args[] = $doc_key;
        }
        if (empty($where)) {
            return null;
        }

        $deleted_sql = '';
        $do_cols = ac_do_wp_table_columns($do_table);
        if (isset($do_cols['deleted_at'])) {
            $deleted_sql = ' AND deleted_at IS NULL';
        }

        $do = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM `{$safe_do_table}` WHERE (" . implode(' OR ', $where) . ") {$deleted_sql} ORDER BY id DESC LIMIT 1",
                $args
            ),
            ARRAY_A
        );

        if (!$do) {
            return null;
        }

        $do_id = (int)($do['id'] ?? 0);
        $item_rows = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT * FROM `{$safe_items_table}` WHERE do_id = %d ORDER BY line_no ASC, id ASC",
                $do_id
            ),
            ARRAY_A
        );

        $lines = array();
        foreach ((array)$item_rows as $line) {
            $basket = (float)($line['basket_qty'] ?? 0);
            $carton = (float)($line['carton_qty'] ?? 0);
            $qty = (float)($line['qty'] ?? 0);
            $pack_type = $carton > 0 ? 'CTN' : ($basket > 0 ? 'BSK' : '');
            $display_qty = $carton > 0 ? $carton : ($basket > 0 ? $basket : $qty);

            // In local DO items, qty/total_weight_kg is the total KG.
            // Derive KG per basket/carton from the saved total so the receipt follows staff edits.
            $total_line_kg = (float)($line['total_weight_kg'] ?? 0);
            if ($total_line_kg <= 0) {
                $total_line_kg = $qty;
            }

            $line_weight_kg = (float)($line['weight_kg'] ?? 0);
            if ($display_qty > 0 && $total_line_kg > 0) {
                $line_weight_kg = $total_line_kg / $display_qty;
            }

            if ($total_line_kg <= 0 && $display_qty > 0 && $line_weight_kg > 0) {
                $total_line_kg = $display_qty * $line_weight_kg;
            }

            $lines[] = array(
                'qty' => $display_qty,
                'kg' => $line_weight_kg,
                'totalKg' => $total_line_kg,
                'description' => (string)($line['description'] ?? '') !== '' ? (string)$line['description'] : (string)($line['item_code'] ?? ''),
                'packType' => $pack_type,
                'cartonQty' => $carton,
                'basketQty' => $basket,
            );
        }

        $doc_date = ac_do_date($do['doc_date'] ?? '');

        return array(
            'docNo' => (string)($do['local_doc_no'] ?? $do['autocount_doc_no'] ?? $doc_no),
            'docKey' => (int)($do['autocount_doc_key'] ?? 0),
            'customerCode' => (string)($do['debtor_code'] ?? ''),
            'customerName' => (string)($do['debtor_name'] ?? ''),
            'docDate' => $doc_date !== '' ? $doc_date : date('Y-m-d'),
            'remark' => (string)($do['remark'] ?? ''),
            'lines' => $lines,
        );
    }
}

if (!function_exists('ac_do_load_autocount_order')) {
    function ac_do_load_autocount_order($doc_no, $doc_key) {
        if (!function_exists('get_mssql')) return null;

        $conn = get_mssql();
        if (!$conn) return null;

        $where = array();
        $params = array();

        $doc_no = trim((string)$doc_no);
        $doc_key = (int)$doc_key;

        if ($doc_key > 0) {
            $where[] = 'DOH.DocKey = ?';
            $params[] = $doc_key;
        }

        if ($doc_no !== '') {
            $where[] = 'DOH.DocNo = ?';
            $params[] = $doc_no;
        }

        if (empty($where)) return null;

        $header_sql = "
            SELECT TOP 1
                DOH.DocKey,
                DOH.DocNo,
                DOH.DocDate,
                DOH.DebtorCode,
                DOH.DebtorName,
                ISNULL(DOH.UDF_SUMBASKET, 0) AS TotalBasket,
                ISNULL(DOH.UDF_SUMCARTON, 0) AS TotalCarton
            FROM dbo.[DO] AS DOH
            WHERE " . implode(' OR ', $where) . "
            ORDER BY DOH.DocKey DESC
        ";

        $stmt = sqlsrv_query($conn, $header_sql, $params);
        if ($stmt === false) return null;

        $header = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC);
        sqlsrv_free_stmt($stmt);

        if (!$header) return null;

        $detail_sql = "
            SELECT
                ISNULL(DTL.ItemCode, '') AS ItemCode,
                ISNULL(DTL.Description, '') AS Description,
                ISNULL(DTL.Qty, 0) AS Qty,
                ISNULL(DTL.UDF_BASKET, 0) AS Basket,
                ISNULL(DTL.UDF_CARTON, 0) AS Carton,
                ISNULL(DTL.UDF_WEIGHTKG, 0) AS WeightKG
            FROM dbo.DODTL AS DTL
            WHERE DTL.DocKey = ?
              AND ISNULL(DTL.MainItem, 'T') = 'T'
            ORDER BY ISNULL(DTL.Seq, 0) ASC, ISNULL(DTL.ItemCode, '') ASC
        ";

        $detail_stmt = sqlsrv_query($conn, $detail_sql, array((int)$header['DocKey']));
        $lines = array();

        if ($detail_stmt !== false) {
            while ($line = sqlsrv_fetch_array($detail_stmt, SQLSRV_FETCH_ASSOC)) {
                $basket = (float)($line['Basket'] ?? 0);
                $carton = (float)($line['Carton'] ?? 0);

                // In this system, AutoCount DODTL.Qty is the total KG.
                // UDF_BASKET / UDF_CARTON is the basket/carton count.
                // UDF_WEIGHTKG can be stale after edits, so derive KG per unit from Qty.
                $total_line_kg = (float)($line['Qty'] ?? 0);

                $pack_type = $carton > 0 ? 'CTN' : ($basket > 0 ? 'BSK' : '');
                $display_qty = $carton > 0 ? $carton : ($basket > 0 ? $basket : $total_line_kg);

                $line_weight_kg = (float)($line['WeightKG'] ?? 0);
                if ($display_qty > 0 && $total_line_kg > 0) {
                    $line_weight_kg = $total_line_kg / $display_qty;
                }

                if ($total_line_kg <= 0 && $display_qty > 0 && $line_weight_kg > 0) {
                    $total_line_kg = $display_qty * $line_weight_kg;
                }

                $lines[] = array(
                    'qty' => $display_qty,
                    'kg' => $line_weight_kg,
                    'totalKg' => $total_line_kg,
                    'description' => $line['Description'] !== '' ? $line['Description'] : $line['ItemCode'],
                    'packType' => $pack_type,
                    'cartonQty' => $carton,
                    'basketQty' => $basket,
                );
            }

            sqlsrv_free_stmt($detail_stmt);
        }

        return array(
            'docNo' => (string)($header['DocNo'] ?? ''),
            'docKey' => (int)($header['DocKey'] ?? 0),
            'customerCode' => (string)($header['DebtorCode'] ?? ''),
            'customerName' => (string)($header['DebtorName'] ?? ''),
            'docDate' => ac_do_date($header['DocDate'] ?? ''),
            'remark' => '',
            'lines' => $lines,
        );
    }
}

if (!function_exists('ac_do_get_proof_url')) {
    function ac_do_get_proof_url($job_id, $doc_key, $doc_no) {
        global $wpdb;

        $table = $wpdb->prefix . 'ac_do_proof_images';
        if (!ac_do_wp_table_exists($table)) return '';

        $cols = ac_do_wp_table_columns($table);
        $where = array();
        $args = array();

        if ((int)$job_id > 0 && isset($cols['job_id'])) {
            $where[] = 'job_id = %d';
       �gp�|�]�����������
N?�^     $args[] = (int)$job_id;
        }

        if ((int)$doc_key > 0 && isset($cols['doc_key'])) {
            $where[] = 'doc_key = %d';
            $args[] = (int)$doc_key;
        }

        $doc_no = trim((string)$doc_no);
        if ($doc_no !== '' && isset($cols['doc_no'])) {
            $where[] = 'doc_no = %s';
            $args[] = $doc_no;
        }

        if (empty($where)) return '';

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $select = array();

        foreach (array('attachment_id', 'image_url') as $col) {
            if (isset($cols[$col])) {
                $select[] = "`{$col}`";
            }
        }

        if (empty($select)) return '';

        $where_sql = '(' . implode(' OR ', $where) . ')';

        if (isset($cols['proof_type'])) {
            $where_sql .= ' AND proof_type = %s';
            $args[] = 'DELIVERY_PROOF';
        }

        if (isset($cols['deleted_at'])) {
            $where_sql .= ' AND deleted_at IS NULL';
        }

        $order_col = isset($cols['captured_at']) ? 'captured_at' : 'id';

        $sql = "
            SELECT " . implode(', ', $select) . "
            FROM `{$safe_table}`
            WHERE {$where_sql}
            ORDER BY `{$order_col}` ASC
            LIMIT 1
        ";

        $proof = $wpdb->get_row($wpdb->prepare($sql, $args), ARRAY_A);
        if (!$proof) return '';

        if (!empty($proof['attachment_id'])) {
            $attachment_url = wp_get_attachment_url((int)$proof['attachment_id']);
            if (!empty($attachment_url)) {
                return $attachment_url;
            }
        }

        return !empty($proof['image_url']) ? esc_url_raw((string)$proof['image_url']) : '';
    }
}

$job_id = isset($_GET['job_id']) ? absint($_GET['job_id']) : 0;
$doc_no_param = isset($_GET['docNo']) ? sanitize_text_field(wp_unslash($_GET['docNo'])) : '';
$doc_key_param = isset($_GET['docKey']) ? absint($_GET['docKey']) : 0;

if ($doc_no_param === '' && isset($_GET['docno'])) {
    $doc_no_param = sanitize_text_field(wp_unslash($_GET['docno']));
}

if ($doc_key_param <= 0 && isset($_GET['dockey'])) {
    $doc_key_param = absint($_GET['dockey']);
}

$job = ac_do_load_job_by_ref($job_id, $doc_no_param, $doc_key_param);
$payload = array();
$result = array();

if ($job) {
    $payload = json_decode((string)($job['payload'] ?? ''), true);
    $result = json_decode((string)($job['result'] ?? ''), true);
    $payload = is_array($payload) ? $payload : array();
    $result = is_array($result) ? $result : array();
}

$doc_no = $doc_no_param !== '' ? strtoupper(trim($doc_no_param)) : ac_do_doc_no_from_data($result);
if ($doc_no === '') {
    $doc_no = ac_do_doc_no_from_data($payload);
}

$doc_key = $doc_key_param > 0 ? $doc_key_param : ac_do_doc_key_from_data($result);
if ($doc_key <= 0) {
    $doc_key = ac_do_doc_key_from_data($payload);
}

$autocount_order = ac_do_load_autocount_order($doc_no, $doc_key);

$wp_only_order = null;
if (!$autocount_order) {
    $wp_only_order = ac_do_load_wp_only_order($doc_no, $doc_key);
}

if ($autocount_order) {
    $doc_no = $autocount_order['docNo'];
    $doc_key = $autocount_order['docKey'];
    $customer_code = $autocount_order['customerCode'];
    $customer_name = $autocount_order['customerName'];
    $doc_date = $autocount_order['docDate'];
    $remark = $autocount_order['remark'];
    $lines = $autocount_order['lines'];
} elseif ($wp_only_order) {
    $doc_no = $wp_only_order['docNo'];
    $doc_key = $wp_only_order['docKey'];
    $customer_code = $wp_only_order['customerCode'];
    $customer_name = $wp_only_order['customerName'];
    $doc_date = $wp_only_order['docDate'];
    $remark = $wp_only_order['remark'];
    $lines = $wp_only_order['lines'];
} elseif (!empty($payload)) {
    $customer_code = ac_do_pick($payload, array('customerCode', 'debtorCode', 'DebtorCode'), '');
    $customer_name = ac_do_pick($payload, array('customerName', 'debtorName', 'DebtorName'), '');
    $doc_date = ac_do_pick($payload, array('docDate', 'DocDate'), date('Y-m-d'));
    $remark = ac_do_pick($payload, array('remark', 'Remark'), '');
    $lines = isset($payload['lines']) && is_array($payload['lines']) ? $payload['lines'] : array();
} else {
    wp_die('Delivery order not found.');
}

if ($doc_no === '' && $job) {
    $doc_no = 'DO-' . str_pad((string)$job['id'], 6, '0', STR_PAD_LEFT);
}

$company_name = 'EXCELLENT VEGE SDN. BHD.';
$company_addr = 'No. 45, 47, Complex Pasar Borong, 3rd Miles, Jalan Ipoh, 51200 Kuala Lumpur';
$company_tel  = '017-4373 752 / 016-963 752 / 012-3013 752';
$company_logo_url = 'https://website.ipohserver.com/excellentvege/wp-content/uploads/2026/05/Untitled-design-15.png';

if (!defined('AC_DO_LINES_PER_PAGE')) {
    /*
     * A5 landscape cannot safely hold unlimited rows.
     * Keep this conservative so totals/signatures never overlap item rows.
     */
    define('AC_DO_LINES_PER_PAGE', 16);
}

$total_ctn = 0;
$total_bsk = 0;
$total_kg = 0;

foreach ($lines as $line) {
    $pack_type = ac_do_line_pack_type($line);

    if ($pack_type === 'CTN') {
        $total_ctn += ac_do_to_float(ac_do_line_qty($line));
    } elseif ($pack_type === 'BSK') {
        $total_bsk += ac_do_to_float(ac_do_line_qty($line));
    }

    $total_kg += ac_do_line_total_kg($line);
}

$display_date = $doc_date;
$timestamp = strtotime((string)$doc_date);
if ($timestamp) {
    $display_date = date('d/m/Y', $timestamp);
}

$proof_url = ac_do_get_proof_url((int)($job['id'] ?? 0), $doc_key, $doc_no);
$auto_print = isset($_GET['autoPrint']) && sanitize_text_field(wp_unslash($_GET['autoPrint'])) === '1';
$print_page = isset($_GET['printPage']) ? sanitize_text_field(wp_unslash($_GET['printPage'])) : '';
$auto_print_do_only = $print_page === 'do';
$pdf_file_name = 'Delivery-Order-' . preg_replace('/[^A-Za-z0-9_-]/', '-', $doc_no !== '' ? $doc_no : 'DO') . '.pdf';
$pdf_lines = array();

foreach ($lines as $line) {
    $pack_type = ac_do_line_pack_type($line);
    $is_ctn = ($pack_type === 'CTN');
    $is_bsk = ($pack_type === 'BSK');
    $qty = ac_do_line_qty($line);
    $kg = ac_do_line_weight_kg($line);
    $total_line_kg = ac_do_line_total_kg($line);

    $pdf_lines[] = array(
        'qty' => ac_do_num($qty),
        'kg' => ac_do_num($kg),
        'description' => (string)ac_do_pick($line, array('description', 'Description', 'itemName', 'ItemName', 'itemDescription', 'ItemDescription', 'itemCode', 'ItemCode'), ''),
        'isCtn' => $is_ctn,
        'isBsk' => $is_bsk,
        'totalKg' => ac_do_num($total_line_kg),
    );
}

$pdf_payload = array(
    'companyName' => $company_name,
    'companyAddr' => $company_addr,
    'companyTel' => $company_tel,
    'companyLogoUrl' => $company_logo_url,
    'docNo' => $doc_no,
    'customerCode' => $customer_code,
    'customerName' => $customer_name,
    'displayDate' => $display_date,
    'remark' => $remark,
    'lines' => $pdf_lines,
    'totalCtn' => ac_do_num($total_ctn),
    'totalBsk' => ac_do_num($total_bsk),
    'totalKg' => ac_do_num($total_kg),
    'proofUrl' => $proof_url,
);

$ac_do_line_pages = array_chunk($lines, AC_DO_LINES_PER_PAGE);
if (empty($ac_do_line_pages)) {
    $ac_do_line_pages = array(array());
}
$ac_do_total_line_pages = count($ac_do_line_pages);
?>

<style>
    * { box-sizing: border-box; }

    .ac-do-page-wrap {
        --ac-do-preview-scale: 1;
        --ac-do-preview-width: 794px;
        --ac-do-preview-height: 559px;
        --ac-do-preview-gap: 20px;
        width: 100%;
        background: #e5e5e5;
        padding: 20px 0 40px;
        font-family: Arial, Helvetica, sans-serif;
        color: #222;
        overflow-x: hidden;
    }

    .ac-do-actions {
        width: min(794px, calc(100vw - 32px));
        margin: 0 auto 14px;
        display: flex;
        justify-content: flex-end;
        gap: 8px;
        text-align: right;
    }

    .ac-do-actions button {
        border: 0;
        background: #0B4A2D;
        color: #fff;
        padding: 10px 18px;
        border-radius: 6px;
        font-size: 14px;
        cursor: pointer;
        font-weight: 700;
    }

    .ac-do-actions button.ac-do-back-btn {
        background: #475569;
        margin-right: auto;
    }

    .ac-do-actions button.ac-do-share-btn {
        background: #128C7E;
    }

    .ac-do-actions button.ac-do-hidden-print-btn {
        display: none !important;
    }

    .ac-do-actions button:disabled {
        cursor: not-allowed;
        opacity: 0.65;
    }

    .ac-do-share-source {
        width: 794px;
        height: 559px;
        margin: 0 auto;
        background: #fff;
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        z-index: 2147483647;
        pointer-events: none;
    }

    .ac-do-share-source .ac-do-paper,
    .ac-do-share-source .ac-do-proof-paper {
        margin: 0 auto;
        box-shadow: none;
    }

    .ac-do-share-source .ac-do-proof-paper {
        margin-top: 0;
        page-break-before: always;
        break-before: page;
    }

    .ac-do-preview-page {
        width: var(--ac-do-preview-width);
        height: var(--ac-do-preview-height);
        margin: 0 auto;
        position: relative;
    }

    .ac-do-preview-page + .ac-do-preview-page {
        margin-top: var(--ac-do-preview-gap);
    }

    .ac-do-preview-page > .ac-do-paper,
    .ac-do-preview-page > .ac-do-proof-paper {
        position: absolute;
        top: 0;
        left: 0;
        transform: scale(var(--ac-do-preview-scale));
        transform-origin: top left;
    }

    .ac-do-preview-page > .ac-do-proof-paper {
        margin-top: 0;
    }

    .ac-do-paper,
    .ac-do-proof-paper {
        width: 794px;
        height: 559px;
        min-height: 559px;
        margin: 0 auto;
        background: #fff;
        padding: 22px 32px;
        border: 1px solid #d1d5db;
        position: relative;
        overflow: hidden;
    }

    .ac-do-proof-paper { margin-top: 20px; }

    .ac-do-top-label {
        position: absolute;
        top: 14px;
        right: 32px;
        text-align: right;
        margin-bottom: 0;
    }

    .ac-do-top-label span {
        background: #444;
        color: #fff;
        font-weight: 700;
        font-size: 12px;
        padding: 3px 12px;
        border-radius: 10px;
        letter-spacing: 0.4px;
    }

    .ac-do-header {
        display: grid;
        grid-template-columns: 120px 1fr 170px;
        gap: 12px;
        align-items: start;
        margin: 8px 0 5px;
    }

    .ac-do-logo {
        height: 64px;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    .ac-do-logo img {
        display: block;
        max-width: 112px;
        max-height: 58px;
        width: auto;
        height: auto;
        object-fit: contain;
    }

    .ac-do-company h1 {
        margin: 0;
        font-size: 26px;
        letter-spacing: 0.2px;
        line-height: 0.98;
        font-weight: 900;
    }

    .ac-do-company p {
        margin: 2px 0;
        font-size: 10.5px;
        line-height: 1.15;
    }

    .ac-do-doc-no {
        text-align: right;
        padding-top: 48px;
        font-size: 14px;
        white-space: nowrap;
    }

    .ac-do-doc-no strong {
        color: #d54b4b;
        font-size: 19px;
        letter-spacing: 1px;
    }

    .ac-do-info-row {
        display: grid;
        grid-template-columns: minmax(0, 1fr) 170px;
        gap: 12px;
        margin: 4px 0 8px;
        font-size: 15px;
    }

    .ac-do-line-field {
        display: grid;
        grid-template-columns: 92px minmax(0, 1fr);
        align-items: end;
        min-width: 0;
    }

    .ac-do-line-field.ac-do-date {
        grid-template-columns: 52px minmax(0, 1fr);
    }

    .ac-do-line-field span {
        font-weight: 700;
        white-space: nowrap;
        padding-right: 8px;
    }

    .ac-do-line-field div {
        border-bottom: 1px dotted #666;
        min-height: 18px;
        padding-left: 8px;
        white-space: normal;
        overflow-wrap: anywhere;
        word-break: break-word;
        line-height: 1.2;
    }

    .ac-do-line-field.ac-do-date div {
        white-space: nowrap;
        overflow-wrap: normal;
        word-break: normal;
    }

    .ac-do-table {
        width: 100%;
        border-collapse: collapse;
        background: rgba(255, 255, 255, 0.15);
        font-size: 13px;
        table-layout: fixed;
    }

    .ac-do-table th,
    .ac-do-table td {
        border: 1px solid #444;
        padding: 1px 5px;
        vertical-align: middle;
        height: 18px;
        line-height: 1.05;
    }

    .ac-do-table th {
        text-align: center;
        font-size: 12px;
        line-height: 1.02;
        font-weight: 700;
        height: 22px;
    }

    .ac-do-qty,
    .ac-do-kg,
    .ac-do-total {
        text-align: center;
        width: 68px;
    }

    .ac-do-desc { width: auto; }

    .ac-do-pack {
        width: 98px;
        text-align: center;
        font-size: 13px;
        white-space: nowrap;
    }

    .ac-do-checkbox {
        display: inline-block;
        width: 10px;
        height: 10px;
        border: 1px solid #222;
        margin-right: 2px;
        vertical-align: -1px;
        position: relative;
    }

    .ac-do-checkbox.checked::after {
        content: "✓";
        position: absolute;
        left: 1px;
        top: -6px;
        font-size: 15px;
        font-weight: 700;
    }

    .ac-do-bottom-area {
        display: grid;
        grid-template-columns: 1fr 220px;
        gap: 18px;
        margin-top: 6px;
        align-items: start;
    }

    .ac-do-slogan {
        font-weight: 700;
        font-style: italic;
        font-size: 14px;
        margin-top: 4px;
    }

    .ac-do-continued {
        margin-top: 8px;
        font-size: 12px;
        font-weight: 700;
        color: #444;
    }

    .ac-do-page-count {
        margin-top: 5px;
        font-size: 10px;
        color: #555;
        font-weight: 700;
    }

    .ac-do-totals {
        display: grid;
        grid-template-columns: 1fr 72px;
        gap: 5px 8px;
        align-items: center;
        font-size: 14px;
    }

    .ac-do-total-label {
        text-align: right;
        line-height: 1.05;
    }

    .ac-do-total-box {
        border: 1px solid #444;
        height: 23px;
        background: rgba(255, 255, 255, 0.25);
        display: flex;
        align-items: center;
        justify-content: center;
        font-weight: 700;
    }

    .ac-do-remark {
        margin-top: 8px;
        font-size: 13px;
    }

    .ac-do-signature-row {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 90px;
        margin-top: 18px;
        font-size: 13px;
    }

    .ac-do-signature {
        border-top: 1px dotted #555;
        padding-top: 5px;
    }

    .ac-proof-title-row {
        display: grid;
        grid-template-columns: minmax(0, 1fr) 390px;
        gap: 20px;
        align-items: start;
        margin-bottom: 14px;
        border-bottom: 2px solid #333;
        padding-bottom: 10px;
    }

    .ac-proof-title h2 {
        margin: 0;
        font-size: 23px;
        line-height: 1.2;
    }

    .ac-proof-title p {
        margin: 4px 0 0;
        font-size: 13px;
    }

    .ac-proof-meta {
        text-align: right;
        font-size: 14px;
        line-height: 1.45;
        width: 390px;
        max-width: 390px;
        margin-left: auto;
        white-space: normal;
        overflow-wrap: anywhere;
        word-break: break-word;
    }

    .ac-proof-image-box {
        width: 100%;
        height: 365px;
        border: 1px solid #444;
        background: rgba(255,255,255,0.28);
        display: ���@�aI^����������
N?�_flex;
        align-items: center;
        justify-content: center;
        padding: 12px;
        overflow: hidden;
    }

    .ac-proof-image-box img {
        max-width: 100%;
        max-height: 340px;
        width: auto;
        height: auto;
        object-fit: contain;
        display: block;
    }

    .ac-proof-empty {
        font-size: 15px;
        font-weight: 700;
        color: #555;
        text-align: center;
    }

    .ac-proof-footer {
        margin-top: 16px;
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 70px;
        font-size: 13px;
    }

    .ac-proof-footer-line {
        border-top: 1px dotted #555;
        padding-top: 5px;
    }

    @media screen and (max-width: 860px) {
        .ac-do-page-wrap {
            --ac-do-preview-scale: 0.9;
            --ac-do-preview-width: 715px;
            --ac-do-preview-height: 503px;
            --ac-do-preview-gap: 18px;
        }
    }

    @media screen and (max-width: 760px) {
        .ac-do-page-wrap {
            --ac-do-preview-scale: 0.82;
            --ac-do-preview-width: 651px;
            --ac-do-preview-height: 458px;
            --ac-do-preview-gap: 16px;
            padding: 14px 0 28px;
        }

        .ac-do-actions {
            justify-content: center;
            flex-wrap: wrap;
            gap: 8px;
        }
    }

    @media screen and (max-width: 680px) {
        .ac-do-page-wrap {
            --ac-do-preview-scale: 0.72;
            --ac-do-preview-width: 572px;
            --ac-do-preview-height: 402px;
            --ac-do-preview-gap: 14px;
        }
    }

    @media screen and (max-width: 600px) {
        .ac-do-page-wrap {
            --ac-do-preview-scale: 0.62;
            --ac-do-preview-width: 492px;
            --ac-do-preview-height: 347px;
            --ac-do-preview-gap: 13px;
        }
    }

    @media screen and (max-width: 520px) {
        .ac-do-page-wrap {
            --ac-do-preview-scale: 0.52;
            --ac-do-preview-width: 413px;
            --ac-do-preview-height: 291px;
            --ac-do-preview-gap: 11px;
        }

        .ac-do-actions button {
            padding: 9px 12px;
            font-size: 13px;
        }
    }

    @media screen and (max-width: 430px) {
        .ac-do-page-wrap {
            --ac-do-preview-scale: 0.45;
            --ac-do-preview-width: 357px;
            --ac-do-preview-height: 252px;
            --ac-do-preview-gap: 10px;
        }
    }

    @media print {
        html,
        body {
            background: #fff !important;
            margin: 0 !important;
            padding: 0 !important;
            width: 210mm !important;
            height: 148mm !important;
            -webkit-print-color-adjust: exact !important;
            print-color-adjust: exact !important;
        }

        #wpadminbar,
        header,
        footer,
        .site-header,
        .site-footer,
        .elementor-location-header,
        .elementor-location-footer,
        .ac-do-actions {
            display: none !important;
        }

        .ac-do-page-wrap {
            width: 210mm !important;
            margin: 0 !important;
            padding: 0 !important;
            background: #fff !important;
            overflow: hidden !important;
        }

        .ac-do-preview-page {
            width: 210mm !important;
            height: 148mm !important;
            margin: 0 !important;
            position: static !important;
            overflow: hidden !important;
            page-break-after: always !important;
            break-after: page !important;
        }

        .ac-do-preview-page + .ac-do-preview-page {
            margin-top: 0 !important;
        }

        .ac-do-paper,
        .ac-do-proof-paper {
            width: 210mm !important;
            height: 148mm !important;
            min-height: 0 !important;
            margin: 0 !important;
            padding: 5mm 8mm !important;
            border: 0 !important;
            box-shadow: none !important;
            overflow: hidden !important;
            background: #fff !important;
            position: relative !important;
            left: auto !important;
            top: auto !important;
            transform: none !important;
        }

        .ac-do-paper {
            page-break-after: always !important;
            break-after: page !important;
        }

        .ac-do-proof-paper {
            margin-top: 0 !important;
            page-break-after: auto !important;
            break-after: auto !important;
        }

        .ac-do-header {
            margin-top: 2mm !important;
        }

        .ac-do-table th,
        .ac-do-table td {
            height: 4mm !important;
            padding: 0.45mm 1.2mm !important;
        }

        .ac-do-table th {
            height: 5mm !important;
        }

        .ac-do-bottom-area {
            margin-top: 2mm !important;
        }

        .ac-do-signature-row {
            margin-top: 4.5mm !important;
        }

        .ac-proof-image-box {
            height: 96mm !important;
            min-height: 96mm !important;
            max-height: 96mm !important;
        }

        .ac-proof-image-box img {
            max-width: 100% !important;
            max-height: 92mm !important;
            object-fit: contain !important;
        }

        @page {
            size: A5 landscape;
            margin: 0;
        }
    }
</style>

<div class="ac-do-page-wrap">
    <div class="ac-do-actions">
        <button type="button" class="ac-do-back-btn" onclick="acDoGoBack()">Back</button>
        <button type="button" class="ac-do-hidden-print-btn" onclick="acDoPrintDeliveryOrderOnly(this)" aria-hidden="true" tabindex="-1">Print</button>
        <button type="button" onclick="acDoPrintFullPdf(this)">Print / Save PDF</button>
        <button type="button" class="ac-do-share-btn" onclick="acDoSharePdf(this)">Share PDF</button>
    </div>

    <?php foreach ($ac_do_line_pages as $page_index => $page_lines): ?>
        <?php
        $is_last_do_page = ((int)$page_index === (int)$ac_do_total_line_pages - 1);
        $minimum_rows = $is_last_do_page ? 10 : AC_DO_LINES_PER_PAGE;
        $remaining_rows = max(0, $minimum_rows - count($page_lines));
        ?>
        <div class="ac-do-preview-page">
        <div class="ac-do-paper">
            <div class="ac-do-top-label">
                <span>DELIVERY ORDER</span>
                <?php if ($ac_do_total_line_pages > 1): ?>
                    <div class="ac-do-page-count">Page <?php echo ac_do_h((int)$page_index + 1); ?> / <?php echo ac_do_h($ac_do_total_line_pages); ?></div>
                <?php endif; ?>
            </div>

            <div class="ac-do-header">
                <div class="ac-do-logo">
                    <img src="<?php echo esc_url($company_logo_url); ?>" alt="<?php echo esc_attr($company_name); ?>">
                </div>

                <div class="ac-do-company">
                    <h1><?php echo ac_do_h($company_name); ?></h1>
                    <p><?php echo ac_do_h($company_addr); ?></p>
                    <p>H/P: <?php echo ac_do_h($company_tel); ?></p>
                </div>

                <div class="ac-do-doc-no">
                    No <strong><?php echo ac_do_h($doc_no); ?></strong>
                </div>
            </div>

            <div class="ac-do-info-row">
                <div class="ac-do-line-field">
                    <span>Customer</span>
                    <div>
                        <?php echo ac_do_h($customer_name); ?>
                        <?php if ($customer_code !== ''): ?>
                            (<?php echo ac_do_h($customer_code); ?>)
                        <?php endif; ?>
                    </div>
                </div>

                <div class="ac-do-line-field ac-do-date">
                    <span>Date</span>
                    <div><?php echo ac_do_h($display_date); ?></div>
                </div>
            </div>

            <table class="ac-do-table">
                <thead>
                    <tr>
                        <th>数量<br>Quantity</th>
                        <th>公斤<br>Kg</th>
                        <th>货物名称<br>Description</th>
                        <th>箱 / 篮<br>Box / Basket</th>
                        <th>总公斤<br>Total Kg</th>
                    </tr>
                </thead>

                <tbody>
                    <?php foreach ($page_lines as $line): ?>
                        <?php
                        $pack_type = ac_do_line_pack_type($line);
                        $is_ctn = ($pack_type === 'CTN');
                        $is_bsk = ($pack_type === 'BSK');
                        $qty = ac_do_line_qty($line);
                        $kg = ac_do_line_weight_kg($line);
                        $total_line_kg = ac_do_line_total_kg($line);
                        $description = ac_do_pick($line, array('description', 'Description', 'itemName', 'ItemName', 'itemDescription', 'ItemDescription', 'itemCode', 'ItemCode'), '');
                        ?>

                        <tr>
                            <td class="ac-do-qty"><?php echo ac_do_h(ac_do_num($qty)); ?></td>
                            <td class="ac-do-kg"><?php echo ac_do_h(ac_do_num($kg)); ?></td>
                            <td class="ac-do-desc"><?php echo ac_do_h($description); ?></td>
                            <td class="ac-do-pack">
                                <span class="ac-do-checkbox <?php echo $is_ctn ? 'checked' : ''; ?>"></span>Ctn
                                &nbsp;
                                <span class="ac-do-checkbox <?php echo $is_bsk ? 'checked' : ''; ?>"></span>Bsk
                            </td>
                            <td class="ac-do-total"><?php echo ac_do_h(ac_do_num($total_line_kg)); ?></td>
                        </tr>
                    <?php endforeach; ?>

                    <?php for ($i = 0; $i < $remaining_rows; $i++): ?>
                        <tr>
                            <td class="ac-do-qty">&nbsp;</td>
                            <td class="ac-do-kg"></td>
                            <td class="ac-do-desc"></td>
                            <td class="ac-do-pack">
                                <span class="ac-do-checkbox"></span>Ctn
                                &nbsp;
                                <span class="ac-do-checkbox"></span>Bsk
                            </td>
                            <td class="ac-do-total"></td>
                        </tr>
                    <?php endfor; ?>
                </tbody>
            </table>

            <?php if ($is_last_do_page): ?>
                <div class="ac-do-bottom-area">
                    <div>
                        <div class="ac-do-slogan">We Do The EXCELLENT Way</div>

                        <?php if ($remark !== ''): ?>
                            <div class="ac-do-remark">
                                <strong>Remark:</strong> <?php echo ac_do_h($remark); ?>
                            </div>
                        <?php endif; ?>
                    </div>

                    <div class="ac-do-totals">
                        <div class="ac-do-total-label">总箱<br>Total Ctn</div>
                        <div class="ac-do-total-box"><?php echo ac_do_h(ac_do_num($total_ctn)); ?></div>

                        <div class="ac-do-total-label">总篮<br>Total Bsk</div>
                        <div class="ac-do-total-box"><?php echo ac_do_h(ac_do_num($total_bsk)); ?></div>

                        <div class="ac-do-total-label">总公斤<br>Total Kg</div>
                        <div class="ac-do-total-box"><?php echo ac_do_h(ac_do_num($total_kg)); ?></div>
                    </div>
                </div>

                <div class="ac-do-signature-row">
                    <div class="ac-do-signature">经手人 Issued by</div>
                    <div class="ac-do-signature">收货人 Received by</div>
                </div>
            <?php else: ?>
                <div class="ac-do-bottom-area">
                    <div>
                        <div class="ac-do-slogan">We Do The EXCELLENT Way</div>
                        <div class="ac-do-continued">Continued on next page...</div>
                    </div>
                    <div class="ac-do-continued" style="text-align:right;">
                        Page <?php echo ac_do_h((int)$page_index + 1); ?> / <?php echo ac_do_h($ac_do_total_line_pages); ?>
                    </div>
                </div>
            <?php endif; ?>
        </div>
        </div>
    <?php endforeach; ?>

    <div class="ac-do-preview-page">
    <div class="ac-do-proof-paper">
        <div class="ac-proof-title-row">
            <div class="ac-proof-title">
                <h2>Proof of Delivery</h2>
                <p><?php echo ac_do_h($company_name); ?></p>
            </div>

            <div class="ac-proof-meta">
                <strong>DO No:</strong> <?php echo ac_do_h($doc_no); ?><br>
                <strong>Customer:</strong> <?php echo ac_do_h($customer_name); ?><br>
                <strong>Date:</strong> <?php echo ac_do_h($display_date); ?>
            </div>
        </div>

        <div class="ac-proof-image-box">
            <?php if ($proof_url !== ''): ?>
                <img
                    src="<?php echo esc_url($proof_url); ?>"
                    alt="Proof of Delivery"
                    loading="eager"
                    decoding="sync"
                >
            <?php else: ?>
                <div class="ac-proof-empty">
                    No proof of delivery image uploaded yet.
                </div>
            <?php endif; ?>
        </div>

        <div class="ac-proof-footer">
            <div class="ac-proof-footer-line">Driver / Issued by</div>
            <div class="ac-proof-footer-line">Customer / Received by</div>
        </div>
    </div>
    </div>
</div>

<script>
var acDoPdfFileName = <?php echo wp_json_encode($pdf_file_name); ?>;
var acDoPdfData = <?php echo wp_json_encode($pdf_payload); ?>;
var acDoLinesPerPage = <?php echo (int)AC_DO_LINES_PER_PAGE; ?>;
var acDoAutoPrint = <?php echo $auto_print ? 'true' : 'false'; ?>;
var acDoAutoPrintDoOnly = <?php echo $auto_print_do_only ? 'true' : 'false'; ?>;
var acDoJsPdfPromise = null;

function acDoGoBack() {
    var fallbackUrl = <?php echo wp_json_encode(home_url('/delivery-order-records/')); ?>;
    var params = new URLSearchParams(window.location.search || '');

    // Important: popup detection must be checked BEFORE history.length.
    // A popup opened from the staff list can still inherit a browser history length,
    // so using history.length first may send the popup "back" instead of closing it.
    var isStaffPrintPopup = params.get('autoPrint') === '1'
        || window.name === 'wstDodPrintWindow'
        || !!window.opener;

    if (isStaffPrintPopup) {
        window.close();

        // Some browsers refuse window.close() in normal tabs.
        // If the close is blocked, send the user back to the staff list instead.
        setTimeout(function() {
            if (!window.closed) {
                window.location.href = fallbackUrl;
            }
        }, 250);

        return;
    }

    if (window.history.length > 1) {
        window.history.back();
        return;
    }

    window.location.href = fallbackUrl;
}

function acDoWaitImage(img) {
    return new Promise(function(resolve) {
        if (!img) {
            resolve();
            return;
        }

        if (img.complete && img.naturalWidth > 0) {
            resolve();
            return;
        }

        var done = false;

        function finish() {
            if (done) return;
            done = true;
            resolve();
        }

        img.on�떁�p$_���������y
N?�`load = finish;
        img.onerror = finish;

        setTimeout(finish, 4000);
    });
}

function acDoLoadJsPdf() {
    if (window.jspdf && window.jspdf.jsPDF) {
        return Promise.resolve(window.jspdf.jsPDF);
    }

    if (acDoJsPdfPromise) {
        return acDoJsPdfPromise;
    }

    acDoJsPdfPromise = new Promise(function(resolve, reject) {
        var script = document.createElement('script');
        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
        script.async = true;
        script.onload = function() {
            if (window.jspdf && window.jspdf.jsPDF) {
                resolve(window.jspdf.jsPDF);
                return;
            }

            reject(new Error('jsPDF library did not load.'));
        };
        script.onerror = function() {
            reject(new Error('PDF library could not be loaded.'));
        };
        document.head.appendChild(script);
    });

    return acDoJsPdfPromise;
}

function acDoCanvas(width, height) {
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');

    canvas.width = width;
    canvas.height = height;
    ctx.imageSmoothingEnabled = true;
    ctx.imageSmoothingQuality = 'high';

    return { canvas: canvas, ctx: ctx };
}

function acDoText(ctx, text, x, y, size, color, weight, align) {
    ctx.fillStyle = color || '#111111';
    ctx.font = (weight || '400') + ' ' + size + 'px Arial, Helvetica, sans-serif';
    ctx.textAlign = align || 'left';
    ctx.textBaseline = 'alphabetic';
    ctx.fillText(String(text || ''), x, y);
}

function acDoLine(ctx, x1, y1, x2, y2, color, width) {
    ctx.strokeStyle = color || '#333333';
    ctx.lineWidth = width || 1;
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.stroke();
}

function acDoRect(ctx, x, y, width, height, color, lineWidth) {
    ctx.strokeStyle = color || '#333333';
    ctx.lineWidth = lineWidth || 1;
    ctx.strokeRect(x, y, width, height);
}

function acDoFillRect(ctx, x, y, width, height, color) {
    ctx.fillStyle = color;
    ctx.fillRect(x, y, width, height);
}

function acDoWrap(ctx, text, x, y, maxWidth, lineHeight, size, color, weight, maxLines) {
    var words = String(text || '').split(/\s+/);
    var line = '';
    var currentY = y;
    var lines = [];
    var i;
    var test;

    ctx.fillStyle = color || '#111111';
    ctx.font = (weight || '400') + ' ' + size + 'px Arial, Helvetica, sans-serif';
    ctx.textAlign = 'left';
    ctx.textBaseline = 'alphabetic';

    function trimWordToWidth(word, limit) {
        if (ctx.measureText(word).width <= limit) {
            return word;
        }
        while (word.length > 0 && ctx.measureText(word + '...').width > limit) {
            word = word.slice(0, -1);
        }
        return word + '...';
    }

    for (i = 0; i < words.length; i++) {
        if (ctx.measureText(words[i]).width > maxWidth) {
            words[i] = trimWordToWidth(words[i], maxWidth);
        }

        test = line ? line + ' ' + words[i] : words[i];

        if (ctx.measureText(test).width > maxWidth && line !== '') {
            lines.push(line);
            line = words[i];
        } else {
            line = test;
        }
    }

    if (line) {
        lines.push(line);
    }

    if (maxLines && lines.length > maxLines) {
        lines = lines.slice(0, maxLines);
        while (lines[lines.length - 1] && ctx.measureText(lines[lines.length - 1] + '...').width > maxWidth) {
            lines[lines.length - 1] = lines[lines.length - 1].slice(0, -1);
        }

        lines[lines.length - 1] = lines[lines.length - 1] + '...';
    }

    for (i = 0; i < lines.length; i++) {
        ctx.fillText(lines[i], x, currentY + (i * lineHeight));
    }
}

function acDoLoadCanvasImage(url) {
    return new Promise(function(resolve) {
        if (!url) {
            resolve(null);
            return;
        }

        var img = new Image();
        var done = false;

        function finish(result) {
            if (done) return;
            done = true;
            resolve(result);
        }

        img.crossOrigin = 'anonymous';
        img.onload = function() {
            finish(img);
        };
        img.onerror = function() {
            finish(null);
        };
        img.src = url;

        setTimeout(function() {
            finish(null);
        }, 4000);
    });
}

function acDoDrawContainImage(ctx, img, x, y, maxWidth, maxHeight) {
    var ratio;
    var width;
    var height;

    if (!img || !img.naturalWidth || !img.naturalHeight) {
        return false;
    }

    ratio = Math.min(maxWidth / img.naturalWidth, maxHeight / img.naturalHeight);
    width = img.naturalWidth * ratio;
    height = img.naturalHeight * ratio;

    ctx.drawImage(img, x + ((maxWidth - width) / 2), y + ((maxHeight - height) / 2), width, height);
    return true;
}

function acDoDrawCheckbox(ctx, x, y, checked) {
    acDoRect(ctx, x, y, 14, 14, '#222222', 1.5);

    if (checked) {
        acDoText(ctx, '\u2713', x + 1, y + 13, 21, '#111111', '700');
    }
}

function acDoChunkArray(items, pageSize) {
    var list = Array.isArray(items) ? items : [];
    var size = parseInt(pageSize, 10) || 16;
    var chunks = [];
    var i;

    for (i = 0; i < list.length; i += size) {
        chunks.push(list.slice(i, i + size));
    }

    if (!chunks.length) {
        chunks.push([]);
    }

    return chunks;
}

function acDoDrawReceiptCanvas(logoImage, pageLines, pageIndex, pageCount) {
    var out = acDoCanvas(1754, 1240);
    var canvas = out.canvas;
    var ctx = out.ctx;
    var data = acDoPdfData || {};
    var lines = Array.isArray(pageLines) ? pageLines : (Array.isArray(data.lines) ? data.lines : []);
    var pageNo = (parseInt(pageIndex, 10) || 0) + 1;
    var totalPages = parseInt(pageCount, 10) || 1;
    var isLastPage = pageNo >= totalPages;
    var pageSize = parseInt(acDoLinesPerPage, 10) || 16;
    var pageX = 56;
    var pageY = 42;
    var pageW = 1642;
    var pageH = 1156;
    var tableX = pageX + 54;
    var tableY = pageY + 285;
    var minRows = Math.max(isLastPage ? 10 : pageSize, lines.length);
    var headerH = 44;
    var rowH = Math.max(28, Math.min(34, Math.floor(560 / (minRows + 1))));
    var tableW = pageW - 108;
    var col = [
        tableX,
        tableX + 120,
        tableX + 240,
        tableX + tableW - 420,
        tableX + tableW - 230,
        tableX + tableW
    ];
    var i;
    var y;
    var item;

    acDoFillRect(ctx, 0, 0, canvas.width, canvas.height, '#ffffff');
    acDoFillRect(ctx, pageX, pageY, pageW, pageH, '#ffffff');
    acDoRect(ctx, pageX, pageY, pageW, pageH, '#d1d5db', 1);

    if (!acDoDrawContainImage(ctx, logoImage, pageX + 52, pageY + 48, 190, 112)) {
        acDoText(ctx, 'Vege', pageX + 100, pageY + 128, 42, '#164f38', '700');
    }

    acDoText(ctx, data.companyName || '', pageX + 270, pageY + 96, 50, '#0f172a', '900');
    acDoText(ctx, data.companyAddr || '', pageX + 272, pageY + 132, 19, '#111111', '400');
    acDoText(ctx, 'H/P: ' + (data.companyTel || ''), pageX + 272, pageY + 160, 19, '#111111', '700');

    acDoFillRect(ctx, pageX + pageW - 300, pageY + 18, 260, 34, '#444444');
    acDoText(ctx, 'DELIVERY ORDER', pageX + pageW - 170, pageY + 43, 22, '#ffffff', '700', 'center');
    if (totalPages > 1) {
        acDoText(ctx, 'Page ' + pageNo + ' / ' + totalPages, pageX + pageW - 170, pageY + 72, 16, '#555555', '700', 'center');
    }
    acDoText(ctx, 'No', pageX + pageW - 300, pageY + 170, 24, '#111111', '400');
    acDoText(ctx, data.docNo || '', pageX + pageW - 258, pageY + 170, 34, '#ef4444', '700');

    var customerText = (data.customerName || '') + (data.customerCode ? ' (' + data.customerCode + ')' : '');
    var customerX = pageX + 190;
    var customerMaxW = 860;
    var customerY = pageY + 218;

    acDoText(ctx, 'Customer', pageX + 54, customerY, 23, '#111111', '700');
    acDoWrap(ctx, customerText, customerX, customerY, customerMaxW, 28, 23, '#111111', '400', 2);
    acDoLine(ctx, pageX + 185, pageY + 270, pageX + 1040, pageY + 270, '#666666', 1);

    acDoText(ctx, 'Date', pageX + pageW - 390, customerY, 23, '#111111', '700');
    acDoText(ctx, data.displayDate || '', pageX + pageW - 320, customerY, 23, '#111111', '400');
    acDoLine(ctx, pageX + pageW - 330, pageY + 270, pageX + pageW - 54, pageY + 270, '#666666', 1);

    acDoRect(ctx, tableX, tableY, tableW, headerH + (rowH * minRows), '#333333', 1.2);
    for (i = 1; i < col.length - 1; i++) {
        acDoLine(ctx, col[i], tableY, col[i], tableY + headerH + (rowH * minRows), '#333333', 1);
    }
    acDoLine(ctx, tableX, tableY + headerH, col[5], tableY + headerH, '#333333', 1);

    for (i = 1; i <= minRows; i++) {
        acDoLine(ctx, tableX, tableY + headerH + (rowH * i), col[5], tableY + headerH + (rowH * i), '#333333', 1);
    }

    var headerZhY = tableY + 19;
    var headerEnY = tableY + 37;

    acDoText(ctx, '\u6570\u91cf', tableX + 60, headerZhY, 19, '#111111', '700', 'center');
    acDoText(ctx, 'Quantity', tableX + 60, headerEnY, 17, '#111111', '700', 'center');
    acDoText(ctx, '\u516c\u65a4', col[1] + 60, headerZhY, 19, '#111111', '700', 'center');
    acDoText(ctx, 'Kg', col[1] + 60, headerEnY, 17, '#111111', '700', 'center');
    acDoText(ctx, '\u8d27\u7269\u540d\u79f0', col[2] + ((col[3] - col[2]) / 2), headerZhY, 19, '#111111', '700', 'center');
    acDoText(ctx, 'Description', col[2] + ((col[3] - col[2]) / 2), headerEnY, 17, '#111111', '700', 'center');
    acDoText(ctx, '\u7bb1 / \u7bee', col[3] + ((col[4] - col[3]) / 2), headerZhY, 19, '#111111', '700', 'center');
    acDoText(ctx, 'Box / Basket', col[3] + ((col[4] - col[3]) / 2), headerEnY, 17, '#111111', '700', 'center');
    acDoText(ctx, '\u603b\u516c\u65a4', col[4] + ((col[5] - col[4]) / 2), headerZhY, 19, '#111111', '700', 'center');
    acDoText(ctx, 'Total Kg', col[4] + ((col[5] - col[4]) / 2), headerEnY, 17, '#111111', '700', 'center');

    for (i = 0; i < minRows; i++) {
        y = tableY + headerH + (rowH * i);
        item = lines[i] || {};

        acDoText(ctx, item.qty || '', tableX + 60, y + Math.round(rowH * 0.66), 22, '#111111', '400', 'center');
        acDoText(ctx, item.kg || '', col[1] + 60, y + Math.round(rowH * 0.66), 22, '#111111', '400', 'center');
        acDoWrap(ctx, item.description || '', col[2] + 14, y + Math.round(rowH * 0.58), col[3] - col[2] - 28, 21, 21, '#111111', '400', 2);
        acDoDrawCheckbox(ctx, col[3] + 42, y + Math.round(rowH * 0.30), !!item.isCtn);
        acDoText(ctx, 'Ctn', col[3] + 62, y + Math.round(rowH * 0.65), 18, '#111111', '400');
        acDoDrawCheckbox(ctx, col[3] + 112, y + Math.round(rowH * 0.30), !!item.isBsk);
        acDoText(ctx, 'Bsk', col[3] + 132, y + Math.round(rowH * 0.65), 18, '#111111', '400');
        acDoText(ctx, item.totalKg || '', col[4] + ((col[5] - col[4]) / 2), y + Math.round(rowH * 0.66), 22, '#111111', '400', 'center');
    }

    y = tableY + headerH + (rowH * minRows) + 26;
    acDoText(ctx, 'We Do The EXCELLENT Way', tableX, y + 26, 25, '#111111', '700');

    if (isLastPage) {
        if (data.remark) {
            acDoText(ctx, 'Remark: ' + data.remark, tableX, y + 60, 20, '#111111', '700');
        }

        var totalX = pageX + pageW - 320;
        var totalY = y;
        var totalRows = [
            ['\u603b\u7bb1', 'Total Ctn', data.totalCtn || ''],
            ['\u603b\u7bee', 'Total Bsk', data.totalBsk || ''],
            ['\u603b\u516c\u65a4', 'Total Kg', data.totalKg || '']
        ];

        for (i = 0; i < totalRows.length; i++) {
            acDoText(ctx, totalRows[i][0], totalX, totalY + 18 + i * 42, 21, '#111111', '700', 'right');
            acDoText(ctx, totalRows[i][1], totalX, totalY + 37 + i * 42, 19, '#111111', '400', 'right');
            acDoFillRect(ctx, totalX + 20, totalY + 4 + i * 42, 105, 34, 'rgba(255,255,255,0.25)');
            acDoRect(ctx, totalX + 20, totalY + 4 + i * 42, 105, 34, '#333333', 1);
            acDoText(ctx, totalRows[i][2], totalX + 72, totalY + 28 + i * 42, 23, '#111111', '700', 'center');
        }

        acDoLine(ctx, tableX, pageY + pageH - 105, tableX + 310, pageY + pageH - 105, '#555555', 1);
        acDoLine(ctx, pageX + pageW - 440, pageY + pageH - 105, pageX + pageW - 130, pageY + pageH - 105, '#555555', 1);
        acDoText(ctx, '\u7ecf\u624b\u4eba Issued by', tableX, pageY + pageH - 76, 21, '#111111', '400');
        acDoText(ctx, '\u6536\u8d27\u4eba Received by', pageX + pageW - 440, pageY + pageH - 76, 21, '#111111', '400');
    } else {
        acDoText(ctx, 'Continued on next page...', pageX + pageW - 58, y + 26, 20, '#444444', '700', 'right');
    }

    return canvas;
}

function acDoDrawProofCanvas(proofImage) {
    var out = acDoCanvas(1754, 1240);
    var canvas = out.canvas;
    var ctx = out.ctx;
    var data = acDoPdfData || {};
    var pageX = 56;
    var pageY = 42;
    var pageW = 1642;
    var pageH = 1156;

    acDoFillRect(ctx, 0, 0, canvas.width, canvas.height, '#ffffff');
    acDoFillRect(ctx, pageX, pageY, pageW, pageH, '#ffffff');
    acDoRect(ctx, pageX, pageY, pageW, pageH, '#d1d5db', 1);

    acDoText(ctx, 'Proof of Delivery', pageX + 58, pageY + 82, 37, '#111111', '700');
    acDoText(ctx, data.companyName || '', pageX + 58, pageY + 120, 19, '#111111', '400');

    var proofMetaX = pageX + pageW - 520;
    var proofMetaW = 460;

    acDoText(ctx, 'DO No: ' + (data.docNo || ''), pageX + pageW - 60, pageY + 76, 21, '#111111', '700', 'right');
    acDoWrap(ctx, 'Customer: ' + (data.customerName || ''), proofMetaX, pageY + 108, proofMetaW, 25, 21, '#111111', '400', 2);
    acDoText(ctx, 'Date: ' + (data.displayDate || ''), pageX + pageW - 60, pageY + 160, 21, '#111111', '400', 'right');
    acDoLine(ctx, pageX + 58, pageY + 166, pageX + pageW - 58, pageY + 166, '#333333', 3);

    acDoRect(ctx, pageX + 58, pageY + 208, pageW - 116, 780, '#333333', 1.5);

    if (!acDoDrawContainImage(ctx, proofImage, pageX + 76, pageY + 226, pageW - 152, 744)) {
        var proofMessage = data.proofUrl
            ? 'Proof image could not be loaded for sharing.'
            : 'No proof of delivery image uploaded yet.';

        acDoText(ctx, proofMessage, pageX + pageW / 2, pageY + 600, 24, '#555555', '700', 'center');
    }

    acDoLine(ctx, pageX + 58, pageY + pageH - 110, pageX + 410, pageY + pageH - 110, '#555555', 1);
    acDoLine(ctx, pageX + pageW - 410, pageY + pageH - 110, pageX + pageW - 58, pageY + pageH - 110, '#555555', 1);
    acDoText(ctx, 'Driver / Issued by', pageX + 58, pageY + pageH - 80, 19, '#111111', '400');
    acDoText(ctx, 'Customer / Received by', pageX + pageW - 410, pageY + pageH - 80, 19, '#111111', '400');

    return canvas;
}

function acDoBuildPdfBlob(includeProof) {
    includeProof = includeProof === true;

    return acDoLoadJsPdf()
        .then(function(jsPDF) {
            var data = acDoPdfData || {};

            return Promise.all([
                Promise.resolve(jsPDF),
                acDoLoadCanvasImage(data.companyLogoUrl),
                includeProof ? acDoLoadCanvasImage(data.proofUrl) : Promise.resolve(null)
            ]);
        })
        .then(function(result) {
            var jsPDF = result[0];
            var logoImage = result[1];
            var proofImage = result[2];
            var data = acDoPdfData || {};
            var receiptPages = acDoChunkArray(data.lines || [], acDoLinesPerPage);
            var pdf = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a5' });
            var pageCanvas;
            var i;

            pdf.setProperties({
                title: acDoPdfFileName.replace(/\.pdf$/i, '')
            });

            for (i = 0; i < receiptPages.length; i++) {
                if (i > 0) {
                    pdf.addPage();
                }

                pageCanvas = acDoDrawReceiptCanvas(logoImage, receiptPage�yW;`���������y
N���s[i], i, receiptPages.length);
                pdf.addImage(pageCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, 210, 148);
            }

            if (includeProof) {
                pdf.addPage();
                pdf.addImage(acDoDrawProofCanvas(proofImage).toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, 210, 148);
            }

            return pdf.output('blob');
        });
}

function acDoDownloadBlob(blob) {
    var url = URL.createObjectURL(blob);
    var link = document.createElement('a');

    link.href = url;
    link.download = acDoPdfFileName;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);

    setTimeout(function() {
        URL.revokeObjectURL(url);
    }, 1000);
}

function acDoPrintPdfBlob(blob) {
    var url = URL.createObjectURL(blob);
    var iframe = document.createElement('iframe');
    var cleanupTimer;

    iframe.style.position = 'fixed';
    iframe.style.right = '0';
    iframe.style.bottom = '0';
    iframe.style.width = '1px';
    iframe.style.height = '1px';
    iframe.style.border = '0';
    iframe.style.opacity = '0';
    iframe.title = acDoPdfFileName.replace(/\.pdf$/i, '');

    function cleanup() {
        if (cleanupTimer) {
            clearTimeout(cleanupTimer);
        }

        setTimeout(function() {
            if (iframe.parentNode) {
                iframe.parentNode.removeChild(iframe);
            }

            URL.revokeObjectURL(url);
        }, 1000);
    }

    iframe.onload = function() {
        cleanupTimer = setTimeout(cleanup, 60000);

        setTimeout(function() {
            try {
                iframe.contentWindow.focus();
                iframe.contentWindow.print();
            } catch (error) {
                acDoDownloadBlob(blob);
                alert('The PDF was downloaded because this browser could not open the printer automatically. Please print the downloaded PDF.');
                cleanup();
            }
        }, 500);
    };

    iframe.src = url;
    document.body.appendChild(iframe);
}

function acDoSetButtonBusy(button, text) {
    if (!button) return '';

    var originalText = button.textContent || '';
    button.disabled = true;
    button.textContent = text || 'Preparing PDF...';

    return originalText;
}

function acDoRestoreButton(button, originalText, fallbackText) {
    if (!button) return;

    button.disabled = false;
    button.textContent = originalText || fallbackText || button.textContent;
}

function acDoWaitForProofImages(includeProof) {
    var images = includeProof ? document.querySelectorAll('.ac-do-proof-paper img') : [];
    var waits = [];

    images.forEach(function(img) {
        waits.push(acDoWaitImage(img));
    });

    return Promise.all(waits);
}

function acDoPrintDeliveryOrderOnly(button) {
    var originalText = acDoSetButtonBusy(button, 'Preparing Print...');

    acDoWaitForProofImages(false)
        .then(function() {
            return acDoBuildPdfBlob(false);
        })
        .then(acDoPrintPdfBlob)
        .catch(function() {
            alert('Unable to prepare the delivery order for printing. Please try again.');
        })
        .finally(function() {
            acDoRestoreButton(button, originalText, 'Print');
        });
}

function acDoPrintFullPdf(button) {
    var originalText = acDoSetButtonBusy(button, 'Preparing PDF...');

    acDoWaitForProofImages(true)
        .then(function() {
            return acDoBuildPdfBlob(true);
        })
        .then(acDoPrintPdfBlob)
        .catch(function() {
            alert('Unable to prepare the full PDF for printing. Please use Share PDF or try again.');
        })
        .finally(function() {
            acDoRestoreButton(button, originalText, 'Print / Save PDF');
        });
}

function acDoSharePdf(button) {
    var originalText;

    if (!navigator.share) {
        alert('This browser does not support the native share interface. Please use Print / Save PDF, then share the saved PDF in WhatsApp.');
        return;
    }

    originalText = acDoSetButtonBusy(button, 'Preparing PDF...');

    acDoWaitForProofImages(true)
        .then(function() {
            return acDoBuildPdfBlob(true);
        })
        .then(function(blob) {
            var file = new File([blob], acDoPdfFileName, { type: 'application/pdf' });
            var shareData = {
                title: acDoPdfFileName.replace(/\.pdf$/i, ''),
                text: 'Delivery Order PDF',
                files: [file]
            };

            if (!navigator.canShare || !navigator.canShare({ files: [file] })) {
                acDoDownloadBlob(blob);
                alert('PDF downloaded. This browser cannot share PDF files directly, so please attach the downloaded PDF in WhatsApp.');
                return null;
            }

            return navigator.share(shareData);
        })
        .catch(function(error) {
            if (error && error.name === 'AbortError') {
                return;
            }

            alert('Unable to prepare the PDF for sharing. Please use Print / Save PDF, then share the saved PDF in WhatsApp.');
        })
        .finally(function() {
            acDoRestoreButton(button, originalText, 'Share PDF');
        });
}

if (acDoAutoPrint) {
    window.addEventListener('load', function() {
        setTimeout(function() {
            // Staff-list Print should auto-print only the Delivery Order page.
            // This does not depend on the visible Print button existing on-screen.
            // Keeping acDoAutoPrintDoOnly for URL clarity/backward compatibility.
            acDoPrintDeliveryOrderOnly(null);
        }, 350);
    });
}
</script>�y~8��a��������<k��
N?�bipt = document.createElement('script');
      script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
      script.onload = () => window.jspdf && window.jspdf.jsPDF ? resolve(window.jspdf.jsPDF) : reject(new Error('PDF library did not load.'));
      script.onerror = () => reject(new Error('PDF library could not be loaded.'));
      document.head.appendChild(script);
    });

    return brJsPdfPromise;
  }

  function loadCanvasImage(url){
    if (!url) return Promise.resolve(null);
    return new Promise(resolve => {
      const img = new Image();
      img.crossOrigin = 'anonymous';
      img.onload = () => resolve(img);
      img.onerror = () => resolve(null);
      img.src = url;
    });
  }

  function drawCanvasText(ctx, text, x, y, size=18, color='#111', weight='400', align='left'){
    ctx.fillStyle = color;
    ctx.font = `${weight} ${size}px Arial, sans-serif`;
    ctx.textAlign = align;
    ctx.textBaseline = 'top';
    ctx.fillText(String(text || ''), x, y);
  }

  function drawWrappedCanvasText(ctx, text, x, y, maxWidth, lineHeight, size=18, color='#111', weight='400'){
    const words = String(text || '').split(/\s+/).filter(Boolean);
    let line = '';

    words.forEach(word => {
      const testLine = line ? `${line} ${word}` : word;
      if (ctx.measureText(testLine).width > maxWidth && line) {
        drawCanvasText(ctx, line, x, y, size, color, weight);
        line = word;
        y += lineHeight;
      } else {
        line = testLine;
      }
    });

    if (line) drawCanvasText(ctx, line, x, y, size, color, weight);
    return y + lineHeight;
  }

  function drawCanvasImageContained(ctx, image, x, y, maxW, maxH){
    if (!image) return;
    const ratio = Math.min(maxW / image.width, maxH / image.height);
    const imgW = image.width * ratio;
    const imgH = image.height * ratio;
    ctx.drawImage(image, x + (maxW - imgW) / 2, y + (maxH - imgH) / 2, imgW, imgH);
  }

  function makeReceiptCanvas(receipt, proofImage=null, logoImage=null){
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = 1240;
    canvas.height = 1754;

    ctx.fillStyle = '#ffffff';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.strokeStyle = '#111111';
    ctx.lineWidth = 2;
    ctx.strokeRect(82, 70, 1076, 1614);

    if (logoImage) {
      drawCanvasImageContained(ctx, logoImage, 138, 112, 360, 128);
    } else {
      drawCanvasText(ctx, receipt.title || 'BASKET RETURN', 140, 130, 30, '#111', '900');
    }

    drawCanvasText(ctx, receipt.title || 'BASKET RETURN', 1100, 130, 24, '#111', '900', 'right');
    drawCanvasText(ctx, receipt.ref || ('BR-' + receipt.id), 1100, 172, 22, '#111', '900', 'right');

    ctx.strokeStyle = '#111111';
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.moveTo(140, 278);
    ctx.lineTo(1100, 278);
    ctx.stroke();

    let y = 350;
    drawCanvasText(ctx, receipt.accountLabel || 'Customer', 160, y, 22, '#555', '700');
    drawWrappedCanvasText(ctx, receipt.customerName || receipt.accountLabel || 'Customer', 160, y + 34, 410, 30, 24, '#111', '800');
    drawCanvasText(ctx, 'Driver', 660, y, 22, '#555', '700');
    drawWrappedCanvasText(ctx, receipt.driverName || '', 660, y + 34, 410, 30, 24, '#111', '800');

    y += 105;
    ctx.strokeStyle = '#e5e7eb';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(160, y);
    ctx.lineTo(1080, y);
    ctx.stroke();

    drawCanvasText(ctx, `${receipt.qty} BASKETS`, 620, y + 54, 74, '#111', '900', 'center');
    y += 210;

    ctx.strokeStyle = '#cbd5e1';
    ctx.setLineDash([12, 10]);
    ctx.strokeRect(160, y, 920, 560);
    ctx.setLineDash([]);

    if (proofImage) {
      drawCanvasText(ctx, 'Image Proof', 620, y + 28, 22, '#334155', '800', 'center');
      drawCanvasImageContained(ctx, proofImage, 210, y + 82, 820, 420);
    } else {
      drawCanvasText(ctx, 'No image proof uploaded', 620, y + 255, 28, '#64748b', '800', 'center');
    }

    return canvas;
  }

  function buildBasketReceiptPdf(receipt){
    return Promise.all([loadJsPdf(), loadCanvasImage(receipt.proofUrl), loadCanvasImage(RECEIPT_LOGO_URL)])
      .then(([jsPDF, proofImage, logoImage]) => {
        const warnings = [];
        if (receipt.proofUrl && !proofImage) warnings.push('Proof image could not be included in the PDF.');
        if (RECEIPT_LOGO_URL && !logoImage) warnings.push('Logo could not be included in the PDF.');
        if (warnings.length && window.Swal && typeof Swal.fire === 'function') {
          Swal.fire({ icon:'warning', title:'PDF image warning', text:warnings.join(' ') });
        }
        const pdf = new jsPDF({orientation:'portrait', unit:'mm', format:'a5'});
        const canvas = makeReceiptCanvas(receipt, proofImage, logoImage);
        pdf.addImage(canvas.toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, 148, 210);
        return pdf.output('blob');
      });
  }

  function downloadBlob(blob, fileName){
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  }

  function preloadReceiptImage(url){
    if (!url) return Promise.resolve();

    return new Promise(resolve => {
      const img = new Image();
      const done = () => resolve();
      img.onload = done;
      img.onerror = done;
      img.src = url;

      if (img.complete) resolve();
      setTimeout(done, 2500);
    });
  }

  function waitForElementImages(el){
    const images = Array.from(el.querySelectorAll('img'));
    if (!images.length) return Promise.resolve();

    return Promise.all(images.map(img => new Promise(resolve => {
      if (img.complete && img.naturalWidth > 0) {
        resolve();
        return;
      }

      const done = () => resolve();
      img.addEventListener('load', done, { once:true });
      img.addEventListener('error', done, { once:true });
      setTimeout(done, 2500);
    }))).then(() => undefined);
  }

  function printCurrentBasketReceipt(){
    const receipt = currentReceiptForShare;
    if (!receipt) {
      showError('Receipt not found', 'Please reopen the basket receipt and try again.');
      return;
    }

    buildBasketReceiptPdf(receipt)
      .then(blob => {
        const fileName = receiptFileName(receipt);
        const url = URL.createObjectURL(blob);
        const opened = window.open(url, '_blank', 'noopener');

        if (!opened) {
          downloadBlob(blob, fileName);
          if (window.Swal && typeof Swal.fire === 'function') {
            Swal.fire({ icon:'info', title:'PDF downloaded', text:'Open the downloaded PDF to print or share.' });
          }
        }

        setTimeout(() => URL.revokeObjectURL(url), 60000);
      })
      .catch(() => {
        showError('Unable to prepare PDF', 'Please try again.');
      });
  }

  function shareCurrentBasketReceipt(button){
    const receipt = currentReceiptForShare;
    if (!receipt) {
      showError('Receipt not found', 'Please reopen the basket receipt and try again.');
      return;
    }

    const originalText = button ? button.textContent : '';
    if (button) {
      button.disabled = true;
      button.textContent = 'Preparing...';
    }

    buildBasketReceiptPdf(receipt)
      .then(blob => {
        const fileName = receiptFileName(receipt);
        const file = new File([blob], fileName, {type:'application/pdf'});

        if (!navigator.share || !navigator.canShare || !navigator.canShare({files:[file]})) {
          downloadBlob(blob, fileName);
          if (window.Swal && typeof Swal.fire === 'function') {
            Swal.fire({ icon:'info', title:'PDF downloaded', text:'Attach the downloaded PDF in WhatsApp.' });
          }
          return null;
        }

        return navigator.share({
          title: fileName.replace(/\.pdf$/i, ''),
          text: 'Basket Return PDF',
          files: [file]
        });
      })
      .catch(error => {
        if (error && error.name === 'AbortError') return;
        showError('Unable to prepare PDF', 'Please print or save PDF, then share it in WhatsApp.');
      })
      .finally(() => {
        if (button) {
          button.disabled = false;
          button.textContent = originalText || 'Share PDF';
        }
      });
  }

  function markSelectedDebtorRow(debtorCode){
    wrap.querySelectorAll('[data-summary-row="1"]').forEach(row => {
      row.classList.toggle('bs-row-selected', (row.dataset.debtorCode || '') === debtorCode);
    });
  }

  async function loadSummary(){
    const cfg = modeConfig();
    if (!cfg.summaryUrl) {
      showError('Missing configuration', cfg.singular + ' summary endpoint missing.');
      return;
    }

    const rangeError = dateRangeError();
    if (rangeError) {
      showError('Invalid date range', rangeError);
      return;
    }

    const requestSeq = ++loadSummarySeq;
    setSummaryLoading(true, 'Loading ' + cfg.singularLower + ' basket summary...');

    try {
      let rows = [];
      if (selectedDebtors.length > 1) {
        const selectedResults = await Promise.all(selectedDebtors.map(debtor => apiGet(buildSummaryUrl(debtor))));
        rows = selectedResults.flatMap(data => Array.isArray(data && data.rows) ? data.rows : []);
      } else {
        const data = await apiGet(buildSummaryUrl());
        rows = Array.isArray(data && data.rows) ? data.rows : [];
      }

      if (requestSeq !== loadSummarySeq) return;

      rows = dedupeSummaryRowsByDebtor(filterRowsBySelectedCustomers(rows.map(normalizeSummaryRow)));
      Object.keys(ledgerCache).forEach(k => delete ledgerCache[k]);
      renderSummary(rows);
      setSummaryLoading(false);

      const receiptMount = $('ac_bs_receipt_mount');
      if (receiptMount) receiptMount.innerHTML = '';

      currentLedgerRows = [];
      currentLedgerCustomer = { code: '', name: '' };
      $('ac_bs_ledger_title').textContent = 'Basket Movement History';
      $('ac_bs_ledger_table').innerHTML = '<tr><td colspan="9" class="bs-empty-cell">Select a ' + esc(cfg.singularLower) + ' to view basket movement history.</td></tr>';
    } catch(err) {
      if (requestSeq !== loadSummarySeq) return;
      showError('Failed to load summary', err && err.message ? err.message : 'Please try again.');
      setSummaryLoading(false);
      $('ac_bs_rows_table').innerHTML = '<tr><td colspan="8" class="bs-empty-cell">Failed to load summary.</td></tr>';
    }
  }

  function showLedgerLoading(debtorCode, debtorName){
    const cfg = modeConfig();
    currentLedgerRows = [];
    currentLedgerCustomer = { code: debtorCode || '', name: debtorName || '' };
    $('ac_bs_ledger_title').textContent = 'Basket Movement History: ' + (debtorName || debtorCode);
    $('ac_bs_ledger_table').innerHTML = `
      <tr>
        <td colspan="9" class="bs-ledger-loading-cell">
          <div class="bs-ledger-loading">
            <div class="bs-ledger-spinner"></div>
            <div>Loading basket movement...</div>
            <div style="font-size:12px;color:#64748b;">${esc(debtorName || debtorCode || cfg.singular)}</div>
          </div>
        </td>
      </tr>`;
    const toolbar = $('ac_bs_ledger_toolbar');
    if (toolbar) toolbar.style.display = 'none';
    resetLedgerFilters();
  }

  function resetLedgerFilters(){
    $('ac_bs_ledger_date_from').value = '';
    $('ac_bs_ledger_date_to').value = '';
    const select = $('ac_bs_ledger_movement_filter');
    if (select) {
      select.innerHTML = '<option value="">All Movement</option>';
    }
    const selectAll = $('ac_bs_ledger_select_all');
    if (selectAll) selectAll.classList.remove('toggled');
  }

  function renderLedger(debtorCode, debtorName, rows){
    const cfg = modeConfig();
    currentLedgerRows = Array.isArray(rows) ? rows.map(normalizeLedgerRow).sort(compareLedgerByLastActivity) : [];
    currentLedgerCustomer = { code: debtorCode || '', name: debtorName || '' };
    $('ac_bs_ledger_title').textContent = 'Basket Movement History: ' + (debtorName || debtorCode);
    const toolbar = $('ac_bs_ledger_toolbar');
    if (toolbar) toolbar.style.display = currentLedgerRows.length ? 'flex' : 'none';
    populateLedgerMovementFilter(currentLedgerRows);

    const table = $('ac_bs_ledger_table');
    if (!currentLedgerRows.length) {
      table.innerHTML = '<tr><td colspan="9" class="bs-empty-cell">No basket movement found for this ' + esc(cfg.singularLower) + '.</td></tr>';
      return;
    }

    applyLedgerRender(currentLedgerRows);
  }

  function populateLedgerMovementFilter(rows){
    const select = $('ac_bs_ledger_movement_filter');
    if (!select) return;
    const labels = new Set();
    rows.forEach(r => labels.add(movementLabel(r)));
    const current = select.value;
    select.innerHTML = '<option value="">All Movement</option>' + Array.from(labels).sort().map(l => `<option value="${esc(l)}">${esc(l)}</option>`).join('');
    if (current && Array.from(labels).includes(current)) select.value = current;
  }

  function applyLedgerRender(rows){
    const dateFrom = ($('ac_bs_ledger_date_from').value || '').trim();
    const dateTo = ($('ac_bs_ledger_date_to').value || '').trim();
    const movementFilter = ($('ac_bs_ledger_movement_filter').value || '').trim();

    let filtered = rows.filter(r => {
      const okDate = matchDateRange(r.txnDate || r.txn_date || r.date, dateFrom, dateTo);
      const okMovement = !movementFilter || movementLabel(r) === movementFilter;
      return okDate && okMovement;
    });

    const table = $('ac_bs_ledger_table');
    if (!filtered.length) {
      table.innerHTML = '<tr><td colspan="9" class="bs-empty-cell">No basket movement matches the selected filters.</td></tr>';
      return;
    }

    table.innerHTML = filtered.map((r, i) => {
      const txnType = getRowTxnType(r);
      const sourceType = getRowSourceType(r);
      const label = movementLabel(r);
      const typeClass = isReturnTxn(r) ? 'bs-type-return' : 'bs-type-send';
      const idx = currentLedgerRows.indexOf(r);
      const ledgerIdx = idx >= 0 ? idx : i;
      const canViewReceipt = isReturnTxn(r) && (sourceType === 'BASKET_RETURN' || sourceType === 'CREDITOR_BASKET_RETURN');
      const receiptBtn = canViewReceipt
        ? `<button class="bs-view-btn bs-receipt-btn" type="button" data-basket-receipt-idx="${ledgerIdx}">View Basket Receipt</button>`
        : '<span style="color:#94a3b8;">-</span>';

      return `<tr data-ledger-idx="${ledgerIdx}">
        <td data-label="Select"><input type="checkbox" class="bs-ledger-row-check" data-ledger-idx="${ledgerIdx}" aria-label="Select row ${i+1}"></td>
        <td data-label="No">${i+1}</td>
        <td data-label="Date">${esc(r.txnDate || r.txn_date || '-')}</td>
        <td data-label="Movement"><span class="${typeClass}">${esc(label)}</span></td>
        <td data-label="Qty">${fmtQty(r.qty || 0)}</td>
        <td data-label="Source">${esc(sourceTypeLabel(r))}</td>
        <td data-label="Document No.">${esc(r.sourceRef || r.source_ref || '-')}</td>
        <td data-label="Note">${esc(r.remark || r.note || '-')}</td>
        <td data-label="Receipt">${receiptBtn}</td>
      </tr>`;
    }).join('');

    const selectAllBtn = $('ac_bs_ledger_select_all');
    if (selectAllBtn) {
      selectAllBtn.classList.remove('toggled');
      selectAllBtn.textContent = 'Select All';
    }
  }

  function matchDateRange(value, from, to){
    if (!value) return true;
    const d = String(value).split(' ')[0];
    if (from && d < from) return false;
    if (to && d > to) return false;
    return true;
  }

  function getSelectedLedgerRows(){
    const selected = [];
    wrap.querySelectorAll('.bs-ledger-row-check:checked').forEach(cb => {
      const idx = parseInt(cb.dataset.ledgerIdx, 10);
      cons<k�êW�)b��������<k�
N?�ct row = currentLedgerRows[idx];
      if (row) selected.push(row);
    });
    return selected;
  }

  function toggleSelectAllLedgerRows(){
    const checks = Array.from(wrap.querySelectorAll('.bs-ledger-row-check'));
    const anyUnchecked = checks.some(cb => !cb.checked);
    checks.forEach(cb => cb.checked = anyUnchecked);
    const btn = $('ac_bs_ledger_select_all');
    if (btn) btn.classList.toggle('toggled', anyUnchecked);
    if (btn) btn.textContent = anyUnchecked ? 'Deselect All' : 'Select All';
  }

  function formatLedgerPrintDateRange(){
    const from = ($('ac_bs_ledger_date_from').value || '').trim();
    const to = ($('ac_bs_ledger_date_to').value || '').trim();
    if (!from && !to) return '';
    if (from === to) return from;
    if (from && !to) return 'From ' + from;
    if (!from && to) return 'Until ' + to;
    return from + ' - ' + to;
  }

  function ledgerRowDateOnly(row){
    const raw = String(row?.txnDate || row?.txn_date || row?.date || '').trim();
    if (!raw) return '';
    return raw.split(' ')[0];
  }

  function formatSelectedLedgerDateRange(rows){
    const dates = Array.from(new Set((rows || [])
      .map(ledgerRowDateOnly)
      .filter(Boolean)))
      .sort();

    if (!dates.length) return '-';
    if (dates.length === 1) return dates[0];
    return dates[0] + ' - ' + dates[dates.length - 1];
  }

  function getOverallOutstandingBalance(){
    const customerCode = debtorKey(currentLedgerCustomer.code || '');
    const customerName = debtorKey(currentLedgerCustomer.name || '');
    const summaryRows = Array.isArray(wrap._lastRows) ? wrap._lastRows : [];

    const summaryRow = summaryRows.find(row => {
      const rowCode = debtorKey(row.debtorCode || row.debtor_code || '');
      const rowName = debtorKey(row.debtorName || row.debtor_name || '');
      return (customerCode && rowCode === customerCode) || (!customerCode && customerName && rowName === customerName);
    });

    if (summaryRow) {
      const summaryOutstanding = Number(summaryRow.outstandingQty ?? summaryRow.outstanding_qty ?? summaryRow.outstandingBasket ?? summaryRow.outstanding_basket);
      if (Number.isFinite(summaryOutstanding)) return summaryOutstanding;
    }

    return (Array.isArray(currentLedgerRows) ? currentLedgerRows : []).reduce((sum, row) => {
      const qty = Number(row.qty || 0) || 0;
      return sum + (isReturnTxn(row) ? -qty : qty);
    }, 0);
  }

  async function buildLedgerStatementPdfBlob(){
    const selected = getSelectedLedgerRows();

    if (!selected.length) {
      showError('No rows selected', 'Please tick at least one row to print or share.');
      return null;
    }

    const cfg = modeConfig();
    const customerName = currentLedgerCustomer.name || currentLedgerCustomer.code || cfg.singular;
    const customerCode = currentLedgerCustomer.code || '';
    const dateRange = formatSelectedLedgerDateRange(selected);
    const selectedRows = selected.slice().sort(compareLedgerByLastActivity);

    const sendQty = selectedRows
      .filter(r => !isReturnTxn(r))
      .reduce((sum, r) => sum + (Number(r.qty) || 0), 0);

    const returnQty = selectedRows
      .filter(r => isReturnTxn(r))
      .reduce((sum, r) => sum + (Number(r.qty) || 0), 0);

    const overallOutstandingQty = getOverallOutstandingBalance();
    const generatedAt = new Date().toLocaleString('en-MY', {
      year:'numeric', month:'2-digit', day:'2-digit',
      hour:'2-digit', minute:'2-digit'
    });

    const jsPDF = await loadJsPdf();
    const pdf = new jsPDF({ orientation:'portrait', unit:'mm', format:'a4' });

    if (pdf.setProperties) {
      pdf.setProperties({
        title: 'Basket Movement Statement - ' + customerName,
        subject: 'Basket Movement Statement',
        author: 'Basket Summary'
      });
    }

    const pageW = pdf.internal.pageSize.getWidth();
    const pageH = pdf.internal.pageSize.getHeight();
    const margin = 12;
    const contentW = pageW - margin * 2;
    let y = 0;

    function cleanText(value){
      const text = String(value === null || value === undefined || value === '' ? '-' : value);
      return text.replace(/\s+/g, ' ').trim();
    }

    function fileSafe(value){
      return String(value || modeConfig().singularLower)
        .replace(/[^A-Za-z0-9_-]+/g, '-')
        .replace(/-+/g, '-')
        .replace(/^-|-$/g, '') || modeConfig().singularLower;
    }

    function drawHeader(){
      pdf.setFillColor(11, 74, 45);
      pdf.rect(0, 0, pageW, 34, 'F');

      pdf.setTextColor(255, 255, 255);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(18);
      pdf.text('Basket Movement Statement', margin, 16);

      pdf.setFontSize(9);
      pdf.setFont(undefined, 'normal');
      pdf.text('Generated: ' + generatedAt, margin, 24);

      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(10);
      pdf.text('Excellent Vege Basket Record', pageW - margin, 16, { align:'right' });

      pdf.setFont(undefined, 'normal');
      pdf.text('Selected movements only', pageW - margin, 24, { align:'right' });

      y = 44;

      pdf.setTextColor(15, 23, 42);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(11);
      pdf.text(cfg.singular, margin, y);
      pdf.text('Date Range', pageW - margin - 62, y);

      pdf.setFont(undefined, 'normal');
      pdf.setFontSize(10);

      const customerLines = pdf.splitTextToSize(cleanText(customerName), 88);
      pdf.text(customerLines, margin, y + 6);

      if (customerCode) {
        pdf.setTextColor(71, 85, 105);
        pdf.text('Code: ' + customerCode, margin, y + 6 + customerLines.length * 4.5);
        pdf.setTextColor(15, 23, 42);
      }

      pdf.text(cleanText(dateRange), pageW - margin - 62, y + 6);
      y += Math.max(24, 8 + customerLines.length * 4.5 + (customerCode ? 5 : 0));
    }

    function drawSummaryBox(x, label, value, width){
      pdf.setFillColor(248, 250, 252);
      pdf.setDrawColor(226, 232, 240);
      pdf.roundedRect(x, y, width, 18, 2, 2, 'FD');

      pdf.setTextColor(100, 116, 139);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(7.5);
      pdf.text(label, x + 4, y + 6);

      pdf.setTextColor(15, 23, 42);
      pdf.setFontSize(13);
      pdf.text(String(value), x + 4, y + 14);
    }

    function drawSummary(){
      const gap = 4;
      const boxW = (contentW - gap * 3) / 4;

      drawSummaryBox(margin, cfg.creditor ? 'TOTAL RECEIVED' : 'TOTAL SENT', fmtQty(sendQty), boxW);
      drawSummaryBox(margin + (boxW + gap), 'TOTAL RETURNED', fmtQty(returnQty), boxW);
      drawSummaryBox(margin + (boxW + gap) * 2, 'OUTSTANDING BALANCE', fmtQty(overallOutstandingQty), boxW);
      drawSummaryBox(margin + (boxW + gap) * 3, 'MOVEMENT ROWS', fmtQty(selectedRows.length), boxW);
      y += 26;
    }

    const tableRight = pageW - margin - 10;
    const tableW = tableRight - margin;
    const columns = [
      { title:'Date', x:margin, w:30, key:'date' },
      { title:'Document No.', x:margin + 32, w:68, key:'doc' },
      { title:'Movement', x:margin + 104, w:46, key:'movement' },
      { title:'Qty', x:tableRight - 24, w:20, key:'qty', align:'right' }
    ];

    function drawTableHeader(){
      pdf.setFillColor(22, 101, 52);
      pdf.rect(margin, y, tableW, 8, 'F');

      pdf.setTextColor(255, 255, 255);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(8.5);

      columns.forEach(col => {
        const tx = col.align === 'right' ? col.x + col.w : col.x + 2;
        pdf.text(col.title, tx, y + 5.3, col.align === 'right' ? { align:'right' } : undefined);
      });

      y += 8;
    }

    function addNewPageWithTableHeader(){
      pdf.addPage();
      y = 18;
      drawTableHeader();
    }

    function drawRow(row, index){
      const values = {
        date: cleanText(row.txnDate || row.txn_date || '-'),
        doc: cleanText(row.sourceRef || row.source_ref || '-'),
        movement: cleanText(movementLabel(row)),
        qty: String(fmtQty(row.qty || 0))
      };

      const lineHeight = 4.3;
      const cellLines = columns.map(col => {
        const maxW = col.align === 'right' ? col.w : col.w - 2;
        return pdf.splitTextToSize(values[col.key], maxW);
      });
      const rowHeight = Math.max(8, Math.max(...cellLines.map(lines => lines.length)) * lineHeight + 4);

      if (y + rowHeight > pageH - 18) addNewPageWithTableHeader();

      if (index % 2 === 0) {
        pdf.setFillColor(249, 250, 251);
        pdf.rect(margin, y, tableW, rowHeight, 'F');
      }

      pdf.setDrawColor(226, 232, 240);
      pdf.line(margin, y + rowHeight, margin + tableW, y + rowHeight);
      pdf.setTextColor(15, 23, 42);
      pdf.setFont(undefined, 'normal');
      pdf.setFontSize(8.2);

      columns.forEach((col, colIndex) => {
        const lines = cellLines[colIndex];
        if (col.align === 'right') {
          pdf.text(lines, col.x + col.w, y + 5, { align:'right' });
        } else {
          pdf.text(lines, col.x + 2, y + 5);
        }
      });

      y += rowHeight;
    }

    function drawTotals(){
      if (y + 22 > pageH - 18) {
        pdf.addPage();
        y = 18;
      }

      y += 7;
      pdf.setDrawColor(226, 232, 240);
      pdf.line(margin, y, margin + contentW, y);
      y += 7;

      pdf.setTextColor(15, 23, 42);
      pdf.setFont(undefined, 'bold');
      pdf.setFontSize(10);
      pdf.text('Statement Totals', margin, y);
      y += 6;

      pdf.setFontSize(9);
      pdf.text('Sent: ' + fmtQty(sendQty), margin, y);
      pdf.text('Returned: ' + fmtQty(returnQty), margin + 42, y);
      pdf.text('Total Outstanding Balance: ' + fmtQty(overallOutstandingQty), margin + 96, y);
      y += 10;
    }

    function drawFooter(){
      const totalPages = pdf.internal.getNumberOfPages();
      for (let page = 1; page <= totalPages; page++) {
        pdf.setPage(page);
        pdf.setDrawColor(226, 232, 240);
        pdf.line(margin, pageH - 12, pageW - margin, pageH - 12);
        pdf.setTextColor(100, 116, 139);
        pdf.setFont(undefined, 'normal');
        pdf.setFontSize(8);
        pdf.text('Basket Movement Statement', margin, pageH - 7);
        pdf.text('Page ' + page + ' of ' + totalPages, pageW - margin, pageH - 7, { align:'right' });
      }
    }

    drawHeader();
    drawSummary();
    drawTableHeader();
    selectedRows.forEach(drawRow);
    drawTotals();
    drawFooter();

    return {
      blob: pdf.output('blob'),
      fileName: 'basket-movement-' + fileSafe(customerCode || customerName) + '.pdf'
    };
  }

  async function printLedgerPdf(button){
    const originalText = setLedgerButtonBusy(button, 'Preparing...');

    try {
      const result = await buildLedgerStatementPdfBlob();
      if (!result) return;
      downloadBlob(result.blob, result.fileName);
    } catch(err) {
      showError('PDF failed', err && err.message ? err.message : 'Could not generate PDF.');
    } finally {
      restoreLedgerButton(button, originalText, 'Print PDF');
    }
  }

  async function shareLedgerPdf(button){
    const originalText = setLedgerButtonBusy(button, 'Preparing...');

    try {
      if (!navigator.share) {
        showInfo('Share not supported', 'This browser cannot open the native share menu. The PDF will be downloaded instead.');
        const fallbackResult = await buildLedgerStatementPdfBlob();
        if (fallbackResult) downloadBlob(fallbackResult.blob, fallbackResult.fileName);
        return;
      }

      const result = await buildLedgerStatementPdfBlob();
      if (!result) return;

      const file = new File([result.blob], result.fileName, { type:'application/pdf' });

      if (!navigator.canShare || !navigator.canShare({ files:[file] })) {
        downloadBlob(result.blob, result.fileName);
        showInfo('PDF downloaded', 'This browser cannot share PDF files directly. Attach the downloaded PDF in WhatsApp.');
        return;
      }

      await navigator.share({
        title: result.fileName.replace(/\.pdf$/i, ''),
        text: 'Basket Movement Statement PDF',
        files: [file]
      });
    } catch(error) {
      if (error && error.name === 'AbortError') return;
      showError('Unable to share PDF', 'Please print or save PDF, then share it in WhatsApp.');
    } finally {
      restoreLedgerButton(button, originalText, 'Share PDF');
    }
  }

  async function loadLedger(debtorCode, debtorName){
    const cfg = modeConfig();
    const requestedMode = accountMode;
    if (!cfg.ledgerUrl) {
      showError('Missing configuration', cfg.singular + ' ledger endpoint missing.');
      return;
    }

    const rangeError = dateRangeError();
    if (rangeError) {
      showError('Invalid date range', rangeError);
      return;
    }

    const cacheKey = accountMode + '|' + debtorCode + '|' + ($('ac_bs_date_from').value || '') + '|' + ($('ac_bs_date_to').value || '');
    showLedgerLoading(debtorCode, debtorName);
    markSelectedDebtorRow(debtorCode);
    openLedgerModal();

    if (ledgerCache[cacheKey]) {
      renderLedger(debtorCode, debtorName, ledgerCache[cacheKey]);
      return;
    }

    try {
      const data = await apiGet(buildLedgerUrl(debtorCode));
      if (requestedMode !== accountMode) return;
      const rows = Array.isArray(data && data.rows) ? data.rows.map(normalizeLedgerRow) : [];
      ledgerCache[cacheKey] = rows;
      renderLedger(debtorCode, debtorName, rows);
    } catch(err) {
      showError('Failed to load movement history', err && err.message ? err.message : 'Please try again.');
      $('ac_bs_ledger_table').innerHTML = '<tr><td colspan="9" class="bs-empty-cell">Failed to load movement history.</td></tr>';
    }
  }

  function updateCustomerClearButton(){
    const hasValue = selectedDebtors.length > 0;
    $('ac_bs_customer_clear')?.classList.toggle('show', hasValue);
  }

  function clearCustomerSelection(){
    selectedDebtors.splice(0, selectedDebtors.length);
    updateCustomerSelectionUi();
    closeSelectedCustomerManager();
    loadSummary();
  }

  async function searchCustomersLive(q){
    const cfg = modeConfig();
    if (!AJAX_URL || !cfg.nonce) return [];

    const url = AJAX_URL + '?action=' + encodeURIComponent(cfg.ajaxAction) + '&nonce=' + encodeURIComponent(cfg.nonce) + '&q=' + encodeURIComponent(q);
    const res = await fetch(url, { credentials:'same-origin', cache:'no-store' });
    const data = await res.json();
    if (!data.success) throw new Error((data.data && data.data.error) ? data.data.error : 'Search failed');

    const items = data.data?.items || [];
    return items.map(it => {
      const name = cfg.creditor
        ? (it.name || it.creditorName || it.companyName || '')
        : (it.name || it.debtorName || it.companyName || '');
      const code = cfg.creditor
        ? (it.code || it.creditorCode || it.accNo || '')
        : (it.code || it.debtorCode || it.accNo || '');
      return { label: name || code, meta: (cfg.showCode && code) ? code : '', raw: { name, code } };
    });
  }

  function renderPickerNote(msg){
    $('ac_bs_picker_results').innerHTML = '<div class="bs-picker-note">' + esc(msg) + '</div>';
  }

  function renderPickerItems(items){
    if (!items.length) {
      renderPickerNote('No result found');
      return;
    }

    $('ac_bs_picker_results').innerHTML = items.map((it, idx) => `<button type="button" class="bs-picker-item" data-picker-idx="${idx}">
      <span class="bs-picker-item-main">${esc(it.label || '')}</span>
      ${it.meta ? '<span class="bs-picker-item-sub">' + esc(it.meta) + '</span>' : ''}
    </button>`).join('');
  }

  function runPickerSearch(q){
    const query = (q || '').trim();
    clearTimeout(pickerTimer);

    if (query.length < 1) {
      pickerState.items = [];
      renderPickerNote('Type to search');
      return;
    }

    pickerTimer = setTimeout(async function(){
      renderPickerNote('Searching...');
      try {
<k�mg�Vc��������<k�
N&a����        pickerState.items = await pickerState.fetchFn(query) || [];
        renderPickerItems(pickerState.items);
      } catch(e) {
        pickerState.items = [];
        renderPickerNote('Failed to load');
      }
    }, 220);
  }

  function openPicker(opts){
    pickerState.items = [];
    pickerState.fetchFn = opts.fetchFn;
    pickerState.onPick = opts.onPick;
    $('ac_bs_picker_title').textContent = opts.title || 'Search';
    $('ac_bs_picker_search').placeholder = opts.placeholder || 'Type to search...';
    $('ac_bs_picker_search').value = '';
    $('ac_bs_picker_modal').classList.add('active');
    renderPickerNote('Type to search');
    setTimeout(() => $('ac_bs_picker_search').focus(), 80);
  }

  function closePicker(){
    $('ac_bs_picker_modal').classList.remove('active');
    $('ac_bs_picker_search').value = '';
    $('ac_bs_picker_results').innerHTML = '';
    pickerState.items = [];
    pickerState.fetchFn = null;
    pickerState.onPick = null;
  }

  function openCustomerPicker(){
    const cfg = modeConfig();
    openPicker({
      title:'Select ' + cfg.singular,
      placeholder:'Search ' + cfg.singularLower + '...',
      fetchFn: searchCustomersLive,
      onPick: function(picked){
        if (!picked) return;
        addSelectedCustomer(picked);
        closePicker();
        loadSummary();
      }
    });
  }

  function updateAccountModeUi(){
    const cfg = modeConfig();

    wrap.querySelectorAll('[data-account-mode]').forEach(btn => {
      btn.classList.toggle('active', btn.dataset.accountMode === accountMode);
    });

    if ($('ac_bs_account_label')) $('ac_bs_account_label').textContent = cfg.singular;
    if ($('ac_bs_customer_input')) $('ac_bs_customer_input').placeholder = 'Search ' + cfg.singularLower + ' to add...';
    if ($('ac_bs_manage_title')) $('ac_bs_manage_title').textContent = cfg.selectedLabel;
    if ($('ac_bs_manage_add')) $('ac_bs_manage_add').textContent = 'Add ' + cfg.singular;
    if ($('ac_bs_code_heading')) $('ac_bs_code_heading').textContent = cfg.singular + ' Code';
    if ($('ac_bs_name_heading')) $('ac_bs_name_heading').textContent = cfg.singular + ' Name';
    if ($('ac_bs_in_heading')) $('ac_bs_in_heading').textContent = cfg.inboundLabel;
    if ($('ac_bs_out_heading')) $('ac_bs_out_heading').textContent = cfg.outboundLabel;
    if ($('ac_bs_picker_title')) $('ac_bs_picker_title').textContent = 'Select ' + cfg.singular;
    if ($('ac_bs_picker_search')) $('ac_bs_picker_search').placeholder = 'Search ' + cfg.singularLower + '...';

    updateCustomerSelectionUi();
  }

  function setAccountMode(nextMode){
    const normalized = String(nextMode || '').toLowerCase() === 'creditor' ? 'creditor' : 'customer';
    if (accountMode === normalized) return;

    accountMode = normalized;
    selectedDebtors.splice(0, selectedDebtors.length);
    Object.keys(ledgerCache).forEach(k => delete ledgerCache[k]);
    currentLedgerRows = [];
    currentLedgerCustomer = { code:'', name:'' };
    wrap._lastRows = [];
    closeSelectedCustomerManager();
    closeLedgerModal();
    closePicker();

    const receiptMount = $('ac_bs_receipt_mount');
    if (receiptMount) receiptMount.innerHTML = '';
    document.body.classList.remove('bs-br-open');

    updateAccountModeUi();
    loadSummary();
  }

  function setDefaultDateRange(){
    const dateFromEl = $('ac_bs_date_from');
    const dateToEl = $('ac_bs_date_to');
    if (!dateFromEl || !dateToEl) return;

    const today = new Date();
    const oneMonthAgo = new Date(today);
    oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);

    function toYmd(d){
      return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
    }

    if (!dateToEl.value) dateToEl.value = toYmd(today);
    if (!dateFromEl.value) dateFromEl.value = toYmd(oneMonthAgo);
  }

  function openLedgerModal(){
    const modal = $('ac_bs_ledger_modal');
    if (!modal) return;
    modal.classList.add('active');
    modal.setAttribute('aria-hidden', 'false');
    document.body.classList.add('bs-ledger-open');
  }

  function closeLedgerModal(){
    const modal = $('ac_bs_ledger_modal');
    if (!modal) return;
    modal.classList.remove('active');
    modal.setAttribute('aria-hidden', 'true');
    document.body.classList.remove('bs-ledger-open');
  }

  wrap.querySelectorAll('[data-account-mode]').forEach(btn => {
    btn.addEventListener('click', e => {
      e.preventDefault();
      setAccountMode(btn.dataset.accountMode || 'customer');
    });
  });

  $('ac_bs_refresh') && $('ac_bs_refresh').addEventListener('click', e => { e.preventDefault(); loadSummary(); });

  // Auto-refresh summary when date range changes
  const dateFromEl = $('ac_bs_date_from');
  const dateToEl = $('ac_bs_date_to');
  let dateLoadTimer = null;
  function onDateChange() {
    clearTimeout(dateLoadTimer);
    dateLoadTimer = setTimeout(loadSummary, 300);
  }
  if (dateFromEl) dateFromEl.addEventListener('input', onDateChange);
  if (dateToEl) dateToEl.addEventListener('input', onDateChange);

  $('ac_bs_rows_table').addEventListener('click', e => {
    const btn = e.target.closest('[data-debtor-code]');
    if (!btn) return;
    const code = btn.dataset.debtorCode || '';
    const name = btn.dataset.debtorName || '';
    if (code) loadLedger(code, name);
  });
  $('ac_bs_ledger_table').addEventListener('click', e => {
    const receiptBtn = e.target.closest('[data-basket-receipt-idx]');
    if (receiptBtn) {
      e.preventDefault();
      e.stopPropagation();
      openBasketReceiptByIndex(receiptBtn.dataset.basketReceiptIdx);
      return;
    }
  });
  $('ac_bs_ledger_date_from')?.addEventListener('input', () => applyLedgerRender(currentLedgerRows));
  $('ac_bs_ledger_date_to')?.addEventListener('input', () => applyLedgerRender(currentLedgerRows));
  $('ac_bs_ledger_movement_filter')?.addEventListener('change', () => applyLedgerRender(currentLedgerRows));
  $('ac_bs_ledger_select_all')?.addEventListener('click', e => { e.preventDefault(); toggleSelectAllLedgerRows(); });
  $('ac_bs_ledger_print')?.addEventListener('click', e => {
    e.preventDefault();
    printLedgerPdf(e.currentTarget);
  });
  $('ac_bs_ledger_share')?.addEventListener('click', e => {
    e.preventDefault();
    shareLedgerPdf(e.currentTarget);
  });
  $('ac_bs_receipt_mount').addEventListener('click', e => {
    const printBtn = e.target.closest('[data-print-current-basket-receipt]');
    if (printBtn) {
      e.preventDefault();
      printCurrentBasketReceipt();
      return;
    }

    const btn = e.target.closest('[data-share-current-basket-receipt]');
    if (!btn) return;
    e.preventDefault();
    shareCurrentBasketReceipt(btn);
  });
  $('ac_bs_customer_input').addEventListener('click', openCustomerPicker);
  $('ac_bs_customer_clear').addEventListener('click', e => {
    e.preventDefault();
    e.stopPropagation();
    clearCustomerSelection();
  });
  $('ac_bs_selected_customers').addEventListener('click', e => {
    const toggleBtn = e.target.closest('[data-toggle-selected-customers]');
    if (toggleBtn) {
      e.preventDefault();
      e.stopPropagation();
      toggleSelectedCustomerManager();
      return;
    }

    const btn = e.target.closest('[data-remove-selected-customer]');
    if (!btn) return;
    e.preventDefault();
    e.stopPropagation();
    removeSelectedCustomer(btn.dataset.removeSelectedCustomer || '');
  });
  $('ac_bs_manage_list').addEventListener('click', e => {
    const btn = e.target.closest('[data-remove-selected-customer]');
    if (!btn) return;
    e.preventDefault();
    e.stopPropagation();
    removeSelectedCustomer(btn.dataset.removeSelectedCustomer || '');
  });
  $('ac_bs_manage_add').addEventListener('click', e => {
    e.preventDefault();
    openCustomerPicker();
  });
  $('ac_bs_manage_clear').addEventListener('click', e => {
    e.preventDefault();
    clearCustomerSelection();
  });
  $('ac_bs_manage_done').addEventListener('click', e => {
    e.preventDefault();
    closeSelectedCustomerManager();
  });
  document.addEventListener('click', function(e){
    const panel = $('ac_bs_manage_selected');
    const selectedArea = $('ac_bs_selected_customers');
    if (!panel || !panel.classList.contains('active')) return;
    if (panel.contains(e.target) || selectedArea.contains(e.target)) return;
    closeSelectedCustomerManager();
  });
  $('ac_bs_picker_close').addEventListener('click', closePicker);
  $('ac_bs_picker_backdrop').addEventListener('click', closePicker);
  $('ac_bs_picker_search').addEventListener('input', function(){ runPickerSearch(this.value); });
  $('ac_bs_picker_results').addEventListener('click', e => {
    const btn = e.target.closest('[data-picker-idx]');
    if (!btn) return;
    const idx = parseInt(btn.dataset.pickerIdx, 10);
    if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) {
      pickerState.onPick(pickerState.items[idx].raw);
    }
  });
  $('ac_bs_ledger_close').addEventListener('click', closeLedgerModal);
  $('ac_bs_ledger_backdrop').addEventListener('click', closeLedgerModal);
  document.addEventListener('keydown', function(e){
    if (e.key === 'Escape') {
      closeSelectedCustomerManager();
      closeLedgerModal();
      const receiptMount = $('ac_bs_receipt_mount');
      if (receiptMount && receiptMount.innerHTML) {
        receiptMount.innerHTML = '';
        document.body.classList.remove('bs-br-open');
      }
    }
  });

  setDefaultDateRange();
  updateAccountModeUi();
  loadSummary();
})();
</script><k���d��������<x�2
N?�e<?php
/**
 * Creditor → Customer/Debtor Mapping Page
 *
 * Optional legacy Creditor → Customer basket offset mapping.
 *
 * Creditor baskets now use a dedicated creditor basket ledger. A mapping only
 * mirrors GRN basket quantities into the customer ledger when the administrator
 * explicitly enables the customer-offset checkbox.
 */

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

if (!is_user_logged_in()) {
    echo '<div style="padding:14px;border:1px solid #fecaca;background:#fff1f2;border-radius:12px;color:#991b1b;">Please log in to manage basket mappings.</div>';
    return;
}

if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
    echo '<div style="padding:14px;border:1px solid #fecaca;background:#fff1f2;border-radius:12px;color:#991b1b;">You do not have permission to manage basket mappings.</div>';
    return;
}

$option_name = 'ac_basket_creditor_debtor_map';
$notice = '';
$notice_type = 'ok';
$swal_notice = null;

if (!function_exists('ac_bmap_find_one_to_one_conflict')) {
    function ac_bmap_find_one_to_one_conflict(array $map, $creditor_code, $debtor_code) {
        $creditor_code = strtoupper(trim((string)$creditor_code));
        $debtor_code = strtoupper(trim((string)$debtor_code));

        foreach ($map as $row) {
            if (!is_array($row)) continue;

            $existing_creditor = strtoupper(trim((string)($row['creditorCode'] ?? '')));
            $existing_debtor = strtoupper(trim((string)($row['debtorCode'] ?? '')));
            if ($existing_creditor === '' || $existing_debtor === '') continue;

            $same_pair = ($existing_creditor === $creditor_code && $existing_debtor === $debtor_code);
            if ($same_pair) continue;

            if ($existing_creditor === $creditor_code) {
                return array(
                    'type' => 'creditor',
                    'message' => sprintf(
                        "This creditor is already mapped.\n\nExisting debtor/customer:\n%s%s",
                        $existing_debtor,
                        trim((string)($row['debtorName'] ?? '')) !== '' ? ' - ' . trim((string)($row['debtorName'] ?? '')) : ''
                    ),
                    'creditorCode' => $existing_creditor,
                    'creditorName' => trim((string)($row['creditorName'] ?? '')),
                    'debtorCode' => $existing_debtor,
                    'debtorName' => trim((string)($row['debtorName'] ?? '')),
                );
            }

            if ($existing_debtor === $debtor_code) {
                return array(
                    'type' => 'debtor',
                    'message' => sprintf(
                        "This debtor/customer is already mapped.\n\nExisting creditor:\n%s%s",
                        $existing_creditor,
                        trim((string)($row['creditorName'] ?? '')) !== '' ? ' - ' . trim((string)($row['creditorName'] ?? '')) : ''
                    ),
                    'creditorCode' => $existing_creditor,
                    'creditorName' => trim((string)($row['creditorName'] ?? '')),
                    'debtorCode' => $existing_debtor,
                    'debtorName' => trim((string)($row['debtorName'] ?? '')),
                );
            }
        }

        return null;
    }
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['ac_basket_map_action'])) {
    $nonce_ok = isset($_POST['_wpnonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'ac_basket_map_save');
    if (!$nonce_ok) {
        $notice = 'Security check failed. Please refresh and try again.';
        $notice_type = 'err';
    } else {
        $action = sanitize_text_field(wp_unslash($_POST['ac_basket_map_action']));
        $map = get_option($option_name, []);
        if (!is_array($map)) $map = [];

        if ($action === 'save') {
            $creditor_code = strtoupper(trim(sanitize_text_field(wp_unslash($_POST['creditor_code'] ?? ''))));
            $creditor_name = trim(sanitize_text_field(wp_unslash($_POST['creditor_name'] ?? '')));
            $debtor_code   = strtoupper(trim(sanitize_text_field(wp_unslash($_POST['debtor_code'] ?? ''))));
            $debtor_name   = trim(sanitize_text_field(wp_unslash($_POST['debtor_name'] ?? '')));
            $apply_customer_offset = !empty($_POST['apply_customer_offset']) ? 1 : 0;

            if ($creditor_code === '' || $debtor_code === '') {
                $notice = 'Please choose both Creditor and Customer.';
                $notice_type = 'err';
            } else {
                $conflict = ac_bmap_find_one_to_one_conflict($map, $creditor_code, $debtor_code);
                if ($conflict) {
                    $notice = (string)($conflict['message'] ?? 'This creditor/customer is already used by another mapping.');
                    $notice_type = 'err';
                    $swal_notice = array(
                        'icon' => 'warning',
                        'title' => 'Mapping already used',
                        'text' => $notice,
                    );
                } else {
                    $map[$creditor_code] = [
                        'creditorCode' => $creditor_code,
                        'creditorName' => $creditor_name,
                        'debtorCode'   => $debtor_code,
                        'debtorName'   => $debtor_name,
                        'applyCustomerOffset' => $apply_customer_offset,
                        'updatedBy'    => get_current_user_id(),
                        'updatedAt'    => current_time('mysql'),
                    ];
                    ksort($map, SORT_NATURAL | SORT_FLAG_CASE);
                    update_option($option_name, $map, false);
                    $notice = 'Mapping saved.';
                }
            }
        } elseif ($action === 'delete') {
            $creditor_code = strtoupper(trim(sanitize_text_field(wp_unslash($_POST['creditor_code'] ?? ''))));
            if ($creditor_code !== '' && isset($map[$creditor_code])) {
                unset($map[$creditor_code]);
                update_option($option_name, $map, false);
                $notice = 'Mapping removed.';
            }
        }
    }
}

$ajax_url = admin_url('admin-ajax.php');
$creditor_nonce = wp_create_nonce('ac_cs_creditor_search');
$debtor_nonce = wp_create_nonce('ac_cs_debtor_search');
$map = get_option($option_name, []);
if (!is_array($map)) $map = [];
ksort($map, SORT_NATURAL | SORT_FLAG_CASE);

$map_for_js = array();
foreach ($map as $row) {
    if (!is_array($row)) continue;
    $cc = strtoupper(trim((string)($row['creditorCode'] ?? '')));
    $dc = strtoupper(trim((string)($row['debtorCode'] ?? '')));
    if ($cc === '' || $dc === '') continue;
    $map_for_js[] = array(
        'creditorCode' => $cc,
        'creditorName' => trim((string)($row['creditorName'] ?? '')),
        'debtorCode' => $dc,
        'debtorName' => trim((string)($row['debtorName'] ?? '')),
        'applyCustomerOffset' => !array_key_exists('applyCustomerOffset', $row) || !empty($row['applyCustomerOffset']),
    );
}
?>

<div id="ac-basket-map-root"
     class="ac-bmap-wrap"
     data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
     data-creditor-nonce="<?php echo esc_attr($creditor_nonce); ?>"
     data-debtor-nonce="<?php echo esc_attr($debtor_nonce); ?>">

    <div class="bmap-card bmap-intro">
        <h2>Optional Creditor → Customer Basket Offset</h2>
        <p>
            Creditor baskets are now tracked in their own creditor basket ledger.
            Use this mapping only when the same business also has a customer account
            and a GRN should additionally reduce that customer&apos;s basket balance.
        </p>
        <div class="bmap-example">
            New mappings default to creditor-only tracking unless the checkbox is enabled.
            Existing legacy mappings remain enabled until you resave them with the checkbox cleared.
        </div>
    </div>

    <?php if ($notice !== ''): ?>
        <div class="bmap-notice <?php echo esc_attr($notice_type); ?>"><?php echo esc_html($notice); ?></div>
    <?php endif; ?>

    <form method="post" class="bmap-card bmap-form" autocomplete="off">
        <?php wp_nonce_field('ac_basket_map_save'); ?>
        <input type="hidden" name="ac_basket_map_action" value="save">
        <input type="hidden" id="bmap_creditor_code" name="creditor_code" value="">
        <input type="hidden" id="bmap_creditor_name_hidden" name="creditor_name" value="">
        <input type="hidden" id="bmap_debtor_code" name="debtor_code" value="">
        <input type="hidden" id="bmap_debtor_name_hidden" name="debtor_name" value="">

        <div class="bmap-grid">
            <div class="bmap-field">
                <label>Creditor / Supplier</label>
                <div class="bmap-search-wrap">
                    <input type="text" id="bmap_creditor_input" class="bmap-input" placeholder="Search creditor..." readonly>
                    <button type="button" id="bmap_creditor_clear" class="bmap-clear" aria-label="Clear creditor">×</button>
                </div>
            </div>

            <div class="bmap-arrow">→</div>

            <div class="bmap-field">
                <label>Customer / Debtor</label>
                <div class="bmap-search-wrap">
                    <input type="text" id="bmap_debtor_input" class="bmap-input" placeholder="Search customer..." readonly>
                    <button type="button" id="bmap_debtor_clear" class="bmap-clear" aria-label="Clear customer">×</button>
                </div>
            </div>

            <div class="bmap-field bmap-offset-field">
                <label class="bmap-check-label">
                    <input type="checkbox" name="apply_customer_offset" value="1">
                    Also reduce the mapped customer basket balance when a GRN is saved
                </label>
            </div>

            <div class="bmap-actions">
                <button type="submit" class="bmap-btn primary">Save Mapping</button>
            </div>
        </div>
    </form>

    <div class="bmap-card">
        <div class="bmap-list-head">
            <h3>Current Mappings</h3>
            <span><?php echo esc_html(count($map)); ?> mapping(s)</span>
        </div>

        <div class="bmap-table-wrap">
            <table class="bmap-table">
                <thead>
                    <tr>
                        <th>Creditor Code</th>
                        <th>Creditor Name</th>
                        <th>Customer Code</th>
                        <th>Customer Name</th>
                        <th>Customer Offset</th>
                        <th>Updated</th>
                        <th style="width:90px;">Action</th>
                    </tr>
                </thead>
                <tbody>
                <?php if (empty($map)): ?>
                    <tr><td colspan="7" class="bmap-empty">No mapping yet.</td></tr>
                <?php else: ?>
                    <?php foreach ($map as $row): ?>
                        <?php
                        $cc = strtoupper(trim((string)($row['creditorCode'] ?? '')));
                        if ($cc === '') continue;
                        ?>
                        <tr>
                            <td><strong><?php echo esc_html($cc); ?></strong></td>
                            <td><?php echo esc_html((string)($row['creditorName'] ?? '')); ?></td>
                            <td><strong><?php echo esc_html((string)($row['debtorCode'] ?? '')); ?></strong></td>
                            <td><?php echo esc_html((string)($row['debtorName'] ?? '')); ?></td>
                            <td>
                                <?php if (!array_key_exists('applyCustomerOffset', $row) || !empty($row['applyCustomerOffset'])): ?>
                                    <span class="bmap-status enabled">Enabled</span>
                                <?php else: ?>
                                    <span class="bmap-status disabled">Disabled</span>
                                <?php endif; ?>
                            </td>
                            <td><?php echo esc_html((string)($row['updatedAt'] ?? '')); ?></td>
                            <td>
                                <form method="post" class="bmap-delete-form" onsubmit="return confirm('Remove this mapping?');">
                                    <?php wp_nonce_field('ac_basket_map_save'); ?>
                                    <input type="hidden" name="ac_basket_map_action" value="delete">
                                    <input type="hidden" name="creditor_code" value="<?php echo esc_attr($cc); ?>">
                                    <button type="submit" class="bmap-btn danger">Delete</button>
                                </form>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                <?php endif; ?>
                </tbody>
            </table>
        </div>
    </div>

    <div class="bmap-picker-modal" id="bmap_picker_modal" aria-hidden="true">
        <div class="bmap-picker-backdrop" id="bmap_picker_backdrop"></div>
        <div class="bmap-picker-sheet">
            <div class="bmap-picker-head">
                <div class="bmap-picker-title" id="bmap_picker_title">Select</div>
                <button type="button" class="bmap-picker-close" id="bmap_picker_close" aria-label="Close">×</button>
            </div>
            <div class="bmap-picker-body">
                <input type="text" id="bmap_picker_search" class="bmap-input" placeholder="Search..." autocomplete="off">
                <div id="bmap_picker_results" class="bmap-picker-results"></div>
            </div>
        </div>
    </div>
</div>

<style>
#ac-basket-map-root{--bmap-green:#166534;--bmap-border:#dbe4ee;--bmap-border-strong:#c4d0dd;--bmap-text:#0f172a;--bmap-muted:#475569;max-width:1180px;margin:0 auto;padding:16px;font-family:"Segoe UI",Roboto,Arial,sans-serif;color:var(--bmap-text);box-sizing:border-box;}
#ac-basket-map-root *{box-sizing:border-box;}
.bmap-card{background:#fff;border:1px solid var(--bmap-border);border-radius:14px;box-shadow:0 4px 16px rgba(15,23,42,.05);padding:16px;margin-bottom:14px;}
.bmap-intro h2{margin:0 0 8px;font-size:24px;line-height:1.2;}
.bmap-intro p{margin:0;color:var(--bmap-muted);font-weight:600;line-height:1.45;}
.bmap-example{margin-top:10px;border:1px solid #bbf7d0;background:#f0fdf4;color:#166534;border-radius:10px;padding:10px;font-weight:800;}
.bmap-notice{border-radius:12px;padding:12px 14px;margin-bottom:14px;font-weight:800;}
.bmap-notice.ok{border:1px solid #86efac;background:#dcfce7;color:#166534;}
.bmap-notice.err{border:1px solid #fca5a5;background:#fee2e2;color:#991b1b;}
.bmap-grid{display:grid;grid-template-columns:minmax(0,1fr) 44px minmax(0,1fr) minmax(250px,.8fr) auto;gap:12px;align-items:end;}
.bmap-field label{display:block;font-size:13px;color:var(--bmap-muted);font-weight:800;margin-bottom:6px;}
.bmap-check-label{display:flex!important;align-items:flex-start;gap:8px;line-height:1.35;margin:0!important;padding:10px;border:1px solid #dbe4ee;border-radius:10px;background:#f8fafc;color:#334155!important;}
.bmap-check-label input{margin-top:2px;flex:0 0 auto;}
.bmap-status{display:inline-flex;align-items:center;border-radius:999px;padding:4px 9px;font-size:12px;font-weight:900;}
.bmap-status.enabled{background:#dcfce7;color:#166534;border:1px solid #86efac;}
.bmap-status.disabled{background:#f1f5f9;color:#64748b;border:1px solid #cbd5e1;}
.bmap-input{width:100%;min-height:46px;border:1px solid var(--bmap-border-strong);border-radius:10px;background:#fff;color:#111;padding:10px 38px 10px 12px;font-size:15px;}
.bmap-input:focus,.bmap-btn:focus{outline:none;border-color:var(--bmap-green);box-shadow:0 0 0 3px rgba(22,101,52,.12);}
.bmap-search-wrap{position:relative;}
.bmap-clear{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:26px;height:26px;border:1px solid var(--bmap-bor<x�2\��Le��������<x�2
N,�����der);background:#fff;color:#64748b;border-radius:7px;display:none;align-items:center;justify-content:center;font-size:16px;font-weight:900;cursor:pointer;padding:0;}
.bmap-clear.show{display:inline-flex;}
.bmap-arrow{min-height:46px;display:flex;align-items:center;justify-content:center;font-size:24px;font-weight:900;color:var(--bmap-green);}
.bmap-actions{display:flex;align-items:end;}
.bmap-btn{border:1px solid #cbd5e1!important;border-radius:10px!important;background:#fff!important;color:#334155!important;font-weight:900!important;font-size:13px!important;line-height:1.1!important;padding:10px 14px!important;cursor:pointer!important;min-height:40px!important;box-shadow:none!important;text-shadow:none!important;}
.bmap-btn.primary{background:var(--bmap-green)!important;border-color:var(--bmap-green)!important;color:#fff!important;min-height:46px!important;white-space:nowrap!important;}
.bmap-btn.danger{background:#fff1f2!important;border-color:#fecaca!important;color:#991b1b!important;padding:7px 10px!important;min-height:34px!important;}
.bmap-list-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;}
.bmap-list-head h3{margin:0;font-size:18px;}
.bmap-list-head span{font-size:13px;font-weight:800;color:var(--bmap-muted);}
.bmap-table-wrap{width:100%;overflow:auto;border:1px solid #e5e7eb;border-radius:12px;}
.bmap-table{width:100%;min-width:880px;border-collapse:collapse;background:#fff;}
.bmap-table th{background:#f8fafc;color:#334155;text-align:left;font-size:13px;font-weight:900;padding:10px 12px;border-bottom:1px solid #e5e7eb;white-space:nowrap;}
.bmap-table td{padding:10px 12px;font-size:14px;line-height:1.25;border-bottom:1px solid #edf2f7;vertical-align:middle;}
.bmap-table tbody tr:nth-child(even){background:#f8fafc;}
.bmap-empty{text-align:center!important;color:#64748b!important;padding:18px!important;font-weight:700;}
.bmap-delete-form{margin:0;}
.bmap-picker-modal{position:fixed;inset:0;z-index:99999;display:none;align-items:center;justify-content:center;padding:16px;}
.bmap-picker-modal.active{display:flex;}
.bmap-picker-backdrop{position:absolute;inset:0;background:rgba(15,23,42,.45);}
.bmap-picker-sheet{position:relative;width:min(560px,100%);max-height:min(720px,90vh);display:flex;flex-direction:column;background:#fff;border-radius:16px;border:1px solid var(--bmap-border);box-shadow:0 24px 70px rgba(15,23,42,.26);overflow:hidden;}
.bmap-picker-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 16px;border-bottom:1px solid #e5e7eb;}
.bmap-picker-title{font-size:17px;font-weight:900;color:#0f172a;}
.bmap-picker-close{
    width:44px;
    height:34px;
    border:1px solid #cbd5e1;
    background:#fff;
    border-radius:10px;
    font-size:20px;
    font-weight:900;
    cursor:pointer;
    color:#334155;

    display:inline-flex;
    align-items:center;
    justify-content:center;
    padding:0!important;
    line-height:1!important;
    text-align:center;
    box-shadow:none;
}
.bmap-picker-body{padding:14px;overflow:auto;}
.bmap-picker-results{margin-top:10px;display:grid;gap:8px;}
.bmap-picker-item{width:100%;text-align:left;border:1px solid #dbe4ee;background:#fff;border-radius:10px;padding:10px 12px;cursor:pointer;}
.bmap-picker-item:hover{border-color:#16a34a;background:#f0fdf4;}
.bmap-picker-main{display:block;font-weight:900;color:#0f172a;}
.bmap-picker-sub{display:block;margin-top:3px;font-size:12px;font-weight:800;color:#64748b;}
.bmap-picker-note{padding:12px;text-align:center;color:#64748b;font-weight:800;}
@media (max-width:760px){.bmap-grid{grid-template-columns:1fr;}.bmap-arrow{min-height:20px;transform:rotate(90deg);}.bmap-actions{display:block;}.bmap-btn.primary{width:100%;}}
</style>

<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
(function(){
    const root = document.getElementById('ac-basket-map-root');
    if (!root || root.dataset.init === '1') return;
    root.dataset.init = '1';

    const $ = id => document.getElementById(id);
    const existingMappings = <?php echo wp_json_encode($map_for_js); ?> || [];
    const serverSwalNotice = <?php echo wp_json_encode($swal_notice); ?>;
    const picker = {
        mode: '',
        items: [],
        timer: null,
        onPick: null
    };

    function esc(s){
        return String(s ?? '').replace(/[&<>'"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#039;','"':'&quot;'}[c]));
    }

    function updateClearButtons(){
        $('bmap_creditor_clear').classList.toggle('show', !!$('bmap_creditor_input').value.trim());
        $('bmap_debtor_clear').classList.toggle('show', !!$('bmap_debtor_input').value.trim());
    }

    function showAlert(icon, title, text){
        text = String(text || '');
        if (window.Swal) {
            Swal.fire({
                icon,
                title,
                html: esc(text).replace(/\n/g, '<br>'),
                confirmButtonText: 'OK'
            });
        } else {
            alert(title + (text ? '\n\n' + text : ''));
        }
    }

    function findMappingConflict(creditorCode, debtorCode){
        creditorCode = String(creditorCode || '').trim().toUpperCase();
        debtorCode = String(debtorCode || '').trim().toUpperCase();
        for (const row of existingMappings) {
            const existingCreditor = String(row.creditorCode || '').trim().toUpperCase();
            const existingDebtor = String(row.debtorCode || '').trim().toUpperCase();
            if (!existingCreditor || !existingDebtor) continue;
            if (existingCreditor === creditorCode && existingDebtor === debtorCode) continue;
            if (existingCreditor === creditorCode) {
                return `This creditor is already mapped.

Existing debtor/customer:
${existingDebtor}${row.debtorName ? ' - ' + row.debtorName : ''}`;
            }
            if (existingDebtor === debtorCode) {
                return `This debtor/customer is already mapped.

Existing creditor:
${existingCreditor}${row.creditorName ? ' - ' + row.creditorName : ''}`;
            }
        }
        return '';
    }

    async function searchAjax(action, nonce, q){
        const url = `${root.dataset.ajaxUrl}?action=${encodeURIComponent(action)}&nonce=${encodeURIComponent(nonce)}&q=${encodeURIComponent(q || '')}`;
        const res = await fetch(url, {credentials:'same-origin', cache:'no-store'});
        const data = await res.json();
        if (!data.success) throw new Error(data.data?.error || 'Search failed');
        return data.data?.items || [];
    }

    function renderNote(msg){
        $('bmap_picker_results').innerHTML = `<div class="bmap-picker-note">${esc(msg)}</div>`;
    }

    function renderItems(items){
        picker.items = items || [];
        if (!picker.items.length) { renderNote('No result found'); return; }
        $('bmap_picker_results').innerHTML = picker.items.map((it, idx) => {
            const code = it.code || '';
            const name = it.name || code;
            return `<button type="button" class="bmap-picker-item" data-idx="${idx}">
                <span class="bmap-picker-main">${esc(name)}</span>
                <span class="bmap-picker-sub">${esc(code)}</span>
            </button>`;
        }).join('');
    }

    function openPicker(mode){
        picker.mode = mode;
        picker.items = [];
        picker.onPick = (item) => {
            const code = item?.code || '';
            const name = item?.name || code;
            if (mode === 'creditor') {
                $('bmap_creditor_input').value = name || code;
                $('bmap_creditor_code').value = code;
                $('bmap_creditor_name_hidden').value = name;
            } else {
                $('bmap_debtor_input').value = name || code;
                $('bmap_debtor_code').value = code;
                $('bmap_debtor_name_hidden').value = name;
            }
            updateClearButtons();
            closePicker();
        };
        $('bmap_picker_title').textContent = mode === 'creditor' ? 'Select Creditor / Supplier' : 'Select Customer / Debtor';
        $('bmap_picker_search').value = '';
        $('bmap_picker_search').placeholder = mode === 'creditor' ? 'Search creditor...' : 'Search customer...';
        $('bmap_picker_modal').classList.add('active');
        renderNote('Type to search');
        setTimeout(() => $('bmap_picker_search').focus(), 80);
    }

    function closePicker(){
        $('bmap_picker_modal').classList.remove('active');
        $('bmap_picker_results').innerHTML = '';
        $('bmap_picker_search').value = '';
        picker.items = [];
        picker.onPick = null;
    }

    async function runSearch(q){
        clearTimeout(picker.timer);
        q = String(q || '').trim();
        if (q.length < 1) { renderNote('Type to search'); return; }
        picker.timer = setTimeout(async () => {
            renderNote('Searching...');
            try {
                const items = picker.mode === 'creditor'
                    ? await searchAjax('ac_cs_creditor_search', root.dataset.creditorNonce, q)
                    : await searchAjax('ac_cs_debtor_search', root.dataset.debtorNonce, q);
                renderItems(items);
            } catch(e) {
                renderNote(e.message || 'Failed to load');
            }
        }, 180);
    }

    if (serverSwalNotice && serverSwalNotice.text) {
        setTimeout(() => showAlert(serverSwalNotice.icon || 'warning', serverSwalNotice.title || 'Mapping already used', serverSwalNotice.text), 100);
    }

    document.querySelector('.bmap-form')?.addEventListener('submit', (e) => {
        const creditorCode = $('bmap_creditor_code').value;
        const debtorCode = $('bmap_debtor_code').value;
        const conflict = findMappingConflict(creditorCode, debtorCode);
        if (conflict) {
            e.preventDefault();
            showAlert('warning', 'Mapping already used', conflict);
        }
    });

    $('bmap_creditor_input').addEventListener('click', () => openPicker('creditor'));
    $('bmap_debtor_input').addEventListener('click', () => openPicker('debtor'));
    $('bmap_creditor_clear').addEventListener('click', () => {
        $('bmap_creditor_input').value = '';
        $('bmap_creditor_code').value = '';
        $('bmap_creditor_name_hidden').value = '';
        updateClearButtons();
    });
    $('bmap_debtor_clear').addEventListener('click', () => {
        $('bmap_debtor_input').value = '';
        $('bmap_debtor_code').value = '';
        $('bmap_debtor_name_hidden').value = '';
        updateClearButtons();
    });
    $('bmap_picker_close').addEventListener('click', closePicker);
    $('bmap_picker_backdrop').addEventListener('click', closePicker);
    $('bmap_picker_search').addEventListener('input', function(){ runSearch(this.value); });
    $('bmap_picker_results').addEventListener('click', (e) => {
        const btn = e.target.closest('[data-idx]');
        if (!btn) return;
        const idx = Number(btn.dataset.idx);
        if (!Number.isNaN(idx) && picker.items[idx] && picker.onPick) picker.onPick(picker.items[idx]);
    });
})();
</script><x�2u�X�f��������7���
N?�g<?php
/**
 * WST Excellent Vege - Daily Customer Delivery Order Pricing
 * UI revision: 2026-07-14-v6 (freight picker, default rate, SweetAlert)
 *
 * Suggested WordPress page slug:
 *   /delivery-order-daily-pricing/
 *
 * Purpose:
 * - Show Delivery Orders for one day, grouped by Debtor Code/customer.
 * - Calculate total KG per customer across every active DO on that date.
 * - Allow staff to update missing/changing item prices without opening each DO.
 * - Rebuild and queue the correct AutoCount payload for every changed DO.
 * - Calculate daily freight from customer KG and add/update it on the latest DO.
 * - Let administrators configure the dedicated AutoCount freight stock item.
 *
 * Canonical data source:
 *   {$wpdb->prefix}ac_do
 *   {$wpdb->prefix}ac_do_items
 *
 * Access:
 * - Administrator and Editor: pricing and freight actions
 * - Administrator only: freight item configuration
 */

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

if (!is_user_logged_in()) {
    echo '<div class="wst-docp-alert wst-docp-alert-error">Please log in to manage Delivery Order prices.</div>';
    return;
}

$wst_docp_user = wp_get_current_user();
$wst_docp_roles = is_array($wst_docp_user->roles ?? null) ? $wst_docp_user->roles : array();
$wst_docp_can_manage = current_user_can('manage_options') || in_array('editor', $wst_docp_roles, true);

if (!$wst_docp_can_manage) {
    echo '<div class="wst-docp-alert wst-docp-alert-error">You do not have permission to manage Delivery Order prices.</div>';
    return;
}

global $wpdb;

if (!$wpdb) {
    echo '<div class="wst-docp-alert wst-docp-alert-error">WordPress database connection is not available.</div>';
    return;
}

if (!defined('WST_DOCP_MAX_DOS_PER_DAY')) {
    define('WST_DOCP_MAX_DOS_PER_DAY', 300);
}

if (!defined('WST_DOCP_MAX_CHANGED_LINES')) {
    define('WST_DOCP_MAX_CHANGED_LINES', 2000);
}

if (!defined('WST_DOCP_MAX_UNIT_PRICE')) {
    define('WST_DOCP_MAX_UNIT_PRICE', 9999999);
}

if (!defined('WST_DOCP_MAX_FREIGHT_RATE')) {
    define('WST_DOCP_MAX_FREIGHT_RATE', 999999);
}

if (!defined('WST_DOCP_MAX_ITEM_CHOICES')) {
    define('WST_DOCP_MAX_ITEM_CHOICES', 5000);
}

if (!defined('WST_DOCP_FREIGHT_ITEM_OPTION')) {
    define('WST_DOCP_FREIGHT_ITEM_OPTION', 'wst_docp_freight_item_code');
}

if (!defined('WST_DOCP_FREIGHT_RATE_OPTION')) {
    define('WST_DOCP_FREIGHT_RATE_OPTION', 'wst_docp_default_freight_rate');
}

if (!function_exists('wst_docp_log')) {
    function wst_docp_log($message) {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('[WST DO Daily Pricing] ' . (string) $message);
        }
    }
}

if (!function_exists('wst_docp_table_exists')) {
    function wst_docp_table_exists($table_name) {
        global $wpdb;
        if (!$wpdb) return false;

        return $wpdb->get_var(
            $wpdb->prepare('SHOW TABLES LIKE %s', $table_name)
        ) === $table_name;
    }
}

if (!function_exists('wst_docp_table_columns')) {
    function wst_docp_table_columns($table_name) {
        global $wpdb;
        static $cache = array();

        if (isset($cache[$table_name])) {
            return $cache[$table_name];
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', (string) $table_name);
        $columns = $wpdb ? $wpdb->get_col("SHOW COLUMNS FROM `{$safe_table}`", 0) : array();
        $cache[$table_name] = is_array($columns) ? array_flip($columns) : array();

        return $cache[$table_name];
    }
}

if (!function_exists('wst_docp_json_array')) {
    function wst_docp_json_array($json) {
        $decoded = json_decode((string) $json, true);
        return is_array($decoded) ? $decoded : array();
    }
}

if (!function_exists('wst_docp_valid_date')) {
    function wst_docp_valid_date($value, $fallback = '') {
        $value = trim((string) $value);
        $date = DateTime::createFromFormat('Y-m-d', $value, wp_timezone());

        return ($date && $date->format('Y-m-d') === $value) ? $value : $fallback;
    }
}

if (!function_exists('wst_docp_float')) {
    function wst_docp_float($value) {
        if (is_string($value)) {
            $value = str_replace(',', '', trim($value));
        }

        return is_numeric($value) ? (float) $value : 0.0;
    }
}

if (!function_exists('wst_docp_clean_doc_no')) {
    function wst_docp_clean_doc_no($value) {
        $value = strtoupper(trim((string) $value));
        $value = preg_replace('/[^A-Z0-9\-\/]/', '', $value);
        return substr($value, 0, 60);
    }
}

if (!function_exists('wst_docp_current_page_url')) {
    function wst_docp_current_page_url() {
        $request_uri = isset($_SERVER['REQUEST_URI'])
            ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI']))
            : '/';

        return home_url($request_uri);
    }
}

if (!function_exists('wst_docp_redirect_notice')) {
    function wst_docp_redirect_notice($type, $message, $filters = array()) {
        $url = wst_docp_current_page_url();
        $url = remove_query_arg(
            array('wst_docp_notice_type', 'wst_docp_notice'),
            $url
        );

        $args = array(
            'wst_docp_notice_type' => sanitize_key($type),
            'wst_docp_notice' => (string) $message,
        );

        foreach (array('date', 'q', 'pricing') as $key) {
            if (isset($filters[$key]) && $filters[$key] !== '') {
                $args[$key] = (string) $filters[$key];
            }
        }

        wp_safe_redirect(add_query_arg($args, $url));
        exit;
    }
}

if (!function_exists('wst_docp_header_doc_no')) {
    function wst_docp_header_doc_no($header) {
        $local = wst_docp_clean_doc_no($header['local_doc_no'] ?? '');
        if ($local !== '') return $local;

        return wst_docp_clean_doc_no($header['autocount_doc_no'] ?? '');
    }
}

if (!function_exists('wst_docp_is_goods_receive_doc')) {
    function wst_docp_is_goods_receive_doc($doc_no) {
        $doc_no = strtoupper(trim((string) $doc_no));
        return strpos($doc_no, 'WPGR') === 0 || strpos($doc_no, 'GRN') === 0;
    }
}

if (!function_exists('wst_docp_header_is_active')) {
    function wst_docp_header_is_active($header) {
        if (!empty($header['deleted_at'])) return false;
        if (!empty($header['hidden_from_staff_list'])) return false;

        $delivery_status = strtoupper(trim((string) ($header['delivery_status'] ?? '')));
        $sync_status = strtoupper(trim((string) ($header['sync_status'] ?? '')));

        if ($delivery_status === 'CANCELLED') return false;
        if (in_array($sync_status, array(
            'VOID_PENDING_AUTOCOUNT',
            'VOID_FAILED',
            'VOIDED_IN_AUTOCOUNT',
            'CANCELLED',
        ), true)) {
            return false;
        }

        return !wst_docp_is_goods_receive_doc(wst_docp_header_doc_no($header));
    }
}

if (!function_exists('wst_docp_line_is_freight')) {
    function wst_docp_line_is_freight($line, $configured_item_code = '') {
        $meta = wst_docp_json_array($line['meta'] ?? '');
        $marker = $meta['_wstFreightCharge'] ?? $meta['wstFreightCharge'] ?? $meta['freightCharge'] ?? false;

        if ($marker === true || $marker === 1 || $marker === '1') {
            return true;
        }

        if (is_string($marker) && in_array(strtolower(trim($marker)), array('true', 'yes', 'y', 't'), true)) {
            return true;
        }

        $configured_item_code = trim((string) $configured_item_code);
        if ($configured_item_code === '') {
            $configured_item_code = trim((string) get_option(WST_DOCP_FREIGHT_ITEM_OPTION, ''));
        }

        $item_code = trim((string) ($line['item_code'] ?? ''));
        return $configured_item_code !== ''
            && $item_code !== ''
            && strcasecmp($configured_item_code, $item_code) === 0;
    }
}

if (!function_exists('wst_docp_line_pack_data')) {
    function wst_docp_line_pack_data($line) {
        $is_freight = wst_docp_line_is_freight($line);

        if ($is_freight) {
            $document_qty = wst_docp_float($line['qty'] ?? 0);
            if ($document_qty <= 0) {
                $document_qty = wst_docp_float($line['unit_qty'] ?? 0);
            }
            if ($document_qty <= 0) {
                $document_qty = 1;
            }

            return array(
                'pack_type' => 'UNIT',
                'unit_qty' => $document_qty,
                'basket_qty' => 0,
                'carton_qty' => 0,
                'kg_per_unit' => 0,
                'total_kg' => 0,
                'amount_qty' => $document_qty,
                'is_freight' => true,
            );
        }

        $basket = wst_docp_float($line['basket_qty'] ?? 0);
        $carton = wst_docp_float($line['carton_qty'] ?? 0);
        $unit_qty = wst_docp_float($line['unit_qty'] ?? 0);
        $pack_type = strtoupper(trim((string) ($line['pack_type'] ?? '')));

        if ($pack_type !== 'CARTON' && $pack_type !== 'BASKET') {
            $pack_type = $carton > 0 ? 'CARTON' : 'BASKET';
        }

        if ($unit_qty <= 0) {
            $unit_qty = $pack_type === 'CARTON' ? $carton : $basket;
        }

        if ($unit_qty <= 0) {
            $unit_qty = 1;
        }

        $total_kg = wst_docp_float($line['total_weight_kg'] ?? 0);
        if ($total_kg <= 0) {
            $total_kg = wst_docp_float($line['qty'] ?? 0);
        }

        $kg_per_unit = wst_docp_float($line['weight_kg'] ?? 0);
        if ($kg_per_unit <= 0 && $total_kg > 0 && $unit_qty > 0) {
            $kg_per_unit = $total_kg / $unit_qty;
        }

        return array(
            'pack_type' => $pack_type,
            'unit_qty' => $unit_qty,
            'basket_qty' => $pack_type === 'BASKET' ? $unit_qty : 0,
            'carton_qty' => $pack_type === 'CARTON' ? $unit_qty : 0,
            'kg_per_unit' => $kg_per_unit,
            'total_kg' => $total_kg,
            'amount_qty' => $total_kg,
            'is_freight' => false,
        );
    }
}

if (!function_exists('wst_docp_active_environment')) {
    function wst_docp_active_environment() {
        $environment = strtolower(trim((string) get_option('ac_bridge_active_environment', '')));

        if ($environment === '') {
            $status = get_option('ac_bridge_sync_status', array());
            $environment = is_array($status)
                ? strtolower(trim((string) ($status['environment'] ?? '')))
                : '';
        }

        return $environment === 'production' ? '' : sanitize_key($environment);
    }
}

if (!function_exists('wst_docp_masterdata_table')) {
    function wst_docp_masterdata_table($base) {
        global $wpdb;

        $base = preg_replace('/[^A-Za-z0-9_]/', '', (string) $base);
        $environment = wst_docp_active_environment();

        if (class_exists('AutoCount_Bridge_Core') && method_exists('AutoCount_Bridge_Core', 'masterdata_table')) {
            return AutoCount_Bridge_Core::masterdata_table($base, $environment);
        }

        return $wpdb->prefix . $base . ($environment !== '' ? '_' . $environment : '');
    }
}

if (!function_exists('wst_docp_get_active_item')) {
    function wst_docp_get_active_item($item_code) {
        global $wpdb;

        $item_code = trim((string) $item_code);
        if ($item_code === '') {
            return new WP_Error('freight_item_required', 'Select a freight item code.');
        }

        $table = wst_docp_masterdata_table('acs_items');
        if (!wst_docp_table_exists($table)) {
            return new WP_Error('item_cache_missing', 'The active AutoCount item cache table was not found.');
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT item_code, description, base_uom, sales_uom, tax_code
                 FROM `{$safe_table}`
                 WHERE item_code = %s
                   AND is_active = 1
                 LIMIT 1",
                $item_code
            ),
            ARRAY_A
        );

        if (!is_array($row)) {
            return new WP_Error('invalid_freight_item', 'The freight item is missing or inactive in the active AutoCount item cache.');
        }

        $resolved_code = trim((string) ($row['item_code'] ?? ''));
        $description = trim((string) ($row['description'] ?? ''));
        $uom = trim((string) ($row['sales_uom'] ?? ''));
        if ($uom === '') {
            $uom = trim((string) ($row['base_uom'] ?? ''));
        }
        if ($uom === '') {
            return new WP_Error('freight_item_uom_missing', 'The selected freight item does not have a Sales UOM or Base UOM.');
        }

        return array(
            'item_code' => $resolved_code,
            'description' => $description !== '' ? $description : $resolved_code,
            'uom' => $uom,
            'tax_code' => trim((string) ($row['tax_code'] ?? '')),
        );
    }
}

if (!function_exists('wst_docp_load_active_items')) {
    function wst_docp_load_active_items($limit = WST_DOCP_MAX_ITEM_CHOICES) {
        global $wpdb;

        $table = wst_docp_masterdata_table('acs_items');
        if (!wst_docp_table_exists($table)) {
            return array();
        }

        $limit = max(1, min((int) $limit, WST_DOCP_MAX_ITEM_CHOICES));
        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $rows = $wpdb->get_results(
            "SELECT item_code, description, base_uom, sales_uom
             FROM `{$safe_table}`
             WHERE is_active = 1
               AND item_code <> ''
             ORDER BY item_code ASC
             LIMIT {$limit}",
            ARRAY_A
        );

        return is_array($rows) ? $rows : array();
    }
}

if (!function_exists('wst_docp_debtor_defaults')) {
    function wst_docp_debtor_defaults($header) {
        global $wpdb;

        $result = array(
            'display_term' => trim((string) ($header['display_term'] ?? '')),
            'sales_agent' => trim((string) ($header['sales_agent'] ?? '')),
        );

        if (strcasecmp($result['display_term'], 'CASH') === 0) {
            $result['display_term'] = '';
        }

        $debtor_code = trim((string) ($header['debtor_code'] ?? ''));
        if ($debtor_code === '') {
            return $result;
        }

        $table = wst_docp_masterdata_table('acs_debtors');

        if (!wst_docp_table_exists($table)) {
            return $result;
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT display_term, sales_agent
                 FROM `{$safe_table}`
                 WHERE acc_no = %s
                   AND is_active = 1
                 LIMIT 1",
                $debtor_code
            ),
            ARRAY_A
        );

        if (!is_array($row)) {
            return $result;
        }

        if ($result['sales_agent'] === '') {
            $result['sales_agent'] = trim((string) ($row['sales_agent'] ?? ''));
        }

        if ($result['display_term'] === '') {
            $cached_term = trim((string) ($row['display_term'] ?? ''));
            if ($cached_term !== '' && strcasecmp($cached_term, 'CASH') !== 0) {
                $result['display_term'] = $cached_term;
            }
        }

        return $result;
    }
}

if (!function_exists('wst_docp_build_payload')) {
    function wst_docp_build_payload($header, $items, $source = 'wp-ui-bulk-pricing') {
        $doc_no = wst_docp_header_doc_no($header);
        $doc_key = absint($header['autocount_doc_key'] ?? 0);
        $debtor_defaults = wst_docp_debtor_defaults($header);
        $sales_agent = $debtor_defaults['sales_agent'];
        $display_term = $debtor_defaults['display_term'];
        $location = trim((string) ($header['location'] ?? 'HQ'));
        if ($location === '') $location 7���9#�g��������7�:�
N?�h= 'HQ';

        $source = sanitize_key((string) $source);
        if ($source === '') {
            $source = 'wp-ui-bulk-pricing';
        }

        $payload_lines = array();

        foreach ((array) $items as $line) {
            $item_code = trim((string) ($line['item_code'] ?? ''));
            if ($item_code === '') continue;

            $pack = wst_docp_line_pack_data($line);
            $unit_price = wst_docp_float($line['unit_price'] ?? 0);
            $description = trim((string) ($line['description'] ?? $item_code));
            $line_location = trim((string) ($line['location'] ?? $location));
            if ($line_location === '') $line_location = $location;
            $tax_code = trim((string) ($line['tax_code'] ?? ''));
            if ($tax_code === '' && empty($pack['is_freight'])) {
                $tax_code = 'SR-0';
            }
            $tax_rate = wst_docp_float($line['tax_rate'] ?? 0);
            $amount_qty = wst_docp_float($pack['amount_qty'] ?? $pack['total_kg']);
            $amount = round($unit_price * $amount_qty, 2);

            $payload_line = array(
                'itemCode' => $item_code,
                'description' => $description,
                'itemName' => $description,
                'ItemName' => $description,
                'itemDesc' => $description,
                'uom' => trim((string) ($line['uom'] ?? 'KG')) ?: 'KG',
                'unitPrice' => $unit_price,
                'amount' => $amount,
                'packType' => $pack['pack_type'],
                'qty' => $amount_qty,
                'unitQty' => $pack['unit_qty'],
                'basketQty' => $pack['basket_qty'],
                'cartonQty' => $pack['carton_qty'],
                'location' => $line_location,
            );

            if (!empty($pack['is_freight'])) {
                // Do not send kg/totalKg for freight. Both aliases map to AutoCount
                // Qty and would overwrite the intended Qty = 1. Weight stays zero
                // through the explicit weightKg UDF value instead.
                $payload_line['weightKg'] = 0;
            } else {
                $payload_line['kg'] = $pack['kg_per_unit'];
                $payload_line['totalKg'] = $pack['total_kg'];
            }

            if ($tax_code !== '') {
                $payload_line['taxCode'] = $tax_code;
            }

            // Let AutoCount resolve a freight item's tax code/rate from the item
            // when the WordPress cache does not provide a non-zero rate.
            if (empty($pack['is_freight']) || $tax_rate > 0) {
                $payload_line['taxRate'] = $tax_rate;
            }

            if (!empty($pack['is_freight'])) {
                $freight_meta = wst_docp_json_array($line['meta'] ?? '');
                $payload_line['_wstFreightCharge'] = true;
                $payload_line['freightDailyKg'] = wst_docp_float($freight_meta['dailyKg'] ?? 0);
                $payload_line['freightRatePerKg'] = wst_docp_float($freight_meta['ratePerKg'] ?? 0);
            }

            $payload_lines[] = $payload_line;
        }

        $common = array(
            'docNo' => $doc_no,
            'DocNo' => $doc_no,
            'sourceDocNo' => $doc_no,
            'docDate' => wst_docp_valid_date($header['doc_date'] ?? '', current_time('Y-m-d')),
            'debtorCode' => trim((string) ($header['debtor_code'] ?? '')),
            'DebtorCode' => trim((string) ($header['debtor_code'] ?? '')),
            'debtorName' => trim((string) ($header['debtor_name'] ?? '')),
            'DebtorName' => trim((string) ($header['debtor_name'] ?? '')),
            'salesAgent' => $sales_agent,
            'SalesAgent' => $sales_agent,
            'displayTerm' => $display_term,
            'location' => $location,
            'Location' => $location,
            'remark' => trim((string) ($header['remark'] ?? '')),
            'assignedDriverId' => absint($header['assigned_driver_id'] ?? 0),
            'driverId' => absint($header['assigned_driver_id'] ?? 0),
            'lines' => $payload_lines,
            '_meta' => array(
                'requestedBy' => get_current_user_id(),
                'requestedAt' => current_time('mysql'),
                'source' => $source,
                'ip' => isset($_SERVER['REMOTE_ADDR'])
                    ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']))
                    : '',
            ),
        );

        if (!empty($common['assignedDriverId'])) {
            $common['_assignment'] = array(
                'assignedDriverId' => $common['assignedDriverId'],
                'assignedAt' => current_time('mysql'),
                'assignedBy' => get_current_user_id(),
            );
        }

        if ($doc_key <= 0) {
            return array_merge($common, array(
                'docKey' => 0,
                'currencyCode' => 'MYR',
                'currencyRate' => 1,
                'ref' => '',
                'refNo2' => '',
                'inclusiveTax' => false,
            ));
        }

        return array_merge($common, array(
            'type' => 'DELIVERY_ORDER',
            'subtype' => 'UPDATE',
            'docKey' => $doc_key,
            'DocKey' => $doc_key,
            'overwriteLines' => true,
            'fullReplacement' => true,
            'protectedStaffEdit' => true,
            'editSource' => $source === 'wp-ui-daily-freight'
                ? 'STAFF_DO_FREIGHT_CHARGE'
                : 'STAFF_DO_BULK_PRICING',
        ));
    }
}

if (!function_exists('wst_docp_job_insert')) {
    function wst_docp_job_insert($table, $columns, $data) {
        global $wpdb;

        $insert = array_intersect_key($data, $columns);
        if (empty($insert)) {
            return false;
        }

        return $wpdb->insert($table, $insert);
    }
}

if (!function_exists('wst_docp_queue_payload')) {
    function wst_docp_queue_payload($header, $payload) {
        global $wpdb;

        $jobs_table = $wpdb->prefix . 'ac_jobs';
        if (!wst_docp_table_exists($jobs_table)) {
            return new WP_Error('jobs_table_missing', 'AutoCount jobs table was not found.');
        }

        $jobs_cols = wst_docp_table_columns($jobs_table);
        $safe_jobs_table = preg_replace('/[^A-Za-z0-9_]/', '', $jobs_table);
        $doc_no = wst_docp_header_doc_no($header);
        $doc_key = absint($header['autocount_doc_key'] ?? 0);
        $driver_id = absint($header['assigned_driver_id'] ?? 0);
        $now = current_time('mysql');
        $source = sanitize_key((string) ($payload['_meta']['source'] ?? 'wp-ui-bulk-pricing'));
        if ($source === '') {
            $source = 'wp-ui-bulk-pricing';
        }
        $request_prefix = $source === 'wp-ui-daily-freight' ? 'do-freight-' : 'do-price-';
        $client_request_id = substr(
            $request_prefix . preg_replace('/[^A-Za-z0-9\-_]/', '-', $doc_no) . '-' . gmdate('YmdHis') . '-' . wp_generate_password(8, false, false),
            0,
            64
        );

        if ($doc_no === '') {
            return new WP_Error('missing_doc_no', 'Delivery Order is missing its document number.');
        }

        if ($doc_key > 0) {
            foreach (array('source_doc_no', 'source_doc_key', 'delivery_status', 'job_subtype', 'status', 'pending_update_key') as $required) {
                if (!isset($jobs_cols[$required])) {
                    return new WP_Error('jobs_schema_old', 'AutoCount job reference columns are missing. Upgrade the bridge schema before using bulk pricing.');
                }
            }

            $pending_key = 'DELIVERY_ORDER_UPDATE:' . $doc_no . ':' . $doc_key;
            $pending_job = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT id, status
                     FROM `{$safe_jobs_table}`
                     WHERE job_type = %s
                       AND source_doc_no = %s
                       AND source_doc_key = %d
                       AND job_subtype IN ('UPDATE', 'EDIT')
                       AND status IN ('PENDING', 'PROCESSING', 'RETRY')
                     ORDER BY id DESC
                     LIMIT 1
                     FOR UPDATE",
                    'DELIVERY_ORDER',
                    $doc_no,
                    $doc_key
                ),
                ARRAY_A
            );

            if ($pending_job) {
                return new WP_Error(
                    'pending_update_exists',
                    'AutoCount update job #' . (int) $pending_job['id'] . ' is already pending for ' . $doc_no . '.'
                );
            }

            $authoritative_job = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT id
                     FROM `{$safe_jobs_table}`
                     WHERE job_type = %s
                       AND source_doc_no = %s
                       AND source_doc_key = %d
                       AND (
                            job_subtype IS NULL
                            OR job_subtype NOT IN ('UPDATE', 'EDIT')
                            OR delivery_status NOT IN ('EDIT_PENDING_AUTOCOUNT', 'EDITED_IN_AUTOCOUNT')
                       )
                     ORDER BY id DESC
                     LIMIT 1
                     FOR UPDATE",
                    'DELIVERY_ORDER',
                    $doc_no,
                    $doc_key
                ),
                ARRAY_A
            );

            if (!$authoritative_job) {
                return new WP_Error('authoritative_job_missing', 'No authoritative AutoCount job was found for ' . $doc_no . '.');
            }

            $insert = array(
                'client_request_id' => $client_request_id,
                'job_type' => 'DELIVERY_ORDER',
                'job_subtype' => 'UPDATE',
                'priority' => 5,
                'payload' => wp_json_encode($payload),
                'status' => 'PENDING',
                'created_by' => get_current_user_id(),
                'source' => $source,
                'max_retries' => 3,
                'source_doc_no' => $doc_no,
                'source_doc_key' => $doc_key,
                'pending_update_key' => $pending_key,
                'delivery_status' => 'EDIT_PENDING_AUTOCOUNT',
                'assigned_driver_id' => $driver_id > 0 ? $driver_id : null,
                'assigned_at' => $driver_id > 0 ? $now : null,
                'assigned_by' => $driver_id > 0 ? get_current_user_id() : null,
                'created_at' => $now,
                'updated_at' => $now,
            );

            $inserted = wst_docp_job_insert($jobs_table, $jobs_cols, $insert);
            if (!$inserted) {
                $existing_pending = isset($jobs_cols['pending_update_key'])
                    ? $wpdb->get_var(
                        $wpdb->prepare(
                            "SELECT id FROM `{$safe_jobs_table}` WHERE pending_update_key = %s LIMIT 1",
                            $pending_key
                        )
                    )
                    : 0;

                if ($existing_pending) {
                    return new WP_Error('pending_update_exists', 'AutoCount update job #' . (int) $existing_pending . ' is already pending for ' . $doc_no . '.');
                }

                return new WP_Error('queue_failed', 'Failed to queue AutoCount Delivery Order update for ' . $doc_no . '.');
            }

            return (int) $wpdb->insert_id;
        }

        $source_job_id = absint($header['source_job_id'] ?? 0);
        $existing_job = null;

        if ($source_job_id > 0) {
            $existing_job = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT *
                     FROM `{$safe_jobs_table}`
                     WHERE id = %d
                       AND job_type = %s
                     LIMIT 1
                     FOR UPDATE",
                    $source_job_id,
                    'DELIVERY_ORDER'
                ),
                ARRAY_A
            );
        }

        if (!$existing_job && isset($jobs_cols['source_doc_no'])) {
            $existing_job = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT *
                     FROM `{$safe_jobs_table}`
                     WHERE job_type = %s
                       AND source_doc_no = %s
                       AND (source_doc_key IS NULL OR source_doc_key = 0)
                     ORDER BY id DESC
                     LIMIT 1
                     FOR UPDATE",
                    'DELIVERY_ORDER',
                    $doc_no
                ),
                ARRAY_A
            );
        }

        if ($existing_job) {
            $existing_status = strtoupper(trim((string) ($existing_job['status'] ?? '')));
            if ($existing_status === 'PROCESSING') {
                return new WP_Error('job_processing', $doc_no . ' is currently being processed by the AutoCount bridge. Retry after it finishes.');
            }

            $update = array(
                'payload' => wp_json_encode($payload),
                'status' => 'PENDING',
                'source' => $source,
                'source_doc_no' => $doc_no,
                'source_doc_key' => 0,
                'updated_at' => $now,
                'result' => null,
                'error_message' => null,
                'error_code' => null,
                'locked_by' => null,
                'locked_until' => null,
                'completed_at' => null,
                'retry_count' => 0,
                'pending_update_key' => null,
            );

            if ($driver_id > 0) {
                $update['assigned_driver_id'] = $driver_id;
                $update['assigned_at'] = $now;
                $update['assigned_by'] = get_current_user_id();
            }

            $update = array_intersect_key($update, $jobs_cols);
            $updated = $wpdb->update(
                $jobs_table,
                $update,
                array('id' => (int) $existing_job['id'])
            );

            if ($updated === false) {
                return new WP_Error('queue_update_failed', 'Failed to refresh the pending WordPress Delivery Order job for ' . $doc_no . '.');
            }

            return (int) $existing_job['id'];
        }

        $insert = array(
            'client_request_id' => $client_request_id,
            'job_type' => 'DELIVERY_ORDER',
            'job_subtype' => 'CREATE',
            'priority' => 5,
            'payload' => wp_json_encode($payload),
            'status' => 'PENDING',
            'created_by' => get_current_user_id(),
            'source' => $source,
            'max_retries' => 3,
            'source_doc_no' => $doc_no,
            'source_doc_key' => 0,
            'assigned_driver_id' => $driver_id > 0 ? $driver_id : null,
            'assigned_at' => $driver_id > 0 ? $now : null,
            'assigned_by' => $driver_id > 0 ? get_current_user_id() : null,
            'delivery_status' => trim((string) ($header['delivery_status'] ?? '')) ?: ($driver_id > 0 ? 'OUT_FOR_DELIVERY' : 'PENDING_DELIVERY'),
            'created_at' => $now,
            'updated_at' => $now,
        );

        $inserted = wst_docp_job_insert($jobs_table, $jobs_cols, $insert);
        if (!$inserted) {
            return new WP_Error('queue_create_failed', 'Failed to create the pending WordPress Delivery Order job for ' . $doc_no . '.');
        }

        return (int) $wpdb->insert_id;
    }
}

if (!function_exists('wst_docp_update_item_price')) {
    function wst_docp_update_item_price($items_table, $item_cols, $item, $new_price) {
        global $wpdb;

        $pack = wst_docp_line_pack_data($item);
        $amount_qty = wst_docp_float($pack['amount_qty'] ?? $pack['total_kg']);
        $amount = round($new_price * $amount_qty, 6);
        $update = array('unit_price' => $new_price);

        if (i7�:ܫ���h��������7�|�
N?�isset($item_cols['sub_total'])) {
            $update['sub_total'] = $amount;
        }
        if (isset($item_cols['total_amount'])) {
            $update['total_amount'] = $amount;
        }
        if (isset($item_cols['updated_at'])) {
            $update['updated_at'] = current_time('mysql');
        }
        if (isset($item_cols['meta'])) {
            $meta = wst_docp_json_array($item['meta'] ?? '');
            $meta['unitPrice'] = $new_price;
            $meta['UnitPrice'] = $new_price;
            $meta['amount'] = round($new_price * $amount_qty, 2);
            $meta['Amount'] = round($new_price * $amount_qty, 2);
            if (!empty($pack['is_freight'])) {
                $meta['finalAmount'] = round($new_price * $amount_qty, 2);
            }
            $update['meta'] = wp_json_encode($meta);
        }

        return $wpdb->update(
            $items_table,
            $update,
            array('id' => (int) $item['id']),
            null,
            array('%d')
        );
    }
}

if (!function_exists('wst_docp_save_do_changes')) {
    function wst_docp_save_do_changes($do_id, $changes, $selected_date) {
        global $wpdb;

        $do_table = $wpdb->prefix . 'ac_do';
        $items_table = $wpdb->prefix . 'ac_do_items';
        $safe_do_table = preg_replace('/[^A-Za-z0-9_]/', '', $do_table);
        $safe_items_table = preg_replace('/[^A-Za-z0-9_]/', '', $items_table);
        $do_cols = wst_docp_table_columns($do_table);
        $item_cols = wst_docp_table_columns($items_table);

        $wpdb->query('START TRANSACTION');

        try {
            $header = $wpdb->get_row(
                $wpdb->prepare(
                    "SELECT * FROM `{$safe_do_table}` WHERE id = %d LIMIT 1 FOR UPDATE",
                    $do_id
                ),
                ARRAY_A
            );

            if (!$header) {
                throw new Exception('Delivery Order #' . $do_id . ' was not found.');
            }

            if (!wst_docp_header_is_active($header)) {
                throw new Exception(wst_docp_header_doc_no($header) . ' is deleted, hidden, cancelled, voided, or otherwise inactive.');
            }

            $header_date = wst_docp_valid_date($header['doc_date'] ?? '', '');
            if ($selected_date !== '' && $header_date !== $selected_date) {
                throw new Exception(wst_docp_header_doc_no($header) . ' no longer belongs to the selected date.');
            }

            $items = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT *
                     FROM `{$safe_items_table}`
                     WHERE do_id = %d
                     ORDER BY line_no ASC, id ASC
                     FOR UPDATE",
                    $do_id
                ),
                ARRAY_A
            );

            if (empty($items)) {
                throw new Exception(wst_docp_header_doc_no($header) . ' has no item lines.');
            }

            $items_by_id = array();
            foreach ($items as $index => $item) {
                $items_by_id[(int) $item['id']] = $index;
            }

            $changed_count = 0;

            foreach ($changes as $change) {
                $item_id = absint($change['itemId'] ?? 0);
                if ($item_id <= 0 || !isset($items_by_id[$item_id])) {
                    throw new Exception('One submitted item does not belong to ' . wst_docp_header_doc_no($header) . '.');
                }

                $raw_price = isset($change['price']) ? trim((string) $change['price']) : '';
                $raw_original = isset($change['original']) ? trim((string) $change['original']) : '';

                if ($raw_price !== '' && !is_numeric(str_replace(',', '', $raw_price))) {
                    throw new Exception('Invalid price submitted for item line #' . $item_id . '.');
                }
                if ($raw_original !== '' && !is_numeric(str_replace(',', '', $raw_original))) {
                    throw new Exception('Invalid original price submitted for item line #' . $item_id . '.');
                }

                $new_price = $raw_price === '' ? 0.0 : wst_docp_float($raw_price);
                $original_price = $raw_original === '' ? 0.0 : wst_docp_float($raw_original);

                if ($new_price < 0 || $new_price > WST_DOCP_MAX_UNIT_PRICE) {
                    throw new Exception('Price for item line #' . $item_id . ' is outside the allowed range.');
                }

                $item_index = $items_by_id[$item_id];
                $current_price = wst_docp_float($items[$item_index]['unit_price'] ?? 0);

                if (abs($current_price - $original_price) > 0.000001) {
                    throw new Exception(
                        wst_docp_header_doc_no($header) . ' was changed by another user. Reload the page before saving.'
                    );
                }

                if (abs($new_price - $current_price) <= 0.000001) {
                    continue;
                }

                $updated = wst_docp_update_item_price(
                    $items_table,
                    $item_cols,
                    $items[$item_index],
                    $new_price
                );

                if ($updated === false) {
                    throw new Exception('Failed to update item price in ' . wst_docp_header_doc_no($header) . '.');
                }

                $pack = wst_docp_line_pack_data($items[$item_index]);
                $amount_qty = wst_docp_float($pack['amount_qty'] ?? $pack['total_kg']);
                $items[$item_index]['unit_price'] = $new_price;
                $items[$item_index]['sub_total'] = round($new_price * $amount_qty, 6);
                $items[$item_index]['total_amount'] = round($new_price * $amount_qty, 6);

                if (isset($item_cols['meta'])) {
                    $meta = wst_docp_json_array($items[$item_index]['meta'] ?? '');
                    $meta['unitPrice'] = $new_price;
                    $meta['UnitPrice'] = $new_price;
                    $meta['amount'] = round($new_price * $amount_qty, 2);
                    $meta['Amount'] = round($new_price * $amount_qty, 2);
                    if (!empty($pack['is_freight'])) {
                        $meta['finalAmount'] = round($new_price * $amount_qty, 2);
                    }
                    $items[$item_index]['meta'] = wp_json_encode($meta);
                }

                $changed_count++;
            }

            if ($changed_count <= 0) {
                $wpdb->query('COMMIT');
                return array(
                    'doc_no' => wst_docp_header_doc_no($header),
                    'changed_lines' => 0,
                    'job_id' => 0,
                );
            }

            $header_update = array();
            if (isset($do_cols['updated_by'])) {
                $header_update['updated_by'] = get_current_user_id();
            }
            if (isset($do_cols['updated_at'])) {
                $header_update['updated_at'] = current_time('mysql');
            }

            if (!empty($header_update)) {
                $header_updated = $wpdb->update(
                    $do_table,
                    $header_update,
                    array('id' => $do_id)
                );

                if ($header_updated === false) {
                    throw new Exception('Failed to update the Delivery Order header for ' . wst_docp_header_doc_no($header) . '.');
                }
            }

            $payload = wst_docp_build_payload($header, $items);
            if (empty($payload['lines'])) {
                throw new Exception(wst_docp_header_doc_no($header) . ' has no valid payload lines.');
            }

            $job_id = wst_docp_queue_payload($header, $payload);
            if (is_wp_error($job_id)) {
                throw new Exception($job_id->get_error_message());
            }

            $wpdb->query('COMMIT');

            return array(
                'doc_no' => wst_docp_header_doc_no($header),
                'changed_lines' => $changed_count,
                'job_id' => (int) $job_id,
            );
        } catch (Throwable $error) {
            $wpdb->query('ROLLBACK');
            return new WP_Error('save_failed', $error->getMessage());
        }
    }
}

if (!function_exists('wst_docp_add_freight_charge')) {
    function wst_docp_add_freight_charge($debtor_code, $selected_date, $rate_per_kg, $requested_amount) {
        global $wpdb;

        $debtor_code = trim((string) $debtor_code);
        $selected_date = wst_docp_valid_date($selected_date, '');
        $rate_per_kg = wst_docp_float($rate_per_kg);
        $requested_amount = wst_docp_float($requested_amount);

        if ($debtor_code === '') {
            return new WP_Error('freight_debtor_missing', 'A debtor code is required before freight can be added.');
        }
        if ($selected_date === '') {
            return new WP_Error('freight_date_invalid', 'The freight Delivery Order date is invalid.');
        }
        if ($rate_per_kg < 0 || $rate_per_kg > WST_DOCP_MAX_FREIGHT_RATE) {
            return new WP_Error('freight_rate_invalid', 'The freight rate is outside the allowed range.');
        }
        if ($requested_amount < 0 || $requested_amount > WST_DOCP_MAX_UNIT_PRICE) {
            return new WP_Error('freight_amount_invalid', 'The freight amount is outside the allowed range.');
        }

        $configured_code = trim((string) get_option(WST_DOCP_FREIGHT_ITEM_OPTION, ''));
        $freight_item = wst_docp_get_active_item($configured_code);
        if (is_wp_error($freight_item)) {
            return $freight_item;
        }

        $do_table = $wpdb->prefix . 'ac_do';
        $items_table = $wpdb->prefix . 'ac_do_items';
        if (!wst_docp_table_exists($do_table) || !wst_docp_table_exists($items_table)) {
            return new WP_Error('freight_tables_missing', 'Delivery Order tables are not available.');
        }

        $do_cols = wst_docp_table_columns($do_table);
        $item_cols = wst_docp_table_columns($items_table);
        foreach (array('id', 'doc_date', 'debtor_code', 'local_doc_no') as $required) {
            if (!isset($do_cols[$required])) {
                return new WP_Error('freight_do_schema_missing', 'The Delivery Order table is missing required column: ' . $required . '.');
            }
        }
        foreach (array('id', 'do_id', 'item_code', 'qty', 'unit_price') as $required) {
            if (!isset($item_cols[$required])) {
                return new WP_Error('freight_item_schema_missing', 'The Delivery Order item table is missing required column: ' . $required . '.');
            }
        }

        $safe_do_table = preg_replace('/[^A-Za-z0-9_]/', '', $do_table);
        $safe_items_table = preg_replace('/[^A-Za-z0-9_]/', '', $items_table);
        $where = array('doc_date = %s', 'debtor_code = %s');
        $params = array($selected_date, $debtor_code);

        if (isset($do_cols['deleted_at'])) {
            $where[] = 'deleted_at IS NULL';
        }
        if (isset($do_cols['hidden_from_staff_list'])) {
            $where[] = 'hidden_from_staff_list = 0';
        }
        if (isset($do_cols['delivery_status'])) {
            $where[] = "UPPER(COALESCE(delivery_status, '')) <> 'CANCELLED'";
        }
        if (isset($do_cols['sync_status'])) {
            $where[] = "UPPER(COALESCE(sync_status, '')) NOT IN ('VOID_PENDING_AUTOCOUNT', 'VOID_FAILED', 'VOIDED_IN_AUTOCOUNT', 'CANCELLED')";
        }

        $order_by = array();
        if (isset($do_cols['created_at'])) {
            $order_by[] = 'created_at DESC';
        }
        $order_by[] = 'id DESC';

        $wpdb->query('START TRANSACTION');

        try {
            $headers = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT *
                     FROM `{$safe_do_table}`
                     WHERE " . implode(' AND ', $where) . "
                     ORDER BY " . implode(', ', $order_by) . "
                     FOR UPDATE",
                    $params
                ),
                ARRAY_A
            );

            if ($wpdb->last_error) {
                throw new Exception('Failed to lock the customer Delivery Orders: ' . $wpdb->last_error);
            }

            $active_headers = array();
            foreach ((array) $headers as $header) {
                if (!wst_docp_header_is_active($header)) continue;
                $do_id = (int) ($header['id'] ?? 0);
                if ($do_id > 0) {
                    $active_headers[$do_id] = $header;
                }
            }

            if (empty($active_headers)) {
                throw new Exception('No active Delivery Order was found for ' . $debtor_code . ' on ' . $selected_date . '.');
            }

            // The query is ordered newest first, so the first active header is the target.
            $target_do_id = 0;
            $target_header = array();
            foreach ($active_headers as $candidate_do_id => $candidate_header) {
                $target_do_id = (int) $candidate_do_id;
                $target_header = $candidate_header;
                break;
            }
            $target_doc_no = wst_docp_header_doc_no($target_header);

            $do_ids = array_keys($active_headers);
            $placeholders = implode(',', array_fill(0, count($do_ids), '%d'));
            $all_items = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT *
                     FROM `{$safe_items_table}`
                     WHERE do_id IN ({$placeholders})
                     ORDER BY do_id ASC, line_no ASC, id ASC
                     FOR UPDATE",
                    $do_ids
                ),
                ARRAY_A
            );

            if ($wpdb->last_error) {
                throw new Exception('Failed to lock Delivery Order lines: ' . $wpdb->last_error);
            }

            $items_by_do = array();
            $daily_kg = 0.0;
            $freight_matches = array();

            foreach ((array) $all_items as $line) {
                $line_do_id = (int) ($line['do_id'] ?? 0);
                if (!isset($active_headers[$line_do_id])) continue;

                $items_by_do[$line_do_id][] = $line;
                $pack = wst_docp_line_pack_data($line);
                $daily_kg += wst_docp_float($pack['total_kg'] ?? 0);

                if (!empty($pack['is_freight'])) {
                    $freight_matches[] = array(
                        'do_id' => $line_do_id,
                        'line' => $line,
                    );
                }
            }

            if ($daily_kg <= 0) {
                throw new Exception('The customer daily KG is zero, so a freight charge cannot be calculated.');
            }

            if (count($freight_matches) > 1) {
                throw new Exception(
                    'More than one freight line already exists for this customer and date. Use Full edit to remove the duplicate lines before continuing.'
                );
            }

            $existing_freight = null;
            if (count($freight_matches) === 1) {
                $existing_match = $freight_matches[0];
                if ((int) $existing_match['do_id'] !== $target_do_id) {
                    $existing_header = $active_headers[(int) $existing_match['do_id']] ?? array();
                    throw new Exception(
                        'A freight line already exists on ' .
                        (wst_docp_header_doc_no($existing_header) ?: 'an earlier Delivery Order') .
                        '. Freight was not duplicated on the latest DO.'
                    );
                }
                $existing_freight = $existing_match['line'];
            }

            $calculated_amount = $rate_per_kg > 0
                ? round($daily_kg * $rate_per_kg, 2)
             7�|��G:�i��������7��
N?�j   : 0.0;
            $freight_amount = $requested_amount > 0
                ? round($requested_amount, 2)
                : $calculated_amount;

            if ($freight_amount <= 0) {
                throw new Exception('Enter a freight rate or a final freight amount greater than zero.');
            }
            if ($freight_amount > WST_DOCP_MAX_UNIT_PRICE) {
                throw new Exception('The final freight amount exceeds the allowed range.');
            }

            $location = trim((string) ($target_header['location'] ?? 'HQ'));
            if ($location === '') $location = 'HQ';

            $calculation_text = number_format($daily_kg, 2, '.', '') . ' KG';
            if ($rate_per_kg > 0) {
                $calculation_text .= ' x RM ' . number_format($rate_per_kg, 4, '.', '') . '/KG';
            }

            $description = trim((string) ($freight_item['description'] ?? $freight_item['item_code']));
            $description .= ' - ' . $calculation_text;
            $description = function_exists('mb_substr')
                ? mb_substr($description, 0, 255)
                : substr($description, 0, 255);

            $now = current_time('mysql');
            $freight_meta = array(
                '_wstFreightCharge' => true,
                'itemCode' => $freight_item['item_code'],
                'description' => $description,
                'uom' => $freight_item['uom'],
                'unitPrice' => $freight_amount,
                'UnitPrice' => $freight_amount,
                'amount' => $freight_amount,
                'Amount' => $freight_amount,
                'qty' => 1,
                'totalKg' => 0,
                'weightKg' => 0,
                'dailyKg' => round($daily_kg, 6),
                'ratePerKg' => round($rate_per_kg, 6),
                'calculatedAmount' => round($calculated_amount, 2),
                'finalAmount' => $freight_amount,
                'selectedDate' => $selected_date,
                'targetDocNo' => $target_doc_no,
                'updatedBy' => get_current_user_id(),
                'updatedAt' => $now,
                'source' => 'wp-ui-daily-freight',
            );

            $line_data = array(
                'item_code' => sanitize_text_field((string) $freight_item['item_code']),
                'description' => sanitize_text_field($description),
                'uom' => sanitize_text_field((string) $freight_item['uom']),
                'location' => sanitize_text_field($location),
                'qty' => 1,
                'unit_price' => $freight_amount,
                'sub_total' => $freight_amount,
                'tax_code' => sanitize_text_field((string) ($freight_item['tax_code'] ?? '')),
                'tax_rate' => 0,
                'total_amount' => $freight_amount,
                'pack_type' => 'UNIT',
                'unit_qty' => 1,
                'basket_qty' => 0,
                'carton_qty' => 0,
                'weight_kg' => 0,
                'total_weight_kg' => 0,
                'remark' => 'Daily customer freight charge',
                'meta' => wp_json_encode($freight_meta),
                'updated_at' => $now,
            );

            $freight_action = 'added';

            if ($existing_freight) {
                $update = array_intersect_key($line_data, $item_cols);
                $updated = $wpdb->update(
                    $items_table,
                    $update,
                    array('id' => (int) $existing_freight['id'])
                );

                if ($updated === false) {
                    throw new Exception('Failed to update the existing freight line on ' . $target_doc_no . '.');
                }

                $freight_action = 'updated';
            } else {
                $target_items = $items_by_do[$target_do_id] ?? array();
                if (empty($target_items)) {
                    throw new Exception($target_doc_no . ' has no item lines.');
                }

                $max_line_no = 0;
                $max_seq = 0;
                foreach ($target_items as $target_line) {
                    $max_line_no = max($max_line_no, (int) ($target_line['line_no'] ?? 0));
                    $max_seq = max($max_seq, (int) ($target_line['seq'] ?? 0));
                }

                $insert = array_merge(
                    array(
                        'do_id' => $target_do_id,
                        'line_no' => $max_line_no + 1,
                        'seq' => $max_seq > 0 ? $max_seq + 16 : ($max_line_no + 1) * 16,
                        'created_at' => $now,
                    ),
                    $line_data
                );
                $insert = array_intersect_key($insert, $item_cols);

                $inserted = $wpdb->insert($items_table, $insert);
                if (!$inserted) {
                    throw new Exception('Failed to add the freight line to ' . $target_doc_no . '.');
                }
            }

            $header_update = array();
            if (isset($do_cols['updated_by'])) {
                $header_update['updated_by'] = get_current_user_id();
            }
            if (isset($do_cols['updated_at'])) {
                $header_update['updated_at'] = $now;
            }
            if (!empty($header_update)) {
                $header_updated = $wpdb->update(
                    $do_table,
                    $header_update,
                    array('id' => $target_do_id)
                );
                if ($header_updated === false) {
                    throw new Exception('Failed to update the Delivery Order header for ' . $target_doc_no . '.');
                }
            }

            $target_items = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT *
                     FROM `{$safe_items_table}`
                     WHERE do_id = %d
                     ORDER BY line_no ASC, id ASC
                     FOR UPDATE",
                    $target_do_id
                ),
                ARRAY_A
            );

            if (empty($target_items)) {
                throw new Exception($target_doc_no . ' has no valid item lines after adding freight.');
            }

            $payload = wst_docp_build_payload($target_header, $target_items, 'wp-ui-daily-freight');
            if (empty($payload['lines'])) {
                throw new Exception($target_doc_no . ' has no valid AutoCount payload lines.');
            }

            $job_id = wst_docp_queue_payload($target_header, $payload);
            if (is_wp_error($job_id)) {
                throw new Exception($job_id->get_error_message());
            }

            $wpdb->query('COMMIT');

            return array(
                'doc_no' => $target_doc_no,
                'action' => $freight_action,
                'daily_kg' => round($daily_kg, 2),
                'rate_per_kg' => round($rate_per_kg, 4),
                'amount' => $freight_amount,
                'job_id' => (int) $job_id,
            );
        } catch (Throwable $error) {
            $wpdb->query('ROLLBACK');
            return new WP_Error('freight_save_failed', $error->getMessage());
        }
    }
}

if (!function_exists('wst_docp_load_page_data')) {
    function wst_docp_load_page_data($selected_date, $search, $pricing_filter) {
        global $wpdb;

        $do_table = $wpdb->prefix . 'ac_do';
        $items_table = $wpdb->prefix . 'ac_do_items';

        if (!wst_docp_table_exists($do_table) || !wst_docp_table_exists($items_table)) {
            return new WP_Error('tables_missing', 'Delivery Order tables are not available.');
        }

        $do_cols = wst_docp_table_columns($do_table);
        $item_cols = wst_docp_table_columns($items_table);

        foreach (array('id', 'doc_date', 'local_doc_no', 'debtor_code', 'debtor_name') as $required) {
            if (!isset($do_cols[$required])) {
                return new WP_Error('do_schema_missing', 'The Delivery Order header table is missing required column: ' . $required . '.');
            }
        }

        foreach (array('id', 'do_id', 'item_code', 'qty', 'unit_price') as $required) {
            if (!isset($item_cols[$required])) {
                return new WP_Error('item_schema_missing', 'The Delivery Order item table is missing required column: ' . $required . '.');
            }
        }

        $safe_do_table = preg_replace('/[^A-Za-z0-9_]/', '', $do_table);
        $safe_items_table = preg_replace('/[^A-Za-z0-9_]/', '', $items_table);
        $where = array('doc_date = %s');
        $params = array($selected_date);

        if (isset($do_cols['deleted_at'])) {
            $where[] = 'deleted_at IS NULL';
        }
        if (isset($do_cols['hidden_from_staff_list'])) {
            $where[] = 'hidden_from_staff_list = 0';
        }
        if (isset($do_cols['delivery_status'])) {
            $where[] = "UPPER(COALESCE(delivery_status, '')) <> 'CANCELLED'";
        }
        if (isset($do_cols['sync_status'])) {
            $where[] = "UPPER(COALESCE(sync_status, '')) NOT IN ('VOID_PENDING_AUTOCOUNT', 'VOID_FAILED', 'VOIDED_IN_AUTOCOUNT', 'CANCELLED')";
        }

        if ($search !== '') {
            $like = '%' . $wpdb->esc_like($search) . '%';
            $search_parts = array('local_doc_no LIKE %s', 'debtor_code LIKE %s', 'debtor_name LIKE %s');
            $params[] = $like;
            $params[] = $like;
            $params[] = $like;

            if (isset($do_cols['autocount_doc_no'])) {
                $search_parts[] = 'autocount_doc_no LIKE %s';
                $params[] = $like;
            }

            $where[] = '(' . implode(' OR ', $search_parts) . ')';
        }

        $sql = "SELECT *
                FROM `{$safe_do_table}`
                WHERE " . implode(' AND ', $where) . "
                ORDER BY debtor_code ASC, debtor_name ASC, local_doc_no ASC, id ASC
                LIMIT " . (int) WST_DOCP_MAX_DOS_PER_DAY;

        $headers = $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A);

        if ($wpdb->last_error) {
            wst_docp_log('Header load failed: ' . $wpdb->last_error);
            return new WP_Error('header_load_failed', 'Failed to load Delivery Orders.');
        }

        $active_headers = array();
        $do_ids = array();

        foreach ((array) $headers as $header) {
            if (!wst_docp_header_is_active($header)) continue;

            $do_id = (int) ($header['id'] ?? 0);
            if ($do_id <= 0) continue;

            $active_headers[$do_id] = $header;
            $do_ids[] = $do_id;
        }

        if (empty($do_ids)) {
            return array(
                'groups' => array(),
                'totals' => array(
                    'customers' => 0,
                    'dos' => 0,
                    'kg' => 0,
                    'amount' => 0,
                    'missing_lines' => 0,
                ),
            );
        }

        $placeholders = implode(',', array_fill(0, count($do_ids), '%d'));
        $item_sql = "SELECT *
                     FROM `{$safe_items_table}`
                     WHERE do_id IN ({$placeholders})
                     ORDER BY do_id ASC, line_no ASC, id ASC";
        $item_rows = $wpdb->get_results($wpdb->prepare($item_sql, $do_ids), ARRAY_A);

        if ($wpdb->last_error) {
            wst_docp_log('Item load failed: ' . $wpdb->last_error);
            return new WP_Error('item_load_failed', 'Failed to load Delivery Order items.');
        }

        $items_by_do = array();
        foreach ((array) $item_rows as $item) {
            $do_id = (int) ($item['do_id'] ?? 0);
            if (!isset($active_headers[$do_id])) continue;
            $items_by_do[$do_id][] = $item;
        }

        $groups = array();
        $daily_group_totals = array();
        $totals = array(
            'customers' => 0,
            'dos' => 0,
            'kg' => 0,
            'amount' => 0,
            'missing_lines' => 0,
        );

        foreach ($active_headers as $do_id => $header) {
            $items = $items_by_do[$do_id] ?? array();
            if (empty($items)) continue;

            $do_total_kg = 0.0;
            $do_total_amount = 0.0;
            $do_missing_lines = 0;

            foreach ($items as &$item) {
                $pack = wst_docp_line_pack_data($item);
                $item['_pack'] = $pack;
                $item['_amount'] = round(
                    wst_docp_float($item['unit_price'] ?? 0) *
                    wst_docp_float($pack['amount_qty'] ?? $pack['total_kg']),
                    2
                );
                $item['_missing_price'] = wst_docp_float($item['unit_price'] ?? 0) <= 0;

                $do_total_kg += $pack['total_kg'];
                $do_total_amount += $item['_amount'];
                if ($item['_missing_price']) $do_missing_lines++;
            }
            unset($item);

            $debtor_code = trim((string) ($header['debtor_code'] ?? ''));
            $debtor_name = trim((string) ($header['debtor_name'] ?? ''));

            if ($debtor_code !== '') {
                $group_key = 'CODE:' . strtoupper($debtor_code);
            } elseif ($debtor_name !== '') {
                $group_key = 'NAME:' . strtoupper($debtor_name);
            } else {
                $group_key = 'UNKNOWN:' . $do_id;
                $debtor_name = 'Unknown Customer';
            }

            if (!isset($daily_group_totals[$group_key])) {
                $daily_group_totals[$group_key] = array(
                    'do_count' => 0,
                    'total_kg' => 0.0,
                    'latest_do_id' => 0,
                    'latest_doc_no' => '',
                    'latest_created_ts' => 0,
                );
            }

            $daily_group_totals[$group_key]['do_count']++;
            $daily_group_totals[$group_key]['total_kg'] += $do_total_kg;

            $header_created_ts = !empty($header['created_at'])
                ? (int) strtotime((string) $header['created_at'])
                : 0;
            $current_latest_ts = (int) ($daily_group_totals[$group_key]['latest_created_ts'] ?? 0);
            $current_latest_id = (int) ($daily_group_totals[$group_key]['latest_do_id'] ?? 0);

            if (
                $header_created_ts > $current_latest_ts ||
                ($header_created_ts === $current_latest_ts && $do_id > $current_latest_id)
            ) {
                $daily_group_totals[$group_key]['latest_do_id'] = $do_id;
                $daily_group_totals[$group_key]['latest_doc_no'] = wst_docp_header_doc_no($header);
                $daily_group_totals[$group_key]['latest_created_ts'] = $header_created_ts;
            }

            $totals['dos']++;
            $totals['kg'] += $do_total_kg;
            $totals['amount'] += $do_total_amount;
            $totals['missing_lines'] += $do_missing_lines;

            $is_complete = $do_missing_lines === 0;
            if ($pricing_filter === 'MISSING' && $is_complete) continue;
            if ($pricing_filter === 'COMPLETE' && !$is_complete) continue;

            if (!isset($groups[$group_key])) {
                $groups[$group_key] = array(
                    'key' => $group_key,
                    'debtor_code' => $debtor_code,
                    'debtor_name' => $debtor_name,
                    'dos' => array(),
                    'daily_do_count' => 0,
                    'latest_do_id' => 0,
                    'latest_doc_no' => '',
                    'total_kg' => 0.0,
                    'total_amount' => 0.0,
                    'missing_lines' => 0,
                );
            }

            $groups[$group_key]['dos'][] = array(
                'header' => $header,
                'items' => $items,
                'total_kg' => $do_total_kg,
                'total_amount' => $do_total_amount,
                'missing_lines'7���*]j��������7�
N?�p => $do_missing_lines,
                'complete' => $is_complete,
            );
            $groups[$group_key]['total_amount'] += $do_total_amount;
            $groups[$group_key]['missing_lines'] += $do_missing_lines;
        }

        foreach ($groups as $group_key => &$group) {
            $group['daily_do_count'] = (int) ($daily_group_totals[$group_key]['do_count'] ?? count($group['dos']));
            $group['latest_do_id'] = (int) ($daily_group_totals[$group_key]['latest_do_id'] ?? 0);
            $group['latest_doc_no'] = (string) ($daily_group_totals[$group_key]['latest_doc_no'] ?? '');
            $group['total_kg'] = (float) ($daily_group_totals[$group_key]['total_kg'] ?? 0);
        }
        unset($group);

        $groups = array_values($groups);
        usort($groups, function($a, $b) {
            $a_label = strtoupper(trim(($a['debtor_code'] ?? '') . ' ' . ($a['debtor_name'] ?? '')));
            $b_label = strtoupper(trim(($b['debtor_code'] ?? '') . ' ' . ($b['debtor_name'] ?? '')));
            return strcmp($a_label, $b_label);
        });

        $totals['customers'] = count($daily_group_totals);

        return array(
            'groups' => $groups,
            'totals' => $totals,
        );
    }
}

$wst_docp_today = current_time('Y-m-d');
$wst_docp_selected_date = wst_docp_valid_date(
    isset($_GET['date']) ? wp_unslash($_GET['date']) : '',
    $wst_docp_today
);
$wst_docp_search = isset($_GET['q'])
    ? substr(sanitize_text_field(wp_unslash($_GET['q'])), 0, 100)
    : '';
$wst_docp_pricing_filter = isset($_GET['pricing'])
    ? strtoupper(sanitize_key(wp_unslash($_GET['pricing'])))
    : 'ALL';

if (!in_array($wst_docp_pricing_filter, array('ALL', 'MISSING', 'COMPLETE'), true)) {
    $wst_docp_pricing_filter = 'ALL';
}

$wst_docp_is_admin = current_user_can('manage_options');

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['wst_docp_action'])) {
    $posted_action = sanitize_key(wp_unslash($_POST['wst_docp_action']));

    if ($posted_action === 'save_freight_settings') {
        $posted_date = wst_docp_valid_date(
            isset($_POST['wst_docp_date']) ? wp_unslash($_POST['wst_docp_date']) : '',
            $wst_docp_today
        );
        $posted_search = isset($_POST['wst_docp_q'])
            ? substr(sanitize_text_field(wp_unslash($_POST['wst_docp_q'])), 0, 100)
            : '';
        $posted_pricing = isset($_POST['wst_docp_pricing'])
            ? strtoupper(sanitize_key(wp_unslash($_POST['wst_docp_pricing'])))
            : 'ALL';
        if (!in_array($posted_pricing, array('ALL', 'MISSING', 'COMPLETE'), true)) {
            $posted_pricing = 'ALL';
        }

        $filters = array(
            'date' => $posted_date,
            'q' => $posted_search,
            'pricing' => $posted_pricing,
        );

        if (!$wst_docp_is_admin) {
            wst_docp_redirect_notice('error', 'Only an administrator can change the freight settings.', $filters);
        }

        $nonce = isset($_POST['wst_docp_settings_nonce'])
            ? sanitize_text_field(wp_unslash($_POST['wst_docp_settings_nonce']))
            : '';
        if (!wp_verify_nonce($nonce, 'wst_docp_save_freight_settings')) {
            wst_docp_redirect_notice('error', 'Security check failed. Reload the page and try again.', $filters);
        }

        $item_code = isset($_POST['wst_docp_freight_item_code'])
            ? sanitize_text_field(wp_unslash($_POST['wst_docp_freight_item_code']))
            : '';
        $item_code = trim($item_code);

        $raw_default_rate = isset($_POST['wst_docp_default_freight_rate'])
            ? trim((string) wp_unslash($_POST['wst_docp_default_freight_rate']))
            : '';

        if ($raw_default_rate !== '' && !is_numeric(str_replace(',', '', $raw_default_rate))) {
            wst_docp_redirect_notice('error', 'The default freight rate must be a valid number.', $filters);
        }

        $default_rate = $raw_default_rate === '' ? 0.0 : wst_docp_float($raw_default_rate);
        if ($default_rate < 0 || $default_rate > WST_DOCP_MAX_FREIGHT_RATE) {
            wst_docp_redirect_notice('error', 'The default freight rate is outside the allowed range.', $filters);
        }

        $saved_item = null;
        if ($item_code === '') {
            delete_option(WST_DOCP_FREIGHT_ITEM_OPTION);
        } else {
            $saved_item = wst_docp_get_active_item($item_code);
            if (is_wp_error($saved_item)) {
                wst_docp_redirect_notice('error', $saved_item->get_error_message(), $filters);
            }

            update_option(
                WST_DOCP_FREIGHT_ITEM_OPTION,
                (string) $saved_item['item_code'],
                false
            );
        }

        if ($default_rate > 0) {
            update_option(WST_DOCP_FREIGHT_RATE_OPTION, $default_rate, false);
        } else {
            delete_option(WST_DOCP_FREIGHT_RATE_OPTION);
        }

        $message_parts = array('Freight settings saved.');
        $message_parts[] = $saved_item
            ? 'Item: ' . $saved_item['item_code'] . ' — ' . $saved_item['description'] . '.'
            : 'Freight item cleared.';
        $message_parts[] = $default_rate > 0
            ? 'Default rate: RM ' . number_format($default_rate, 4, '.', '') . '/KG.'
            : 'Default rate cleared.';

        wst_docp_redirect_notice(
            'success',
            implode(' ', $message_parts),
            $filters
        );
    }

    if ($posted_action === 'add_freight') {
        $posted_date = wst_docp_valid_date(
            isset($_POST['wst_docp_date']) ? wp_unslash($_POST['wst_docp_date']) : '',
            ''
        );
        $posted_search = isset($_POST['wst_docp_q'])
            ? substr(sanitize_text_field(wp_unslash($_POST['wst_docp_q'])), 0, 100)
            : '';
        $posted_pricing = isset($_POST['wst_docp_pricing'])
            ? strtoupper(sanitize_key(wp_unslash($_POST['wst_docp_pricing'])))
            : 'ALL';
        if (!in_array($posted_pricing, array('ALL', 'MISSING', 'COMPLETE'), true)) {
            $posted_pricing = 'ALL';
        }

        $filters = array(
            'date' => $posted_date !== '' ? $posted_date : $wst_docp_today,
            'q' => $posted_search,
            'pricing' => $posted_pricing,
        );

        $nonce = isset($_POST['wst_docp_freight_nonce'])
            ? sanitize_text_field(wp_unslash($_POST['wst_docp_freight_nonce']))
            : '';
        if (!wp_verify_nonce($nonce, 'wst_docp_add_freight')) {
            wst_docp_redirect_notice('error', 'Security check failed. Reload the page and try again.', $filters);
        }

        $debtor_code = isset($_POST['wst_docp_freight_debtor'])
            ? sanitize_text_field(wp_unslash($_POST['wst_docp_freight_debtor']))
            : '';
        $rate_per_kg = isset($_POST['wst_docp_freight_rate'])
            ? wp_unslash($_POST['wst_docp_freight_rate'])
            : '';
        $freight_amount = isset($_POST['wst_docp_freight_amount'])
            ? wp_unslash($_POST['wst_docp_freight_amount'])
            : '';

        $result = wst_docp_add_freight_charge(
            $debtor_code,
            $posted_date,
            $rate_per_kg,
            $freight_amount
        );

        if (is_wp_error($result)) {
            wst_docp_redirect_notice('error', $result->get_error_message(), $filters);
        }

        $action_word = ($result['action'] ?? '') === 'updated' ? 'updated' : 'added';
        $rate_text = (float) ($result['rate_per_kg'] ?? 0) > 0
            ? ' at RM ' . number_format((float) $result['rate_per_kg'], 4) . '/KG'
            : '';

        wst_docp_redirect_notice(
            'success',
            sprintf(
                'Freight RM %s was %s on %s using %s KG%s. AutoCount job #%d was queued.',
                number_format((float) ($result['amount'] ?? 0), 2),
                $action_word,
                (string) ($result['doc_no'] ?? ''),
                number_format((float) ($result['daily_kg'] ?? 0), 2),
                $rate_text,
                (int) ($result['job_id'] ?? 0)
            ),
            $filters
        );
    }

    if ($posted_action === 'save_prices') {
        $nonce = isset($_POST['wst_docp_nonce'])
            ? sanitize_text_field(wp_unslash($_POST['wst_docp_nonce']))
            : '';

        if (!wp_verify_nonce($nonce, 'wst_docp_save_prices')) {
            wst_docp_redirect_notice('error', 'Security check failed. Reload the page and try again.');
        }

        $posted_date = wst_docp_valid_date(
            isset($_POST['wst_docp_date']) ? wp_unslash($_POST['wst_docp_date']) : '',
            ''
        );
        $posted_search = isset($_POST['wst_docp_q'])
            ? substr(sanitize_text_field(wp_unslash($_POST['wst_docp_q'])), 0, 100)
            : '';
        $posted_pricing = isset($_POST['wst_docp_pricing'])
            ? strtoupper(sanitize_key(wp_unslash($_POST['wst_docp_pricing'])))
            : 'ALL';

        if (!in_array($posted_pricing, array('ALL', 'MISSING', 'COMPLETE'), true)) {
            $posted_pricing = 'ALL';
        }

        $filters = array(
            'date' => $posted_date !== '' ? $posted_date : $wst_docp_today,
            'q' => $posted_search,
            'pricing' => $posted_pricing,
        );

        if ($posted_date === '') {
            wst_docp_redirect_notice('error', 'Invalid Delivery Order date.', $filters);
        }

        $changes_json = isset($_POST['wst_docp_changes_json'])
            ? wp_unslash($_POST['wst_docp_changes_json'])
            : '';
        $changes = json_decode((string) $changes_json, true);

        if (!is_array($changes)) {
            wst_docp_redirect_notice('error', 'The submitted price changes are invalid.', $filters);
        }

        if (count($changes) > WST_DOCP_MAX_CHANGED_LINES) {
            wst_docp_redirect_notice('error', 'Too many price lines were submitted at once.', $filters);
        }

        $changes_by_do = array();

        foreach ($changes as $change) {
            if (!is_array($change)) continue;

            $do_id = absint($change['doId'] ?? 0);
            $item_id = absint($change['itemId'] ?? 0);
            if ($do_id <= 0 || $item_id <= 0) continue;

            $changes_by_do[$do_id][] = array(
                'itemId' => $item_id,
                'price' => isset($change['price']) ? (string) $change['price'] : '',
                'original' => isset($change['original']) ? (string) $change['original'] : '',
            );
        }

        if (empty($changes_by_do)) {
            wst_docp_redirect_notice('info', 'No price changes were detected.', $filters);
        }

        $saved_docs = 0;
        $saved_lines = 0;
        $queued_jobs = array();
        $errors = array();

        foreach ($changes_by_do as $do_id => $do_changes) {
            $result = wst_docp_save_do_changes($do_id, $do_changes, $posted_date);

            if (is_wp_error($result)) {
                $errors[] = $result->get_error_message();
                continue;
            }

            if ((int) ($result['changed_lines'] ?? 0) > 0) {
                $saved_docs++;
                $saved_lines += (int) $result['changed_lines'];
                if (!empty($result['job_id'])) {
                    $queued_jobs[] = (int) $result['job_id'];
                }
            }
        }

        if ($saved_docs > 0 && empty($errors)) {
            wst_docp_redirect_notice(
                'success',
                sprintf(
                    'Updated %d price line(s) across %d Delivery Order(s). AutoCount jobs were queued successfully.',
                    $saved_lines,
                    $saved_docs
                ),
                $filters
            );
        }

        if ($saved_docs > 0 && !empty($errors)) {
            wst_docp_redirect_notice(
                'warning',
                sprintf(
                    'Updated %d price line(s) across %d Delivery Order(s), but some DOs were not saved: %s',
                    $saved_lines,
                    $saved_docs,
                    implode(' | ', array_slice(array_unique($errors), 0, 4))
                ),
                $filters
            );
        }

        if (empty($errors)) {
            wst_docp_redirect_notice('info', 'No price changes were detected.', $filters);
        }

        wst_docp_redirect_notice(
            'error',
            implode(' | ', array_slice(array_unique($errors), 0, 4)),
            $filters
        );
    }
}

$wst_docp_freight_item_code = trim((string) get_option(WST_DOCP_FREIGHT_ITEM_OPTION, ''));
$wst_docp_freight_item = null;
$wst_docp_freight_item_error = '';

if ($wst_docp_freight_item_code !== '') {
    $resolved_freight_item = wst_docp_get_active_item($wst_docp_freight_item_code);
    if (is_wp_error($resolved_freight_item)) {
        $wst_docp_freight_item_error = $resolved_freight_item->get_error_message();
    } else {
        $wst_docp_freight_item = $resolved_freight_item;
        $wst_docp_freight_item_code = (string) $resolved_freight_item['item_code'];
    }
}

$wst_docp_default_freight_rate = wst_docp_float(
    get_option(WST_DOCP_FREIGHT_RATE_OPTION, 0)
);
if ($wst_docp_default_freight_rate < 0 || $wst_docp_default_freight_rate > WST_DOCP_MAX_FREIGHT_RATE) {
    $wst_docp_default_freight_rate = 0.0;
}

$wst_docp_item_choices = $wst_docp_is_admin
    ? wst_docp_load_active_items()
    : array();

$wst_docp_page_data = wst_docp_load_page_data(
    $wst_docp_selected_date,
    $wst_docp_search,
    $wst_docp_pricing_filter
);

$wst_docp_load_error = is_wp_error($wst_docp_page_data)
    ? $wst_docp_page_data->get_error_message()
    : '';
$wst_docp_groups = $wst_docp_load_error === ''
    ? ($wst_docp_page_data['groups'] ?? array())
    : array();
$wst_docp_totals = $wst_docp_load_error === ''
    ? ($wst_docp_page_data['totals'] ?? array())
    : array();
$wst_docp_notice_type = isset($_GET['wst_docp_notice_type'])
    ? sanitize_key(wp_unslash($_GET['wst_docp_notice_type']))
    : '';
$wst_docp_notice = isset($_GET['wst_docp_notice'])
    ? sanitize_text_field(wp_unslash($_GET['wst_docp_notice']))
    : '';
$wst_docp_nonce = wp_create_nonce('wst_docp_save_prices');
$wst_docp_freight_nonce = wp_create_nonce('wst_docp_add_freight');
$wst_docp_settings_nonce = wp_create_nonce('wst_docp_save_freight_settings');
$wst_docp_edit_url = home_url('/edit-delivery-order/');
$wst_docp_view_url = home_url('/view-delivery-order/');
?>

<div id="wst-docp-root" class="wst-docp-root" data-ui-revision="2026-07-14-v6">

    <form method="get" class="wst-docp-filter-form">
        <div class="wst-docp-filter-field">
            <label for="wst_docp_date">Delivery date</label>
            <input type="date" id="wst_docp_date" name="date" value="<?php echo esc_attr($wst_docp_selected_date); ?>" required>
        </div>

        <div class="wst-docp-filter-field wst-docp-filter-search">
            <label for="wst_docp_q">Customer or DocNo</label>
            <div class="wst-docp-search-wrap">
                <input type="search" id="wst_docp_q" name="q" value="<?php echo esc_attr($wst_docp_search); ?>" placeholder="Debtor code, name or DocNo">
                <?php if ($wst_docp_search !== ''): ?>
                    <a class="wst-docp-clear-search" href="<?php echo esc_url(add_query_arg(array('date' => $wst_docp_selected_date, 'pricing' => $wst_docp_pricing_filter), remove_query_arg(array('q', 'wst_docp_notice_type', 'wst_docp_notice')))); ?>" aria-label="Clear search">&times;</a>
                <?php endif; ?>
            </div>
        </div>

        <div class="wst-docp-filter-field">
            <label for="wst_docp_pricing">Pricing status</label>
            <select id="wst_docp_pricing" name="pricing">
                <option value="ALL" <?php selected($wst_docp_pricing_filter, '7�����k��������+�
N?�l<?php
if (!defined('ABSPATH')) exit;

/*
 * VegeBasketDO staff Delivery Order list - MySQL-only version.
 *
 * Keeps the original staff-list style and action buttons:
 * Print | Edit | View | Delete
 * Hidden-row show toggle is available only for Administrator users.
 *
 * Data source:
 * WordPress MySQL tables: {$wpdb->prefix}ac_do + {$wpdb->prefix}ac_do_items
 * ac_jobs is only the bridge queue/history and is not used as the list source.
 *
 * Page URLs:
 * Edit: /edit-delivery-order/?docNo=DO-0001&docKey=123
 * View: /view-delivery-order/?docNo=DO-0001&docKey=123
 */

if (!is_user_logged_in()) {
    echo '<div class="wst-dod-alert wst-dod-alert-error">Please log in to view Delivery Order records.</div>';
    return;
}

if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
    echo '<div class="wst-dod-alert wst-dod-alert-error">You do not have permission to view Delivery Order records.</div>';
    return;
}

global $wpdb;

if (!$wpdb) {
    echo '<div class="wst-dod-alert wst-dod-alert-error">WordPress database connection is not available.</div>';
    return;
}

$edit_page_url = home_url('/edit-delivery-order/');
$view_page_url = home_url('/view-delivery-order/');
$show_technical_errors = current_user_can('manage_options') && defined('WP_DEBUG') && WP_DEBUG;

if (!function_exists('wst_dod_log_error')) {
    function wst_dod_log_error($message) {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('[VegeBasketDO DO List] ' . $message);
        }
    }
}

if (!function_exists('wst_dod_valid_date')) {
    function wst_dod_valid_date($value, $fallback) {
        $value = trim((string)$value);
        if ($value === '') return $fallback;

        $dt = DateTime::createFromFormat('Y-m-d', $value, wp_timezone());
        if (!$dt || $dt->format('Y-m-d') !== $value) return $fallback;

        return $value;
    }
}

if (!function_exists('wst_dod_date')) {
    function wst_dod_date($v) {
        if ($v instanceof DateTime) return $v->format('Y-m-d');

        if (is_string($v) && $v !== '') {
            $ts = strtotime($v);
            if ($ts) return date('Y-m-d', $ts);
            return $v;
        }

        return '';
    }
}

if (!function_exists('wst_dod_datetime')) {
    function wst_dod_datetime($v) {
        if ($v instanceof DateTime) return $v->format('Y-m-d H:i:s');

        if (is_string($v) && $v !== '') {
            $v = trim($v);
            if ($v === '' || $v === '0000-00-00 00:00:00') return '';
            $ts = strtotime($v);
            if ($ts) return date('Y-m-d H:i:s', $ts);
            return $v;
        }

        return '';
    }
}

if (!function_exists('wst_dod_fmt_qty')) {
    function wst_dod_fmt_qty($v, $decimals = 2) {
        $n = (float)$v;

        if (abs($n - round($n)) < 0.00001) {
            return number_format_i18n($n, 0);
        }

        return number_format_i18n($n, $decimals);
    }
}

if (!function_exists('wst_dod_fmt_weight')) {
    function wst_dod_fmt_weight($v) {
        return number_format_i18n((float)$v, 2);
    }
}

if (!function_exists('wst_dod_read_json_array')) {
    function wst_dod_read_json_array($json) {
        $data = json_decode((string)$json, true);
        return is_array($data) ? $data : array();
    }
}

if (!function_exists('wst_dod_pick_payload_value')) {
    function wst_dod_pick_payload_value($payload, $keys, $fallback = '') {
        if (!is_array($payload)) return $fallback;

        foreach ((array)$keys as $key) {
            if (isset($payload[$key]) && trim((string)$payload[$key]) !== '') {
                return trim((string)$payload[$key]);
            }
        }

        return $fallback;
    }
}

if (!function_exists('wst_dod_pick_payload_any')) {
    function wst_dod_pick_payload_any($payload, $keys, $fallback = null) {
        if (!is_array($payload)) return $fallback;

        foreach ((array)$keys as $key) {
            if (array_key_exists($key, $payload) && $payload[$key] !== '' && $payload[$key] !== null) {
                return $payload[$key];
            }
        }

        return $fallback;
    }
}

if (!function_exists('wst_dod_doc_key_from_data')) {
    function wst_dod_doc_key_from_data($data) {
        if (!is_array($data)) return 0;

        foreach (array('docKey', 'DocKey', 'dockey', 'doc_key', 'sourceDocKey') as $key) {
            if (isset($data[$key]) && is_numeric($data[$key])) {
                return (int)$data[$key];
            }
        }

        return 0;
    }
}

if (!function_exists('wst_dod_doc_no_from_data')) {
    function wst_dod_doc_no_from_data($data) {
        if (!is_array($data)) return '';

        foreach (array('docNo', 'DocNo', 'docno', 'doc_no', 'sourceDocNo', 'oldDocNo', 'originalDocNo') as $key) {
            if (!empty($data[$key])) {
                return strtoupper(trim((string)$data[$key]));
            }
        }

        return '';
    }
}

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

        $labels = array(
            'PENDING' => 'Pending AutoCount',
            'PROCESSING' => 'Processing AutoCount',
            'SUCCESS' => 'Created',
            'FAILED' => 'AutoCount Failed',
            'FAILED_FINAL' => 'AutoCount Failed',
            'PENDING_DELIVERY' => 'Pending Delivery',
            'ASSIGNED' => 'Assigned',
            'SCHEDULED' => 'Scheduled',
            'DRIVER_ACKNOWLEDGED' => 'Driver Received',
            'RECEIVED' => 'Driver Received',
            'OUT_FOR_DELIVERY' => 'Out for Delivery',
            'DELIVERED' => 'Delivered',
            'NEEDS_STAFF_EDIT' => 'Needs Staff Edit',
            'EDIT_PENDING_AUTOCOUNT' => 'Edit Pending',
            'EDITED_IN_AUTOCOUNT' => 'Edited',
            'VOID_PENDING_AUTOCOUNT' => 'Void Pending',
            'VOID_FAILED' => 'Void Failed',
            'VOIDED_IN_AUTOCOUNT' => 'Voided',
            'CANCELLED' => 'Cancelled',
            'ACTIVE' => 'Active',
            'HIDDEN' => 'Hidden',
            'UNASSIGNED' => 'Unassigned',
        );

        return $labels[$value] ?? ($value !== '' ? ucwords(strtolower(str_replace('_', ' ', $value))) : '-');
    }
}

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

        if (in_array($value, array('DELIVERED', 'SUCCESS', 'EDITED_IN_AUTOCOUNT', 'ACTIVE'), true)) {
            return 'wst-dod-badge-good';
        }

        if (in_array($value, array('FAILED', 'FAILED_FINAL', 'VOID_FAILED', 'CANCELLED', 'HIDDEN'), true)) {
            return 'wst-dod-badge-danger';
        }

        if (in_array($value, array('PENDING', 'PROCESSING', 'EDIT_PENDING_AUTOCOUNT', 'VOID_PENDING_AUTOCOUNT', 'NEEDS_STAFF_EDIT', 'SCHEDULED'), true)) {
            return 'wst-dod-badge-warn';
        }

        return 'wst-dod-badge-info';
    }
}

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

        $help = array(
            'PENDING' => 'Order is waiting for AutoCount bridge processing.',
            'PROCESSING' => 'AutoCount bridge is currently processing this order.',
            'SUCCESS' => 'Order was created successfully in AutoCount.',
            'FAILED' => 'AutoCount bridge failed to create or update this order.',
            'FAILED_FINAL' => 'AutoCount bridge failed after all retries.',
            'PENDING_DELIVERY' => 'Order exists but has not been assigned to a driver yet.',
            'ASSIGNED' => 'Order has been assigned to a driver.',
            'SCHEDULED' => 'This delivery order is scheduled for a future date.',
            'DRIVER_ACKNOWLEDGED' => 'Driver confirmed receiving the delivery list or goods.',
            'RECEIVED' => 'Driver confirmed receiving the delivery list or goods.',
            'OUT_FOR_DELIVERY' => 'Driver is currently delivering this order.',
            'DELIVERED' => 'Driver marked this order as delivered.',
            'NEEDS_STAFF_EDIT' => 'Driver reported not enough item. Staff should edit and reprint this DO.',
            'EDIT_PENDING_AUTOCOUNT' => 'Staff edited this order and the AutoCount update is still pending.',
            'EDITED_IN_AUTOCOUNT' => 'The edited order was updated successfully in AutoCount.',
            'VOID_PENDING_AUTOCOUNT' => 'This record was deleted from the normal staff list and its AutoCount void request is waiting for the bridge.',
            'VOID_FAILED' => 'AutoCount did not confirm the void. The record remains hidden from normal staff and can be reviewed from the hidden-record recovery view.',
            'VOIDED_IN_AUTOCOUNT' => 'AutoCount confirmed that this delivery order was voided.',
            'CANCELLED' => 'This delivery order was cancelled.',
            'ACTIVE' => 'This delivery order is active in AutoCount.',
            'HIDDEN' => 'This row is hidden from normal staff.',
        );

        return $help[$value] ?? 'Current delivery order status.';
    }
}

if (!function_exists('wst_dod_wp_table_exists')) {
    function wst_dod_wp_table_exists($table_name) {
        global $wpdb;
        if (!$wpdb) return false;

        return $wpdb->get_var(
            $wpdb->prepare('SHOW TABLES LIKE %s', $table_name)
        ) === $table_name;
    }
}

if (!function_exists('wst_dod_wp_table_columns')) {
    function wst_dod_wp_table_columns($table_name) {
        global $wpdb;

        static $cache = array();
        if (!$wpdb) return array();

        $refresh = false;
        if (substr($table_name, -9) === '__refresh') {
            $refresh = true;
            $table_name = substr($table_name, 0, -9);
        }

        if (!$refresh && isset($cache[$table_name])) {
            return $cache[$table_name];
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table_name);
        $cols = $wpdb->get_col("SHOW COLUMNS FROM `{$safe_table}`", 0);

        $cache[$table_name] = is_array($cols) ? array_flip($cols) : array();

        return $cache[$table_name];
    }
}

if (!function_exists('wst_dod_is_administrator')) {
    function wst_dod_is_administrator() {
        $user = wp_get_current_user();

        return in_array('administrator', (array)($user->roles ?? array()), true);
    }
}

if (!function_exists('wst_dod_job_soft_delete_available')) {
    function wst_dod_job_soft_delete_available() {
        global $wpdb;
        if (!$wpdb) return false;

        $table = $wpdb->prefix . 'ac_do';
        if (!wst_dod_wp_table_exists($table)) return false;

        $cols = wst_dod_wp_table_columns($table);

        return isset($cols['hidden_from_staff_list']);
    }
}

if (!function_exists('wst_dod_redirect_with_notice')) {
    function wst_dod_redirect_with_notice($type, $message) {
        $request_uri = isset($_SERVER['REQUEST_URI'])
            ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI']))
            : '/';

        $redirect_url = home_url($request_uri);

        $redirect_url = remove_query_arg(
            array('wst_dod_notice_type', 'wst_dod_notice', 'wst_dod_row_action', 'job_id', 'do_id', 'wst_dod_row_nonce'),
            $redirect_url
        );

        $redirect_url = add_query_arg(
            array(
                'wst_dod_notice_type' => sanitize_key($type),
                'wst_dod_notice' => (string)$message,
            ),
            $redirect_url
        );

        wp_safe_redirect($redirect_url);
        exit;
    }
}

if (!function_exists('wst_dod_notice_from_query')) {
    function wst_dod_notice_from_query() {
        $type = isset($_GET['wst_dod_notice_type'])
            ? sanitize_key(wp_unslash($_GET['wst_dod_notice_type']))
            : '';

        $message = isset($_GET['wst_dod_notice'])
            ? rawurldecode((string)wp_unslash($_GET['wst_dod_notice']))
            : '';

        $message = trim($message);

        if ($message === '') {
            return '';
        }

        $class = $type === 'error'
            ? 'wst-dod-alert-error'
            : ($type === 'warning' ? 'wst-dod-alert-warning' : 'wst-dod-alert-success');

        return '<div class="wst-dod-alert ' . esc_attr($class) . '">' . esc_html($message) . '</div>';
    }
}

if (!function_exists('wst_dod_get_job_label')) {
    function wst_dod_get_job_label($job_id) {
        global $wpdb;

        $do_id = (int)$job_id;
        if (!$wpdb || $do_id <= 0) return 'DO-' . $do_id;

        $table = $wpdb->prefix . 'ac_do';
        if (!wst_dod_wp_table_exists($table)) return 'DO-' . $do_id;

        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT local_doc_no, autocount_doc_no
                 FROM `{$table}`
                 WHERE id = %d
                 LIMIT 1",
                $do_id
            ),
            ARRAY_A
        );

        if (!$row) return 'DO-' . $do_id;

        $doc_label = strtoupper(trim((string)($row['local_doc_no'] ?? '')));
        if ($doc_label === '') {
            $doc_label = strtoupper(trim((string)($row['autocount_doc_no'] ?? '')));
        }

        return $doc_label !== '' ? $doc_label : 'DO-' . $do_id;
    }
}

if (!function_exists('wst_dod_handle_soft_delete_action')) {
    function wst_dod_handle_soft_delete_action() {
        global $wpdb;

        if (!$wpdb || strtoupper((string)($_SERVER['REQUEST_METHOD'] ?? '')) !== 'POST') {
            return '';
        }

        $posted_action = isset($_POST['wst_dod_row_action'])
            ? sanitize_key(wp_unslash($_POST['wst_dod_row_action']))
            : '';

        if ($posted_action === '') {
            return '';
        }

        if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
            wst_dod_redirect_with_notice('error', 'You do not have permission to update this row.');
        }

        $do_id = isset($_POST['do_id']) ? absint($_POST['do_id']) : (isset($_POST['job_id']) ? absint($_POST['job_id']) : 0);

        if ($do_id <= 0) {
            wst_dod_redirect_with_notice('error', 'This row cannot be updated because it has no local Delivery Order record.');
        }

        if (
            !isset($_POST['wst_dod_row_nonce'])
            || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['wst_dod_row_nonce'])), 'wst_dod_row_action_' . $do_id)
        ) {
            wst_dod_redirect_with_notice('error', 'Security check failed. Please refresh and try again.');
        }

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

        if (!wst_dod_wp_table_exists($table)) {
            wst_dod_redirect_with_notice('error', 'Local Delivery Order table is not available.');
        }

        if (!wst_dod_job_soft_delete_available()) {
            wst_dod_redirect_with_notice('error', 'Soft delete columns are not available on the local Delivery Order table. Please add the hidden_from_staff_list column first.');
        }

        $cols = wst_dod_wp_table_columns($table);
        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT *
                 FROM `{$table}`
                 WHERE id = %d
                 LIMIT 1",
                $do_id
            ),
            ARRAY_A
        );

        if (!$row) {
            wst_dod_redirect_with_notice('error', 'The local Delivery Order record could not be found.');
        }

        $job_label = wst_dod_get_job_label($do_id);

        if ($posted_action === 'delete' || $posted_action === 'void') {
            if (!empty($row['hidden_from_staff_list'])) {
                wst_dod_redirect_with_notice('warning', $job_label . ' is already hidden.');
            }

            $sync_status = strtoupper(trim((string)($row['sync_status'] ?? '')));
            if ($sync_status === 'VOID_PENDING_AUTOCOUNT') {
                wst_dod_redirect_with_notice('warning', $job_label . +����l��������+W�
N?�m' already has a pending AutoCount void request.');
            }

            $autocount_doc_no = strtoupper(trim((string)($row['autocount_doc_no'] ?? '')));
            $autocount_doc_key = (int)($row['autocount_doc_key'] ?? 0);
            $local_doc_no = strtoupper(trim((string)($row['local_doc_no'] ?? '')));

            if ($autocount_doc_no === '' && $autocount_doc_key <= 0) {
                wst_dod_redirect_with_notice(
                    'error',
                    $job_label . ' cannot be deleted because it has no confirmed AutoCount document number or key.'
                );
            }

            $payload = array(
                'action' => 'void',
                'documentType' => 'DELIVERY_ORDER',
                'manifestVersion' => 1,
                'docNo' => $autocount_doc_no,
                'docKey' => $autocount_doc_key,
                'localDoId' => $do_id,
                'localDocNo' => $local_doc_no,
                'previousDeliveryStatus' => strtoupper(trim((string)($row['delivery_status'] ?? ''))),
                'previousSyncStatus' => $sync_status,
                'hideReason' => 'Deleted from staff list; AutoCount void confirmed',
                'hideImmediately' => true,
            );

            $request = new WP_REST_Request('POST', '/ac/v1/job');
            $request->set_header('Content-Type', 'application/json');
            $request->set_body(wp_json_encode(array(
                'type' => 'DELIVERY_ORDER',
                'subtype' => 'VOID',
                'priority' => 10,
                'source' => 'staff_do_delete',
                'client_request_id' => 'do-delete-' . $do_id . '-' . wp_generate_uuid4(),
                'payload' => $payload,
            )));

            $response = rest_do_request($request);
            if (is_wp_error($response)) {
                wst_dod_log_error('Void enqueue failed for local DO ' . $do_id . ': ' . $response->get_error_message());
                wst_dod_redirect_with_notice('error', 'Could not delete this record because the AutoCount void request was not queued: ' . $response->get_error_message());
            }

            $response_data = $response->get_data();
            $response_code = (int)$response->get_status();

            if (
                $response_code < 200
                || $response_code >= 300
                || !is_array($response_data)
                || empty($response_data['ok'])
            ) {
                $message = is_array($response_data) && !empty($response_data['message'])
                    ? (string)$response_data['message']
                    : 'Unknown bridge queue error.';

                wst_dod_log_error('Void enqueue failed for local DO ' . $do_id . ': ' . $message);
                wst_dod_redirect_with_notice('error', 'Could not delete this record because the AutoCount void request was not queued: ' . $message);
            }

            $queued_job_id = (int)($response_data['jobId'] ?? 0);
            $suffix = $queued_job_id > 0 ? ' as bridge job #' . $queued_job_id : '';

            /*
             * The bridge job is safely queued first. Only after the queue
             * accepts it do we hide the WordPress row immediately.
             *
             * This is a soft delete: the database record remains available to
             * recovery users and the AutoCount action remains VOID/CANCEL.
             */
            $delete_update = array(
                'hidden_from_staff_list' => 1,
            );
            $delete_formats = array('%d');

            if (isset($cols['hidden_reason'])) {
                $delete_update['hidden_reason'] = 'Deleted from staff list; AutoCount void queued';
                $delete_formats[] = '%s';
            }

            if (isset($cols['hidden_at'])) {
                $delete_update['hidden_at'] = current_time('mysql');
                $delete_formats[] = '%s';
            }

            if (isset($cols['hidden_by'])) {
                $delete_update['hidden_by'] = get_current_user_id();
                $delete_formats[] = '%d';
            }

            if (isset($cols['updated_at'])) {
                $delete_update['updated_at'] = current_time('mysql');
                $delete_formats[] = '%s';
            }

            $deleted = $wpdb->update(
                $table,
                $delete_update,
                array('id' => $do_id),
                $delete_formats,
                array('%d')
            );

            if ($deleted === false) {
                wst_dod_log_error(
                    'Delete hide failed after AutoCount void job was queued for local DO ' .
                    $do_id . ': ' . $wpdb->last_error
                );

                wst_dod_redirect_with_notice(
                    'error',
                    $job_label . ' AutoCount void was queued' . $suffix .
                    ', but WordPress could not hide the row. Please contact an administrator.'
                );
            }

            wst_dod_redirect_with_notice(
                'success',
                $job_label . ' was deleted from the staff list. AutoCount void was queued' . $suffix . '.'
            );
        }

        if ($posted_action === 'activate') {
            if (!wst_dod_is_administrator()) {
                wst_dod_redirect_with_notice('error', 'Only an Administrator can show hidden rows.');
            }

            $update = array('hidden_from_staff_list' => 0);
            $formats = array('%d');

            if (isset($cols['hidden_reason'])) {
                $update['hidden_reason'] = null;
                $formats[] = '%s';
            }

            if (isset($cols['hidden_at'])) {
                $update['hidden_at'] = null;
                $formats[] = '%s';
            }

            if (isset($cols['hidden_by'])) {
                $update['hidden_by'] = null;
                $formats[] = '%d';
            }

            if (isset($cols['updated_at'])) {
                $update['updated_at'] = current_time('mysql');
                $formats[] = '%s';
            }

            $ok = $wpdb->update(
                $table,
                $update,
                array('id' => $do_id),
                $formats,
                array('%d')
            );

            if ($ok === false) {
                wst_dod_log_error('Show hidden row failed for local DO ' . $do_id . ': ' . $wpdb->last_error);
                wst_dod_redirect_with_notice('error', 'Could not show this row. Please try again.');
            }

            wst_dod_redirect_with_notice(
                'success',
                $job_label . ' is shown again in WordPress. This does not reactivate a document that is already voided in AutoCount.'
            );
        }

        wst_dod_redirect_with_notice('error', 'Unknown row action.');
    }
}

if (!function_exists('wst_dod_get_proof_image_by_doc')) {
    function wst_dod_get_proof_image_by_doc($docNo, $docKey) {
        global $wpdb;

        if (!$wpdb) return '';

        $table = $wpdb->prefix . 'ac_do_proof_images';
        if (!wst_dod_wp_table_exists($table)) return '';

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $cols = wst_dod_wp_table_columns($table);

        $orWhere = array();
        $args = array();

        $docNo = trim((string)$docNo);
        $docKey = (int)$docKey;

        if ($docNo !== '' && isset($cols['doc_no'])) {
            $orWhere[] = 'doc_no = %s';
            $args[] = $docNo;
        }

        if ($docKey > 0 && isset($cols['doc_key'])) {
            $orWhere[] = 'doc_key = %d';
            $args[] = $docKey;
        }

        if (empty($orWhere) || !isset($cols['image_url'])) return '';

        $whereSql = '(' . implode(' OR ', $orWhere) . ')';

        if (isset($cols['proof_type'])) {
            $whereSql .= ' AND proof_type = %s';
            $args[] = 'DELIVERY_PROOF';
        }

        if (isset($cols['deleted_at'])) {
            $whereSql .= ' AND deleted_at IS NULL';
        }

        $orderCol = isset($cols['id']) ? 'id' : (isset($cols['captured_at']) ? 'captured_at' : 'image_url');

        $sql = "
            SELECT image_url
            FROM `{$safe_table}`
            WHERE {$whereSql}
            ORDER BY `{$orderCol}` DESC
            LIMIT 1
        ";

        $url = $wpdb->get_var($wpdb->prepare($sql, $args));

        return $url ? esc_url_raw((string)$url) : '';
    }
}

if (!function_exists('wst_dod_driver_label_from_job')) {
    function wst_dod_driver_label_from_job($job, $payload) {
        $driver_id = (int)($job['assigned_driver_id'] ?? 0);

        if ($driver_id > 0) {
            $user = get_userdata($driver_id);
            if ($user) {
                $display = trim((string)$user->display_name);
                return $display !== '' ? $display : (string)$user->user_login;
            }
        }

        foreach (array('assignedDriverName', 'driverName', 'driver_name', 'assignedDriverLogin', 'driverLogin', 'driver_login', 'assignedDriver', 'assigned_driver', 'driver') as $key) {
            if (!empty($payload[$key])) {
                return trim((string)$payload[$key]);
            }
        }

        if (!empty($job['assigned_driver'])) {
            return trim((string)$job['assigned_driver']);
        }

        return '';
    }
}

if (!function_exists('wst_dod_is_goods_receive_row')) {
    function wst_dod_is_goods_receive_row($row) {
        $doc_values = array(
            $row['local_doc_no'] ?? '',
            $row['autocount_doc_no'] ?? '',
        );

        foreach ($doc_values as $doc_no) {
            $doc_no = strtoupper(trim((string)$doc_no));
            if ($doc_no !== '' && (strpos($doc_no, 'WPGR') === 0 || strpos($doc_no, 'GRN') === 0)) {
                return true;
            }
        }

        return false;
    }
}

if (!function_exists('wst_dod_calc_status')) {
    function wst_dod_calc_status($syncStatus, $deliveryStatus, $docDate, $hidden = false, $isGoodsReceive = false) {
        $syncStatus = strtoupper(trim((string)$syncStatus));
        $deliveryStatus = strtoupper(trim((string)$deliveryStatus));
        $docDate = trim((string)$docDate);

        if ($hidden) return 'HIDDEN';

        if (!$isGoodsReceive) {
            if ($syncStatus === 'VOID_PENDING_AUTOCOUNT') return 'VOID_PENDING_AUTOCOUNT';
            if ($syncStatus === 'VOID_FAILED') return 'VOID_FAILED';
            if ($syncStatus === 'VOIDED_IN_AUTOCOUNT') return 'CANCELLED';
        }

        if ($isGoodsReceive) {
            return $syncStatus !== '' ? $syncStatus : 'ACTIVE';
        }

        if ($deliveryStatus === 'DELIVERED') return 'DELIVERED';
        if ($deliveryStatus === 'CANCELLED') return 'CANCELLED';

        $today = current_time('Y-m-d');
        if ($docDate !== '' && $docDate > $today) return 'SCHEDULED';

        return 'OUT_FOR_DELIVERY';
    }
}

if (!function_exists('wst_dod_item_from_payload_line')) {
    function wst_dod_item_from_payload_line($line) {
        $line = is_array($line) ? $line : array();

        return array(
            'itemCode' => wst_dod_pick_payload_value($line, array('itemCode', 'ItemCode', 'item_code', 'code'), ''),
            'name' => wst_dod_pick_payload_value($line, array('description', 'Description', 'description1', 'itemName', 'item_name', 'name'), ''),
            'qty' => (float)wst_dod_pick_payload_any($line, array('qty', 'Qty', 'quantity'), 0),
            'basket' => (float)wst_dod_pick_payload_any($line, array('basketQty', 'basket_qty', 'basket', 'Basket', 'bsk', 'UDF_BASKET'), 0),
            'carton' => (float)wst_dod_pick_payload_any($line, array('cartonQty', 'carton_qty', 'carton', 'Carton', 'ctn', 'UDF_CARTON'), 0),
            'weightKg' => (float)wst_dod_pick_payload_any($line, array('kg', 'weight', 'weightKg', 'WeightKG', 'weight_kg', 'UDF_WEIGHTKG'), 0),
        );
    }
}

if (!function_exists('wst_dod_can_staff_edit_row')) {
    function wst_dod_can_staff_edit_row($row) {
        if (!empty($row['isGoodsReceive'])) return false;
        if (trim((string)($row['docNo'] ?? '')) === '') return false;

        return true;
    }
}

if (!function_exists('wst_dod_staff_edit_disabled_reason')) {
    function wst_dod_staff_edit_disabled_reason($row) {
        if (trim((string)($row['docNo'] ?? '')) === '') {
            return 'This order is missing a document number.';
        }

        return 'This order cannot be edited because it is a Goods Receive record.';
    }
}

if (!function_exists('wst_dod_load_mysql_rows')) {
    function wst_dod_load_mysql_rows($customer = '', $status = 'ALL', $dateFrom = '', $dateTo = '', $limit = 25, $includeHidden = false) {
        global $wpdb;

        $doTable = $wpdb->prefix . 'ac_do';
        $itemTable = $wpdb->prefix . 'ac_do_items';

        if (!wst_dod_wp_table_exists($doTable)) {
            return array('rows' => array(), 'error' => 'Local Delivery Order table is not available: ' . $doTable);
        }
        if (!wst_dod_wp_table_exists($itemTable)) {
            return array('rows' => array(), 'error' => 'Local Delivery Order item table is not available: ' . $itemTable);
        }

        $safeDoTable = preg_replace('/[^A-Za-z0-9_]/', '', $doTable);
        $safeItemTable = preg_replace('/[^A-Za-z0-9_]/', '', $itemTable);
        $doCols = wst_dod_wp_table_columns($doTable);

        $where = array('1=1');
        $params = array();

        if (isset($doCols['deleted_at'])) {
            $where[] = 'deleted_at IS NULL';
        }

        if (!$includeHidden && isset($doCols['hidden_from_staff_list'])) {
            $where[] = 'hidden_from_staff_list = 0';
        }

        if ($customer !== '') {
            $like = '%' . $wpdb->esc_like($customer) . '%';
            $customerParts = array('local_doc_no LIKE %s', 'debtor_code LIKE %s', 'debtor_name LIKE %s');
            $params[] = $like;
            $params[] = $like;
            $params[] = $like;

            if (isset($doCols['autocount_doc_no'])) {
                $customerParts[] = 'autocount_doc_no LIKE %s';
                $params[] = $like;
            }

            $where[] = '(' . implode(' OR ', $customerParts) . ')';
        }

        if ($dateFrom !== '') {
            $where[] = 'doc_date >= %s';
            $params[] = $dateFrom;
        }
        if ($dateTo !== '') {
            $where[] = 'doc_date <= %s';
            $params[] = $dateTo;
        }

        $sql = "SELECT *
                FROM `{$safeDoTable}`
                WHERE " . implode(' AND ', $where) . "
                ORDER BY doc_date DESC, updated_at DESC, id DESC
                LIMIT 1000";

        if (!empty($params)) {
            $sql = $wpdb->prepare($sql, $params);
        }

        $doRows = $wpdb->get_results($sql, ARRAY_A);
        if ($wpdb->last_error) {
            wst_dod_log_error('Local DO load failed: ' . $wpdb->last_error);
            return array('rows' => array(), 'error' => $wpdb->last_error);
        }

        $ids = array();
        foreach ((array)$doRows as $row) {
            $id = (int)($row['id'] ?? 0);
            if ($id > 0) $ids[] = $id;
        }

        $itemsByDo = array();
        if (!empty($ids)) {
            $placeholders = implode(',', array_fill(0, count($ids), '%d'));
            $itemSql = "SELECT * FROM `{$safeItemTable}` WHERE do_id IN ({$placeholders}) ORDER BY do_id ASC, line_no ASC, id ASC";
            $itemRows = $wpdb->get_results($wpdb->prepare($itemSql, $ids), ARRAY_A);

            if ($wpdb->last_error) {
                wst_dod_log_error('Local DO item load failed: ' . $wpdb->last_error);
                return array('rows' => array(), 'error' => $wpdb->last_error);
            }

            foreach ((array)$itemRows as $item) {
                $doId = (int)($item['do_id'] ?? 0);
                if ($doId <= 0) continue;

        +W��}�m��������+��
N?�n        $itemsByDo[$doId][] = array(
                    'itemCode' => (string)($item['item_code'] ?? ''),
                    'name' => (string)($item['description'] ?? ''),
                    'qty' => (float)($item['qty'] ?? 0),
                    'basket' => (float)($item['basket_qty'] ?? 0),
                    'carton' => (float)($item['carton_qty'] ?? 0),
                    'weightKg' => (float)($item['weight_kg'] ?? 0),
                );
            }
        }

        $out = array();
        $wantedStatus = strtoupper(trim((string)$status));

        foreach ((array)$doRows as $do) {
            $doId = (int)($do['id'] ?? 0);
            if ($doId <= 0) continue;

            $docDate = wst_dod_date($do['doc_date'] ?? '');
            $hidden = !empty($do['hidden_from_staff_list']);
            $syncStatus = strtoupper(trim((string)($do['sync_status'] ?? '')));
            $deliveryStatus = strtoupper(trim((string)($do['delivery_status'] ?? '')));
            $isGoodsReceive = wst_dod_is_goods_receive_row($do);
            $displayStatus = wst_dod_calc_status($syncStatus, $deliveryStatus, $docDate, $hidden, $isGoodsReceive);

            if ($wantedStatus !== 'ALL' && $displayStatus !== $wantedStatus) continue;

            $driver = '';
            $driverId = (int)($do['assigned_driver_id'] ?? 0);
            if ($driverId > 0) {
                $driverUser = get_userdata($driverId);
                if ($driverUser) {
                    $driver = trim((string)$driverUser->display_name);
                    if ($driver === '') $driver = trim((string)$driverUser->user_login);
                }
            }
            if ($driver === '') $driver = $isGoodsReceive ? '-' : 'UNASSIGNED';

            $items = $itemsByDo[$doId] ?? array();
            $totalBasket = 0.0;
            $totalCarton = 0.0;
            foreach ($items as $line) {
                $totalBasket += (float)($line['basket'] ?? 0);
                $totalCarton += (float)($line['carton'] ?? 0);
            }

            $docNo = strtoupper(trim((string)($do['local_doc_no'] ?? '')));
            $autoDocNo = strtoupper(trim((string)($do['autocount_doc_no'] ?? '')));
            $docKey = (int)($do['autocount_doc_key'] ?? 0);

            $out[] = array(
                'docKey' => $docKey,
                'docNo' => $docNo !== '' ? $docNo : $autoDocNo,
                'autoCountDocNo' => $autoDocNo,
                'docDate' => $docDate,
                'debtorCode' => (string)($do['debtor_code'] ?? ''),
                'debtorName' => (string)($do['debtor_name'] ?? ''),
                'autoCountStatus' => $syncStatus,
                'displayStatus' => $displayStatus,
                'driver' => $driver,
                'isGoodsReceive' => $isGoodsReceive,
                'documentTypeLabel' => $isGoodsReceive ? 'Goods Receive' : 'Delivery Order',
                'partyLabel' => $isGoodsReceive ? 'Supplier' : 'Customer',
                'jobId' => $doId,
                'doId' => $doId,
                'jobSubtype' => '',
                'syncStatus' => $syncStatus,
                'deliveryStatus' => $deliveryStatus,
                'jobError' => (string)($do['last_sync_error'] ?? ''),
                'createdAt' => wst_dod_datetime($do['created_at'] ?? ''),
                'lastModified' => wst_dod_datetime($do['updated_at'] ?? ''),
                'totalBasket' => $totalBasket,
                'totalCarton' => $totalCarton,
                'items' => $items,
                'proofImage' => wst_dod_get_proof_image_by_doc(($autoDocNo !== '' ? $autoDocNo : $docNo), $docKey),
                'hasAuthoritativeJob' => true,
                'isPendingJob' => false,
                'hiddenFromStaffList' => (int)($do['hidden_from_staff_list'] ?? 0),
                'hiddenReason' => (string)($do['hidden_reason'] ?? ''),
                'hiddenAt' => (string)($do['hidden_at'] ?? ''),
                'hiddenBy' => (int)($do['hidden_by'] ?? 0),
            );
        }

        $limit = (int)$limit;
        if ($limit > 0) {
            $out = array_slice($out, 0, $limit);
        }

        return array('rows' => $out, 'error' => '');
    }
}

$isAdministrator = wst_dod_is_administrator();
wst_dod_handle_soft_delete_action();
$wst_dod_action_notice = wst_dod_notice_from_query();

$customer = isset($_GET['customer']) ? trim(sanitize_text_field(wp_unslash($_GET['customer']))) : '';
$status = isset($_GET['status']) ? strtoupper(trim(sanitize_text_field(wp_unslash($_GET['status'])))) : 'ALL';
$limit_input = isset($_GET['limit']) ? (int)$_GET['limit'] : 25;
$allowed_limits = array(25, 50, 100);
$limit = in_array($limit_input, $allowed_limits, true) ? $limit_input : 25;

$status_options = array(
    'ALL' => 'All',
    'SCHEDULED' => 'Scheduled',
    'OUT_FOR_DELIVERY' => 'Out for Delivery',
    'DELIVERED' => 'Delivered',
);

if ($isAdministrator) {
    $status_options['HIDDEN'] = 'Hidden';
}

if (!isset($status_options[$status])) {
    $status = 'ALL';
}

$todayObj = new DateTime('now', wp_timezone());
$defaultDateTo = $todayObj->format('Y-m-d');

$fromObj = clone $todayObj;
$fromObj->modify('-1 month');
$defaultDateFrom = $fromObj->format('Y-m-d');

$dateFromRaw = isset($_GET['dateFrom']) ? wp_unslash($_GET['dateFrom']) : '';
$dateToRaw = isset($_GET['dateTo']) ? wp_unslash($_GET['dateTo']) : '';
$dateFrom = wst_dod_valid_date(sanitize_text_field($dateFromRaw), $defaultDateFrom);
$dateTo = wst_dod_valid_date(sanitize_text_field($dateToRaw), $defaultDateTo);

/*
 * Hidden / inactive row visibility:
 * - Non-administrators never see hidden rows, the Hidden status option, or the toggle.
 * - Administrators see hidden rows by default.
 * - Administrators can toggle hidden rows off using show_hidden=0.
 */
$showHiddenRows = false;
if ($isAdministrator) {
    $showHiddenRaw = isset($_GET['show_hidden'])
        ? sanitize_text_field(wp_unslash($_GET['show_hidden']))
        : '1';

    $showHiddenRows = ($showHiddenRaw !== '0');
}

$loadResult = wst_dod_load_mysql_rows($customer, $status, $dateFrom, $dateTo, $limit, $showHiddenRows);
$rows = $loadResult['rows'];
$loadWarning = $loadResult['error'];

$hiddenToggleUrl = '';
$hiddenToggleLabel = '';
if ($isAdministrator) {
    $hiddenToggleUrl = add_query_arg(
        array(
            'customer' => $customer,
            'status' => $status,
            'dateFrom' => $dateFrom,
            'dateTo' => $dateTo,
            'limit' => $limit,
            'show_hidden' => $showHiddenRows ? '0' : '1',
        ),
        get_permalink()
    );

    $hiddenToggleLabel = $showHiddenRows ? 'Hide Hidden' : 'Show Hidden';
}

$clearFilterUrl = get_permalink();
if ($isAdministrator) {
    $clearFilterUrl = add_query_arg(
        'show_hidden',
        $showHiddenRows ? '1' : '0',
        $clearFilterUrl
    );
}
?>

<div class="wst-dod-wrap">
    <?php echo $wst_dod_action_notice; ?>

    <?php if ($loadWarning !== ''): ?>
        <div class="wst-dod-alert wst-dod-alert-error">
            Failed to load Delivery Order records.
            <?php if ($show_technical_errors): ?>
                <?php echo esc_html($loadWarning); ?>
            <?php endif; ?>
        </div>
    <?php endif; ?>

    <div class="wst-dod-filter-card">
        <form method="get" class="wst-dod-form">
            <?php if ($isAdministrator): ?>
                <input type="hidden" name="show_hidden" value="<?php echo esc_attr($showHiddenRows ? '1' : '0'); ?>">
            <?php endif; ?>

            <div class="wst-dod-filter-main">
                <div class="wst-dod-field wst-dod-search-field">
                    <label class="wst-dod-label" for="wstDodCustomer">Customer / Supplier / Doc No</label>
                    <input id="wstDodCustomer" name="customer" class="wst-dod-input" type="search" value="<?php echo esc_attr($customer); ?>" placeholder="Search customer, supplier, DO or GRN no..." autocomplete="off">
                </div>

                <div class="wst-dod-filter-actions">
                    <button type="submit" class="wst-dod-btn wst-dod-btn-primary">Search</button>
                    <a class="wst-dod-btn wst-dod-btn-secondary" href="<?php echo esc_url($clearFilterUrl); ?>" aria-label="Clear search filters">Clear</a>
                </div>
            </div>

            <div class="wst-dod-filter-grid">
                <div class="wst-dod-field">
                    <label class="wst-dod-label" for="wstDodStatus">Status</label>
                    <select id="wstDodStatus" name="status" class="wst-dod-input">
                        <?php foreach ($status_options as $status_value => $status_label): ?>
                            <option value="<?php echo esc_attr($status_value); ?>" <?php selected($status, $status_value); ?>>
                                <?php echo esc_html($status_label); ?>
                            </option>
                        <?php endforeach; ?>
                    </select>
                </div>

                <div class="wst-dod-field">
                    <label class="wst-dod-label" for="wstDodDateFrom">From</label>
                    <input id="wstDodDateFrom" name="dateFrom" class="wst-dod-input" type="date" value="<?php echo esc_attr($dateFrom); ?>">
                </div>

                <div class="wst-dod-field">
                    <label class="wst-dod-label" for="wstDodDateTo">To</label>
                    <input id="wstDodDateTo" name="dateTo" class="wst-dod-input" type="date" value="<?php echo esc_attr($dateTo); ?>">
                </div>

                <div class="wst-dod-field">
                    <label class="wst-dod-label" for="wstDodLimit">Rows</label>
                    <select id="wstDodLimit" name="limit" class="wst-dod-input">
                        <?php foreach ($allowed_limits as $allowed_limit): ?>
                            <option value="<?php echo esc_attr($allowed_limit); ?>" <?php selected($limit, $allowed_limit); ?>>
                                <?php echo esc_html($allowed_limit); ?>
                            </option>
                        <?php endforeach; ?>
                    </select>
                </div>
            </div>
        </form>
    </div>

    <div class="wst-dod-summary">
        <span>Showing <?php echo esc_html(number_format_i18n(count($rows))); ?> rows</span>

        <?php if ($isAdministrator): ?>
            <a class="wst-dod-hidden-toggle" href="<?php echo esc_url($hiddenToggleUrl); ?>">
                <?php echo esc_html($hiddenToggleLabel); ?>
            </a>
        <?php endif; ?>
    </div>

    <div class="wst-dod-table-card">
        <div class="wst-dod-table-scroll">
            <table class="wst-dod-table">
                <thead>
                    <tr>
                        <th class="wst-dod-col-date">Date</th>
                        <th class="wst-dod-col-doc">Doc No</th>
                        <th class="wst-dod-col-customer">Customer / Supplier</th>
                        <th class="wst-dod-col-driver">Driver</th>
                        <th class="wst-dod-col-status">Status</th>
                        <th class="wst-dod-col-summary">Bsk / Ctn</th>
                        <th class="wst-dod-col-items">Items</th>
                        <th class="wst-dod-col-action">Actions</th>
                    </tr>
                </thead>

                <tbody>
                    <?php if (empty($rows)): ?>
                        <tr>
                            <td colspan="8" class="wst-dod-empty">No matching delivery order records.</td>
                        </tr>
                    <?php else: ?>
                        <?php foreach ($rows as $r): ?>
                            <?php
                            $isHidden = !empty($r['hiddenFromStaffList']);
                            $rowJobId = (int)($r['jobId'] ?? 0);
                            $rowActionNonce = $rowJobId > 0 ? wp_create_nonce('wst_dod_row_action_' . $rowJobId) : '';

                            $view_args = !empty($r['isPendingJob'])
                                ? array('job_id' => (int)$r['jobId'])
                                : array('docNo' => $r['docNo'], 'docKey' => (int)$r['docKey']);

                            $view_url = add_query_arg($view_args, $view_page_url);

                            $edit_url = add_query_arg(
                                array(
                                    'docNo' => (string)($r['docNo'] ?? ''),
                                    'docKey' => (int)($r['docKey'] ?? 0),
                                ),
                                $edit_page_url
                            );

                            $can_edit_row = wst_dod_can_staff_edit_row($r);
                            $edit_disabled_reason = $can_edit_row ? '' : wst_dod_staff_edit_disabled_reason($r);

                            $row_display_status_key = strtoupper(trim((string)($r['displayStatus'] ?? '')));
                            $row_delivery_status_key = strtoupper(trim((string)($r['deliveryStatus'] ?? '')));
                            $is_delivered_row = ($row_display_status_key === 'DELIVERED' || $row_delivery_status_key === 'DELIVERED');
                            $row_sync_status_key = strtoupper(trim((string)($r['syncStatus'] ?? '')));
                            $is_void_pending = ($row_sync_status_key === 'VOID_PENDING_AUTOCOUNT');
                            $has_autocount_reference =
                                trim((string)($r['autoCountDocNo'] ?? '')) !== ''
                                || (int)($r['docKey'] ?? 0) > 0;

                            // WordPress-first: allow opening any row that has a real local DO record.
                            // docKey is not required when the order was created in WordPress and not yet synced to AutoCount.
                            $can_open_document = !empty($r['isPendingJob']) || ((int)($r['jobId'] ?? 0) > 0 && trim((string)($r['docNo'] ?? '')) !== '');

                            $print_url = add_query_arg(
                                array(
                                    'autoPrint' => '1',
                                    'printPage' => 'do',
                                ),
                                $view_url
                            );

                            $badgeClass = wst_dod_status_class($r['displayStatus']);
                            ?>

                            <tr class="wst-dod-main-row <?php echo $isHidden ? 'wst-dod-row-hidden' : ''; ?>">
                                <td class="wst-dod-date">
                                    <div class="wst-dod-date-main"><?php echo esc_html($r['docDate'] ?: '-'); ?></div>
                                    <div class="wst-dod-date-sub">Created: <?php echo esc_html($r['createdAt'] ?: '-'); ?></div>
                                    <div class="wst-dod-date-sub">Updated: <?php echo esc_html($r['lastModified'] ?: '-'); ?></div>
                                </td>

                                <td class="wst-dod-docno">
                                    <?php if (!empty($r['isPendingJob'])): ?>
                                        <span class="wst-dod-muted">JOB-<?php echo esc_html((int)$r['jobId']); ?></span>
                                    <?php else: ?>
                                        <?php echo esc_html($r['docNo'] ?: '-'); ?>
                                        <div class="wst-dod-date-sub"><?php echo esc_html($r['documentTypeLabel'] ?? 'Delivery Order'); ?></div>
                                    <?php endif; ?>
                                </td>

                                <td class="wst-dod-customer">
                                    <div class="wst-dod-customer-name"><?php echo esc_html($r['debtorName'] ?: '-'); ?></div>
                                    <div class="wst-dod-customer-code"><?php echo esc_html(($r['partyLabel'] ?? 'Customer')+���7"�n��������+�L
N?�o . ': ' . ($r['debtorCode'] ?: '-')); ?></div>
                                </td>

                                <td class="wst-dod-driver">
                                    <?php echo esc_html(strtoupper($r['driver'] ?: 'Unassigned')); ?>
                                </td>

                                <td class="wst-dod-status">
                                    <span
                                        class="wst-dod-badge <?php echo esc_attr($badgeClass); ?>"
                                        data-status-help="<?php echo esc_attr(wst_dod_status_help($r['displayStatus'])); ?>"
                                    >
                                        <?php echo esc_html(wst_dod_label_status($r['displayStatus'])); ?>
                                    </span>

                                    <?php if ($isHidden && $isAdministrator): ?>
                                        <span
                                            class="wst-dod-badge wst-dod-badge-hidden"
                                            data-status-help="<?php echo esc_attr($r['hiddenReason'] ?: 'This row is hidden from normal staff.'); ?>"
                                        >
                                            Hidden
                                        </span>
                                    <?php endif; ?>
                                </td>

                                <td class="wst-dod-summary-cell">
                                    <div>Bsk <strong><?php echo esc_html(wst_dod_fmt_qty($r['totalBasket'])); ?></strong></div>
                                    <div>Ctn <strong><?php echo esc_html(wst_dod_fmt_qty($r['totalCarton'])); ?></strong></div>
                                </td>

                                <td class="wst-dod-items">
                                    <?php if (empty($r['items'])): ?>
                                        <div class="wst-dod-muted">No item detail.</div>
                                    <?php else: ?>
                                        <?php foreach ($r['items'] as $item): ?>
                                            <div class="wst-dod-item">
                                                <div class="wst-dod-item-name">
                                                    <?php echo esc_html($item['name'] !== '' ? $item['name'] : ($item['itemCode'] ?: '-')); ?>
                                                </div>
                                                <div class="wst-dod-item-meta">
                                                    <?php echo esc_html($item['itemCode'] ?: '-'); ?>
                                                    | Qty <?php echo esc_html(wst_dod_fmt_qty($item['qty'])); ?>
                                                    | Basket <?php echo esc_html(wst_dod_fmt_qty($item['basket'])); ?>
                                                    | Carton <?php echo esc_html(wst_dod_fmt_qty($item['carton'])); ?>
                                                    | KG <?php echo esc_html(wst_dod_fmt_weight($item['weightKg'])); ?>
                                                </div>
                                            </div>
                                        <?php endforeach; ?>
                                    <?php endif; ?>
                                </td>

                                <td class="wst-dod-action">
                                    <?php if ($can_open_document): ?>
                                        <a
                                            class="wst-dod-action-btn wst-dod-action-print"
                                            href="<?php echo esc_url($print_url); ?>"
                                            onclick="return wstDodOpenPrintPopup(this.href);"
                                        >
                                            Print
                                        </a>
                                    <?php else: ?>
                                        <span
                                            class="wst-dod-action-btn wst-dod-action-disabled"
                                            title="Cannot print because this row has no document reference."
                                            aria-label="Cannot print because this row has no document reference."
                                        >Print</span>
                                    <?php endif; ?>

                                    <?php if ($can_edit_row): ?>
                                        <a class="wst-dod-action-btn wst-dod-action-edit" href="<?php echo esc_url($edit_url); ?>">
                                            Edit
                                        </a>
                                    <?php else: ?>
                                        <span
                                            class="wst-dod-action-btn wst-dod-action-disabled"
                                            title="<?php echo esc_attr($edit_disabled_reason); ?>"
                                            aria-label="<?php echo esc_attr($edit_disabled_reason); ?>"
                                        >Edit</span>
                                    <?php endif; ?>

                                    <?php if ($can_open_document): ?>
                                        <a class="wst-dod-action-btn wst-dod-action-view" href="<?php echo esc_url($view_url); ?>">
                                            View
                                        </a>
                                    <?php else: ?>
                                        <span
                                            class="wst-dod-action-btn wst-dod-action-disabled"
                                            title="Cannot view because this row has no document reference."
                                            aria-label="Cannot view because this row has no document reference."
                                        >View</span>
                                    <?php endif; ?>

                                    <?php if (!$is_delivered_row || $isAdministrator): ?>
                                        <?php if ($rowJobId > 0): ?>
                                            <?php if ($isHidden && $isAdministrator): ?>
                                                <form method="post" class="wst-dod-inline-form" onsubmit="return wstDodConfirmSoftAction(this, 'activate');">
                                                    <input type="hidden" name="do_id" value="<?php echo esc_attr($rowJobId); ?>">
                                                    <input type="hidden" name="job_id" value="<?php echo esc_attr($rowJobId); ?>">
                                                    <input type="hidden" name="wst_dod_row_nonce" value="<?php echo esc_attr($rowActionNonce); ?>">
                                                    <input type="hidden" name="wst_dod_row_action" value="activate">
                                                    <button type="submit" class="wst-dod-action-btn wst-dod-action-active">Show</button>
                                                </form>
                                            <?php elseif ($is_void_pending): ?>
                                                <span
                                                    class="wst-dod-action-btn wst-dod-action-disabled"
                                                    title="This record is already deleted from the normal staff list and its AutoCount void request is pending."
                                                    aria-label="This record is already deleted from the normal staff list and its AutoCount void request is pending."
                                                >Delete Pending</span>
                                            <?php elseif (!$has_autocount_reference): ?>
                                                <span
                                                    class="wst-dod-action-btn wst-dod-action-disabled"
                                                    title="Cannot delete because this row has no confirmed AutoCount document number or key."
                                                    aria-label="Cannot delete because this row has no confirmed AutoCount document number or key."
                                                >Delete</span>
                                            <?php else: ?>
                                                <form method="post" class="wst-dod-inline-form" onsubmit="return wstDodConfirmSoftAction(this, 'delete');">
                                                    <input type="hidden" name="do_id" value="<?php echo esc_attr($rowJobId); ?>">
                                                    <input type="hidden" name="job_id" value="<?php echo esc_attr($rowJobId); ?>">
                                                    <input type="hidden" name="wst_dod_row_nonce" value="<?php echo esc_attr($rowActionNonce); ?>">
                                                    <input type="hidden" name="wst_dod_row_action" value="delete">
                                                    <button type="submit" class="wst-dod-action-btn wst-dod-action-delete">Delete</button>
                                                </form>
                                            <?php endif; ?>
                                        <?php else: ?>
                                            <span
                                                class="wst-dod-action-btn wst-dod-action-disabled"
                                                title="Cannot delete because this row has no local Delivery Order record."
                                                aria-label="Cannot delete because this row has no local Delivery Order record."
                                            >Delete</span>
                                        <?php endif; ?>
                                    <?php endif; ?>
                                </td>
                            </tr>
                        <?php endforeach; ?>
                    <?php endif; ?>
                </tbody>
            </table>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
function wstDodConfirmSoftAction(form, actionType) {
    var isActivate = actionType === 'activate';
    var title = isActivate
        ? 'Show this hidden record?'
        : 'Delete this Delivery Order?';

    var text = isActivate
        ? 'This only shows the row again in WordPress. It does not reactivate a voided AutoCount document.'
        : 'This will immediately delete the Delivery Order from the staff list and cancel it in AutoCount.';

    var confirmText = isActivate ? 'Yes, show' : 'Yes, delete';
    var confirmColor = isActivate ? '#166534' : '#dc2626';
    var cancelColor = '#64748b';

    if (window.Swal && typeof window.Swal.fire === 'function') {
        window.Swal.fire({
            title: title,
            text: text,
            icon: isActivate ? 'question' : 'warning',
            showCancelButton: true,
            confirmButtonText: confirmText,
            cancelButtonText: 'Cancel',
            confirmButtonColor: confirmColor,
            cancelButtonColor: cancelColor,
            reverseButtons: true,
            focusCancel: true
        }).then(function(result) {
            if (result && result.isConfirmed) {
                form.submit();
            }
        });

        return false;
    }

    if (window.confirm(text)) {
        form.submit();
    }

    return false;
}

function wstDodOpenPrintPopup(url) {
    var width = 920;
    var height = 760;
    var left = Math.max(0, Math.round((window.screen.width - width) / 2));
    var top = Math.max(0, Math.round((window.screen.height - height) / 2));
    var features = [
        'popup=yes',
        'width=' + width,
        'height=' + height,
        'left=' + left,
        'top=' + top,
        'resizable=yes',
        'scrollbars=yes',
        'noopener=yes'
    ].join(',');

    var popup = window.open(url, 'wstDodPrintWindow', features);

    if (!popup) {
        window.open(url, '_blank', 'noopener=yes');
        return false;
    }

    try {
        popup.focus();
    } catch (error) {}

    return false;
}

var wstDodStatusTooltip = null;

function wstDodGetStatusTooltip() {
    if (wstDodStatusTooltip) {
        return wstDodStatusTooltip;
    }

    wstDodStatusTooltip = document.createElement('div');
    wstDodStatusTooltip.className = 'wst-dod-status-tooltip';
    wstDodStatusTooltip.setAttribute('role', 'tooltip');
    document.body.appendChild(wstDodStatusTooltip);

    return wstDodStatusTooltip;
}

function wstDodPositionStatusTooltip(target) {
    var tooltip = wstDodGetStatusTooltip();
    var targetRect = target.getBoundingClientRect();
    var tooltipRect = tooltip.getBoundingClientRect();
    var gap = 8;
    var viewportPadding = 10;
    var left = targetRect.left;
    var top;

    if (left + tooltipRect.width > window.innerWidth - viewportPadding) {
        left = window.innerWidth - tooltipRect.width - viewportPadding;
    }

    left = Math.max(viewportPadding, left);

    var spaceBelow = window.innerHeight - targetRect.bottom;
    var spaceAbove = targetRect.top;

    if (spaceBelow >= tooltipRect.height + gap || spaceBelow >= spaceAbove) {
        top = targetRect.bottom + gap;
    } else {
        top = targetRect.top - tooltipRect.height - gap;
    }

    top = Math.max(
        viewportPadding,
        Math.min(top, window.innerHeight - tooltipRect.height - viewportPadding)
    );

    tooltip.style.left = Math.round(left) + 'px';
    tooltip.style.top = Math.round(top) + 'px';
}

function wstDodShowStatusTooltip(target) {
    var message = target.getAttribute('data-status-help');
    if (!message) {
        return;
    }

    var tooltip = wstDodGetStatusTooltip();
    tooltip.textContent = message;
    tooltip.classList.add('is-visible');
    wstDodPositionStatusTooltip(target);
}

function wstDodHideStatusTooltip() {
    if (wstDodStatusTooltip) {
        wstDodStatusTooltip.classList.remove('is-visible');
    }
}

document.querySelectorAll('.wst-dod-badge[data-status-help]').forEach(function(badge) {
    badge.setAttribute('tabindex', '0');
    badge.addEventListener('mouseenter', function() {
        wstDodShowStatusTooltip(badge);
    });
    badge.addEventListener('mouseleave', wstDodHideStatusTooltip);
    badge.addEventListener('focus', function() {
        wstDodShowStatusTooltip(badge);
    });
    badge.addEventListener('blur', wstDodHideStatusTooltip);
});

window.addEventListener('resize', wstDodHideStatusTooltip);
window.addEventListener('scroll', wstDodHideStatusTooltip, true);
</script>

<style>
.wst-dod-wrap{
    --dod-green:#166534;
    --dod-green-dark:#14532d;
    --dod-line:#e5e7eb;
    --dod-text:#0f172a;
    --dod-muted:#64748b;
    width:100%;
    max-width:100%;
    margin:0 auto;
    padding:6px;
    box-sizing:border-box;
    font-family:"Segoe UI", Roboto, Arial, sans-serif;
    color:var(--dod-text);
    background:#f4faf5;
}

.wst-dod-alert{
    padding:12px 14px;
    border-radius:8px;
    margin:8px 0;
    font-size:14px;
    font-weight:700;
}

.wst-dod-alert-error{
    border:1px solid #fecaca;
    background:#fff1f2;
    color:#991b1b;
}

.wst-dod-alert-warning{
    border:1px solid #fed7aa;
    background:#fff7ed;
    color:#9a3412;
}

.wst-dod-alert-success{
    border:1px solid #86efac;
    background:#f0fdf4;
    color:#166534;
}

.wst-dod-filter-card,
.wst-dod-table-card{
    background:#fff;
    border:1px solid var(--dod-line);
    border-radius:8px;
    padding:8px;
    margin-bottom:8px;
    box-sizing:border-box;
}

.wst-dod-form{
    display:flex;
    flex-direction:column;
    gap:10px;
}

.wst-dod-filter-main{
    display:grid;
    g+�Lq!o��������+�L
N(�����rid-template-columns:minmax(280px, 1fr) auto;
    gap:10px;
    align-items:end;
}

.wst-dod-filter-actions{
    display:flex;
    align-items:center;
    justify-content:flex-end;
    gap:8px;
}

.wst-dod-filter-grid{
    display:grid;
    grid-template-columns:minmax(150px, 1fr) repeat(2, minmax(170px, 1fr)) minmax(90px, .55fr);
    gap:8px;
}

.wst-dod-field{
    min-width:0;
    display:flex;
    flex-direction:column;
    gap:4px;
}

.wst-dod-label{
    font-size:13px;
    line-height:1.1;
    font-weight:800;
    color:#334155;
}

.wst-dod-input{
    width:100%;
    min-height:38px;
    border:1px solid #cbd5e1;
    border-radius:6px;
    padding:7px 9px;
    font-size:14px;
    color:var(--dod-text);
    background:#fff;
    box-sizing:border-box;
}

.wst-dod-input:focus{
    outline:none;
    border-color:var(--dod-green);
    box-shadow:0 0 0 3px rgba(22,101,52,.12);
}

.wst-dod-btn{
    min-height:38px;
    min-width:92px;
    border:1px solid transparent;
    border-radius:7px;
    padding:8px 16px;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    box-sizing:border-box;
    font-size:14px;
    font-weight:900;
    line-height:1;
    text-decoration:none !important;
    cursor:pointer;
    white-space:nowrap;
}

.wst-dod-btn-primary{
    background:var(--dod-green);
    border-color:var(--dod-green);
    color:#fff !important;
}

.wst-dod-btn-primary:hover,
.wst-dod-btn-primary:focus{
    background:var(--dod-green-dark);
    border-color:var(--dod-green-dark);
    color:#fff !important;
}

.wst-dod-btn-secondary{
    background:#fff;
    border-color:#cbd5e1;
    color:#334155 !important;
}

.wst-dod-btn-secondary:hover,
.wst-dod-btn-secondary:focus{
    background:#f8fafc;
    border-color:#94a3b8;
    color:#0f172a !important;
}

.wst-dod-summary{
    display:flex;
    justify-content:space-between;
    align-items:center;
    gap:10px;
    margin:0 0 8px;
    color:#334155;
    font-size:13px;
    font-weight:800;
}

.wst-dod-hidden-toggle{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    min-height:30px;
    padding:7px 12px;
    border:1px solid #cbd5e1;
    border-radius:999px;
    background:#ffffff;
    color:#334155 !important;
    font-size:12px;
    font-weight:900;
    line-height:1;
    text-decoration:none !important;
    white-space:nowrap;
}

.wst-dod-hidden-toggle:hover,
.wst-dod-hidden-toggle:focus{
    background:#f8fafc;
    border-color:#94a3b8;
    color:#0f172a !important;
    text-decoration:none !important;
}

.wst-dod-table-card{
    padding:0;
    overflow:hidden;
}

.wst-dod-table-scroll{
    display:block;
    width:100%;
    max-width:100%;
    overflow-x:auto;
    overflow-y:hidden;
    -webkit-overflow-scrolling:touch;
    scrollbar-width:thin;
    scrollbar-color:#94a3b8 #e5e7eb;
}

.wst-dod-table-scroll::-webkit-scrollbar{
    height:12px;
}

.wst-dod-table-scroll::-webkit-scrollbar-thumb{
    background:#94a3b8;
    border-radius:999px;
}

.wst-dod-table-scroll::-webkit-scrollbar-track{
    background:#e5e7eb;
    border-radius:999px;
}

.wst-dod-table{
    width:100%;
    min-width:1160px;
    border-collapse:collapse;
    table-layout:fixed;
    background:#fff;
}

.wst-dod-table th{
    background:#f8fafc;
    color:#334155;
    font-size:12px;
    font-weight:900;
    text-align:left;
    padding:8px 7px;
    border-bottom:1px solid var(--dod-line);
    white-space:nowrap;
}

.wst-dod-table td{
    padding:8px 7px;
    vertical-align:top;
    color:var(--dod-text);
    font-size:13px;
    line-height:1.25;
}

.wst-dod-table tbody tr{
    box-shadow:inset 0 -1px 0 #edf2f7;
}

.wst-dod-table tbody tr:nth-child(odd){
    background:#ffffff;
}

.wst-dod-table tbody tr:nth-child(even){
    background:#f1f8f3;
}

.wst-dod-table tbody tr:hover{
    background:#e8f5ec;
}

.wst-dod-table tbody tr.wst-dod-row-hidden{
    background:#f8fafc;
    opacity:.78;
}

.wst-dod-table tbody tr.wst-dod-row-hidden:hover{
    background:#eef2f7;
    opacity:1;
}

.wst-dod-col-date{width:12%;}
.wst-dod-col-doc{width:9%;}
.wst-dod-col-customer{width:19%;}
.wst-dod-col-driver{width:10%;}
.wst-dod-col-status{width:10%;}
.wst-dod-col-summary{width:7%;}
.wst-dod-col-items{width:18%;}
.wst-dod-col-action{width:15%;}

.wst-dod-docno{
    font-weight:900;
    white-space:nowrap;
}

.wst-dod-date{
    white-space:normal;
}

.wst-dod-date-main{
    font-weight:900;
    white-space:nowrap;
}

.wst-dod-date-sub{
    margin-top:2px;
    color:#64748b;
    font-size:11px;
    font-weight:800;
    line-height:1.25;
    word-break:break-word;
}

.wst-dod-customer-name{
    font-size:14px;
    font-weight:900;
    line-height:1.15;
    color:#020617;
    word-break:break-word;
}

.wst-dod-customer-code{
    margin-top:2px;
    color:#64748b;
    font-size:11px;
    font-weight:800;
    word-break:break-word;
}

.wst-dod-driver{
    font-weight:800;
    word-break:break-word;
    text-transform:uppercase;
}

.wst-dod-summary-cell{
    white-space:nowrap;
    font-size:12px;
}

.wst-dod-summary-cell strong{
    font-weight:900;
}

.wst-dod-badge{
    position:relative;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    border-radius:999px;
    border:1px solid;
    padding:3px 7px;
    max-width:100%;
    font-size:10px;
    line-height:1.1;
    font-weight:900;
    text-transform:uppercase;
    white-space:normal;
}

.wst-dod-status-tooltip{
    position:fixed;
    z-index:999999;
    width:220px;
    max-width:calc(100vw - 20px);
    padding:8px 10px;
    border:1px solid #cbd5e1;
    border-radius:8px;
    background:#0f172a;
    color:#fff;
    box-sizing:border-box;
    font-family:"Segoe UI", Roboto, Arial, sans-serif;
    font-size:12px;
    font-weight:800;
    line-height:1.35;
    text-transform:none;
    white-space:normal;
    overflow-wrap:anywhere;
    box-shadow:0 12px 24px rgba(15,23,42,.2);
    pointer-events:none;
    visibility:hidden;
    opacity:0;
}

.wst-dod-status-tooltip.is-visible{
    visibility:visible;
    opacity:1;
}

.wst-dod-badge-good{
    color:#166534;
    background:#dcfce7;
    border-color:#86efac;
}

.wst-dod-badge-info{
    color:#075985;
    background:#e0f2fe;
    border-color:#7dd3fc;
}

.wst-dod-badge-warn{
    color:#92400e;
    background:#fef3c7;
    border-color:#fbbf24;
}

.wst-dod-badge-danger{
    color:#9f1239;
    background:#ffe4e6;
    border-color:#fda4af;
}

.wst-dod-badge-hidden{
    margin-top:4px;
    color:#475569;
    background:#f1f5f9;
    border-color:#cbd5e1;
}

.wst-dod-action{
    display:flex;
    align-items:flex-start;
    gap:5px;
    flex-wrap:wrap;
    vertical-align:top;
}

.wst-dod-action-btn{
    appearance:none;
    -webkit-appearance:none;
    display:inline-flex !important;
    align-items:center;
    justify-content:center;
    min-height:30px;
    min-width:54px;
    padding:7px 8px;
    border-radius:999px;
    border:1px solid;
    font-size:10.5px;
    font-weight:900;
    line-height:1;
    text-decoration:none !important;
    box-shadow:none !important;
    cursor:pointer;
    flex:0 0 auto;
    transition:background .15s ease, border-color .15s ease, color .15s ease, transform .15s ease;
}

.wst-dod-action-print{
    background:var(--dod-green);
    border-color:var(--dod-green);
    color:#ffffff !important;
}

.wst-dod-action-edit{
    background:#eff6ff;
    border-color:#93c5fd;
    color:#1d4ed8 !important;
}

.wst-dod-action-view{
    background:#ffffff;
    border-color:#cbd5e1;
    color:#334155 !important;
}

.wst-dod-inline-form{
    display:inline-flex;
    margin:0;
    padding:0;
}

.wst-dod-inline-form button{
    font-family:inherit;
}

.wst-dod-action-delete{
    background:#fff1f2;
    border-color:#fda4af;
    color:#9f1239 !important;
}

.wst-dod-action-active{
    background:#f0fdf4;
    border-color:#86efac;
    color:#166534 !important;
}

.wst-dod-action-disabled{
    background:#f8fafc;
    border-color:#e2e8f0;
    color:#94a3b8 !important;
    cursor:not-allowed;
}

.wst-dod-action-btn:hover{
    filter:none;
    transform:translateY(-1px);
}

.wst-dod-action-print:hover{
    background:var(--dod-green-dark);
    border-color:var(--dod-green-dark);
}

.wst-dod-action-edit:hover{
    background:#dbeafe;
    border-color:#60a5fa;
}

.wst-dod-action-view:hover{
    background:#f8fafc;
    border-color:#94a3b8;
}

.wst-dod-action-delete:hover{
    background:#ffe4e6;
    border-color:#fb7185;
}

.wst-dod-action-active:hover{
    background:#dcfce7;
    border-color:#4ade80;
}

.wst-dod-item{
    padding:0 0 6px;
    margin-bottom:6px;
}

.wst-dod-item:last-child{
    border-bottom:0;
    margin-bottom:0;
    padding-bottom:0;
}

.wst-dod-item-name{
    font-size:13px;
    font-weight:900;
    line-height:1.2;
    color:#020617;
    text-transform:uppercase;
}

.wst-dod-item-meta{
    margin-top:2px;
    color:#64748b;
    font-size:11px;
    font-weight:800;
    word-break:break-word;
}

.wst-dod-muted,
.wst-dod-empty{
    color:var(--dod-muted);
    font-weight:800;
}

.wst-dod-empty{
    text-align:center;
    padding:22px 12px !important;
}

@media (max-width:900px){
    .wst-dod-filter-main{
        grid-template-columns:1fr;
    }

    .wst-dod-filter-actions{
        justify-content:flex-end;
    }

    .wst-dod-filter-grid{
        grid-template-columns:1fr 1fr;
    }
}

@media (max-width:760px){
    .wst-dod-wrap{
        padding:6px;
    }

    .wst-dod-summary{
        align-items:flex-start;
        flex-direction:column;
    }

    .wst-dod-table{
        min-width:1160px;
    }
}

@media (max-width:480px){
    .wst-dod-filter-actions{
        display:grid;
        grid-template-columns:1fr 1fr;
        width:100%;
    }

    .wst-dod-filter-actions .wst-dod-btn{
        width:100%;
        min-width:0;
    }

    .wst-dod-filter-grid{
        grid-template-columns:1fr;
    }

    .wst-dod-table{
        min-width:1120px;
    }
}
</style>+�L����p��������7�C)
N?�{ALL'); ?>>All DOs</option>
                <option value="MISSING" <?php selected($wst_docp_pricing_filter, 'MISSING'); ?>>Missing price</option>
                <option value="COMPLETE" <?php selected($wst_docp_pricing_filter, 'COMPLETE'); ?>>Price complete</option>
            </select>
        </div>

        <div class="wst-docp-filter-actions">
            <button type="submit" class="wst-docp-btn wst-docp-btn-primary">Apply</button>
            <?php if ($wst_docp_is_admin): ?>
                <button type="button" class="wst-docp-btn wst-docp-btn-secondary" id="wst_docp_open_settings">
                    Freight settings
                </button>
            <?php endif; ?>
        </div>
    </form>

    <?php if ($wst_docp_notice !== ''): ?>
        <div class="wst-docp-alert wst-docp-alert-<?php echo esc_attr($wst_docp_notice_type ?: 'info'); ?>">
            <?php echo esc_html($wst_docp_notice); ?>
        </div>
    <?php endif; ?>

    <?php if ($wst_docp_load_error !== ''): ?>
        <div class="wst-docp-alert wst-docp-alert-error"><?php echo esc_html($wst_docp_load_error); ?></div>
    <?php endif; ?>

    <?php if (!$wst_docp_freight_item): ?>
        <div class="wst-docp-alert wst-docp-alert-warning">
            <?php if ($wst_docp_freight_item_error !== ''): ?>
                <?php echo esc_html('Freight is unavailable: ' . $wst_docp_freight_item_error); ?>
            <?php elseif ($wst_docp_is_admin): ?>
                Configure the dedicated AutoCount freight item before staff add freight charges.
            <?php else: ?>
                Freight is unavailable until an administrator configures the freight item.
            <?php endif; ?>
        </div>
    <?php else: ?>
        <div class="wst-docp-freight-status">
            <span>Freight item</span>
            <strong><?php echo esc_html($wst_docp_freight_item['item_code']); ?></strong>
            <span><?php echo esc_html($wst_docp_freight_item['description']); ?></span>
            <small><?php echo esc_html($wst_docp_freight_item['uom']); ?></small>
            <?php if ($wst_docp_default_freight_rate > 0): ?>
                <span class="wst-docp-freight-default-rate">
                    Default rate RM <?php echo esc_html(number_format_i18n($wst_docp_default_freight_rate, 4)); ?>/KG
                </span>
            <?php endif; ?>
        </div>
    <?php endif; ?>

    <div class="wst-docp-summary-grid">
        <div class="wst-docp-summary-card">
            <span>Daily customers</span>
            <strong><?php echo esc_html(number_format_i18n((int) ($wst_docp_totals['customers'] ?? 0))); ?></strong>
        </div>
        <div class="wst-docp-summary-card">
            <span>Daily Delivery Orders</span>
            <strong><?php echo esc_html(number_format_i18n((int) ($wst_docp_totals['dos'] ?? 0))); ?></strong>
        </div>
        <div class="wst-docp-summary-card wst-docp-summary-card-accent">
            <span>Daily total KG</span>
            <strong><?php echo esc_html(number_format_i18n((float) ($wst_docp_totals['kg'] ?? 0), 2)); ?> KG</strong>
        </div>
        <div class="wst-docp-summary-card">
            <span>Daily missing price lines</span>
            <strong><?php echo esc_html(number_format_i18n((int) ($wst_docp_totals['missing_lines'] ?? 0))); ?></strong>
        </div>
    </div>

    <noscript>
        <div class="wst-docp-alert wst-docp-alert-error">JavaScript is required to save bulk price changes on this page.</div>
    </noscript>

    <?php if ($wst_docp_load_error === '' && empty($wst_docp_groups)): ?>
        <div class="wst-docp-empty">
            <strong>No Delivery Orders found.</strong>
            <span>Try another date, search term, or pricing filter.</span>
        </div>
    <?php endif; ?>

    <?php if (!empty($wst_docp_groups)): ?>
        <form method="post" id="wst-docp-save-form">
            <input type="hidden" name="wst_docp_action" value="save_prices">
            <input type="hidden" name="wst_docp_nonce" value="<?php echo esc_attr($wst_docp_nonce); ?>">
            <input type="hidden" name="wst_docp_date" value="<?php echo esc_attr($wst_docp_selected_date); ?>">
            <input type="hidden" name="wst_docp_q" value="<?php echo esc_attr($wst_docp_search); ?>">
            <input type="hidden" name="wst_docp_pricing" value="<?php echo esc_attr($wst_docp_pricing_filter); ?>">
            <input type="hidden" name="wst_docp_changes_json" id="wst_docp_changes_json" value="[]">
            <button type="submit" id="wst_docp_hidden_submit" class="wst-docp-hidden-submit" tabindex="-1" aria-hidden="true">Submit</button>

            <div class="wst-docp-group-list">
                <?php foreach ($wst_docp_groups as $group_index => $group): ?>
                    <?php
                    $group_do_ids = array_map(function($do) {
                        return (int) ($do['header']['id'] ?? 0);
                    }, $group['dos']);
                    $group_label = trim((string) ($group['debtor_name'] ?? ''));
                    if ($group_label === '') $group_label = trim((string) ($group['debtor_code'] ?? ''));

                    $group_debtor_code = trim((string) ($group['debtor_code'] ?? ''));
                    $group_latest_doc_no = trim((string) ($group['latest_doc_no'] ?? ''));
                    $group_daily_kg = (float) ($group['total_kg'] ?? 0);
                    $group_freight_enabled = $wst_docp_freight_item
                        && $group_debtor_code !== ''
                        && $group_latest_doc_no !== ''
                        && $group_daily_kg > 0;
                    $group_default_freight_rate = $wst_docp_default_freight_rate > 0
                        ? $wst_docp_default_freight_rate
                        : 0.0;
                    $group_default_freight_amount = $group_default_freight_rate > 0
                        ? round($group_daily_kg * $group_default_freight_rate, 2)
                        : 0.0;
                    ?>
                    <section class="wst-docp-customer" data-customer-index="<?php echo esc_attr((string) $group_index); ?>">
                        <header class="wst-docp-customer-head">
                            <div class="wst-docp-customer-identity">
                                <span class="wst-docp-customer-code"><?php echo esc_html($group['debtor_code'] !== '' ? $group['debtor_code'] : 'NO DEBTOR CODE'); ?></span>
                                <h2><?php echo esc_html($group_label !== '' ? $group_label : 'Unknown Customer'); ?></h2>
                            </div>

                            <div class="wst-docp-customer-metrics">
                                <div>
                                    <span>Daily DOs</span>
                                    <strong><?php echo esc_html((string) ($group['daily_do_count'] ?? count($group['dos']))); ?></strong>
                                </div>
                                <div>
                                    <span>Daily KG</span>
                                    <strong><?php echo esc_html(number_format_i18n((float) $group['total_kg'], 2)); ?> KG</strong>
                                </div>
                                <div>
                                    <span>Shown value</span>
                                    <strong class="wst-docp-customer-total" data-customer-index="<?php echo esc_attr((string) $group_index); ?>">RM <?php echo esc_html(number_format_i18n((float) $group['total_amount'], 2)); ?></strong>
                                </div>
                                <div>
                                    <span>Shown missing</span>
                                    <strong class="wst-docp-customer-missing" data-customer-index="<?php echo esc_attr((string) $group_index); ?>"><?php echo esc_html((string) $group['missing_lines']); ?></strong>
                                </div>
                            </div>

                            <button type="button"
                                    class="wst-docp-btn wst-docp-btn-secondary wst-docp-save-selection"
                                    data-do-ids="<?php echo esc_attr(implode(',', $group_do_ids)); ?>">
                                Save customer
                            </button>
                        </header>

                        <div class="wst-docp-freight-panel"
                             data-customer-index="<?php echo esc_attr((string) $group_index); ?>"
                             data-daily-kg="<?php echo esc_attr(number_format($group_daily_kg, 6, '.', '')); ?>">
                            <div class="wst-docp-freight-copy">
                                <strong>Daily freight charge</strong>
                                <span>
                                    Calculate from <?php echo esc_html(number_format_i18n($group_daily_kg, 2)); ?> KG
                                    and add or update the freight item on
                                    <b><?php echo esc_html($group_latest_doc_no !== '' ? $group_latest_doc_no : 'the latest DO'); ?></b>.
                                </span>
                            </div>

                            <label class="wst-docp-freight-field">
                                <span>Rate / KG</span>
                                <div class="wst-docp-money-input">
                                    <span>RM</span>
                                    <input type="number"
                                           class="wst-docp-freight-rate"
                                           inputmode="decimal"
                                           min="0"
                                           max="<?php echo esc_attr((string) WST_DOCP_MAX_FREIGHT_RATE); ?>"
                                           step="0.0001"
                                           value="<?php echo $group_default_freight_rate > 0 ? esc_attr(number_format($group_default_freight_rate, 4, '.', '')) : ''; ?>"
                                           placeholder="0.0000"
                                           data-customer-index="<?php echo esc_attr((string) $group_index); ?>">
                                </div>
                            </label>

                            <label class="wst-docp-freight-field">
                                <span>Final amount</span>
                                <div class="wst-docp-money-input">
                                    <span>RM</span>
                                    <input type="number"
                                           class="wst-docp-freight-amount"
                                           inputmode="decimal"
                                           min="0"
                                           max="<?php echo esc_attr((string) WST_DOCP_MAX_UNIT_PRICE); ?>"
                                           step="0.01"
                                           value="<?php echo $group_default_freight_amount > 0 ? esc_attr(number_format($group_default_freight_amount, 2, '.', '')) : ''; ?>"
                                           placeholder="0.00"
                                           data-customer-index="<?php echo esc_attr((string) $group_index); ?>">
                                </div>
                            </label>

                            <button type="button"
                                    class="wst-docp-btn wst-docp-btn-primary wst-docp-add-freight"
                                    data-customer-index="<?php echo esc_attr((string) $group_index); ?>"
                                    data-debtor-code="<?php echo esc_attr($group_debtor_code); ?>"
                                    data-customer-name="<?php echo esc_attr($group_label); ?>"
                                    data-latest-doc-no="<?php echo esc_attr($group_latest_doc_no); ?>"
                                    data-daily-kg="<?php echo esc_attr(number_format($group_daily_kg, 6, '.', '')); ?>"
                                    <?php disabled(!$group_freight_enabled); ?>>
                                Add / update freight
                            </button>
                        </div>

                        <div class="wst-docp-do-list">
                            <?php foreach ($group['dos'] as $do): ?>
                                <?php
                                $header = $do['header'];
                                $do_id = (int) ($header['id'] ?? 0);
                                $doc_no = wst_docp_header_doc_no($header);
                                $doc_key = absint($header['autocount_doc_key'] ?? 0);
                                $edit_link = add_query_arg(
                                    array(
                                        'docNo' => $doc_no,
                                        'docKey' => $doc_key,
                                        'job_id' => absint($header['source_job_id'] ?? 0),
                                    ),
                                    $wst_docp_edit_url
                                );
                                $view_link = add_query_arg(
                                    array(
                                        'docNo' => $doc_no,
                                        'docKey' => $doc_key,
                                        'job_id' => absint($header['source_job_id'] ?? 0),
                                    ),
                                    $wst_docp_view_url
                                );
                                ?>
                                <details class="wst-docp-do" data-do-id="<?php echo esc_attr((string) $do_id); ?>" data-customer-index="<?php echo esc_attr((string) $group_index); ?>" <?php echo !$do['complete'] ? 'open' : ''; ?>>
                                    <summary class="wst-docp-do-summary">
                                        <div class="wst-docp-do-title">
                                            <strong><?php echo esc_html($doc_no); ?></strong>
                                            <span><?php echo esc_html(number_format_i18n((float) $do['total_kg'], 2)); ?> KG</span>
                                        </div>
                                        <div class="wst-docp-do-summary-right">
                                            <span class="wst-docp-price-badge <?php echo $do['complete'] ? 'is-complete' : 'is-missing'; ?>" data-do-badge="<?php echo esc_attr((string) $do_id); ?>">
                                                <?php echo $do['complete'] ? 'Price complete' : esc_html($do['missing_lines'] . ' missing'); ?>
                                            </span>
                                            <strong class="wst-docp-do-total" data-do-total="<?php echo esc_attr((string) $do_id); ?>">RM <?php echo esc_html(number_format_i18n((float) $do['total_amount'], 2)); ?></strong>
                                        </div>
                                    </summary>

                                    <div class="wst-docp-do-body">
                                        <div class="wst-docp-do-actions">
                                            <div>
                                                <span class="wst-docp-status-text"><?php echo esc_html(str_replace('_', ' ', trim((string) ($header['delivery_status'] ?? '')))); ?></span>
                                            </div>
                                            <div>
                                                <a href="<?php echo esc_url($view_link); ?>" target="_blank" rel="noopener">View</a>
                                                <a href="<?php echo esc_url($edit_link); ?>">Full edit</a>
                                                <button type="button" class="wst-docp-btn-link wst-docp-save-selection" data-do-ids="<?php echo esc_attr((string) $do_id); ?>">Save this DO</button>
                                            </div>
                                        </div>

7�C)q�|�q��������O;R
N?�r<?php
if (!defined('ABSPATH')) exit;

/*
 * WST Excellent Vege — Purchase Invoice single-record view and print page.
 *
 * Canonical data source:
 *   {$wpdb->prefix}ac_pi
 *   {$wpdb->prefix}ac_pi_items
 *
 * Accepted query parameters:
 *   ?pi_id=123
 *   ?docNo=WPPI-2607/001
 *   ?print=1
 */

if (!is_user_logged_in()) {
    echo '<div class="wst-piv-alert wst-piv-alert-error">Please log in to view this Purchase Invoice.</div>';
    return;
}

if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
    echo '<div class="wst-piv-alert wst-piv-alert-error">You do not have permission to view Purchase Invoice records.</div>';
    return;
}

global $wpdb;

if (!$wpdb) {
    echo '<div class="wst-piv-alert wst-piv-alert-error">WordPress database connection is not available.</div>';
    return;
}

if (!function_exists('wst_piv_table_exists')) {
    function wst_piv_table_exists($table_name) {
        global $wpdb;
        return $wpdb && $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table_name)) === $table_name;
    }
}

if (!function_exists('wst_piv_money')) {
    function wst_piv_money($value) {
        return number_format_i18n((float)$value, 2);
    }
}

if (!function_exists('wst_piv_qty')) {
    function wst_piv_qty($value, $decimals = 2) {
        $number = (float)$value;
        if (abs($number - round($number)) < 0.00001) {
            return number_format_i18n($number, 0);
        }
        return number_format_i18n($number, $decimals);
    }
}

if (!function_exists('wst_piv_status_label')) {
    function wst_piv_status_label($status) {
        $status = strtoupper(trim((string)$status));

        $labels = array(
            'PENDING' => 'Pending AutoCount',
            'PROCESSING' => 'Processing AutoCount',
            'SUCCESS' => 'Created',
            'SYNCED' => 'Created',
            'FAILED' => 'AutoCount Failed',
            'FAILED_FINAL' => 'AutoCount Failed',
            'CANCELLED' => 'Cancelled',
        );

        return $labels[$status] ?? ($status !== '' ? ucwords(strtolower(str_replace('_', ' ', $status))) : '-');
    }
}

if (!function_exists('wst_piv_status_class')) {
    function wst_piv_status_class($status) {
        $status = strtoupper(trim((string)$status));
        if (in_array($status, array('SUCCESS', 'SYNCED'), true)) return 'wst-piv-badge-good';
        if (in_array($status, array('FAILED', 'FAILED_FINAL', 'CANCELLED'), true)) return 'wst-piv-badge-danger';
        if (in_array($status, array('PENDING', 'PROCESSING'), true)) return 'wst-piv-badge-warn';
        return 'wst-piv-badge-info';
    }
}

$table_pi = $wpdb->prefix . 'ac_pi';
$table_items = $wpdb->prefix . 'ac_pi_items';

if (!wst_piv_table_exists($table_pi) || !wst_piv_table_exists($table_items)) {
    echo '<div class="wst-piv-alert wst-piv-alert-error">Purchase Invoice storage tables are missing.</div>';
    return;
}

$pi_id = isset($_GET['pi_id']) ? absint($_GET['pi_id']) : 0;
$doc_no = isset($_GET['docNo'])
    ? strtoupper(sanitize_text_field(wp_unslash($_GET['docNo'])))
    : '';

if ($pi_id > 0) {
    $header = $wpdb->get_row(
        $wpdb->prepare("SELECT * FROM `{$table_pi}` WHERE id = %d AND deleted_at IS NULL LIMIT 1", $pi_id),
        ARRAY_A
    );
} elseif ($doc_no !== '') {
    $header = $wpdb->get_row(
        $wpdb->prepare("SELECT * FROM `{$table_pi}` WHERE local_doc_no = %s AND deleted_at IS NULL LIMIT 1", $doc_no),
        ARRAY_A
    );
} else {
    $header = null;
}

if (!is_array($header)) {
    echo '<div class="wst-piv-alert wst-piv-alert-error">Purchase Invoice record was not found.</div>';
    return;
}

$pi_id = (int)$header['id'];
$items = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT *
         FROM `{$table_items}`
         WHERE pi_id = %d
         ORDER BY line_no ASC, id ASC",
        $pi_id
    ),
    ARRAY_A
);

if (!is_array($items)) {
    $items = array();
}

$created_by_name = '-';
if (!empty($header['created_by'])) {
    $created_user = get_userdata((int)$header['created_by']);
    if ($created_user) {
        $created_by_name = $created_user->display_name ?: $created_user->user_login;
    }
}

$updated_by_name = '-';
if (!empty($header['updated_by'])) {
    $updated_user = get_userdata((int)$header['updated_by']);
    if ($updated_user) {
        $updated_by_name = $updated_user->display_name ?: $updated_user->user_login;
    }
}

$list_url = home_url('/purchase-invoice-records/');
$print_mode = !empty($_GET['print']);
$currency = trim((string)$header['currency_code']) ?: 'MYR';
$status = strtoupper(trim((string)$header['sync_status']));

$calculated_subtotal = 0.0;
$calculated_tax = 0.0;
$calculated_total = 0.0;
$total_baskets = 0.0;
$total_cartons = 0.0;
$total_weight = 0.0;

foreach ($items as $item) {
    $calculated_subtotal += (float)$item['sub_total'];
    $calculated_tax += (float)$item['tax_amount'];
    $calculated_total += (float)$item['total_amount'];
    $total_baskets += (float)$item['basket_qty'];
    $total_cartons += (float)$item['carton_qty'];
    $total_weight += (float)$item['total_weight_kg'];
}

$display_subtotal = (float)$header['sub_total'];
$display_tax = (float)$header['tax_amount'];
$display_total = (float)$header['total_amount'];

if ($display_subtotal == 0.0 && $calculated_subtotal != 0.0) $display_subtotal = $calculated_subtotal;
if ($display_tax == 0.0 && $calculated_tax != 0.0) $display_tax = $calculated_tax;
if ($display_total == 0.0 && $calculated_total != 0.0) $display_total = $calculated_total;
?>

<div class="wst-piv-wrap <?php echo $print_mode ? 'wst-piv-print-mode' : ''; ?>">
    <div class="wst-piv-toolbar no-print">
        <a href="<?php echo esc_url($list_url); ?>" class="wst-piv-btn wst-piv-btn-light">← Back to PI Records</a>
        <button type="button" class="wst-piv-btn wst-piv-btn-primary" onclick="window.print()">Print</button>
    </div>

    <article class="wst-piv-document">
        <header class="wst-piv-header">
            <div>
                <div class="wst-piv-kicker">Purchase Invoice</div>
                <h1><?php echo esc_html($header['local_doc_no']); ?></h1>
            </div>
            <div class="wst-piv-header-status">
                <span class="wst-piv-badge <?php echo esc_attr(wst_piv_status_class($status)); ?>">
                    <?php echo esc_html(wst_piv_status_label($status)); ?>
                </span>
            </div>
        </header>

        <section class="wst-piv-grid wst-piv-grid-main">
            <div class="wst-piv-card">
                <h2>Creditor</h2>
                <div class="wst-piv-creditor-name"><?php echo esc_html($header['creditor_name'] ?: $header['creditor_code']); ?></div>
                <div class="wst-piv-muted"><?php echo esc_html($header['creditor_code']); ?></div>
                <?php if (!empty($header['supplier_phone'])): ?>
                    <div class="wst-piv-muted"><?php echo esc_html($header['supplier_phone']); ?></div>
                <?php endif; ?>
            </div>

            <div class="wst-piv-card">
                <dl class="wst-piv-meta">
                    <div>
                        <dt>Document Date</dt>
                        <dd><?php echo esc_html(mysql2date('d/m/Y', $header['doc_date'])); ?></dd>
                    </div>
                    <div>
                        <dt>Supplier Invoice No.</dt>
                        <dd><?php echo esc_html($header['supplier_invoice_no'] ?: '-'); ?></dd>
                    </div>
                    <div>
                        <dt>AutoCount PI No.</dt>
                        <dd><?php echo esc_html($header['autocount_doc_no'] ?: '-'); ?></dd>
                    </div>
                    <div>
                        <dt>Location</dt>
                        <dd><?php echo esc_html($header['location'] ?: '-'); ?></dd>
                    </div>
                    <div>
                        <dt>Display Term</dt>
                        <dd><?php echo esc_html($header['display_term'] ?: '-'); ?></dd>
                    </div>
                    <div>
                        <dt>Purchase Agent</dt>
                        <dd><?php echo esc_html($header['purchase_agent'] ?: '-'); ?></dd>
                    </div>
                </dl>
            </div>
        </section>

        <?php if (!empty($header['last_sync_error'])): ?>
            <section class="wst-piv-sync-error no-print">
                <strong>AutoCount error:</strong>
                <?php echo esc_html($header['last_sync_error']); ?>
                <?php if (!empty($header['last_sync_error_code'])): ?>
                    <span>(<?php echo esc_html($header['last_sync_error_code']); ?>)</span>
                <?php endif; ?>
            </section>
        <?php endif; ?>

        <section class="wst-piv-lines-section">
            <div class="wst-piv-table-scroll">
                <table class="wst-piv-table">
                    <thead>
                        <tr>
                            <th class="wst-piv-center">#</th>
                            <th>Item</th>
                            <th>Type</th>
                            <th class="wst-piv-number">Qty</th>
                            <th class="wst-piv-number">KG / Unit</th>
                            <th class="wst-piv-number">Total KG</th>
                            <th class="wst-piv-number">Unit Price</th>
                            <th class="wst-piv-number">Subtotal</th>
                            <th class="wst-piv-number">Tax</th>
                            <th class="wst-piv-number">Total</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php if (empty($items)): ?>
                            <tr>
                                <td colspan="10" class="wst-piv-empty">No line items were found.</td>
                            </tr>
                        <?php else: ?>
                            <?php foreach ($items as $index => $item): ?>
                                <?php
                                $pack_type = strtoupper(trim((string)$item['pack_type']));
                                $pack_qty = $pack_type === 'BASKET'
                                    ? $item['basket_qty']
                                    : ($pack_type === 'CARTON' ? $item['carton_qty'] : $item['unit_qty']);
                                ?>
                                <tr>
                                    <td data-label="#" class="wst-piv-center"><?php echo esc_html($index + 1); ?></td>
                                    <td data-label="Item">
                                        <strong><?php echo esc_html($item['description'] ?: $item['item_code']); ?></strong>
                                        <small><?php echo esc_html($item['item_code']); ?></small>
                                        <?php if (!empty($item['uom'])): ?>
                                            <small>UOM: <?php echo esc_html($item['uom']); ?></small>
                                        <?php endif; ?>
                                    </td>
                                    <td data-label="Type">
                                        <?php echo esc_html($pack_type ?: '-'); ?>
                                    </td>
                                    <td data-label="Qty" class="wst-piv-number">
                                        <?php echo esc_html(wst_piv_qty($pack_qty)); ?>
                                    </td>
                                    <td data-label="KG / Unit" class="wst-piv-number">
                                        <?php echo esc_html(wst_piv_qty($item['weight_kg'], 2)); ?>
                                    </td>
                                    <td data-label="Total KG" class="wst-piv-number">
                                        <?php echo esc_html(wst_piv_qty($item['total_weight_kg'], 2)); ?>
                                    </td>
                                    <td data-label="Unit Price" class="wst-piv-number">
                                        <?php echo esc_html(wst_piv_money($item['unit_price'])); ?>
                                    </td>
                                    <td data-label="Subtotal" class="wst-piv-number">
                                        <?php echo esc_html(wst_piv_money($item['sub_total'])); ?>
                                    </td>
                                    <td data-label="Tax" class="wst-piv-number">
                                        <?php echo esc_html(wst_piv_money($item['tax_amount'])); ?>
                                    </td>
                                    <td data-label="Total" class="wst-piv-number">
                                        <?php echo esc_html(wst_piv_money($item['total_amount'])); ?>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                        <?php endif; ?>
                    </tbody>
                </table>
            </div>
        </section>

        <section class="wst-piv-bottom">
            <div class="wst-piv-notes">
                <?php if (!empty($header['remark'])): ?>
                    <div>
                        <h3>Remark</h3>
                        <p><?php echo nl2br(esc_html($header['remark'])); ?></p>
                    </div>
                <?php endif; ?>

                <?php if (!empty($header['internal_note']) && current_user_can('manage_options')): ?>
                    <div class="no-print">
                        <h3>Internal Note</h3>
                        <p><?php echo nl2br(esc_html($header['internal_note'])); ?></p>
                    </div>
                <?php endif; ?>

                <dl class="wst-piv-audit no-print">
                    <div>
                        <dt>Created By</dt>
                        <dd><?php echo esc_html($created_by_name); ?></dd>
                    </div>
                    <div>
                        <dt>Created At</dt>
                        <dd><?php echo esc_html(mysql2date('d/m/Y H:i:s', $header['created_at'])); ?></dd>
                    </div>
                    <div>
                        <dt>Updated By</dt>
                        <dd><?php echo esc_html($updated_by_name); ?></dd>
                    </div>
                    <div>
                        <dt>Last Updated</dt>
                        <dd><?php echo esc_html(mysql2date('d/m/Y H:i:s', $header['updated_at'])); ?></dd>
                    </div>
                    <div>
                        <dt>Job ID</dt>
                        <dd><?php echo esc_html($header['source_job_id'] ?: '-'); ?></dd>
                    </div>
                    <div>
                        <dt>AutoCount Doc Key</dt>
                        <dd><?php echo esc_html($header['autocount_doc_key'] ?: '-'); ?></dd>
                    </div>
                </dl>
            </div>

            <div class="wst-piv-summary">
                <div><span>Total Basket</span><strong><?php echo esc_html(wst_piv_qty($total_baskets)); ?></strong></div>
                <div><span>Total Carton</span><strong><?php echo esc_html(wst_piv_qty($total_cartons)); ?></strong></div>
                <div><span>Total Weight</span><strong><?php echo esc_html(wst_piv_qty($total_weight, 2)); ?> KG</strong></div>
                <div><span>Subtotal</span><strong><?php echo esc_html($currency . ' ' . wst_piv_money($display_subtotal)); ?></strong></div>
                <div><span>Tax</span><strong><?php echo esc_html($currency . ' ' . wst_piv_money($display_tax)); ?></strong></div>
                <div class="wst-piv-grand-total"><span>Total</span><strong><?php echo esc_html($currency . ' ' . wst_piv_money($display_total)); ?></strong></div>
            </div>
        </section>
    </article>
</div>

<?php if ($O;R.=<�r��������O;R
N"�����print_mode): ?>
<script>
window.addEventListener('load', function () {
    window.setTimeout(function () {
        window.print();
    }, 250);
});
</script>
<?php endif; ?>

<style>
.wst-piv-wrap {
    --piv-green: #166534;
    --piv-green-dark: #14532d;
    --piv-green-soft: #f0fdf4;
    --piv-border: #dbe4ee;
    --piv-text: #0f172a;
    --piv-muted: #64748b;
    color: var(--piv-text);
    font-family: "Segoe UI", Roboto, system-ui, sans-serif;
}
.wst-piv-toolbar {
    display: flex;
    justify-content: space-between;
    gap: .75rem;
    margin-bottom: .9rem;
}
.wst-piv-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 2.7rem;
    padding: .62rem .9rem;
    border: 1px solid transparent;
    border-radius: .58rem;
    font: inherit;
    font-weight: 800;
    text-decoration: none !important;
    cursor: pointer;
}
.wst-piv-btn-primary {
    border-color: var(--piv-green);
    background: var(--piv-green);
    color: #fff !important;
}
.wst-piv-btn-primary:hover {
    background: var(--piv-green-dark);
}
.wst-piv-btn-light {
    border-color: #cbd5e1;
    background: #fff;
    color: #334155 !important;
}
.wst-piv-document {
    overflow: hidden;
    border: 1px solid var(--piv-border);
    border-radius: .9rem;
    background: #fff;
    box-shadow: 0 .75rem 1.8rem rgba(15, 23, 42, .07);
}
.wst-piv-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    padding: 1.2rem 1.3rem;
    border-bottom: 1px solid var(--piv-border);
    background: #fbfefc;
}
.wst-piv-kicker {
    margin-bottom: .18rem;
    color: var(--piv-green);
    font-size: .78rem;
    font-weight: 900;
    letter-spacing: .08em;
    text-transform: uppercase;
}
.wst-piv-header h1 {
    margin: 0;
    font-size: 1.65rem;
}
.wst-piv-badge {
    display: inline-flex;
    padding: .4rem .65rem;
    border-radius: 999px;
    font-size: .78rem;
    font-weight: 900;
}
.wst-piv-badge-good {
    border: 1px solid #86efac;
    background: #dcfce7;
    color: #166534;
}
.wst-piv-badge-warn {
    border: 1px solid #fcd34d;
    background: #fef3c7;
    color: #92400e;
}
.wst-piv-badge-danger {
    border: 1px solid #fca5a5;
    background: #fee2e2;
    color: #991b1b;
}
.wst-piv-badge-info {
    border: 1px solid #bae6fd;
    background: #e0f2fe;
    color: #075985;
}
.wst-piv-grid {
    display: grid;
    gap: .9rem;
}
.wst-piv-grid-main {
    grid-template-columns: minmax(0, 1fr) minmax(20rem, 1.25fr);
    padding: 1rem 1.2rem;
}
.wst-piv-card {
    padding: 1rem;
    border: 1px solid var(--piv-border);
    border-radius: .75rem;
    background: #fff;
}
.wst-piv-card h2 {
    margin: 0 0 .55rem;
    color: #475569;
    font-size: .8rem;
    font-weight: 900;
    letter-spacing: .06em;
    text-transform: uppercase;
}
.wst-piv-creditor-name {
    font-size: 1.1rem;
    font-weight: 900;
}
.wst-piv-muted {
    margin-top: .18rem;
    color: var(--piv-muted);
}
.wst-piv-meta,
.wst-piv-audit {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .7rem 1rem;
    margin: 0;
}
.wst-piv-meta div,
.wst-piv-audit div {
    min-width: 0;
}
.wst-piv-meta dt,
.wst-piv-audit dt {
    margin-bottom: .15rem;
    color: var(--piv-muted);
    font-size: .72rem;
    font-weight: 900;
    text-transform: uppercase;
}
.wst-piv-meta dd,
.wst-piv-audit dd {
    margin: 0;
    overflow-wrap: anywhere;
    font-weight: 750;
}
.wst-piv-sync-error {
    margin: 0 1.2rem 1rem;
    padding: .75rem .85rem;
    border: 1px solid #fca5a5;
    border-radius: .65rem;
    background: #fff1f2;
    color: #991b1b;
}
.wst-piv-lines-section {
    padding: 0 1.2rem 1rem;
}
.wst-piv-table-scroll {
    overflow-x: auto;
    border: 1px solid var(--piv-border);
    border-radius: .7rem;
}
.wst-piv-table {
    width: 100%;
    min-width: 70rem;
    border-collapse: collapse;
}
.wst-piv-table th,
.wst-piv-table td {
    padding: .7rem .72rem;
    border-bottom: 1px solid #edf2f7;
    vertical-align: middle;
    text-align: left;
}
.wst-piv-table th {
    background: #f8fafc;
    color: #334155;
    font-size: .76rem;
    font-weight: 900;
    white-space: nowrap;
}
.wst-piv-table small {
    display: block;
    margin-top: .15rem;
    color: var(--piv-muted);
}
.wst-piv-number {
    text-align: right !important;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.wst-piv-center {
    text-align: center !important;
}
.wst-piv-empty {
    padding: 1.2rem !important;
    color: var(--piv-muted);
    text-align: center !important;
}
.wst-piv-bottom {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(18rem, .8fr);
    gap: 1rem;
    padding: 0 1.2rem 1.2rem;
}
.wst-piv-notes h3 {
    margin: 0 0 .35rem;
    font-size: .82rem;
}
.wst-piv-notes p {
    margin: 0 0 1rem;
    color: #334155;
}
.wst-piv-audit {
    margin-top: 1rem;
    padding: .9rem;
    border: 1px solid var(--piv-border);
    border-radius: .7rem;
    background: #f8fafc;
}
.wst-piv-summary {
    align-self: start;
    overflow: hidden;
    border: 1px solid var(--piv-border);
    border-radius: .7rem;
}
.wst-piv-summary > div {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    padding: .67rem .8rem;
    border-bottom: 1px solid #edf2f7;
}
.wst-piv-summary > div:last-child {
    border-bottom: 0;
}
.wst-piv-summary span {
    color: #475569;
    font-weight: 750;
}
.wst-piv-summary strong {
    font-variant-numeric: tabular-nums;
    text-align: right;
}
.wst-piv-grand-total {
    background: var(--piv-green-soft);
    color: var(--piv-green-dark);
    font-size: 1.05rem;
}
.wst-piv-alert {
    padding: 1rem;
    border-radius: .75rem;
}
.wst-piv-alert-error {
    border: 1px solid #fecaca;
    background: #fff1f2;
    color: #991b1b;
}
@media (max-width: 800px) {
    .wst-piv-grid-main,
    .wst-piv-bottom {
        grid-template-columns: 1fr;
    }
    .wst-piv-meta,
    .wst-piv-audit {
        grid-template-columns: 1fr;
    }
}
@media (max-width: 640px) {
    .wst-piv-header {
        flex-direction: column;
    }
    .wst-piv-toolbar .wst-piv-btn {
        flex: 1;
    }
    .wst-piv-table-scroll {
        overflow: visible;
        border: 0;
    }
    .wst-piv-table,
    .wst-piv-table tbody,
    .wst-piv-table tr,
    .wst-piv-table td {
        display: block;
        width: 100%;
        min-width: 0;
    }
    .wst-piv-table thead {
        display: none;
    }
    .wst-piv-table tr {
        margin-bottom: .8rem;
        padding: .35rem .75rem;
        border: 1px solid var(--piv-border);
        border-radius: .7rem;
    }
    .wst-piv-table td {
        display: grid;
        grid-template-columns: 7.5rem minmax(0, 1fr);
        gap: .6rem;
        padding: .52rem 0;
        border-bottom: 1px solid #edf2f7;
        text-align: left !important;
    }
    .wst-piv-table td::before {
        content: attr(data-label);
        color: var(--piv-muted);
        font-size: .72rem;
        font-weight: 900;
        text-transform: uppercase;
    }
    .wst-piv-table td:last-child {
        border-bottom: 0;
    }
}
@media print {
    @page {
        size: A5 landscape;
        margin: 7mm;
    }
    body {
        background: #fff !important;
    }
    .no-print,
    header,
    footer,
    nav,
    .site-header,
    .site-footer {
        display: none !important;
    }
    .wst-piv-wrap,
    .wst-piv-document {
        width: 100% !important;
        margin: 0 !important;
        border: 0 !important;
        border-radius: 0 !important;
        box-shadow: none !important;
    }
    .wst-piv-header {
        padding: 0 0 4mm;
    }
    .wst-piv-grid-main,
    .wst-piv-lines-section,
    .wst-piv-bottom {
        padding-left: 0;
        padding-right: 0;
    }
    .wst-piv-grid-main {
        grid-template-columns: 1fr 1.25fr;
        padding-top: 3mm;
        padding-bottom: 3mm;
    }
    .wst-piv-card {
        padding: 3mm;
    }
    .wst-piv-table {
        min-width: 0;
        font-size: 8.2pt;
    }
    .wst-piv-table th,
    .wst-piv-table td {
        padding: 1.6mm 1.8mm;
    }
    .wst-piv-bottom {
        grid-template-columns: 1.5fr .8fr;
        gap: 4mm;
        padding-bottom: 0;
    }
    .wst-piv-summary > div {
        padding: 1.7mm 2mm;
    }
    .wst-piv-badge {
        border: 1px solid #777 !important;
        background: #fff !important;
        color: #111 !important;
    }
    .wst-piv-table tr,
    .wst-piv-card,
    .wst-piv-summary {
        break-inside: avoid;
    }
}
</style>O;RKMs��������<b�
N?�t var(--acd-muted);
    margin-bottom: 0.35rem;
}
.acd-resp-label-note {
    color: #64748b;
    font-size: 0.78rem;
    font-weight: 700;
}
.acd-resp-input {
    width: 100%;
    min-height: 3rem;
    padding: 0.72rem 0.85rem;
    border: 1px solid var(--acd-border-strong);
    border-radius: 0.65rem;
    background: #fff;
    font-size: 1rem;
    transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.acd-resp-file-input {
    width: 100%;
    min-height: 3rem;
    padding: 0.66rem 0.75rem;
    border: 1px dashed var(--acd-border-strong);
    border-radius: 0.65rem;
    background: #fff;
    font-size: 0.95rem;
}
.acd-resp-file-input:focus {
    outline: none;
    border-color: var(--acd-green);
    box-shadow: 0 0 0 0.2rem rgba(22, 101, 52, 0.10);
}
.acd-resp-input:focus {
    outline: none;
    border-color: var(--acd-green);
    box-shadow: 0 0 0 0.2rem rgba(22, 101, 52, 0.10);
}

/* Make date input fully clickable — expand the native calendar picker to full width */
#acd-resp-root input[type="date"].acd-resp-input,
#acd-resp-root input[type="date"] {
    position: relative;
    cursor: pointer;
}
#acd-resp-root input[type="date"].acd-resp-input::-webkit-calendar-picker-indicator,
#acd-resp-root input[type="date"]::-webkit-calendar-picker-indicator {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}
/* Firefox fallback */
#acd-resp-root input[type="date"].acd-resp-input::-moz-calendar-picker-indicator,
#acd-resp-root input[type="date"]::-moz-calendar-picker-indicator {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

.acd-resp-search-wrap {
    position: relative;
}
.acd-resp-search-wrap .acd-resp-input {
    padding-right: 3.1rem;
    cursor: pointer;
}
.acd-resp-field-clear {
    position: absolute;
    top: 50%;
    right: 0.5rem;
    transform: translateY(-50%);
    width: 2.15rem;
    height: 2.15rem;
    border: 1px solid var(--acd-border);
    background: #fff;
    color: #64748b;
    border-radius: 0.5rem;
    display: none;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    cursor: pointer;
    transition: all 0.18s ease;
}
.acd-resp-field-clear.show {
    display: inline-flex;
}
.acd-resp-field-clear:hover {
    background: var(--acd-green-soft);
    border-color: #bbf7d0;
    color: var(--acd-green);
}

/* Type Toggle Buttons */
.acd-resp-type-toggle {
    display: flex;
    gap: 0.55rem;
}
.acd-resp-type-btn {
    flex: 1;
    min-height: 3rem;
    padding: 0.7rem 0.8rem;
    border: 1px solid var(--acd-border-strong);
    background: #f8fafc;
    color: #334155;
    border-radius: 0.65rem;
    font-weight: 700;
    font-size: 1rem;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}
.acd-resp-type-btn:hover {
    background: #ecfdf3;
    border-color: #86efac;
    color: var(--acd-green);
}
.acd-resp-type-btn.active {
    background: var(--acd-green-light);
    border-color: #16a34a;
    color: var(--acd-green);
    box-shadow: 0 0 0 1px rgba(22, 101, 52, 0.05) inset;
}
.acd-resp-row-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.8rem;
    margin-bottom: 0.5rem;
}
@media (max-width: 480px) {
    .acd-resp-row-2 {
        grid-template-columns: 1fr;
        gap: 0;
    }
}
.acd-resp-preview {
    background: var(--acd-green-soft);
    border: 1px solid #bbf7d0;
    border-radius: 0.65rem;
    padding: 0.7rem 0.8rem;
    margin: 0.6rem 0;
    font-size: 0.95rem;
}

/* ========== PRIMARY BUTTONS - STRONG OVERRIDES ========== */
#acd-resp-root .acd-resp-btn-primary,
#acd-resp-root button.acd-resp-btn-primary {
    width: 100%;
    min-height: 3.05rem;
    padding: 0.78rem 1rem;
    border: 1px solid var(--acd-green);
    border-radius: 0.7rem;
    background: var(--acd-green);
    color: #ffffff;
    font-weight: 800;
    font-size: 1rem;
    line-height: 1.2;
    font-family: inherit;
    text-align: center;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
    appearance: none;
    -webkit-appearance: none;
    box-shadow: none;
}

#acd-resp-root .acd-resp-btn-primary:hover,
#acd-resp-root button.acd-resp-btn-primary:hover {
    background: var(--acd-green-dark);
    border-color: var(--acd-green-dark);
    color: #ffffff;
    box-shadow: 0 4px 12px rgba(22, 101, 52, 0.14);
}

#acd-resp-root .acd-resp-btn-primary:focus,
#acd-resp-root button.acd-resp-btn-primary:focus {
    outline: none;
    box-shadow: 0 0 0 0.2rem rgba(22, 101, 52, 0.12);
}

#acd-resp-root .acd-resp-btn-primary:disabled,
#acd-resp-root button.acd-resp-btn-primary:disabled {
    background: #94a3b8;
    border-color: #94a3b8;
    color: #ffffff;
    cursor: not-allowed;
    opacity: 1;
    box-shadow: none;
}

/* Save button inside items card */
#acd-resp-root .acd-resp-save-btn {
    width: 100%;
}

/* Item details table */
.acd-resp-lines-header {
    display: grid;
    grid-template-columns: minmax(12rem, 2fr) 1fr 0.8fr 0.8fr 0.55fr 0.55fr 0.75fr 0.85fr 0.9fr 3rem;
    gap: 0.45rem;
    align-items: center;
    text-align: center;
    background: #f1f5f9;
    border: 1px solid var(--acd-border);
    border-radius: 0.65rem 0.65rem 0 0;
    padding: 0.72rem 0.8rem;
    font-size: 0.85rem;
    font-weight: 800;
    margin-bottom: 0.25rem;
}
.acd-resp-lines-header span:first-child {
    text-align: left;
}

.acd-resp-line {
    display: grid;
    grid-template-columns: minmax(12rem, 2fr) 1fr 0.8fr 0.8fr 0.55fr 0.55fr 0.75fr 0.85fr 0.9fr 3rem;
    gap: 0.45rem;
    align-items: center;
    text-align: center;
    padding: 0.7rem 0.8rem;
    border-right: 1px solid #eef2f6;
    border-left: 1px solid #eef2f6;
    border-bottom: 1px solid #eef2f6;
    font-size: 0.95rem;
}
.acd-resp-line > div:first-child {
    text-align: left;
}

.acd-resp-price-input {
    width: 100%;
    min-height: 2.35rem;
    padding: 0.45rem 0.55rem;
    border: 1px solid var(--acd-border-strong);
    border-radius: 0.55rem;
    background: #fff;
    color: var(--acd-text);
    font: inherit;
    font-weight: 700;
    text-align: center;
}
.acd-resp-price-input:focus {
    outline: none;
    border-color: var(--acd-green);
    box-shadow: 0 0 0 0.18rem rgba(22, 101, 52, 0.10);
}
.acd-resp-money-cell,
.acd-resp-number-cell {
    font-variant-numeric: tabular-nums;
}
.acd-resp-money-cell,
.acd-resp-price-cell,
.acd-resp-number-cell {
    text-align: center;
}
.acd-resp-type-pill {
    display: inline-flex;
    padding: 0.3rem 0.7rem;
    border-radius: 999px;
    background: var(--acd-green-soft);
    border: 1px solid #bbf7d0;
    color: var(--acd-green);
    font-size: 0.8rem;
    font-weight: 800;
}

/* ========== DESKTOP DELETE BUTTON STYLES ========== */
#acd-resp-root .acd-resp-delete-btn,
#acd-resp-root button.acd-resp-delete-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.4rem;
    min-width: 2.4rem;
    min-height: 2.35rem;
    padding: 0.45rem;
    border: 1px solid #fecaca;
    background: #fff5f5;
    color: #dc2626;
    border-radius: 0.65rem;
    font-size: 1rem;
    font-weight: 700;
    line-height: 1.2;
    font-family: inherit;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
    appearance: none;
    -webkit-appearance: none;
    box-shadow: none;
}

#acd-resp-root .acd-resp-delete-btn:hover,
#acd-resp-root button.acd-resp-delete-btn:hover {
    background: #fee2e2;
    border-color: #fca5a5;
    color: #b91c1c;
}

#acd-resp-root .acd-resp-delete-btn:focus,
#acd-resp-root button.acd-resp-delete-btn:focus {
    outline: none;
    box-shadow: 0 0 0 0.2rem rgba(220, 38, 38, 0.12);
}

.acd-resp-lines-container {
    max-height: min(32rem, 64vh);
    overflow: auto;
    padding: 0.15rem;
}
.acd-resp-lines-header,
.acd-resp-line {
    min-width: 88rem;
}
.acd-resp-empty {
    padding: 1.2rem;
    text-align: center;
    color: var(--acd-muted);
    font-style: italic;
}
.acd-resp-status {
    margin-top: 0.8rem;
    font-size: 0.9rem;
    text-align: center;
}

/* ========== SUCCESS ACTIONS PANEL ========== */
#acd-resp-root .acd-resp-success-actions {
    margin-top: 0.75rem;
    padding: 0.85rem;
    border: 1px solid #bbf7d0;
    background: var(--acd-green-soft);
    border-radius: 0.75rem;
}

#acd-resp-root .acd-resp-success-text {
    font-size: 0.9rem;
    font-weight: 700;
    color: var(--acd-green-dark);
    margin-bottom: 0.55rem;
}

#acd-resp-root .acd-resp-success-btns {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.5rem;
}

@media (min-width: 768px) {
    #acd-resp-root .acd-resp-success-btns {
        grid-template-columns: repeat(2, 1fr);
    }
}

#acd-resp-root .acd-resp-action-btn,
#acd-resp-root a.acd-resp-action-btn,
#acd-resp-root button.acd-resp-action-btn {
    min-height: 2.8rem;
    padding: 0.7rem 0.8rem;
    border-radius: 0.65rem;
    font-size: 0.92rem;
    font-weight: 800;
    line-height: 1.2;
    font-family: inherit;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    appearance: none;
    -webkit-appearance: none;
    box-shadow: none;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}

#acd-resp-root .acd-resp-action-green {
    background: var(--acd-green);
    border: 1px solid var(--acd-green);
    color: #ffffff;
}

#acd-resp-root .acd-resp-action-green:hover {
    background: var(--acd-green-dark);
    border-color: var(--acd-green-dark);
    color: #ffffff;
}

#acd-resp-root .acd-resp-action-soft {
    background: #ffffff;
    border: 1px solid #86efac;
    color: var(--acd-green);
}

#acd-resp-root .acd-resp-action-soft:hover {
    background: #dcfce7;
    border-color: #22c55e;
    color: var(--acd-green-dark);
}

#acd-resp-root .acd-resp-action-danger {
    background: #fff5f5;
    border: 1px solid #fecaca;
    color: var(--acd-danger);
}

#acd-resp-root .acd-resp-action-danger:hover {
    background: #fee2e2;
    border-color: #fca5a5;
    color: #b91c1c;
}


/* Picker Modal - Base styles (centered) */
.acd-resp-picker-modal {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 0.75rem;
}
.acd-resp-picker-modal.active {
    display: flex;
}
.acd-resp-picker-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
}
.acd-resp-picker-sheet {
    position: relative;
    width: 100%;
    max-width: 42rem;
    background: #fff;
    border-radius: 0.9rem;
    box-shadow: 0 1.4rem 2.4rem rgba(0, 0, 0, 0.18);
    overflow: hidden;
}
.acd-resp-picker-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.85rem 0.95rem;
    border-bottom: 1px solid var(--acd-border);
}
.acd-resp-picker-title {
    font-size: 1.05rem;
    font-weight: 800;
}
/* Picker close button - fixed alignment */
.acd-resp-picker-close {
    flex: 0 0 auto;
    width: 2.35rem;
    height: 2.35rem;
    padding: 0;
    border: 1px solid var(--acd-border-strong);
    background: #fff;
    color: var(--acd-text);
    border-radius: 0.55rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    font-size: 1.35rem;
    font-weight: 500;
    font-family: Arial, sans-serif;
    cursor: pointer;
    transition: all 0.18s ease;
    appearance: none;
    -webkit-appearance: none;
}
.acd-resp-picker-close span {
    display: block;
    line-height: 1;
    transform: translateY(-1px);
}
.acd-resp-picker-close:hover {
    background: var(--acd-green-soft);
    border-color: #bbf7d0;
    color: var(--acd-green);
}
.acd-resp-picker-body {
    padding: 0.85rem 0.95rem 0.95rem;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.acd-resp-picker-results {
    max-height: min(24rem, calc(86vh - 9rem));
    overflow-y: auto;
}
/* Override modal text colours to ensure dark text on white background */
.acd-resp-picker-title,
.acd-resp-picker-search,
.acd-resp-picker-results,
.acd-resp-picker-item,
.acd-resp-picker-item-main {
    color: var(--acd-text);
}
.acd-resp-picker-note,
.acd-resp-picker-item-sub {
    color: var(--acd-muted);
}
.acd-resp-picker-item {
    color: var(--acd-text);
}
.acd-resp-picker-item {
    display: block;
    width: 100%;
    text-align: left;
    min-height: 3rem;
    padding: 0.78rem 0.85rem;
    border: 1px solid var(--acd-border);
    border-radius: 0.65rem;
    background: #fff;
    margin-bottom: 0.5rem;
    cursor: pointer;
    transition: all 0.18s ease;
}
.acd-resp-picker-item:hover {
    background: var(--acd-green-soft);
    border-color: #bbf7d0;
}
.acd-resp-picker-item-main {
    font-weight: 800;
}
.acd-resp-picker-item-sub {
    font-size: 0.8rem;
    color: var(--acd-muted);
}

/* Force picker modal to stay centered on desktop, tablet, and mobile (overrides previous bottom-sheet behavior) */
#acd-resp-root .acd-resp-picker-modal {
    align-items: center !important;
    justify-content: center !important;
    padding: 0.75rem !important;
}

#acd-resp-root .acd-resp-picker-sheet {
    width: 100% !important;
    max-width: min(42rem, calc(100vw - 2rem)) !important;
    border-radius: 0.9rem !important;
    max-height: 86vh !important;
    overflow: hidden !important;
}
</style>

<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>

<script>
(function(){
    // --------------------------------------------------------------
    // TAB SWITCHING
    // --------------------------------------------------------------
    const tabs = document.querySelectorAll('#acd-resp-root .acd-resp-tab-btn');
    const panes = {
        delivery: document.getElementById('acd-resp-delivery-tab'),
        goods: document.getElementById('acd-resp-grn-tab'),
        basket: document.getElementById('acd-resp-basket-tab')
    };
    function activateTab(tabId) {
        tabs.forEach(btn => btn.classList.toggle('active', btn.dataset.tab === tabId));
        Object.keys(panes).forEach(id => panes[id].classList.toggle('active', id === tabId));
    }
    tabs.forEach(btn => btn.addEventListener('click', () => {
        const tabId = btn.dataset.tab;
        if (tabId && panes[tabId]) activateTab(tabId);
    }));

    // --------------------------------------------------------------
    // DELIVERY ORDER MODULE (with customer moved into Add Item)
    // --------------------------------------------------------------
    const root = document.getElementById('acd-resp-root');

    // Shared state between Delivery Order and Basket Return for customer sync
    const sharedCustomerState = {
        basketCustomerManuallyCleared: false,
        deliveryCustomer: { name: '', code: '' },
        basketCustomer: { name: '', code: '' }
    };

    const doContainer = document.getElementById('acd-resp-delivery-tab');
    if (doContainer && !doContainer.dataset.doInit) {
        doContainer.dataset.doInit = '1';

        const REST_NONCE    = root.dataset.restNonce;
        const RECEIPT_BASE  = root.dataset.receiptBase;
        const RECORDS_BASE  = root.dataset.recordsBase || '';
        const REST_JOB_POST = root.dataset.restJobPost;
        const REST_JOB_BASE = root.dataset.restJobBase;
        const REST_RECEIPT_TOKEN = root.dataset.restReceiptToken;
        const LOCAL_DO_MODE = root.dataset.localDoMode || 'legacy';
        const REQUESTED_DOC_PREFIX = root.dataset.requestedDocPrefix || 'WPDO';
     <b����t��������<b]�
N?�u   const AJAX_URL      = root.dataset.ajaxUrl;
        const DEBTOR_NONCE  = root.dataset.debtorNonce;
        const ITEM_NONCE    = root.dataset.itemNonce;
        let DRIVER_ITEMS = [];
        try {
            DRIVER_ITEMS = JSON.parse(root.dataset.drivers || '[]');
        } catch(e) {
            DRIVER_ITEMS = [];
        }
        const DROPDOWN_META = {
            showDebtorCode: root.dataset.showDebtorCode === '1',
            showItemCode: root.dataset.showItemCode === '1',
            showSalesAgent: false
        };

        const state = {
            lines: [],
            jobFinished: false,
            isSubmitting: false,
            savedPendingClear: false,
        };
        const pickerState = { items: [], defaultItems: [], fetchFn: null, onPick: null };
        let pickerTimer = null;

        function $(id) { return document.getElementById(id); }
        function submitIdleText() { return 'Save Delivery Order'; }
        function submitDoneText() { return 'Saved - Ready for Next Batch'; }
        function submitProgressText() { return 'Queuing...'; }
        function successToastText(count = 1) { return count === 1 ? 'Delivery Order queued' : `${count} Delivery Orders queued`; }

        function escapeHtml(s) {
            if (!s) return '';
            return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));
        }

        // NEW helper functions for quantity and KG (decimal support)
        function fmtQty(n) {
            const x = Number(n);
            return (!isFinite(x)) ? '0' : String(Math.round(x));
        }

        function fmtKg(n) {
            const x = Number(n);
            return (!isFinite(x)) ? '0.00' : x.toFixed(2);
        }

        function fmtMoney(n) {
            const x = Number(n);
            return (!isFinite(x)) ? '0.00' : x.toFixed(2);
        }

        function parseQty(n) {
            const x = Number(n);
            return (!isFinite(x) || x < 0) ? 0 : Math.round(x);
        }

        function parseKg(n) {
            const x = Number(n);
            return (!isFinite(x) || x < 0) ? 0 : Number(x.toFixed(2));
        }

        function parseMoney(n) {
            const x = Number(n);
            return (!isFinite(x) || x < 0) ? 0 : x;
        }

        function roundMoney(n) {
            return Number(parseMoney(n).toFixed(2));
        }

        function calcTotalKg(qty, kg) {
            return Number(((parseQty(qty) || 0) * (parseKg(kg) || 0)).toFixed(2));
        }

        function kgKey(n) {
            return fmtKg(parseKg(n));
        }

        function calcTotalPrice(line) {
            return parseMoney(line?.price) * (parseFloat(line?.total) || 0);
        }

        function normalizeBatchId(value) {
            return String(value || '').replace(/[^a-zA-Z0-9\-_.:]/g, '').slice(0, 80);
        }
        function makeBulkBatchId() {
            return normalizeBatchId(`DOBULK-${Date.now()}-${Math.random().toString(36).slice(2)}`);
        }
        function buildRecordsUrl(bulkBatchId) {
            if (!RECORDS_BASE) {
                throw new Error('Delivery Order records page URL is missing.');
            }

            const base = RECORDS_BASE;
            const url = new URL(base, window.location.origin);
            url.searchParams.set('bulkBatchId', normalizeBatchId(bulkBatchId));
            url.searchParams.set('print', '1');
            return url.toString();
        }

        function extractReturnedDocNo(response) {
            return response?.localDocNo
                || response?.local_doc_no
                || response?.sourceDocNo
                || response?.source_doc_no
                || response?.docNo
                || response?.doc_no
                || '';
        }

        function buildLocalDoCompatMeta(group, bulkBatchId, groupIndex) {
            return {
                mode: LOCAL_DO_MODE,
                schemaVersion: 'wpdo-local-v1',
                legacyQueueCompatible: true,
                sourceType: 'DELIVERY_ORDER',
                sourceSystem: 'WORDPRESS',
                requestedDocPrefix: REQUESTED_DOC_PREFIX,
                requestedDocNoMode: 'SERVER_GENERATED',
                requestedDocNoFormat: `${REQUESTED_DOC_PREFIX}{yyMM}/0000`,
                localDocNo: '',
                localDoId: null,
                bulkBatchId,
                groupIndex,
                customerCode: group?.customerCode || '',
                assignedDriverId: group?.assignedDriverId || 0
            };
        }

        function showToast(icon, title, text='') {
            if (window.Swal) Swal.fire({ toast: true, position: 'center', icon, title, text, showConfirmButton: false, timer: 2600, timerProgressBar: true });
        }
        function showModal(icon, title, html) {
            if (window.Swal) Swal.fire({ icon, title, html, confirmButtonText: 'OK' });
        }
        function showBulkSuccessModal(result) {
            const count = result?.count || 0;
            const recordsUrl = result?.recordsUrl || '#';
            const label = count === 1 ? '1 delivery order' : `${count} delivery orders`;

            if (window.Swal) {
                Swal.fire({
                    icon: 'success',
                    title: 'Delivery Orders Queued',
                    html: `<p>${escapeHtml(label)} queued for AutoCount.</p><p>DO numbers are still generating. Use the status page to print when ready.</p>`,
                    showCancelButton: true,
                    confirmButtonText: 'View Status / Print When Ready',
                    cancelButtonText: 'Close'
                }).then(res => {
                    if (res.isConfirmed && recordsUrl !== '#') {
                        window.open(recordsUrl, '_blank', 'noopener');
                    }
                });
            } else if (recordsUrl !== '#') {
                window.open(recordsUrl, '_blank', 'noopener');
            }
        }

        function savedJobListHtml(savedJobs) {
            if (!savedJobs.length) return '<p>No delivery orders were queued.</p>';

            const rows = savedJobs.map(job => {
                const customer = escapeHtml(job.customerName || job.customerCode || '-');
                const driver = escapeHtml(job.assignedDriverLabel || '-');
                const docNo = escapeHtml(job.docNo || 'Queued');
                const jobId = escapeHtml(job.jobId || '-');

                return `<li><strong>${docNo}</strong> | ${customer} | Driver: ${driver} | Job #${jobId}</li>`;
            }).join('');

            return `<ul style="text-align:left;margin:.75rem 0 0;padding-left:1.25rem;">${rows}</ul>`;
        }

        function showBulkPartialFailureModal(result) {
            const savedJobs = result?.savedJobs || [];
            const recordsUrl = result?.recordsUrl || '#';
            const errorMessage = result?.errorMessage || 'Submit failed';
            const savedCount = savedJobs.length;
            const title = savedCount
                ? `${savedCount} DO${savedCount === 1 ? '' : 's'} already queued`
                : 'Delivery Order submit failed';
            const html = `
                <p>${escapeHtml(errorMessage)}</p>
                ${savedCount ? '<p><strong>Do not resubmit these queued DOs.</strong> They were removed from the form, so retry will submit only the remaining unsaved items.</p>' : ''}
                ${savedJobListHtml(savedJobs)}
            `;

            if (window.Swal) {
                Swal.fire({
                    icon: savedCount ? 'warning' : 'error',
                    title,
                    html,
                    showCancelButton: savedCount && recordsUrl !== '#',
                    confirmButtonText: 'OK',
                    cancelButtonText: 'View Queued DOs'
                }).then(res => {
                    if (res.dismiss === Swal.DismissReason.cancel && recordsUrl !== '#') {
                        window.open(recordsUrl, '_blank', 'noopener');
                    }
                });
                return;
            }

            showModal(savedCount ? 'warning' : 'error', title, html);
        }

        function updateEntryTotal() {
            const itemCode = ($('acd_resp_do_item').value || '').trim();
            const itemName = ($('acd_resp_do_item_display').value || '').trim() || itemCode;
            const packType = ($('acd_resp_do_pack_type').value || '').trim();
            const qtyRaw = ($('acd_resp_do_qty').value || '').trim();
            const kgRaw = ($('acd_resp_do_kg').value || '').trim();
            const priceRaw = ($('acd_resp_do_price').value || '').trim();
            const qty = parseQty(qtyRaw || '0');
            const kg = parseKg(kgRaw || '0');
            const total = calcTotalKg(qty, kg);
            const defaultItemPrice = parseMoney($('acd_resp_do_item_price').value || '0');
            const enteredPrice = parseMoney(priceRaw);
            const price = enteredPrice > 0 ? enteredPrice : defaultItemPrice;
            const lineTotal = roundMoney(price * total);
            const pv = $('acd_resp_do_line_preview');
            if (!itemCode || qtyRaw === '' || kgRaw === '') {
                pv.style.display = 'none';
                pv.innerHTML = '';
                return;
            }
            pv.style.display = 'block';
            pv.innerHTML = `<div><b>${escapeHtml(itemName)}</b></div>
                            <div>Type: ${escapeHtml(packType)} | Qty: ${fmtQty(qty)} | KG: ${fmtKg(kg)} | Total KG: ${fmtKg(total)} | Price: ${fmtMoney(price)} | Line Total: ${fmtMoney(lineTotal)}</div>`;
        }

        function setPackType(type) {
            const nextType = String(type || '').toUpperCase() === 'BASKET' ? 'BASKET' : 'CARTON';
            $('acd_resp_do_pack_type').value = nextType;
            document.querySelectorAll('#acd_resp_do_pack_type_toggle .acd-resp-type-btn').forEach(btn => {
                btn.classList.toggle('active', (btn.dataset.packType || '').toUpperCase() === nextType);
            });
            updateEntryTotal();
        }

        function updateUI() {
            const lines = state.lines;
            const badge = document.getElementById('acd_resp_do_lines_count_badge');
            if (badge) badge.innerText = lines.length;

            const container = $('acd_resp_do_lines');
            if (!lines.length) {
                container.innerHTML = '<div class="acd-resp-empty">No items added</div>';
                return;
            }

            container.innerHTML = lines.map((l, idx) => `
                <div class="acd-resp-line" data-idx="${idx}">
                    <div><strong>${escapeHtml(l.itemName || l.itemCode)}</strong></div>
                    <div><strong>${escapeHtml(l.customerName || l.customerCode)}</strong></div>
                    <div>${escapeHtml(l.assignedDriverLabel || l.assignedDriverLogin)}</div>
                    <div><span class="acd-resp-type-pill">${l.packType}</span></div>
                    <div class="acd-resp-number-cell">${fmtQty(l.qty)}</div>
                    <div class="acd-resp-number-cell">${fmtKg(l.kg)}</div>
                    <div class="acd-resp-number-cell">${fmtKg(l.total)}</div>
                    <div class="acd-resp-price-cell"><input type="number" class="acd-resp-price-input" data-price-idx="${idx}" value="${fmtMoney(l.price)}" min="0" step="0.01" inputmode="decimal" aria-label="Price for ${escapeHtml(l.itemName || l.itemCode)}"></div>
                    <div class="acd-resp-money-cell" data-total-price-idx="${idx}">${fmtMoney(calcTotalPrice(l))}</div>
                    <div><button type="button" class="acd-resp-delete-btn" data-idx="${idx}" aria-label="Delete ${escapeHtml(l.itemName || l.itemCode)}" title="Delete">&#128465;</button></div>
                </div>
            `).join('');
        }

        function updateLinePrice(idx, value, shouldFormatInput = false) {
            if (isNaN(idx) || !state.lines[idx]) return;
            state.lines[idx].price = parseMoney(value);
            const nextTotal = fmtMoney(calcTotalPrice(state.lines[idx]));
            document.querySelectorAll(`[data-total-price-idx="${idx}"]`).forEach(el => {
                el.textContent = nextTotal;
            });
            if (shouldFormatInput) {
                document.querySelectorAll(`[data-price-idx="${idx}"]`).forEach(input => {
                    input.value = fmtMoney(state.lines[idx].price);
                });
            }
        }

        async function apiGet(url) {
            const res = await fetch(url, { method: 'GET', credentials: 'same-origin', headers: { 'Accept': 'application/json', 'X-WP-Nonce': REST_NONCE }, cache: 'no-store' });
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            const text = await res.text();
            return text ? JSON.parse(text) : null;
        }
        async function apiPost(url, body) {
            const res = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': REST_NONCE }, body: JSON.stringify(body) });
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            return await res.json();
        }

        async function createReceiptToken(jobId) {
            if (!REST_RECEIPT_TOKEN) {
                throw new Error('Receipt token endpoint missing');
            }
            return await apiPost(REST_RECEIPT_TOKEN, { job_id: jobId });
        }

        function buildPayloadLine(l, location) {
            const displayName = String(l.itemName || l.itemCode || '').trim();
            const isBasket = (l.packType === 'BASKET');
            const count = l.qty;
            const weightPerUnit = l.kg;
            const totalWeight = l.total;
            const unitPrice = roundMoney(l.price || 0);
            const amount = roundMoney(unitPrice * totalWeight);

            return {
                itemCode: l.itemCode,
                description: displayName,
                itemName: displayName,
                ItemName: displayName,
                itemDesc: displayName,
                uom: 'KG',
                unitPrice,
                amount,
                taxCode: 'SR-0',
                taxRate: 0,
                packType: l.packType,
                qty: totalWeight,
                kg: weightPerUnit,
                totalKg: totalWeight,
                unitQty: count,
                basketQty: isBasket ? count : null,
                cartonQty: !isBasket ? count : null,
                location
            };
        }

        function groupKey(customerCode, assignedDriverId) {
            return `${customerCode}::${assignedDriverId}`;
        }

        function groupLinesByCustomerDriver(lines) {
            const groups = new Map();
            lines.forEach(line => {
                const key = groupKey(line.customerCode, line.assignedDriverId);
                if (!groups.has(key)) {
                    groups.set(key, {
                        key,
                        customerCode: line.customerCode,
                        customerName: line.customerName,
                        salesAgent: line.salesAgent || '',
                        assignedDriverId: line.assignedDriverId,
                        assignedDriverLabel: line.assignedDriverLabel,
                        assignedDriverLogin: line.assignedDriverLogin,
                        lines: []
                    });
                }
                groups.get(key).lines.push(line);
            });
            return Array.from(groups.values());
        }

        function removeSavedGroupsFromForm(savedJobs) {
            const savedKeys = new Set(savedJobs.map(job => job.groupKey).filter(Boolean));
            if (!savedKeys.size) return;

            state.lines = state.lines.filter(line => !savedKeys.has(groupKey(line.custom<b]��^�Hu��������<b��
N?�verCode, line.assignedDriverId)));
            updateUI();
        }

        function clearDeliveryFormAfterSave() {
            clearCustomerSelection();
            clearDriverSelection();
            clearLineEntry();
            state.lines = [];
            state.jobFinished = false;
            state.savedPendingClear = false;
            const submitBtn = $('acd_resp_do_submit');
            if (submitBtn) {
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            }
            updateUI();
            updateClearButtons();
        }

        async function searchItemsLive(q) {
            if (!AJAX_URL || !ITEM_NONCE) {
                console.warn('[Item Search] Missing AJAX_URL or ITEM_NONCE', { AJAX_URL, ITEM_NONCE });
                return [];
            }

            const url =
                `${AJAX_URL}?action=ac_itemcode_suggest` +
                `&nonce=${encodeURIComponent(ITEM_NONCE)}` +
                `&_ajax_nonce=${encodeURIComponent(ITEM_NONCE)}` +
                `&term=${encodeURIComponent(q)}` +
                `&q=${encodeURIComponent(q)}` +
                `&keyword=${encodeURIComponent(q)}`;

            const res = await fetch(url, {
                method: 'GET',
                credentials: 'same-origin',
                cache: 'no-store'
            });

            const text = await res.text();
            let data = null;

            try {
                data = text ? JSON.parse(text) : null;
            } catch (e) {
                console.error('[Item Search] Non-JSON response:', text);
                throw new Error('Item search returned invalid response.');
            }

            console.log('[Item Search] Response:', data);

            if (!data) {
                return [];
            }

            let rows = [];

            if (Array.isArray(data)) {
                rows = data;
            } else if (Array.isArray(data.items)) {
                rows = data.items;
            } else if (Array.isArray(data.data)) {
                rows = data.data;
            } else if (Array.isArray(data.data?.items)) {
                rows = data.data.items;
            } else if (Array.isArray(data.results)) {
                rows = data.results;
            } else if (Array.isArray(data.data?.results)) {
                rows = data.data.results;
            }

            return rows.map(it => {
                const code =
                    it.code ||
                    it.itemCode ||
                    it.ItemCode ||
                    it.item_code ||
                    it.value ||
                    '';

                const name =
                    it.desc ||
                    it.description ||
                    it.Description ||
                    it.name ||
                    it.itemName ||
                    it.ItemName ||
                    it.label ||
                    code;

                const price =
                    it.price ??
                    it.Price ??
                    it.unitPrice ??
                    it.UnitPrice ??
                    it.salesPrice ??
                    it.SalesPrice ??
                    0;

                return {
                    code: String(code || '').trim(),
                    name: String(name || code || '').trim(),
                    price: parseMoney(price)
                };
            }).filter(it => it.code || it.name);
        }

        async function searchDebtorsLive(q) {
            const wrapper = $('acdRespDebtorWrapper');
            const url = `${wrapper.dataset.ajaxUrl}?action=ac_cs_debtor_search&nonce=${encodeURIComponent(wrapper.dataset.nonce)}&q=${encodeURIComponent(q)}`;
            const res = await fetch(url, { credentials: 'same-origin' });
            const data = await res.json();
            if (!data.success) throw new Error(data.data?.error || 'Search failed');
            const items = data.data?.items || [];
            return items.map(it => {
                const name = it.name || it.debtorName || '';
                const code = it.code || it.debtorCode || '';
                const sa = (it.salesAgent || it.sales_agent || '').trim();
                const meta = [];
                if (DROPDOWN_META.showDebtorCode && code) meta.push(code);
                if (DROPDOWN_META.showSalesAgent && sa) meta.push('SA: ' + sa);
                return { label: name || code, meta: meta.join('  |  '), raw: { name, code, salesAgent: sa } };
            });
        }

        function renderPickerNote(msg) { $('acd_resp_do_picker_results').innerHTML = `<div class="acd-resp-picker-note">${escapeHtml(msg)}</div>`; }
        function renderPickerItems(items) {
            const box = $('acd_resp_do_picker_results');
            if (!items.length) { renderPickerNote('No result found'); return; }
            box.innerHTML = items.map((it, idx) => `<button type="button" class="acd-resp-picker-item" data-picker-idx="${idx}"><span class="acd-resp-picker-item-main">${escapeHtml(it.label || '')}</span>${it.meta ? `<span class="acd-resp-picker-item-sub">${escapeHtml(it.meta)}</span>` : ''}</button>`).join('');
        }
        async function runPickerSearch(q) {
            const query = (q || '').trim();
            clearTimeout(pickerTimer);
            if (query.length < 1) {
                pickerState.items = pickerState.defaultItems || [];
                if (pickerState.items.length) {
                    renderPickerItems(pickerState.items);
                } else {
                    renderPickerNote('Type to search');
                }
                return;
            }
            pickerTimer = setTimeout(async () => {
                renderPickerNote('Searching...');
                try {
                    const items = await pickerState.fetchFn(query);
                    pickerState.items = items || [];
                    renderPickerItems(pickerState.items);
                } catch(e) { pickerState.items = []; renderPickerNote('Failed to load'); }
            }, 220);
        }
        function openPicker(opts) {
            pickerState.defaultItems = opts.initialItems || [];
            pickerState.items = pickerState.defaultItems;
            pickerState.fetchFn = opts.fetchFn;
            pickerState.onPick = opts.onPick;
            $('acd_resp_do_picker_title').textContent = opts.title || 'Search';
            $('acd_resp_do_picker_search').placeholder = opts.placeholder || 'Type to search...';
            $('acd_resp_do_picker_search').value = '';
            $('acd_resp_do_picker_modal').classList.add('active');
            if (pickerState.items.length) {
                renderPickerItems(pickerState.items);
            } else {
                renderPickerNote('Type to search');
            }
            setTimeout(() => $('acd_resp_do_picker_search').focus(), 80);
        }
        function closePicker() {
            $('acd_resp_do_picker_modal').classList.remove('active');
            $('acd_resp_do_picker_search').value = '';
            $('acd_resp_do_picker_results').innerHTML = '';
            pickerState.items = []; pickerState.defaultItems = []; pickerState.fetchFn = null; pickerState.onPick = null;
        }
        function updateClearButtons() {
            const debtorHas = !!($('acdRespDebtorInput')?.value.trim());
            const driverHas = !!($('acd_resp_do_driver_name')?.value.trim());
            const itemHas = !!($('acd_resp_do_item_name')?.value.trim());
            $('acdRespDebtorClear')?.classList.toggle('show', debtorHas);
            $('acdRespDriverClear')?.classList.toggle('show', driverHas);
            $('acdRespItemClear')?.classList.toggle('show', itemHas);
        }

        // ---- Customer sync functions ----
        function setBasketCustomerFromDelivery(customer) {
            const basketAccountType = String($('acd_resp_br_account_type')?.value || 'CUSTOMER').toUpperCase();
            if (basketAccountType !== 'CUSTOMER' || sharedCustomerState.basketCustomerManuallyCleared) {
                return;
            }
            const brInput = $('acdRespBrDebtorInput');
            const brCode = $('acd_resp_br_debtor_code');
            const brName = $('acd_resp_br_debtor_name');
            const brClear = $('acdRespBrDebtorClear');

            const name = customer?.name || '';
            const code = customer?.code || '';

            if (brInput) brInput.value = name || code || '';
            if (brCode) brCode.value = code;
            if (brName) brName.value = name;
            sharedCustomerState.basketCustomer = { name, code };
            if (brClear) {
                brClear.classList.toggle('show', !!(name || code));
            }
        }

        function clearBasketCustomerFromDelivery() {
            const basketAccountType = String($('acd_resp_br_account_type')?.value || 'CUSTOMER').toUpperCase();
            if (basketAccountType !== 'CUSTOMER') {
                return;
            }
            const brInput = $('acdRespBrDebtorInput');
            const brCode = $('acd_resp_br_debtor_code');
            const brName = $('acd_resp_br_debtor_name');
            const brClear = $('acdRespBrDebtorClear');

            if (brInput) brInput.value = '';
            if (brCode) brCode.value = '';
            if (brName) brName.value = '';
            sharedCustomerState.basketCustomer = { name: '', code: '' };
            if (brClear) brClear.classList.remove('show');
        }

        function setDeliveryCustomer(picked) {
            const name = picked?.name || '';
            const code = picked?.code || '';
            const salesAgent = picked?.salesAgent || '';

            $('acdRespDebtorInput').value = name || code || '';
            $('acd_resp_do_customer').value = code;
            $('acd_resp_do_customer_name').value = name;
            $('acd_resp_do_sales_agent').value = salesAgent;

            sharedCustomerState.basketCustomerManuallyCleared = false;
            sharedCustomerState.deliveryCustomer = { name, code };

            setBasketCustomerFromDelivery({
                name,
                code
            });

            updateClearButtons();
        }

        function clearCustomerSelection() {
            $('acdRespDebtorInput').value = '';
            $('acd_resp_do_customer').value = '';
            $('acd_resp_do_customer_name').value = '';
            $('acd_resp_do_sales_agent').value = '';

            sharedCustomerState.basketCustomerManuallyCleared = true;
            sharedCustomerState.deliveryCustomer = { name: '', code: '' };
            clearBasketCustomerFromDelivery();

            updateClearButtons();
        }

        function searchDriversLive(q) {
            const query = String(q || '').trim().toLowerCase();
            if (!query) return Promise.resolve(DRIVER_ITEMS);
            return Promise.resolve(DRIVER_ITEMS.filter(driver => {
                const haystack = [
                    String(driver.label || '').toUpperCase(),
                    String(driver.name || '').toUpperCase(),
                    driver.login || ''
                ].join(' ').toLowerCase();
                return haystack.includes(query);
            }));
        }

        function setDeliveryDriver(picked) {
            const id = parseInt(picked?.id || 0, 10) || 0;
            const label = String(picked?.login || picked?.label || picked?.name || '').toUpperCase();
            const login = picked?.login || '';
            $('acd_resp_do_driver_name').value = label;
            $('acd_resp_do_driver').value = id ? String(id) : '';
            $('acd_resp_do_driver_login').value = login;
            updateClearButtons();
        }

        function clearDriverSelection() {
            $('acd_resp_do_driver_name').value = '';
            $('acd_resp_do_driver').value = '';
            $('acd_resp_do_driver_login').value = '';
            updateClearButtons();
        }

        function openDebtorPicker() {
            openPicker({
                title: 'Select Customer',
                placeholder: 'Search customer...',
                fetchFn: searchDebtorsLive,
                onPick: (picked) => {
                    if (!picked) return;
                    setDeliveryCustomer(picked);
                    closePicker();
                }
            });
        }

        function openDriverPicker() {
            const driverOptions = DRIVER_ITEMS.map(driver => ({
                label: String(driver.login || '').toUpperCase(),
                meta: '',
                raw: driver
            }));
            openPicker({
                title: 'Select Driver',
                placeholder: 'Search driver...',
                initialItems: driverOptions,
                fetchFn: async (q) => {
                    const drivers = await searchDriversLive(q);
                    return drivers.map(driver => ({
                        label: String(driver.login || '').toUpperCase(),
                        meta: '',
                        raw: driver
                    }));
                },
                onPick: (picked) => {
                    if (!picked) return;
                    setDeliveryDriver(picked);
                    closePicker();
                }
            });
        }

        function clearItemSelection() {
            $('acd_resp_do_item_name').value = '';
            $('acd_resp_do_item').value = '';
            $('acd_resp_do_item_display').value = '';
            $('acd_resp_do_item_price').value = '0';
            updateEntryTotal();
            updateClearButtons();
        }

        function openItemPicker() {
            openPicker({
                title: 'Select Item',
                placeholder: 'Search item...',
                fetchFn: async (q) => {
                    const items = await searchItemsLive(q);
                    return items.map(it => ({
                        label: it.name || it.code,
                        meta: (DROPDOWN_META.showItemCode && it.code) ? it.code : '',
                        raw: { code: it.code, name: it.name || it.code, price: it.price || 0 }
                    }));
                },
                onPick: (picked) => {
                    if (!picked) return;
                    $('acd_resp_do_item_name').value = picked.name || picked.code || '';
                    $('acd_resp_do_item').value = picked.code || '';
                    $('acd_resp_do_item_display').value = picked.name || picked.code || '';
                    const rawPrice = Number(picked.price || 0);
                    $('acd_resp_do_item_price').value = fmtMoney(rawPrice);
                    $('acd_resp_do_price').value = rawPrice > 0 ? String(rawPrice.toFixed(2)) : '';
                    console.log('[DO item pick]', picked.code, 'price', rawPrice, 'field value', $('acd_resp_do_price').value);
                    updateEntryTotal();
                    updateClearButtons();
                    closePicker();
                }
            });
        }

        function initPickerModal() {
            $('acd_resp_do_picker_close').addEventListener('click', closePicker);
            $('acd_resp_do_picker_backdrop').addEventListener('click', closePicker);
            $('acd_resp_do_picker_search').addEventListener('input', function() { runPickerSearch(this.value); });
            $('acd_resp_do_picker_results').addEventListener('click', (e) => {
                const btn = e.target.closest('[data-picker-idx]');
                if (!btn) return;
                const idx = parseInt(btn.dataset.pickerIdx);
                if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) pickerState.onPick(pickerState.items[idx].raw);
            });
        }
        function initPickerTriggers() {
            $('acdRespDebtorInput').setAttribute('readonly', 'readonly');
<b�����v��������<b��
N?�w            $('acd_resp_do_driver_name').setAttribute('readonly', 'readonly');
            $('acd_resp_do_item_name').setAttribute('readonly', 'readonly');
            $('acdRespDebtorInput').addEventListener('click', openDebtorPicker);
            $('acd_resp_do_driver_name').addEventListener('click', openDriverPicker);
            $('acd_resp_do_item_name').addEventListener('click', openItemPicker);
            $('acdRespDebtorClear')?.addEventListener('click', (e) => { e.preventDefault(); clearCustomerSelection(); });
            $('acdRespDriverClear')?.addEventListener('click', (e) => { e.preventDefault(); clearDriverSelection(); });
            $('acdRespItemClear')?.addEventListener('click', (e) => { e.preventDefault(); clearItemSelection(); });
        }
        function makeClientRequestId(prefix='DO') { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`; }

        function clearLineEntry() {
            $('acd_resp_do_qty').value = '';
            $('acd_resp_do_kg').value = '';
            $('acd_resp_do_price').value = '';
            $('acd_resp_do_item_name').value = '';
            $('acd_resp_do_item').value = '';
            $('acd_resp_do_item_display').value = '';
            $('acd_resp_do_item_price').value = '0';
            updateEntryTotal();
            updateClearButtons();
        }

        // ---- MERGE LOGIC (same customer+driver+item+type+KG) ----
        function findMergeableLineIndex(nextLine) {
            return state.lines.findIndex(line => {
                return String(line.customerCode || '') === String(nextLine.customerCode || '')
                    && String(line.assignedDriverId || '') === String(nextLine.assignedDriverId || '')
                    && String(line.itemCode || '').toUpperCase() === String(nextLine.itemCode || '').toUpperCase()
                    && String(line.packType || '').toUpperCase() === String(nextLine.packType || '').toUpperCase()
                    && kgKey(line.kg) === kgKey(nextLine.kg);
            });
        }

        function mergeLine(existingLine, nextLine) {
            const mergedQty = parseQty(existingLine.qty || 0) + parseQty(nextLine.qty || 0);
            const sameKg = parseKg(existingLine.kg || 0);

            existingLine.qty = mergedQty;
            existingLine.kg = sameKg;
            existingLine.total = calcTotalKg(mergedQty, sameKg);

            if (parseMoney(existingLine.price || 0) <= 0 && parseMoney(nextLine.price || 0) > 0) {
                existingLine.price = parseMoney(nextLine.price || 0);
            }

            return existingLine;
        }

        // ---- Success actions panel ----
        function hideDeliverySuccessActions() {
            const box = $('acd_resp_do_success_actions');
            const docNoEl = $('acd_resp_do_success_docno');
            const receiptBtn = $('acd_resp_do_receipt_btn');

            if (box) box.style.display = 'none';
            if (docNoEl) docNoEl.textContent = '-';
            if (receiptBtn) {
                receiptBtn.href = '#';
                receiptBtn.style.display = 'none';
            }
        }

        function showDeliverySuccessActions(data) {
            const box = $('acd_resp_do_success_actions');
            const docNoEl = $('acd_resp_do_success_docno');
            const receiptBtn = $('acd_resp_do_receipt_btn');

            const docNo = data?.docNo || data?.batchLabel || '-';
            const receiptUrl = data?.receiptUrl || '';

            if (docNoEl) docNoEl.textContent = docNo;
            if (receiptBtn && receiptUrl) {
                receiptBtn.href = receiptUrl;
                receiptBtn.style.display = 'inline-flex';
            }
            if (box) box.style.display = 'block';
        }

        function resetDeliveryOrderForm() {
            clearCustomerSelection();
            clearLineEntry();

            state.lines = [];
            state.jobFinished = false;
            state.savedPendingClear = false;

            const dateField = $('acd_resp_do_date');
            if (dateField) dateField.value = root.dataset.today || '';
            const driverSelect = $('acd_resp_do_driver');
            if (driverSelect) clearDriverSelection();

            const submitBtn = $('acd_resp_do_submit');
            if (submitBtn) {
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            }

            hideDeliverySuccessActions();
            updateUI();
            updateClearButtons();
        }

        // Init
        initPickerModal();
        initPickerTriggers();
        $('acd_resp_do_qty').addEventListener('input', updateEntryTotal);
        $('acd_resp_do_kg').addEventListener('input', updateEntryTotal);
        $('acd_resp_do_price').addEventListener('input', updateEntryTotal);
        $('acd_resp_do_pack_type').addEventListener('change', () => setPackType($('acd_resp_do_pack_type').value));
        document.querySelectorAll('#acd_resp_do_pack_type_toggle .acd-resp-type-btn').forEach(btn => btn.addEventListener('click', () => setPackType(btn.dataset.packType)));
        setPackType('BASKET');
        updateUI();

        // Add Item click with merge
        $('acd_resp_do_addline').addEventListener('click', () => {
            const itemCode = ($('acd_resp_do_item').value || '').trim();
            const itemName = ($('acd_resp_do_item_display').value || '').trim() || itemCode;
            const packType = ($('acd_resp_do_pack_type').value || '').trim();
            const qtyRaw = ($('acd_resp_do_qty').value || '').trim();
            const kgRaw = ($('acd_resp_do_kg').value || '').trim();
            const qty = parseQty(qtyRaw || '0');
            const kg = parseKg(kgRaw || '0');
            const defaultItemPrice = parseMoney($('acd_resp_do_item_price').value || '0');
            const enteredPrice = parseMoney($('acd_resp_do_price').value || '');
            const price = enteredPrice > 0 ? enteredPrice : defaultItemPrice;
            const customerCode = ($('acd_resp_do_customer').value || '').trim();
            const customerName = ($('acd_resp_do_customer_name').value || '').trim();
            const salesAgent = ($('acd_resp_do_sales_agent').value || '').trim();
            const assignedDriverId = parseInt($('acd_resp_do_driver')?.value || '0', 10) || 0;
            const assignedDriverLabel = ($('acd_resp_do_driver_name')?.value || '').trim();
            const assignedDriverLogin = ($('acd_resp_do_driver_login')?.value || '').trim();
            if (!customerCode) { showToast('error', 'Select customer'); return; }
            if (!assignedDriverId) { showToast('error', 'Select driver'); return; }
            if (!itemCode) { showToast('error', 'Select an item'); return; }
            if (qty <= 0) { showToast('error', 'Qty must be >0'); return; }
            if (kg <= 0) { showToast('error', 'KG must be >0'); return; }

            const nextLine = {
                customerCode,
                customerName,
                salesAgent,
                assignedDriverId,
                assignedDriverLabel,
                assignedDriverLogin,
                itemCode,
                itemName,
                packType,
                qty,
                kg,
                total: calcTotalKg(qty, kg),
                price
            };

            const existingIdx = findMergeableLineIndex(nextLine);

            if (existingIdx >= 0) {
                mergeLine(state.lines[existingIdx], nextLine);
                updateUI();
                clearLineEntry();
                showToast(
                    'warning',
                    'Same item + KG merged',
                    `${itemName} ${fmtKg(kg)}KG already exists for ${customerName || customerCode}. Quantity has been added into the same row.`
                );
                return;
            }

            state.lines.push(nextLine);
            updateUI();
            clearLineEntry();
            showToast('success', 'Item added');
        });

        // Item detail events for editable price and delete buttons.
        document.getElementById('acd_resp_do_lines').addEventListener('click', (e) => {
            const btn = e.target.closest('.acd-resp-delete-btn');
            if (!btn) return;
            const idx = parseInt(btn.dataset.idx);
            if (!isNaN(idx)) {
                state.lines.splice(idx, 1);
                updateUI();
                showToast('info', 'Item removed');
            }
        });
        document.getElementById('acd_resp_do_lines').addEventListener('input', (e) => {
            const input = e.target.closest('.acd-resp-price-input');
            if (!input) return;
            updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, false);
        });
        document.getElementById('acd_resp_do_lines').addEventListener('change', (e) => {
            const input = e.target.closest('.acd-resp-price-input');
            if (!input) return;
            updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, true);
        });

        const clearNewBtn = $('acd_resp_do_clear_new_btn');
        if (clearNewBtn) {
            clearNewBtn.addEventListener('click', () => {
                resetDeliveryOrderForm();
                showToast('info', 'Ready for new DO');
            });
        }

        $('acd_resp_do_submit').addEventListener('click', async () => {
            if (state.isSubmitting) { showToast('warning', 'Already submitting'); return; }

            const submitBtn = $('acd_resp_do_submit');
            let saveSucceeded = false;
            state.isSubmitting = true;
            state.jobFinished = false;
            submitBtn.disabled = true;
            submitBtn.textContent = submitProgressText();

            const savedJobs = [];
            let recordsUrl = '#';

            try {
                const location = ($('acd_resp_do_location').value || '').trim();
                const docDate = ($('acd_resp_do_date').value || '').trim();
                if (!state.lines.length) throw new Error('Add at least one item');

                const groups = groupLinesByCustomerDriver(state.lines);
                if (!groups.length) throw new Error('Add at least one valid item');

                groups.forEach((group, groupIdx) => {
                    if (!group.customerCode) throw new Error(`Group ${groupIdx + 1}: customer missing`);
                    if (!group.assignedDriverId) throw new Error(`Group ${groupIdx + 1}: driver missing`);
                    if (!group.lines.length) throw new Error(`Group ${groupIdx + 1}: item lines missing`);
                    group.lines.forEach((line, lineIdx) => {
                        if (!line.itemCode) throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: item missing`);
                        if (line.qty <= 0 || line.kg <= 0 || line.total <= 0) {
                            throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: quantity and KG must be >0`);
                        }
                    });
                });

                const bulkBatchId = makeBulkBatchId();
                recordsUrl = buildRecordsUrl(bulkBatchId);

                for (let i = 0; i < groups.length; i++) {
                    const group = groups[i];
                    const payloadLines = group.lines.map(line => buildPayloadLine(line, location));
                    const payload = {
                        bulkBatchId,
                        customerCode: group.customerCode,
                        customerName: group.customerName,
                        debtorCode: group.customerCode,
                        DebtorCode: group.customerCode,
                        debtorName: group.customerName,
                        DebtorName: group.customerName,
                        location,
                        Location: location,
                        docDate,
                        remark: '',
                        assignedDriverId: group.assignedDriverId,
                        assignedDriverName: group.assignedDriverLabel,
                        assignedDriverLogin: group.assignedDriverLogin,
                        driverId: group.assignedDriverId,
                        driverName: group.assignedDriverLabel,
                        driverLogin: group.assignedDriverLogin,

                        // Compatibility metadata for the new WordPress-first DO structure.
                        // Current/old endpoint and bridge can safely ignore this.
                        // New endpoint will use it to create wp_vege_ac_do + wp_vege_ac_do_items first,
                        // then keep wp_vege_ac_jobs as the sync queue.
                        localDoCompat: buildLocalDoCompatMeta(group, bulkBatchId, i + 1),

                        // Server must generate WPDO number. Do not generate DocNo in browser.
                        localDocNo: '',
                        sourceType: 'DELIVERY_ORDER',
                        sourceSystem: 'WORDPRESS',
                        requestedDocPrefix: REQUESTED_DOC_PREFIX,
                        requestedDocNoMode: 'SERVER_GENERATED',

                        lines: payloadLines
                    };

                    const salesAgent = String(group.salesAgent || '').trim();
                    if (salesAgent) {
                        payload.salesAgent = salesAgent;
                        payload.SalesAgent = salesAgent;
                    }

                    const body = {
                        type: 'DELIVERY_ORDER',
                        bulkBatchId,
                        client_request_id: normalizeBatchId(`${bulkBatchId}-${i + 1}`),
                        source: 'wp-ui',
                        assignedDriverId: group.assignedDriverId,
                        payload
                    };
                    const r = await apiPost(REST_JOB_POST, body);
                    const jobId = r.jobId || r.id;
                    const returnedDocNo = extractReturnedDocNo(r);
                    if (!jobId) throw new Error(`No job ID returned for ${group.customerName || group.customerCode}`);
                    showToast('info', 'Job queued', `${group.customerName || group.customerCode}${returnedDocNo ? ' | ' + returnedDocNo : ' | Job #' + jobId}`);

                    savedJobs.push({
                        jobId,
                        groupKey: group.key,
                        docNo: returnedDocNo,
                        customerCode: group.customerCode,
                        customerName: group.customerName,
                        assignedDriverLabel: group.assignedDriverLabel
                    });
                }

                showDeliverySuccessActions({
                    batchLabel: `${savedJobs.length} DO${savedJobs.length === 1 ? '' : 's'} | ${bulkBatchId}`,
                    receiptUrl: recordsUrl
                });
                showBulkSuccessModal({ count: savedJobs.length, recordsUrl });
                clearDeliveryFormAfterSave();
                saveSucceeded = true;
                submitBtn.textContent = submitDoneText();
            } catch(err) {
                if (savedJobs.length) {
                    removeSavedGroupsFromForm(savedJobs);
                }
                showBulkPartialFailureModal({
                    savedJobs,
                    recordsUrl,
                    errorMessage: err.message
                });
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            } finally {
                state.isSubmitting = false;
                if (!saveSucceeded) {
                    submitBtn.disabled = false;
                    submitBtn.textContent = submitIdleText();
                }
            }
        });
    }

    // --------------------------------------------------------------
    // BASKET RETURN MODULE (c<b�ƛ��w��������<c#�
N?�xustomer + creditor)
    // --------------------------------------------------------------
    const basketContainer = document.getElementById('acd-resp-basket-tab');
    if (basketContainer && !basketContainer.dataset.brInit) {
        basketContainer.dataset.brInit = '1';

        function $(id) { return document.getElementById(id); }

        const REST_NONCE = root.dataset.restNonce;
        const REST_RETURN_URL = root.dataset.restReturnPost;
        const SHOW_DEBTOR_CODE = root.dataset.showDebtorCode === '1';
        const SHOW_CREDITOR_CODE = root.dataset.showCreditorCode === '1';
        const AJAX_URL = root.dataset.ajaxUrl;
        const DEBTOR_NONCE = root.dataset.debtorNonce;
        const CREDITOR_NONCE = root.dataset.creditorNonce;

        let isSubmitting = false;
        const pickerState = { items: [], fetchFn: null, onPick: null };
        let pickerTimer = null;

        function esc(s) {
            return s ? String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c])) : '';
        }

        function toast(icon, title, text='') {
            if (window.Swal) {
                Swal.fire({
                    toast: true,
                    position: 'center',
                    icon,
                    title,
                    text,
                    showConfirmButton: false,
                    timer: 2400,
                    timerProgressBar: true
                });
            }
        }

        function whole(n) {
            const x = Number(n);
            return (!isFinite(x) || x < 0) ? 0 : Math.round(x);
        }

        function currentAccountType() {
            return String($('acd_resp_br_account_type')?.value || 'CUSTOMER').toUpperCase() === 'CREDITOR'
                ? 'CREDITOR'
                : 'CUSTOMER';
        }

        function accountConfig() {
            const creditor = currentAccountType() === 'CREDITOR';
            return {
                creditor,
                type: creditor ? 'CREDITOR' : 'CUSTOMER',
                typeApi: creditor ? 'creditor' : 'customer',
                label: creditor ? 'Creditor' : 'Customer',
                title: creditor ? 'Creditor Basket Return' : 'Customer Basket Return',
                pickerTitle: creditor ? 'Select Creditor' : 'Select Customer',
                searchPlaceholder: creditor ? 'Search creditor...' : 'Search customer...',
                qtyLabel: creditor ? 'Baskets Returned to Creditor' : 'Basket Returned by Customer',
                buttonText: creditor ? 'Save Creditor Basket Return' : 'Save Customer Basket Return',
                savingText: creditor ? 'Saving Creditor Return...' : 'Saving Customer Return...',
                ajaxAction: creditor ? 'ac_cs_creditor_search' : 'ac_cs_debtor_search',
                nonce: creditor ? CREDITOR_NONCE : DEBTOR_NONCE,
                showCode: creditor ? SHOW_CREDITOR_CODE : SHOW_DEBTOR_CODE
            };
        }

        function renderPickerNote(msg) {
            const div = $('acd_resp_br_picker_results');
            if (div) div.innerHTML = `<div class="acd-resp-picker-note">${esc(msg)}</div>`;
        }

        function renderPickerItems(items) {
            const box = $('acd_resp_br_picker_results');
            if (!box) return;
            if (!items.length) {
                renderPickerNote('No result found');
                return;
            }
            box.innerHTML = items.map((it, idx) =>
                `<button type="button" class="acd-resp-picker-item" data-picker-idx="${idx}">
                    <span class="acd-resp-picker-item-main">${esc(it.label || '')}</span>
                    ${it.meta ? `<span class="acd-resp-picker-item-sub">${esc(it.meta)}</span>` : ''}
                </button>`
            ).join('');
        }

        async function runPickerSearch(q) {
            const query = (q || '').trim();
            clearTimeout(pickerTimer);
            if (query.length < 1) {
                pickerState.items = [];
                renderPickerNote('Type to search');
                return;
            }

            pickerTimer = setTimeout(async () => {
                renderPickerNote('Searching...');
                try {
                    const items = await pickerState.fetchFn(query);
                    pickerState.items = items || [];
                    renderPickerItems(pickerState.items);
                } catch (e) {
                    pickerState.items = [];
                    renderPickerNote('Failed to load');
                }
            }, 220);
        }

        function openBasketPicker(opts) {
            pickerState.items = [];
            pickerState.fetchFn = opts.fetchFn;
            pickerState.onPick = opts.onPick;

            const titleEl = $('acd_resp_br_picker_title');
            const searchInput = $('acd_resp_br_picker_search');
            const modal = $('acd_resp_br_picker_modal');

            if (titleEl) titleEl.textContent = opts.title || 'Search';
            if (searchInput) {
                searchInput.placeholder = opts.placeholder || 'Type to search...';
                searchInput.value = '';
            }
            if (modal) {
                modal.classList.add('active');
                renderPickerNote('Type to search');
                setTimeout(() => searchInput?.focus(), 80);
            }
        }

        function closeBasketPicker() {
            const modal = $('acd_resp_br_picker_modal');
            const searchInput = $('acd_resp_br_picker_search');
            const resultsDiv = $('acd_resp_br_picker_results');

            if (modal) modal.classList.remove('active');
            if (searchInput) searchInput.value = '';
            if (resultsDiv) resultsDiv.innerHTML = '';

            pickerState.items = [];
            pickerState.fetchFn = null;
            pickerState.onPick = null;
        }

        async function searchBasketAccountsLive(q) {
            const cfg = accountConfig();
            if (!AJAX_URL || !cfg.nonce) return [];

            const url = `${AJAX_URL}?action=${encodeURIComponent(cfg.ajaxAction)}&nonce=${encodeURIComponent(cfg.nonce)}&q=${encodeURIComponent(q)}`;
            const res = await fetch(url, { credentials: 'same-origin', cache: 'no-store' });
            const data = await res.json();

            if (!data.success) {
                throw new Error(data.data?.error || 'Search failed');
            }

            const items = data.data?.items || [];
            return items.map(it => {
                const name = cfg.creditor
                    ? (it.name || it.creditorName || it.companyName || '')
                    : (it.name || it.debtorName || it.companyName || '');
                const code = cfg.creditor
                    ? (it.code || it.creditorCode || it.accNo || '')
                    : (it.code || it.debtorCode || it.accNo || '');

                return {
                    label: name || code,
                    meta: (cfg.showCode && code) ? code : '',
                    raw: { name, code }
                };
            });
        }

        function initBasketPickerModal() {
            const closeBtn = $('acd_resp_br_picker_close');
            const backdrop = $('acd_resp_br_picker_backdrop');
            const searchInput = $('acd_resp_br_picker_search');
            const resultsDiv = $('acd_resp_br_picker_results');

            if (closeBtn) closeBtn.addEventListener('click', closeBasketPicker);
            if (backdrop) backdrop.addEventListener('click', closeBasketPicker);
            if (searchInput) searchInput.addEventListener('input', function() {
                runPickerSearch(this.value);
            });
            if (resultsDiv) {
                resultsDiv.addEventListener('click', e => {
                    const btn = e.target.closest('[data-picker-idx]');
                    if (!btn) return;
                    const idx = parseInt(btn.dataset.pickerIdx, 10);
                    if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) {
                        pickerState.onPick(pickerState.items[idx].raw);
                    }
                });
            }
        }

        function updateBasketClearButton() {
            const input = $('acdRespBrDebtorInput');
            const clearBtn = $('acdRespBrDebtorClear');
            if (clearBtn) clearBtn.classList.toggle('show', !!(input?.value.trim()));
        }

        function clearBasketAccount(manual = false) {
            const input = $('acdRespBrDebtorInput');
            if (input) input.value = '';

            ['acd_resp_br_debtor_code', 'acd_resp_br_debtor_name', 'acd_resp_br_creditor_code', 'acd_resp_br_creditor_name']
                .forEach(id => {
                    const field = $(id);
                    if (field) field.value = '';
                });

            if (manual && currentAccountType() === 'CUSTOMER') {
                sharedCustomerState.basketCustomerManuallyCleared = true;
                sharedCustomerState.basketCustomer = { name: '', code: '' };
            }

            updateBasketClearButton();
        }

        function setBasketAccount(picked) {
            if (!picked) return;

            const cfg = accountConfig();
            const name = String(picked.name || '').trim();
            const code = String(picked.code || '').trim();
            const input = $('acdRespBrDebtorInput');

            clearBasketAccount(false);
            if (input) input.value = name || code;

            if (cfg.creditor) {
                $('acd_resp_br_creditor_code').value = code;
                $('acd_resp_br_creditor_name').value = name;
            } else {
                $('acd_resp_br_debtor_code').value = code;
                $('acd_resp_br_debtor_name').value = name;
                sharedCustomerState.basketCustomerManuallyCleared = false;
                sharedCustomerState.basketCustomer = { name, code };
            }

            updateBasketClearButton();
        }

        function applyBasketAccountType(type) {
            const nextType = String(type || '').toUpperCase() === 'CREDITOR' ? 'CREDITOR' : 'CUSTOMER';
            const select = $('acd_resp_br_account_type');
            if (select) select.value = nextType;

            document.querySelectorAll('#acd_resp_br_account_type_toggle .acd-resp-type-btn').forEach(btn => {
                btn.classList.toggle('active', String(btn.dataset.accountType || '').toUpperCase() === nextType);
            });

            clearBasketAccount(false);
            const cfg = accountConfig();

            if ($('acd_resp_br_title')) $('acd_resp_br_title').textContent = cfg.title;
            if ($('acd_resp_br_account_label')) $('acd_resp_br_account_label').textContent = cfg.label;
            if ($('acd_resp_br_qty_label')) $('acd_resp_br_qty_label').textContent = cfg.qtyLabel;
            if ($('acdRespBrDebtorInput')) $('acdRespBrDebtorInput').placeholder = cfg.searchPlaceholder;
            if ($('acd_resp_br_submit')) $('acd_resp_br_submit').textContent = cfg.buttonText;

            if (!cfg.creditor && !sharedCustomerState.basketCustomerManuallyCleared) {
                const savedCustomer = sharedCustomerState.basketCustomer || {};
                const deliveryCustomer = sharedCustomerState.deliveryCustomer || {};
                const restoreCustomer = (savedCustomer.code || savedCustomer.name) ? savedCustomer : deliveryCustomer;
                if (restoreCustomer.code || restoreCustomer.name) {
                    setBasketAccount(restoreCustomer);
                }
            }
        }

        function openBasketAccountPicker() {
            const cfg = accountConfig();
            openBasketPicker({
                title: cfg.pickerTitle,
                placeholder: cfg.searchPlaceholder,
                fetchFn: searchBasketAccountsLive,
                onPick: picked => {
                    setBasketAccount(picked);
                    closeBasketPicker();
                }
            });
        }

        initBasketPickerModal();

        const input = $('acdRespBrDebtorInput');
        const clearBtn = $('acdRespBrDebtorClear');

        if (input) {
            input.setAttribute('readonly', 'readonly');
            input.addEventListener('click', openBasketAccountPicker);
        }

        if (clearBtn) {
            clearBtn.addEventListener('click', e => {
                e.preventDefault();
                clearBasketAccount(true);
            });
        }

        document.querySelectorAll('#acd_resp_br_account_type_toggle .acd-resp-type-btn').forEach(btn => {
            btn.addEventListener('click', () => applyBasketAccountType(btn.dataset.accountType));
        });

        $('acd_resp_br_account_type')?.addEventListener('change', e => {
            applyBasketAccountType(e.target.value);
        });

        applyBasketAccountType('CUSTOMER');
        updateBasketClearButton();

        $('acd_resp_br_submit').addEventListener('click', async () => {
            if (isSubmitting) return;

            const cfg = accountConfig();
            const code = cfg.creditor
                ? ($('acd_resp_br_creditor_code').value || '').trim()
                : ($('acd_resp_br_debtor_code').value || '').trim();
            const name = cfg.creditor
                ? ($('acd_resp_br_creditor_name').value || '').trim()
                : ($('acd_resp_br_debtor_name').value || '').trim();

            const docDate = $('acd_resp_br_date').value;
            const basketQty = whole($('acd_resp_br_qty').value);
            const proofInput = $('acd_resp_br_proof');
            const proofFile = proofInput?.files?.[0] || null;

            if (!code) {
                toast('error', `Select ${cfg.label.toLowerCase()}`);
                return;
            }
            if (basketQty <= 0) {
                toast('error', 'Quantity must be >0');
                return;
            }

            isSubmitting = true;
            const btn = $('acd_resp_br_submit');
            btn.disabled = true;
            btn.textContent = cfg.savingText;

            try {
                let body;
                const headers = { 'X-WP-Nonce': REST_NONCE };

                if (proofFile) {
                    body = new FormData();
                    body.append('accountType', cfg.typeApi);
                    body.append('docDate', docDate);
                    body.append('basketQty', String(basketQty));
                    body.append('basketReturnProof', proofFile);

                    if (cfg.creditor) {
                        body.append('creditorCode', code);
                        body.append('creditorName', name);
                    } else {
                        body.append('debtorCode', code);
                        body.append('debtorName', name);
                    }
                } else {
                    const payload = {
                        accountType: cfg.typeApi,
                        docDate,
                        basketQty
                    };

                    if (cfg.creditor) {
                        payload.creditorCode = code;
                        payload.creditorName = name;
                    } else {
                        payload.debtorCode = code;
                        payload.debtorName = name;
                    }

                    body = JSON.stringify(payload);
                    headers['Content-Type'] = 'application/json';
                }

                const res = await fetch(REST_RETURN_URL, {
                    method: 'POST',
                    headers,
                    body,
                    credentials: 'same-origin'
                });

                const text = await res.text();
                let data = {};
                try {
                    data =<c#�6x��������<ce�
N?�y text ? JSON.parse(text) : {};
                } catch (e) {
                    data = { message: text };
                }

                if (!res.ok || data?.ok === false) {
                    throw new Error(data?.message || `HTTP ${res.status}`);
                }

                const proofText = data?.proofSaved
                    ? ' | Proof saved'
                    : (proofFile && data?.proofMessage ? ' | Return saved, proof failed' : '');

                toast(
                    'success',
                    cfg.creditor ? 'Creditor basket return saved' : 'Customer basket return saved',
                    `${name || code} | Qty ${basketQty}${proofText}`
                );

                $('acd_resp_br_qty').value = '';
                if (proofInput) proofInput.value = '';

                const dateField = $('acd_resp_br_date');
                if (dateField) dateField.value = root.dataset.today || '';
            } catch (err) {
                toast('error', 'Save failed', err.message);
            } finally {
                isSubmitting = false;
                btn.disabled = false;
                btn.textContent = accountConfig().buttonText;
            }
        });
    }
})();
</script>

<script>
(function(){
    // --------------------------------------------------------------
    // GOODS RECEIVE NOTE MODULE
    // --------------------------------------------------------------
    const root = document.getElementById('acd-resp-root');
    const grnContainer = document.getElementById('acd-resp-grn-tab');
    if (!grnContainer || grnContainer.dataset.grnInit) return;
    grnContainer.dataset.grnInit = '1';

    const REST_NONCE    = root.dataset.restNonce;
    const REST_JOB_POST = root.dataset.restJobPost;
    const REST_JOB_BASE = root.dataset.restJobBase;
    const GRN_MODE      = root.dataset.grnMode || 'compat-v1';
    const REQUESTED_DOC_PREFIX = root.dataset.grnDocPrefix || 'WPGR';
    const AJAX_URL      = root.dataset.ajaxUrl;
    const CREDITOR_NONCE = root.dataset.creditorNonce;
    const ITEM_NONCE    = root.dataset.itemNonce;

    const DROPDOWN_META = {
        showCreditorCode: root.dataset.showCreditorCode === '1',
        showItemCode: root.dataset.showItemCode === '1'
    };

    const state = {
        lines: [],
        jobFinished: false,
        isSubmitting: false,
        savedPendingClear: false,
    };
    const pickerState = { items: [], defaultItems: [], fetchFn: null, onPick: null };
    let pickerTimer = null;

    function $(id) { return document.getElementById(id); }
    function submitIdleText() { return 'Save Goods Receive Note'; }
    function submitDoneText() { return 'Saved - Ready for Next Batch'; }
    function submitProgressText() { return 'Queuing...'; }
    function successToastText(count = 1) { return count === 1 ? 'Goods Receive Note queued' : `${count} Goods Receive Notes queued`; }

    function escapeHtml(s) {
        if (!s) return '';
        return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));
    }

    function fmtQty(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0' : String(Math.round(x));
    }

    function fmtKg(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0.00' : x.toFixed(2);
    }

    function fmtMoney(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0.00' : x.toFixed(2);
    }

    function parseQty(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : Math.round(x);
    }

    function parseKg(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : Number(x.toFixed(2));
    }

    function parseMoney(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : x;
    }

    function roundMoney(n) {
        return Number(parseMoney(n).toFixed(2));
    }

    function calcTotalKg(qty, kg) {
        return Number(((parseQty(qty) || 0) * (parseKg(kg) || 0)).toFixed(2));
    }

    function kgKey(n) {
        return fmtKg(parseKg(n));
    }

    function moneyKey(n) {
        return fmtMoney(parseMoney(n));
    }

    function calcTotalPrice(line) {
        return roundMoney(parseMoney(line?.price || 0) * (parseFloat(line?.total) || 0));
    }

    function normalizeBatchId(value) {
        return String(value || '').replace(/[^a-zA-Z0-9\-_.:]/g, '').slice(0, 80);
    }
    function makeBulkBatchId() {
        return normalizeBatchId(`GRNBULK-${Date.now()}-${Math.random().toString(36).slice(2)}`);
    }

    function extractReturnedDocNo(response) {
        return response?.localDocNo
            || response?.local_doc_no
            || response?.sourceDocNo
            || response?.source_doc_no
            || response?.docNo
            || response?.doc_no
            || '';
    }

    function buildGrnCompatMeta(group, bulkBatchId, groupIndex) {
        return {
            mode: GRN_MODE,
            schemaVersion: 'wpgrn-local-v1',
            legacyQueueCompatible: true,
            sourceType: 'GOODS_RECEIVE_NOTE',
            sourceSystem: 'WORDPRESS',
            requestedDocPrefix: REQUESTED_DOC_PREFIX,
            requestedDocNoMode: 'SERVER_GENERATED',
            requestedDocNoFormat: `${REQUESTED_DOC_PREFIX}{yyMM}/0000`,
            localDocNo: '',
            deliveryStatus: '',
            delivery_status: '',
            localGrnId: null,
            bulkBatchId,
            groupIndex,
            creditorCode: group?.creditorCode || ''
        };
    }

    function showToast(icon, title, text='') {
        if (window.Swal) {
            Swal.fire({ toast: true, position: 'center', icon, title, text, showConfirmButton: false, timer: 2600, timerProgressBar: true });
        } else {
            alert(title + (text ? '\n' + text : ''));
        }
    }
    function showModal(icon, title, html) {
        if (window.Swal) {
            Swal.fire({ icon, title, html, confirmButtonText: 'OK' });
        } else {
            alert(title + '\n' + html);
        }
    }

    function savedJobListHtml(savedJobs) {
        if (!savedJobs.length) return '<p>No Goods Receive Notes were queued.</p>';
        const rows = savedJobs.map(job => {
            const creditor = escapeHtml(job.creditorName || job.creditorCode || '-');
            const docNo = escapeHtml(job.docNo || 'Queued');
            const jobId = escapeHtml(job.jobId || '-');
            return `<li><strong>${docNo}</strong> | ${creditor} | Job #${jobId}</li>`;
        }).join('');
        return `<ul style="text-align:left;margin:.75rem 0 0;padding-left:1.25rem;">${rows}</ul>`;
    }

    function showBulkSuccessModal(result) {
        const count = result?.count || 0;
        const label = count === 1 ? '1 Goods Receive Note' : `${count} Goods Receive Notes`;
        if (window.Swal) {
            Swal.fire({
                icon: 'success',
                title: 'Goods Receive Notes Queued',
                html: `<p>${escapeHtml(label)} queued for AutoCount.</p><p>GRN numbers are still generating.</p>`,
                confirmButtonText: 'OK'
            });
        } else {
            alert(label + ' queued for AutoCount. GRN numbers are still generating.');
        }
    }

    function showBulkPartialFailureModal(result) {
        const savedJobs = result?.savedJobs || [];
        const errorMessage = result?.errorMessage || 'Submit failed';
        const savedCount = savedJobs.length;
        const title = savedCount
            ? `${savedCount} GRN${savedCount === 1 ? '' : 's'} already queued`
            : 'Goods Receive Note submit failed';
        const html = `
            <p>${escapeHtml(errorMessage)}</p>
            ${savedCount ? '<p><strong>Do not resubmit these queued GRNs.</strong> They were removed from the form, so retry will submit only the remaining unsaved items.</p>' : ''}
            ${savedJobListHtml(savedJobs)}
        `;
        showModal(savedCount ? 'warning' : 'error', title, html);
    }

    function updateEntryTotal() {
        const itemCode = ($('acd_resp_grn_item').value || '').trim();
        const itemName = ($('acd_resp_grn_item_display').value || '').trim() || itemCode;
        const packType = ($('acd_resp_grn_pack_type').value || '').trim();
        const qtyRaw = ($('acd_resp_grn_qty').value || '').trim();
        const kgRaw = ($('acd_resp_grn_kg').value || '').trim();
        const priceRaw = ($('acd_resp_grn_price').value || '').trim();
        const qty = parseQty(qtyRaw || '0');
        const kg = parseKg(kgRaw || '0');
        const price = parseMoney(priceRaw || '0');
        const total = calcTotalKg(qty, kg);
        const totalPrice = roundMoney(price * total);
        const pv = $('acd_resp_grn_line_preview');
        if (!itemCode || qtyRaw === '' || kgRaw === '') {
            pv.style.display = 'none';
            pv.innerHTML = '';
            return;
        }
        pv.style.display = 'block';
        pv.innerHTML = `<div><b>${escapeHtml(itemName)}</b></div>
                        <div>Type: ${escapeHtml(packType)} | Qty: ${fmtQty(qty)} | KG: ${fmtKg(kg)} | Total KG: ${fmtKg(total)} | Price: ${fmtMoney(price)} | Total: ${fmtMoney(totalPrice)}</div>`;
    }

    function setPackType(type) {
        const nextType = String(type || '').toUpperCase() === 'BASKET' ? 'BASKET' : 'CARTON';
        $('acd_resp_grn_pack_type').value = nextType;
        document.querySelectorAll('#acd_resp_grn_pack_type_toggle .acd-resp-type-btn').forEach(btn => {
            btn.classList.toggle('active', (btn.dataset.packType || '').toUpperCase() === nextType);
        });
        updateEntryTotal();
    }

    function updateUI() {
        const lines = state.lines;
        const badge = document.getElementById('acd_resp_grn_lines_count_badge');
        if (badge) badge.innerText = lines.length;

        const container = $('acd_resp_grn_lines');
        if (!lines.length) {
            container.innerHTML = '<div class="acd-resp-empty">No items added</div>';
            return;
        }

        container.innerHTML = lines.map((l, idx) => `
            <div class="acd-resp-line" data-idx="${idx}">
                <div><strong>${escapeHtml(l.itemName || l.itemCode)}</strong></div>
                <div><strong>${escapeHtml(l.creditorName || l.creditorCode)}</strong></div>
                <div><span class="acd-resp-type-pill">${l.packType}</span></div>
                <div class="acd-resp-number-cell">${fmtQty(l.qty)}</div>
                <div class="acd-resp-number-cell">${fmtKg(l.kg)}</div>
                <div class="acd-resp-number-cell">${fmtKg(l.total)}</div>
                <div class="acd-resp-price-cell"><input type="number" class="acd-resp-price-input" data-price-idx="${idx}" value="${fmtMoney(l.price)}" min="0" step="0.01" inputmode="decimal" aria-label="Price for ${escapeHtml(l.itemName || l.itemCode)}"></div>
                <div class="acd-resp-money-cell" data-total-price-idx="${idx}">${fmtMoney(calcTotalPrice(l))}</div>
                <div><button type="button" class="acd-resp-delete-btn" data-idx="${idx}" aria-label="Delete ${escapeHtml(l.itemName || l.itemCode)}" title="Delete">&#128465;</button></div>
            </div>
        `).join('');
    }

    function updateLinePrice(idx, value, shouldFormatInput = false) {
        if (isNaN(idx) || !state.lines[idx]) return;
        state.lines[idx].price = parseMoney(value);
        const nextTotal = fmtMoney(calcTotalPrice(state.lines[idx]));
        document.querySelectorAll(`[data-total-price-idx="${idx}"]`).forEach(el => {
            el.textContent = nextTotal;
        });
        if (shouldFormatInput) {
            document.querySelectorAll(`[data-price-idx="${idx}"]`).forEach(input => {
                input.value = fmtMoney(state.lines[idx].price);
            });
        }
    }

    async function apiGet(url) {
        const res = await fetch(url, { method: 'GET', credentials: 'same-origin', headers: { 'Accept': 'application/json', 'X-WP-Nonce': REST_NONCE }, cache: 'no-store' });
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const text = await res.text();
        return text ? JSON.parse(text) : null;
    }
    async function apiPost(url, body) {
        const res = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': REST_NONCE }, body: JSON.stringify(body) });
        let data = null;
        const text = await res.text();
        try { data = text ? JSON.parse(text) : null; } catch (e) { data = { raw: text }; }
        if (!res.ok) {
            const message = data?.message || data?.error || `HTTP ${res.status}`;
            const err = new Error(message);
            err.status = res.status;
            err.data = data;
            throw err;
        }
        return data;
    }

    function buildPayloadLine(l, location) {
        const displayName = String(l.itemName || l.itemCode || '').trim();
        const isBasket = (l.packType === 'BASKET');
        const count = l.qty;
        const weightPerUnit = l.kg;
        const totalWeight = l.total;
        const unitPrice = roundMoney(l.price || 0);
        const amount = roundMoney(unitPrice * totalWeight);

        return {
            itemCode: l.itemCode,
            description: displayName,
            itemName: displayName,
            ItemName: displayName,
            itemDesc: displayName,
            uom: 'KG',
            unitPrice,
            amount,
            taxCode: 'SR-0',
            taxRate: 0,
            packType: l.packType,
            qty: totalWeight,
            kg: weightPerUnit,
            totalKg: totalWeight,
            unitQty: count,
            basketQty: isBasket ? count : null,
            cartonQty: !isBasket ? count : null,
            location
        };
    }

    function groupKey(creditorCode) {
        return `${creditorCode}`;
    }

    function groupLinesByCreditor(lines) {
        const groups = new Map();
        lines.forEach(line => {
            const key = groupKey(line.creditorCode);
            if (!groups.has(key)) {
                groups.set(key, {
                    key,
                    creditorCode: line.creditorCode,
                    creditorName: line.creditorName,
                    lines: []
                });
            }
            groups.get(key).lines.push(line);
        });
        return Array.from(groups.values());
    }

    function removeSavedGroupsFromForm(savedJobs) {
        const savedKeys = new Set(savedJobs.map(job => job.groupKey).filter(Boolean));
        if (!savedKeys.size) return;
        state.lines = state.lines.filter(line => !savedKeys.has(groupKey(line.creditorCode)));
        updateUI();
    }

    function clearGrnFormAfterSave() {
        clearCreditorSelection();
        clearLineEntry();
        state.lines = [];
        state.jobFinished = false;
        state.savedPendingClear = false;
        const submitBtn = $('acd_resp_grn_submit');
        if (submitBtn) {
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        }
        updateUI();
        updateClearButtons();
    }

    async function searchItemsLive(q) {
        if (!AJAX_URL || !ITEM_NONCE) {
            console.warn('[Item Search] Missing AJAX_URL or ITEM_NONCE', { AJAX_URL, ITEM_NONCE });
            return [];
        }

        const url =
            `${AJAX_URL}?action=ac_itemcode_suggest` +
            `&nonce=${encodeURIComponent(ITEM_NONCE)}` +
            `&_ajax_nonce=${encodeURIComponent(ITEM_NONCE)}` +
            `&term=${encodeURIComponent(q)}` +
            `&q=${encodeURIComponent(q)}` +
            `&keyword=${encodeURIComponent(q)}`;

        const res = await fetch(url, {
            method: 'GET',
            credentials: 'same-origin',
            cache: 'no-store'
        <ce�`�[y��������<cw�
N?�z});

        const text = await res.text();
        let data = null;

        try {
            data = text ? JSON.parse(text) : null;
        } catch (e) {
            console.error('[Item Search] Non-JSON response:', text);
            throw new Error('Item search returned invalid response.');
        }

        console.log('[Item Search] Response:', data);

        if (!data) {
            return [];
        }

        let rows = [];

        if (Array.isArray(data)) {
            rows = data;
        } else if (Array.isArray(data.items)) {
            rows = data.items;
        } else if (Array.isArray(data.data)) {
            rows = data.data;
        } else if (Array.isArray(data.data?.items)) {
            rows = data.data.items;
        } else if (Array.isArray(data.results)) {
            rows = data.results;
        } else if (Array.isArray(data.data?.results)) {
            rows = data.data.results;
        }

        return rows.map(it => {
            const code =
                it.code ||
                it.itemCode ||
                it.ItemCode ||
                it.item_code ||
                it.value ||
                '';

            const name =
                it.desc ||
                it.description ||
                it.Description ||
                it.name ||
                it.itemName ||
                it.ItemName ||
                it.label ||
                code;

            const price =
                it.price ??
                it.Price ??
                it.unitPrice ??
                it.UnitPrice ??
                it.salesPrice ??
                it.SalesPrice ??
                0;

            return {
                code: String(code || '').trim(),
                name: String(name || code || '').trim(),
                price: parseMoney(price)
            };
        }).filter(it => it.code || it.name);
    }

    async function searchCreditorsLive(q) {
        const wrapper = $('acdRespCreditorWrapper');
        const url = `${wrapper.dataset.ajaxUrl}?action=ac_cs_creditor_search&nonce=${encodeURIComponent(wrapper.dataset.nonce)}&q=${encodeURIComponent(q)}`;
        const res = await fetch(url, { credentials: 'same-origin' });
        const data = await res.json();
        if (!data.success) throw new Error(data.data?.error || 'Search failed');
        const items = data.data?.items || [];
        return items.map(it => {
            const name = it.name || it.creditorName || '';
            const code = it.code || it.creditorCode || '';
            const meta = [];
            if (DROPDOWN_META.showCreditorCode && code) meta.push(code);
            return { label: name || code, meta: meta.join('  |  '), raw: { name, code } };
        });
    }

    function renderPickerNote(msg) { $('acd_resp_grn_picker_results').innerHTML = `<div class="acd-resp-picker-note">${escapeHtml(msg)}</div>`; }
    function renderPickerItems(items) {
        const box = $('acd_resp_grn_picker_results');
        if (!items.length) { renderPickerNote('No result found'); return; }
        box.innerHTML = items.map((it, idx) => `<button type="button" class="acd-resp-picker-item" data-picker-idx="${idx}"><span class="acd-resp-picker-item-main">${escapeHtml(it.label || '')}</span>${it.meta ? `<span class="acd-resp-picker-item-sub">${escapeHtml(it.meta)}</span>` : ''}</button>`).join('');
    }
    async function runPickerSearch(q) {
        const query = (q || '').trim();
        clearTimeout(pickerTimer);
        if (query.length < 1) {
            pickerState.items = pickerState.defaultItems || [];
            if (pickerState.items.length) {
                renderPickerItems(pickerState.items);
            } else {
                renderPickerNote('Type to search');
            }
            return;
        }
        pickerTimer = setTimeout(async () => {
            renderPickerNote('Searching...');
            try {
                const items = await pickerState.fetchFn(query);
                pickerState.items = items || [];
                renderPickerItems(pickerState.items);
            } catch(e) { pickerState.items = []; renderPickerNote('Failed to load'); }
        }, 220);
    }
    function openPicker(opts) {
        pickerState.defaultItems = opts.initialItems || [];
        pickerState.items = pickerState.defaultItems;
        pickerState.fetchFn = opts.fetchFn;
        pickerState.onPick = opts.onPick;
        $('acd_resp_grn_picker_title').textContent = opts.title || 'Search';
        $('acd_resp_grn_picker_search').placeholder = opts.placeholder || 'Type to search...';
        $('acd_resp_grn_picker_search').value = '';
        $('acd_resp_grn_picker_modal').classList.add('active');
        if (pickerState.items.length) {
            renderPickerItems(pickerState.items);
        } else {
            renderPickerNote('Type to search');
        }
        setTimeout(() => $('acd_resp_grn_picker_search').focus(), 80);
    }
    function closePicker() {
        $('acd_resp_grn_picker_modal').classList.remove('active');
        $('acd_resp_grn_picker_search').value = '';
        $('acd_resp_grn_picker_results').innerHTML = '';
        pickerState.items = []; pickerState.defaultItems = []; pickerState.fetchFn = null; pickerState.onPick = null;
    }
    function updateClearButtons() {
        const creditorHas = !!($('acdRespCreditorInput')?.value.trim());
        const itemHas = !!($('acd_resp_grn_item_name')?.value.trim());
        $('acdRespCreditorClear')?.classList.toggle('show', creditorHas);
        $('acdRespGrnItemClear')?.classList.toggle('show', itemHas);
    }

    function setCreditor(picked) {
        const name = picked?.name || '';
        const code = picked?.code || '';
        $('acdRespCreditorInput').value = name || code || '';
        $('acd_resp_grn_creditor').value = code;
        $('acd_resp_grn_creditor_name').value = name;
        updateClearButtons();
    }

    function clearCreditorSelection() {
        $('acdRespCreditorInput').value = '';
        $('acd_resp_grn_creditor').value = '';
        $('acd_resp_grn_creditor_name').value = '';
        updateClearButtons();
    }

    function clearItemSelection() {
        $('acd_resp_grn_item_name').value = '';
        $('acd_resp_grn_item').value = '';
        $('acd_resp_grn_item_display').value = '';
        $('acd_resp_grn_price').value = '';
        updateEntryTotal();
        updateClearButtons();
    }

    function openCreditorPicker() {
        openPicker({
            title: 'Select Creditor',
            placeholder: 'Search creditor...',
            fetchFn: searchCreditorsLive,
            onPick: (picked) => {
                if (!picked) return;
                setCreditor(picked);
                closePicker();
            }
        });
    }

    function openItemPicker() {
        openPicker({
            title: 'Select Item',
            placeholder: 'Search item...',
            fetchFn: async (q) => {
                const items = await searchItemsLive(q);
                return items.map(it => ({
                    label: it.name || it.code,
                    meta: (DROPDOWN_META.showItemCode && it.code) ? it.code : '',
                    raw: { code: it.code, name: it.name || it.code, price: it.price || 0 }
                }));
            },
            onPick: (picked) => {
                if (!picked) return;
                $('acd_resp_grn_item_name').value = picked.name || picked.code || '';
                $('acd_resp_grn_item').value = picked.code || '';
                $('acd_resp_grn_item_display').value = picked.name || picked.code || '';
                const rawPrice = Number(picked.price || 0);
                $('acd_resp_grn_price').value = rawPrice > 0 ? String(rawPrice.toFixed(2)) : '';
                updateEntryTotal();
                updateClearButtons();
                closePicker();
            }
        });
    }

    function initPickerModal() {
        $('acd_resp_grn_picker_close').addEventListener('click', closePicker);
        $('acd_resp_grn_picker_backdrop').addEventListener('click', closePicker);
        $('acd_resp_grn_picker_search').addEventListener('input', function() { runPickerSearch(this.value); });
        $('acd_resp_grn_picker_results').addEventListener('click', (e) => {
            const btn = e.target.closest('[data-picker-idx]');
            if (!btn) return;
            const idx = parseInt(btn.dataset.pickerIdx);
            if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) pickerState.onPick(pickerState.items[idx].raw);
        });
    }
    function initPickerTriggers() {
        $('acdRespCreditorInput').setAttribute('readonly', 'readonly');
        $('acd_resp_grn_item_name').setAttribute('readonly', 'readonly');
        $('acdRespCreditorInput').addEventListener('click', openCreditorPicker);
        $('acd_resp_grn_item_name').addEventListener('click', openItemPicker);
        $('acdRespCreditorClear')?.addEventListener('click', (e) => { e.preventDefault(); clearCreditorSelection(); });
        $('acdRespGrnItemClear')?.addEventListener('click', (e) => { e.preventDefault(); clearItemSelection(); });
    }
    function makeClientRequestId(prefix='GRN') { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`; }

    function clearLineEntry() {
        $('acd_resp_grn_qty').value = '';
        $('acd_resp_grn_kg').value = '';
        $('acd_resp_grn_price').value = '';
        $('acd_resp_grn_item_name').value = '';
        $('acd_resp_grn_item').value = '';
        $('acd_resp_grn_item_display').value = '';
        updateEntryTotal();
        updateClearButtons();
    }

    // ---- MERGE LOGIC (same creditor + item + type + KG + price) ----
    function findMergeableLineIndex(nextLine) {
        return state.lines.findIndex(line => {
            return String(line.creditorCode || '') === String(nextLine.creditorCode || '')
                && String(line.itemCode || '').toUpperCase() === String(nextLine.itemCode || '').toUpperCase()
                && String(line.packType || '').toUpperCase() === String(nextLine.packType || '').toUpperCase()
                && kgKey(line.kg) === kgKey(nextLine.kg)
                && moneyKey(line.price) === moneyKey(nextLine.price);
        });
    }

    function mergeLine(existingLine, nextLine) {
        const mergedQty = parseQty(existingLine.qty || 0) + parseQty(nextLine.qty || 0);
        const sameKg = parseKg(existingLine.kg || 0);
        existingLine.qty = mergedQty;
        existingLine.kg = sameKg;
        existingLine.total = calcTotalKg(mergedQty, sameKg);
        return existingLine;
    }

    function hideGrnSuccessActions() {
        const box = $('acd_resp_grn_success_actions');
        const docNoEl = $('acd_resp_grn_success_docno');
        if (box) box.style.display = 'none';
        if (docNoEl) docNoEl.textContent = '-';
    }

    function showGrnSuccessActions(data) {
        const box = $('acd_resp_grn_success_actions');
        const docNoEl = $('acd_resp_grn_success_docno');
        const docNo = data?.docNo || data?.batchLabel || '-';
        if (docNoEl) docNoEl.textContent = docNo;
        if (box) box.style.display = 'block';
    }

    function resetGrnForm() {
        clearCreditorSelection();
        clearLineEntry();
        state.lines = [];
        state.jobFinished = false;
        state.savedPendingClear = false;
        const dateField = $('acd_resp_grn_date');
        if (dateField) dateField.value = root.dataset.today || '';
        const submitBtn = $('acd_resp_grn_submit');
        if (submitBtn) {
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        }
        hideGrnSuccessActions();
        updateUI();
        updateClearButtons();
    }

    initPickerModal();
    initPickerTriggers();
    $('acd_resp_grn_qty').addEventListener('input', updateEntryTotal);
    $('acd_resp_grn_kg').addEventListener('input', updateEntryTotal);
    $('acd_resp_grn_price').addEventListener('input', updateEntryTotal);
    $('acd_resp_grn_pack_type').addEventListener('change', () => setPackType($('acd_resp_grn_pack_type').value));
    document.querySelectorAll('#acd_resp_grn_pack_type_toggle .acd-resp-type-btn').forEach(btn => btn.addEventListener('click', () => setPackType(btn.dataset.packType)));
    setPackType('BASKET');
    updateUI();

    $('acd_resp_grn_addline').addEventListener('click', () => {
        const itemCode = ($('acd_resp_grn_item').value || '').trim();
        const itemName = ($('acd_resp_grn_item_display').value || '').trim() || itemCode;
        const packType = ($('acd_resp_grn_pack_type').value || '').trim();
        const qtyRaw = ($('acd_resp_grn_qty').value || '').trim();
        const kgRaw = ($('acd_resp_grn_kg').value || '').trim();
        const priceRaw = ($('acd_resp_grn_price').value || '').trim();
        const qty = parseQty(qtyRaw || '0');
        const kg = parseKg(kgRaw || '0');
        const price = parseMoney(priceRaw || '0');
        const creditorCode = ($('acd_resp_grn_creditor').value || '').trim();
        const creditorName = ($('acd_resp_grn_creditor_name').value || '').trim();

        if (!creditorCode) { showToast('error', 'Select creditor'); return; }
        if (!itemCode) { showToast('error', 'Select an item'); return; }
        if (qty <= 0) { showToast('error', 'Qty must be >0'); return; }
        if (kg <= 0) { showToast('error', 'KG must be >0'); return; }

        const nextLine = {
            creditorCode,
            creditorName,
            itemCode,
            itemName,
            packType,
            qty,
            kg,
            total: calcTotalKg(qty, kg),
            price
        };

        const existingIdx = findMergeableLineIndex(nextLine);
        if (existingIdx >= 0) {
            mergeLine(state.lines[existingIdx], nextLine);
            updateUI();
            clearLineEntry();
            showToast(
                'warning',
                'Same item + KG + price merged',
                `${itemName} ${fmtKg(kg)}KG @ ${fmtMoney(price)} already exists for ${creditorName || creditorCode}. Quantity has been added into the same row.`
            );
            return;
        }

        state.lines.push(nextLine);
        updateUI();
        clearLineEntry();
        showToast('success', 'Item added');
    });

    document.getElementById('acd_resp_grn_lines').addEventListener('click', (e) => {
        const btn = e.target.closest('.acd-resp-delete-btn');
        if (!btn) return;
        const idx = parseInt(btn.dataset.idx);
        if (!isNaN(idx)) {
            state.lines.splice(idx, 1);
            updateUI();
            showToast('info', 'Item removed');
        }
    });
    document.getElementById('acd_resp_grn_lines').addEventListener('input', (e) => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, false);
    });
    document.getElementById('acd_resp_grn_lines').addEventListener('change', (e) => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, true);
    });

    const clearNewBtn = $('acd_resp_grn_clear_new_btn');
    if (clearNewBtn) {
        clearNewBtn.addEventListener('click', () => {
            resetGrnForm();
            showToast('info', 'Ready for new GRN');
        });
    }

    $('acd_resp_grn_submit').addEventListener('click', async () => {
        if (state.isSubmitting) { showToast('warning', 'Already submitting'); return; }

        const submitBtn = $('acd_resp_grn_submit');
        let saveSucceeded = false;
        state.isSubmitting = true;
        state.jobFinished = false;
        submitBtn.disabled = true;
        submitBtn.textContent = submitProgressText()<cw�W_�<z��������S,�t
N?��<?php
if (!defined('ABSPATH')) {
    exit;
}

/**
 * WST Excellent Vege — Operations Dashboard
 *
 * Read-only operational dashboard covering:
 * - AutoCount bridge jobs
 * - Delivery Orders
 * - Purchase Invoices
 * - Goods Receive Notes
 * - Debtor basket balances
 * - Creditor basket balances
 * - Recent operational activity
 *
 * Recommended WordPress page slug:
 * /operations-dashboard/
 *
 * Permission:
 * - Administrator
 * - Users with edit_posts capability
 */

if (!is_user_logged_in()) {
    echo '<div class="wst-ops-message wst-ops-message-error">'
        . esc_html__('Please log in to view the operations dashboard.', 'wst-excellent-vege')
        . '</div>';
    return;
}

if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
    echo '<div class="wst-ops-message wst-ops-message-error">'
        . esc_html__('You do not have permission to view this dashboard.', 'wst-excellent-vege')
        . '</div>';
    return;
}

global $wpdb;

$is_administrator = current_user_can('manage_options');

if (!$wpdb) {
    echo '<div class="wst-ops-message wst-ops-message-error">'
        . esc_html__('The WordPress database connection is unavailable.', 'wst-excellent-vege')
        . '</div>';
    return;
}

/*
|--------------------------------------------------------------------------
| Helpers
|--------------------------------------------------------------------------
*/

if (!function_exists('wst_ops_table_exists')) {
    function wst_ops_table_exists($table_name) {
        global $wpdb;

        if (!$wpdb || trim((string)$table_name) === '') {
            return false;
        }

        $found = $wpdb->get_var(
            $wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($table_name))
        );

        return (string)$found === (string)$table_name;
    }
}

if (!function_exists('wst_ops_table_columns')) {
    function wst_ops_table_columns($table_name) {
        global $wpdb;

        static $cache = array();

        if (isset($cache[$table_name])) {
            return $cache[$table_name];
        }

        $cache[$table_name] = array();

        if (!wst_ops_table_exists($table_name)) {
            return $cache[$table_name];
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', (string)$table_name);
        if ($safe_table === '') {
            return $cache[$table_name];
        }

        $rows = $wpdb->get_results("SHOW COLUMNS FROM `{$safe_table}`", ARRAY_A);

        foreach ((array)$rows as $row) {
            if (!empty($row['Field'])) {
                $cache[$table_name][(string)$row['Field']] = true;
            }
        }

        return $cache[$table_name];
    }
}

if (!function_exists('wst_ops_has_column')) {
    function wst_ops_has_column($table_name, $column_name) {
        $columns = wst_ops_table_columns($table_name);
        return isset($columns[$column_name]);
    }
}

if (!function_exists('wst_ops_first_column')) {
    function wst_ops_first_column($table_name, array $candidates) {
        foreach ($candidates as $candidate) {
            if (wst_ops_has_column($table_name, $candidate)) {
                return $candidate;
            }
        }

        return '';
    }
}

if (!function_exists('wst_ops_valid_date')) {
    function wst_ops_valid_date($value, $fallback) {
        $value = trim((string)$value);

        if ($value === '') {
            return $fallback;
        }

        $date = DateTime::createFromFormat('Y-m-d', $value, wp_timezone());

        if (!$date || $date->format('Y-m-d') !== $value) {
            return $fallback;
        }

        return $value;
    }
}

if (!function_exists('wst_ops_safe_int')) {
    function wst_ops_safe_int($value) {
        return is_numeric($value) ? (int)$value : 0;
    }
}

if (!function_exists('wst_ops_safe_float')) {
    function wst_ops_safe_float($value) {
        return is_numeric($value) ? (float)$value : 0.0;
    }
}

if (!function_exists('wst_ops_format_number')) {
    function wst_ops_format_number($value, $decimals = 0) {
        return number_format_i18n((float)$value, (int)$decimals);
    }
}

if (!function_exists('wst_ops_format_money')) {
    function wst_ops_format_money($value, $currency = 'RM') {
        return trim((string)$currency) . ' ' . number_format_i18n((float)$value, 2);
    }
}

if (!function_exists('wst_ops_format_date')) {
    function wst_ops_format_date($value, $include_time = false) {
        $value = trim((string)$value);

        if ($value === '' || $value === '0000-00-00' || $value === '0000-00-00 00:00:00') {
            return '—';
        }

        $timestamp = strtotime($value);

        if (!$timestamp) {
            return $value;
        }

        return wp_date($include_time ? 'd M Y, g:i A' : 'd M Y', $timestamp);
    }
}

if (!function_exists('wst_ops_status_label')) {
    function wst_ops_status_label($status) {
        $status = strtoupper(trim((string)$status));

        $labels = array(
            'PENDING'                 => 'Pending',
            'PROCESSING'              => 'Processing',
            'SUCCESS'                 => 'Successful',
            'COMPLETED'               => 'Completed',
            'FAILED'                  => 'Failed',
            'FAILED_FINAL'            => 'Failed Final',
            'CANCELLED'               => 'Cancelled',
            'ACTIVE'                  => 'Active',
            'PENDING_DELIVERY'        => 'Pending Delivery',
            'ASSIGNED'                => 'Assigned',
            'SCHEDULED'               => 'Scheduled',
            'DRIVER_ACKNOWLEDGED'     => 'Driver Received',
            'RECEIVED'                => 'Driver Received',
            'OUT_FOR_DELIVERY'        => 'Out for Delivery',
            'DELIVERED'               => 'Delivered',
            'EDIT_PENDING_AUTOCOUNT'  => 'Edit Pending',
            'EDITED_IN_AUTOCOUNT'     => 'Edited in AutoCount',
            'VOID_PENDING_AUTOCOUNT'  => 'Delete Pending',
            'VOID_FAILED'             => 'Delete Failed',
            'VOIDED_IN_AUTOCOUNT'     => 'Deleted in AutoCount',
            'SYNCED'                  => 'Synced',
        );

        if (isset($labels[$status])) {
            return $labels[$status];
        }

        return $status !== ''
            ? ucwords(strtolower(str_replace('_', ' ', $status)))
            : 'Unknown';
    }
}

if (!function_exists('wst_ops_status_class')) {
    function wst_ops_status_class($status) {
        $status = strtoupper(trim((string)$status));

        if (in_array($status, array(
            'SUCCESS',
            'COMPLETED',
            'DELIVERED',
            'EDITED_IN_AUTOCOUNT',
            'VOIDED_IN_AUTOCOUNT',
            'SYNCED',
            'ACTIVE',
        ), true)) {
            return 'wst-ops-status-good';
        }

        if (in_array($status, array(
            'FAILED',
            'FAILED_FINAL',
            'VOID_FAILED',
            'CANCELLED',
        ), true)) {
            return 'wst-ops-status-danger';
        }

        if (in_array($status, array(
            'PENDING',
            'PROCESSING',
            'PENDING_DELIVERY',
            'ASSIGNED',
            'SCHEDULED',
            'OUT_FOR_DELIVERY',
            'EDIT_PENDING_AUTOCOUNT',
            'VOID_PENDING_AUTOCOUNT',
        ), true)) {
            return 'wst-ops-status-warning';
        }

        return 'wst-ops-status-neutral';
    }
}

if (!function_exists('wst_ops_query_value')) {
    function wst_ops_query_value($sql, array $params = array(), $fallback = 0) {
        global $wpdb;

        if (!$wpdb || trim((string)$sql) === '') {
            return $fallback;
        }

        if ($params) {
            $sql = $wpdb->prepare($sql, $params);
        }

        $value = $wpdb->get_var($sql);

        if ($wpdb->last_error) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('[WST Operations Dashboard] ' . $wpdb->last_error);
            }

            return $fallback;
        }

        return $value !== null ? $value : $fallback;
    }
}

if (!function_exists('wst_ops_query_rows')) {
    function wst_ops_query_rows($sql, array $params = array()) {
        global $wpdb;

        if (!$wpdb || trim((string)$sql) === '') {
            return array();
        }

        if ($params) {
            $sql = $wpdb->prepare($sql, $params);
        }

        $rows = $wpdb->get_results($sql, ARRAY_A);

        if ($wpdb->last_error) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                error_log('[WST Operations Dashboard] ' . $wpdb->last_error);
            }

            return array();
        }

        return is_array($rows) ? $rows : array();
    }
}

if (!function_exists('wst_ops_date_condition')) {
    function wst_ops_date_condition($column, $start_date, $end_date) {
        $safe_column = preg_replace('/[^A-Za-z0-9_]/', '', (string)$column);

        if ($safe_column === '') {
            return array('', array());
        }

        return array(
            "DATE(`{$safe_column}`) BETWEEN %s AND %s",
            array($start_date, $end_date)
        );
    }
}

if (!function_exists('wst_ops_build_page_url')) {
    function wst_ops_build_page_url($slug, array $args = array()) {
        $url = home_url('/' . trim((string)$slug, '/') . '/');

        if ($args) {
            $url = add_query_arg($args, $url);
        }

        return $url;
    }
}

/*
|--------------------------------------------------------------------------
| Filter period
|--------------------------------------------------------------------------
*/

$today = current_time('Y-m-d');
$default_start = wp_date(
    'Y-m-d',
    strtotime('-6 days', current_time('timestamp'))
);

$start_date = isset($_GET['wst_start'])
    ? wst_ops_valid_date(wp_unslash($_GET['wst_start']), $default_start)
    : $default_start;

$end_date = isset($_GET['wst_end'])
    ? wst_ops_valid_date(wp_unslash($_GET['wst_end']), $today)
    : $today;

if ($start_date > $end_date) {
    $temporary_date = $start_date;
    $start_date = $end_date;
    $end_date = $temporary_date;
}

$range_days = max(
    1,
    ((int)floor((strtotime($end_date) - strtotime($start_date)) / DAY_IN_SECONDS)) + 1
);

/*
|--------------------------------------------------------------------------
| Tables
|--------------------------------------------------------------------------
*/

$tables = array(
    'jobs'                    => $wpdb->prefix . 'ac_jobs',
    'delivery_orders'         => $wpdb->prefix . 'ac_do',
    'delivery_order_items'    => $wpdb->prefix . 'ac_do_items',
    'delivery_proofs'         => $wpdb->prefix . 'ac_do_proof_images',
    'purchase_invoices'       => $wpdb->prefix . 'ac_pi',
    'purchase_invoice_items'  => $wpdb->prefix . 'ac_pi_items',
    'goods_receive'           => $wpdb->prefix . 'ac_gr',
    'goods_receive_items'     => $wpdb->prefix . 'ac_gr_items',
    'debtor_baskets'          => $wpdb->prefix . 'ac_basket_ledger',
    'creditor_baskets'        => $wpdb->prefix . 'ac_creditor_basket_ledger',
    'sync_log'                => $wpdb->prefix . 'ac_sync_log',
);

$table_availability = array();

foreach ($tables as $table_key => $table_name) {
    $table_availability[$table_key] = wst_ops_table_exists($table_name);
}

/*
|--------------------------------------------------------------------------
| Job queue statistics
|--------------------------------------------------------------------------
*/

$job_stats = array(
    'total'      => 0,
    'pending'    => 0,
    'processing' => 0,
    'success'    => 0,
    'failed'     => 0,
    'retrying'   => 0,
);

$recent_failed_jobs = array();
$job_type_breakdown = array();

if ($table_availability['jobs']) {
    $jobs_table = preg_replace('/[^A-Za-z0-9_]/', '', $tables['jobs']);
    $job_date_column = wst_ops_first_column(
        $tables['jobs'],
        array('created_at', 'updated_at')
    );

    $job_status_column = wst_ops_first_column(
        $tables['jobs'],
        array('status')
    );

    if ($job_date_column !== '' && $job_status_column !== '') {
        list($job_date_sql, $job_date_params) = wst_ops_date_condition(
            $job_date_column,
            $start_date,
            $end_date
        );

        $job_rows = wst_ops_query_rows(
            "SELECT
                UPPER(COALESCE(`{$job_status_column}`, '')) AS status_name,
                COUNT(*) AS total_count
             FROM `{$jobs_table}`
             WHERE {$job_date_sql}
             GROUP BY UPPER(COALESCE(`{$job_status_column}`, ''))",
            $job_date_params
        );

        foreach ($job_rows as $row) {
            $status = strtoupper((string)($row['status_name'] ?? ''));
            $count = wst_ops_safe_int($row['total_count'] ?? 0);

            $job_stats['total'] += $count;

            if ($status === 'PENDING') {
                $job_stats['pending'] += $count;
            } elseif ($status === 'PROCESSING') {
                $job_stats['processing'] += $count;
            } elseif (in_array($status, array('SUCCESS', 'COMPLETED'), true)) {
                $job_stats['success'] += $count;
            } elseif (in_array($status, array('FAILED', 'FAILED_FINAL'), true)) {
                $job_stats['failed'] += $count;
            }
        }

        $retry_column = wst_ops_first_column(
            $tables['jobs'],
            array('retry_count')
        );

        if ($retry_column !== '') {
            $job_stats['retrying'] = wst_ops_safe_int(
                wst_ops_query_value(
                    "SELECT COUNT(*)
                     FROM `{$jobs_table}`
                     WHERE {$job_date_sql}
                       AND COALESCE(`{$retry_column}`, 0) > 0
                       AND UPPER(COALESCE(`{$job_status_column}`, '')) NOT IN ('SUCCESS', 'COMPLETED')",
                    $job_date_params
                )
            );
        }

        $job_type_column = wst_ops_first_column(
            $tables['jobs'],
            array('job_type', 'document_type')
        );

        if ($job_type_column !== '') {
            $job_type_breakdown = wst_ops_query_rows(
                "SELECT
                    UPPER(COALESCE(`{$job_type_column}`, 'UNKNOWN')) AS document_type,
                    COUNT(*) AS total_count,
                    SUM(
                        CASE
                            WHEN UPPER(COALESCE(`{$job_status_column}`, '')) IN ('FAILED', 'FAILED_FINAL')
                            THEN 1
                            ELSE 0
                        END
                    ) AS failed_count,
                    SUM(
                        CASE
                            WHEN UPPER(COALESCE(`{$job_status_column}`, '')) IN ('PENDING', 'PROCESSING')
                            THEN 1
                            ELSE 0
                        END
                    ) AS open_count
                 FROM `{$jobs_table}`
                 WHERE {$job_date_sql}
                 GROUP BY UPPER(COALESCE(`{$job_type_column}`, 'UNKNOWN'))
                 ORDER BY total_count DESC
                 LIMIT 8",
                $job_date_params
            );
        }

        $job_id_column = wst_ops_first_column(
            $tables['jobs'],
            array('id')
        );

        $job_doc_column = wst_ops_first_column(
            $tables['jobs'],
            array('local_doc_no', 'source_doc_no')
        );

        $job_error_column = wst_ops_first_column(
            $tables['jobs'],
            array('error_message', 'last_error')
        );

        $job_updated_column = wst_ops_first_column(
            $tables['jobs'],
            array('updated_at', 'created_at')
        );

        if (
            $job_id_column !== ''
            && $job_type_column !== ''S,�tϽ�{��������7��<
N?�|                                        <div class="wst-docp-table-wrap">
                                            <table class="wst-docp-table">
                                                <thead>
                                                    <tr>
                                                        <th>Item</th>
                                                        <th>Pack</th>
                                                        <th class="wst-docp-num">Qty</th>
                                                        <th class="wst-docp-num">KG / Unit</th>
                                                        <th class="wst-docp-num">Total KG</th>
                                                        <th class="wst-docp-num">Unit Price</th>
                                                        <th class="wst-docp-num">Amount</th>
                                                    </tr>
                                                </thead>
                                                <tbody>
                                                    <?php foreach ($do['items'] as $item): ?>
                                                        <?php
                                                        $item_id = (int) ($item['id'] ?? 0);
                                                        $price = wst_docp_float($item['unit_price'] ?? 0);
                                                        $pack = $item['_pack'];
                                                        $description = trim((string) ($item['description'] ?? ''));
                                                        $item_code = trim((string) ($item['item_code'] ?? ''));
                                                        ?>
                                                        <tr class="wst-docp-line <?php echo $item['_missing_price'] ? 'is-missing-price' : ''; ?> <?php echo !empty($pack['is_freight']) ? 'is-freight-line' : ''; ?>"
                                                            data-do-id="<?php echo esc_attr((string) $do_id); ?>"
                                                            data-customer-index="<?php echo esc_attr((string) $group_index); ?>">
                                                            <td data-label="Item">
                                                                <strong><?php echo esc_html($description !== '' ? $description : $item_code); ?></strong>
                                                                <?php if ($item_code !== ''): ?>
                                                                    <small><?php echo esc_html($item_code); ?></small>
                                                                <?php endif; ?>
                                                                <?php if (!empty($pack['is_freight'])): ?>
                                                                    <span class="wst-docp-freight-line-badge">Freight charge</span>
                                                                <?php endif; ?>
                                                            </td>
                                                            <td data-label="Pack">
                                                                <?php echo esc_html(ucfirst(strtolower($pack['pack_type']))); ?>
                                                            </td>
                                                            <td data-label="Qty" class="wst-docp-num">
                                                                <?php echo esc_html(number_format_i18n((float) $pack['unit_qty'], 2)); ?>
                                                            </td>
                                                            <td data-label="KG / Unit" class="wst-docp-num">
                                                                <?php echo esc_html(number_format_i18n((float) $pack['kg_per_unit'], 2)); ?>
                                                            </td>
                                                            <td data-label="Total KG" class="wst-docp-num">
                                                                <?php echo esc_html(number_format_i18n((float) $pack['total_kg'], 2)); ?>
                                                            </td>
                                                            <td data-label="Unit Price" class="wst-docp-num wst-docp-price-cell">
                                                                <span class="wst-docp-rm">RM</span>
                                                                <input type="number"
                                                                       class="wst-docp-price-input"
                                                                       inputmode="decimal"
                                                                       min="0"
                                                                       max="<?php echo esc_attr((string) WST_DOCP_MAX_UNIT_PRICE); ?>"
                                                                       step="0.01"
                                                                       value="<?php echo $price > 0 ? esc_attr(number_format($price, 2, '.', '')) : ''; ?>"
                                                                       placeholder="Missing"
                                                                       aria-label="Unit price for <?php echo esc_attr($description !== '' ? $description : $item_code); ?>"
                                                                       data-do-id="<?php echo esc_attr((string) $do_id); ?>"
                                                                       data-item-id="<?php echo esc_attr((string) $item_id); ?>"
                                                                       data-customer-index="<?php echo esc_attr((string) $group_index); ?>"
                                                                       data-original="<?php echo esc_attr(number_format($price, 6, '.', '')); ?>"
                                                                       data-total-kg="<?php echo esc_attr(number_format((float) $pack['total_kg'], 6, '.', '')); ?>"
                                                                       data-amount-qty="<?php echo esc_attr(number_format((float) ($pack['amount_qty'] ?? $pack['total_kg']), 6, '.', '')); ?>">
                                                            </td>
                                                            <td data-label="Amount" class="wst-docp-num wst-docp-line-amount" data-line-amount="<?php echo esc_attr((string) $item_id); ?>">
                                                                RM <?php echo esc_html(number_format_i18n((float) $item['_amount'], 2)); ?>
                                                            </td>
                                                        </tr>
                                                    <?php endforeach; ?>
                                                </tbody>
                                            </table>
                                        </div>
                                    </div>
                                </details>
                            <?php endforeach; ?>
                        </div>
                    </section>
                <?php endforeach; ?>
            </div>

            <div class="wst-docp-save-bar">
                <div>
                    <strong id="wst_docp_changed_count">0 changed price lines</strong>
                    <span>Only modified lines will be submitted.</span>
                </div>
                <button type="button" id="wst_docp_save_all" class="wst-docp-btn wst-docp-btn-primary" disabled>Save all changes</button>
            </div>
        </form>
    <?php endif; ?>

    <form method="post" id="wst-docp-freight-form" class="wst-docp-hidden-form">
        <input type="hidden" name="wst_docp_action" value="add_freight">
        <input type="hidden" name="wst_docp_freight_nonce" value="<?php echo esc_attr($wst_docp_freight_nonce); ?>">
        <input type="hidden" name="wst_docp_date" value="<?php echo esc_attr($wst_docp_selected_date); ?>">
        <input type="hidden" name="wst_docp_q" value="<?php echo esc_attr($wst_docp_search); ?>">
        <input type="hidden" name="wst_docp_pricing" value="<?php echo esc_attr($wst_docp_pricing_filter); ?>">
        <input type="hidden" name="wst_docp_freight_debtor" id="wst_docp_freight_debtor" value="">
        <input type="hidden" name="wst_docp_freight_rate" id="wst_docp_freight_rate" value="">
        <input type="hidden" name="wst_docp_freight_amount" id="wst_docp_freight_amount" value="">
    </form>

    <?php if ($wst_docp_is_admin): ?>
        <?php
        $wst_docp_freight_item_display = $wst_docp_freight_item
            ? $wst_docp_freight_item['description'] . ' — ' . $wst_docp_freight_item['item_code'] . ' [' . $wst_docp_freight_item['uom'] . ']'
            : '';
        ?>
        <div class="wst-docp-modal" id="wst_docp_settings_modal" hidden>
            <div class="wst-docp-modal-backdrop" data-wst-docp-close-settings></div>
            <section class="wst-docp-modal-dialog" role="dialog" aria-modal="true" aria-labelledby="wst_docp_settings_title">
                <form method="post" class="wst-docp-settings-form" id="wst_docp_settings_form">
                    <input type="hidden" name="wst_docp_action" value="save_freight_settings">
                    <input type="hidden" name="wst_docp_settings_nonce" value="<?php echo esc_attr($wst_docp_settings_nonce); ?>">
                    <input type="hidden" name="wst_docp_date" value="<?php echo esc_attr($wst_docp_selected_date); ?>">
                    <input type="hidden" name="wst_docp_q" value="<?php echo esc_attr($wst_docp_search); ?>">
                    <input type="hidden" name="wst_docp_pricing" value="<?php echo esc_attr($wst_docp_pricing_filter); ?>">

                    <header class="wst-docp-modal-head">
                        <div>
                            <span>Administrator setting</span>
                            <h2 id="wst_docp_settings_title">Freight settings</h2>
                        </div>
                        <button type="button" class="wst-docp-modal-close" data-wst-docp-close-settings aria-label="Close settings">&times;</button>
                    </header>

                    <div class="wst-docp-modal-body">
                        <p>
                            Choose the active AutoCount stock item used for freight and set an optional default rate.
                            Staff can still edit the rate and final amount before adding the freight charge.
                        </p>

                        <label class="wst-docp-settings-field" for="wst_docp_freight_item_display">
                            <span>Freight stock item</span>
                            <div class="wst-docp-picker-input-wrap">
                                <input type="text"
                                       id="wst_docp_freight_item_display"
                                       value="<?php echo esc_attr($wst_docp_freight_item_display); ?>"
                                       placeholder="Select freight item..."
                                       autocomplete="off"
                                       readonly>
                                <button type="button"
                                        class="wst-docp-picker-clear"
                                        id="wst_docp_clear_freight_item"
                                        aria-label="Clear freight item"
                                        <?php echo $wst_docp_freight_item_code === '' ? 'hidden' : ''; ?>>
                                    &times;
                                </button>
                            </div>
                            <input type="hidden"
                                   id="wst_docp_freight_item_code"
                                   name="wst_docp_freight_item_code"
                                   value="<?php echo esc_attr($wst_docp_freight_item_code); ?>">
                            <small class="wst-docp-settings-help">Click the field to search the synced AutoCount item list.</small>
                        </label>

                        <label class="wst-docp-settings-field" for="wst_docp_default_freight_rate">
                            <span>Default freight rate / KG</span>
                            <div class="wst-docp-money-input wst-docp-settings-rate-input">
                                <span>RM</span>
                                <input type="number"
                                       id="wst_docp_default_freight_rate"
                                       name="wst_docp_default_freight_rate"
                                       inputmode="decimal"
                                       min="0"
                                       max="<?php echo esc_attr((string) WST_DOCP_MAX_FREIGHT_RATE); ?>"
                                       step="0.0001"
                                       value="<?php echo $wst_docp_default_freight_rate > 0 ? esc_attr(number_format($wst_docp_default_freight_rate, 4, '.', '')) : ''; ?>"
                                       placeholder="0.0000">
                            </div>
                            <small class="wst-docp-settings-help">Leave blank or enter 0 to keep the staff rate field empty.</small>
                        </label>

                        <div class="wst-docp-settings-current">
                            <span>Current configuration</span>
                            <strong id="wst_docp_current_item_code">
                                <?php echo esc_html($wst_docp_freight_item ? $wst_docp_freight_item['item_code'] : 'No freight item selected'); ?>
                            </strong>
                            <small id="wst_docp_current_item_detail">
                                <?php if ($wst_docp_freight_item): ?>
                                    <?php echo esc_html($wst_docp_freight_item['description'] . ' · ' . $wst_docp_freight_item['uom']); ?>
                                <?php else: ?>
                                    Select an item before staff can add freight charges.
                                <?php endif; ?>
                            </small>
                        </div>
                    </div>

                    <footer class="wst-docp-modal-actions">
                        <button type="button" class="wst-docp-btn wst-docp-btn-secondary" data-wst-docp-close-settings>Cancel</button>
                        <button type="submit" class="wst-docp-btn wst-docp-btn-primary">Save freight settings</button>
                    </footer>
                </form>
            </section>
        </div>

        <div class="wst-docp-modal wst-docp-picker-modal" id="wst_docp_item_picker_modal" hidden>
            <div class="wst-docp-modal-backdrop" data-wst-docp-close-item-picker></div>
            <section class="wst-docp-modal-dialog wst-docp-picker-dialog" role="dialog" aria-modal="true" aria-labelledby="wst_docp_item_picker_title">
                <header class="wst-docp-modal-head">
                    <div>
                        <span>AutoCount item list</span>
                        <h2 id="wst_docp_item_picker_title">Select freight item</h2>
                    </div>
                    <button type="button" class="wst-docp-modal-close" data-wst-docp-close-item-picker aria-label="Close item picker">&times;</button>
                </header>

                <div class="wst-docp-picker-body">
                    <input type="search"
                           id="wst_docp_item_picker_search"
                           class="wst-docp-picker-search"
                           placeholder="Search item code or description"
                           autocomplete="off">

                    <di7��<��>|��������7��P
N?�}v class="wst-docp-picker-list" id="wst_docp_item_picker_list" role="listbox">
                        <?php foreach ($wst_docp_item_choices as $choice): ?>
                            <?php
                            $choice_code = trim((string) ($choice['item_code'] ?? ''));
                            $choice_desc = trim((string) ($choice['description'] ?? ''));
                            $choice_uom = trim((string) ($choice['sales_uom'] ?? ''));
                            if ($choice_uom === '') {
                                $choice_uom = trim((string) ($choice['base_uom'] ?? ''));
                            }
                            if ($choice_code === '') continue;
                            $choice_label = $choice_desc !== '' ? $choice_desc : $choice_code;
                            $choice_search = strtolower(trim($choice_code . ' ' . $choice_desc . ' ' . $choice_uom));
                            ?>
                            <button type="button"
                                    class="wst-docp-picker-option <?php echo strcasecmp($choice_code, $wst_docp_freight_item_code) === 0 ? 'is-selected' : ''; ?>"
                                    role="option"
                                    aria-selected="<?php echo strcasecmp($choice_code, $wst_docp_freight_item_code) === 0 ? 'true' : 'false'; ?>"
                                    data-item-code="<?php echo esc_attr($choice_code); ?>"
                                    data-item-description="<?php echo esc_attr($choice_label); ?>"
                                    data-item-uom="<?php echo esc_attr($choice_uom); ?>"
                                    data-item-search="<?php echo esc_attr($choice_search); ?>">
                                <strong><?php echo esc_html($choice_label); ?></strong>
                                <span>
                                    <?php echo esc_html($choice_code . ($choice_uom !== '' ? ' · ' . $choice_uom : '')); ?>
                                </span>
                            </button>
                        <?php endforeach; ?>
                    </div>

                    <div class="wst-docp-picker-empty" id="wst_docp_item_picker_empty" hidden>
                        No active items match your search.
                    </div>
                </div>

                <footer class="wst-docp-modal-actions">
                    <button type="button" class="wst-docp-btn wst-docp-btn-secondary" data-wst-docp-close-item-picker>Cancel</button>
                </footer>
            </section>
        </div>
    <?php endif; ?>
</div>

<style>
/* Scoped, restrained styling that stays consistent across Elementor and the active theme. */
.wst-docp-root {
    --wst-docp-primary: #176b46;
    --wst-docp-primary-dark: #0f5135;
    --wst-docp-primary-soft: #eaf4ee;
    --wst-docp-primary-soft-2: #f4f8f5;
    --wst-docp-ink: #18231d;
    --wst-docp-muted: #66736b;
    --wst-docp-line: #d8e2dc;
    --wst-docp-line-strong: #bfd2c6;
    --wst-docp-warning: #9a6700;
    --wst-docp-warning-soft: #fff8e7;
    --wst-docp-danger: #a33a3a;
    --wst-docp-danger-soft: #fff2f2;
    width: 100%;
    max-width: 1500px;
    margin: 0 auto;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    color: var(--wst-docp-ink);
    font-family: "Segoe UI", Arial, Helvetica, sans-serif;
    font-size: 15px;
    line-height: 1.45;
    box-shadow: none;
    isolation: isolate;
}

.wst-docp-root,
.wst-docp-root * { box-sizing: border-box; }
.wst-docp-root button,
.wst-docp-root input,
.wst-docp-root select { font-family: "Segoe UI", Arial, Helvetica, sans-serif; }
.wst-docp-root a { color: inherit; }

.wst-docp-alert {
    margin: 14px 0 0;
    padding: 13px 15px;
    border: 1px solid var(--wst-docp-line);
    border-left-width: 4px;
    border-radius: 10px;
    font-weight: 700;
    line-height: 1.45;
    box-shadow: none;
}
.wst-docp-alert-success { color: #24583e; background: #f1f8f4; border-left-color: #4f9b70; }
.wst-docp-alert-warning { color: #795500; background: var(--wst-docp-warning-soft); border-left-color: #d6a11d; }
.wst-docp-alert-error { color: #7d2f2f; background: var(--wst-docp-danger-soft); border-left-color: #c95b5b; }
.wst-docp-alert-info { color: #315467; background: #f1f6f8; border-left-color: #6d98ad; }

.wst-docp-filter-form {
    display: grid;
    grid-template-columns: minmax(175px, .8fr) minmax(300px, 1.65fr) minmax(190px, .85fr) auto;
    gap: 14px;
    align-items: end;
    padding: 18px 20px;
    border: 1px solid var(--wst-docp-line-strong);
    border-top: 4px solid var(--wst-docp-primary);
    border-radius: 14px;
    background: #ffffff;
    box-shadow: 0 5px 16px rgba(24, 35, 29, .05);
}
.wst-docp-filter-field { min-width: 0; }
.wst-docp-filter-field label {
    display: block;
    margin: 0 0 6px;
    color: #2c3d34;
    font-size: 12px;
    font-weight: 750;
    letter-spacing: .01em;
}
.wst-docp-filter-field input,
.wst-docp-filter-field select {
    width: 100%;
    min-height: 46px;
    margin: 0;
    padding: 10px 13px;
    border: 1px solid #cbd8d0;
    border-radius: 9px;
    outline: none;
    background: #ffffff;
    color: var(--wst-docp-ink);
    font-size: 15px;
    font-weight: 600;
    line-height: 1.2;
    box-shadow: none;
    appearance: auto;
}
.wst-docp-filter-field input::placeholder { color: #87928b; opacity: 1; }
.wst-docp-filter-field input:hover,
.wst-docp-filter-field select:hover { border-color: #9db7a7; }
.wst-docp-filter-field input:focus,
.wst-docp-filter-field select:focus,
.wst-docp-price-input:focus {
    outline: 3px solid rgba(23, 107, 70, .12);
    border-color: var(--wst-docp-primary);
    background: #fff;
}
.wst-docp-search-wrap { position: relative; }
.wst-docp-search-wrap input { padding-right: 42px; }
.wst-docp-clear-search {
    position: absolute;
    top: 50%;
    right: 8px;
    display: grid;
    place-items: center;
    width: 29px;
    height: 29px;
    transform: translateY(-50%);
    border-radius: 7px;
    background: #eef3f0;
    color: #496057 !important;
    font-size: 20px;
    font-weight: 700;
    line-height: 1;
    text-align: center;
    text-decoration: none !important;
}
.wst-docp-clear-search:hover { background: #e1ebe5; }

.wst-docp-root .wst-docp-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    margin: 0;
    padding: 9px 17px;
    border: 1px solid transparent;
    border-radius: 9px;
    outline: none;
    font-size: 14px;
    font-weight: 750;
    line-height: 1.1;
    cursor: pointer;
    text-decoration: none !important;
    text-transform: none;
    box-shadow: none;
    appearance: none;
    transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.wst-docp-root .wst-docp-btn:hover:not(:disabled) { transform: none; box-shadow: none; }
.wst-docp-root button.wst-docp-btn:disabled,
.wst-docp-root button.wst-docp-btn[disabled] {
    cursor: not-allowed !important;
    opacity: 1 !important;
    transform: none !important;
    color: #7d8a82 !important;
    background: #f0f4f1 !important;
    border-color: #d4ded8 !important;
    box-shadow: none !important;
    filter: none !important;
}
.wst-docp-root .wst-docp-btn-primary {
    color: #fff;
    background: var(--wst-docp-primary);
    border-color: var(--wst-docp-primary);
}
.wst-docp-root .wst-docp-btn-primary:hover:not(:disabled) {
    background: var(--wst-docp-primary-dark);
    border-color: var(--wst-docp-primary-dark);
}
.wst-docp-root .wst-docp-btn-secondary {
    color: var(--wst-docp-primary-dark);
    background: #fff;
    border-color: #a9c7b5;
}
.wst-docp-root .wst-docp-btn-secondary:hover:not(:disabled) {
    background: var(--wst-docp-primary-soft-2);
    border-color: #78a98c;
}

.wst-docp-summary-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
    margin: 18px 0 22px;
}
.wst-docp-summary-card {
    min-height: 94px;
    padding: 16px 18px;
    border: 1px solid var(--wst-docp-line);
    border-radius: 13px;
    background: #fff;
    box-shadow: 0 4px 12px rgba(24, 35, 29, .045);
}
.wst-docp-summary-card::before { content: none; }
.wst-docp-summary-card:nth-child(1),
.wst-docp-summary-card:nth-child(2),
.wst-docp-summary-card:nth-child(4) {
    color: var(--wst-docp-ink);
    background: #fff;
    border-color: var(--wst-docp-line);
}
.wst-docp-summary-card span,
.wst-docp-summary-card strong { display: block; }
.wst-docp-summary-card span { color: var(--wst-docp-muted); font-size: 12px; font-weight: 700; }
.wst-docp-summary-card strong { margin-top: 8px; color: var(--wst-docp-ink); font-size: 25px; line-height: 1.05; }
.wst-docp-summary-card-accent {
    color: var(--wst-docp-primary-dark);
    background: var(--wst-docp-primary-soft);
    border-color: #b8d5c3;
}
.wst-docp-summary-card-accent::before { content: none; }
.wst-docp-summary-card-accent span { color: #547064; }
.wst-docp-summary-card-accent strong { color: var(--wst-docp-primary-dark); }

.wst-docp-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 210px;
    padding: 30px;
    border: 1px dashed #b8c9bf;
    border-radius: 14px;
    background: #fafcfb;
    color: #2c3d34;
    text-align: center;
}
.wst-docp-empty strong { font-size: 20px; }
.wst-docp-empty span { margin-top: 6px; color: var(--wst-docp-muted); }

.wst-docp-group-list { display: grid; gap: 18px; }
.wst-docp-customer {
    overflow: hidden;
    border: 1px solid var(--wst-docp-line-strong);
    border-radius: 15px;
    background: #ffffff;
    box-shadow: 0 7px 20px rgba(24, 35, 29, .06);
}
.wst-docp-customer-head {
    display: grid;
    grid-template-columns: minmax(230px, 1fr) minmax(500px, 2.2fr) auto;
    gap: 18px;
    align-items: center;
    padding: 17px 20px;
    color: var(--wst-docp-ink);
    background: #f1f6f3;
    border-bottom: 1px solid var(--wst-docp-line);
}
.wst-docp-customer-code {
    display: inline-block;
    margin-bottom: 4px;
    color: var(--wst-docp-primary);
    font-size: 11px;
    font-weight: 850;
    letter-spacing: .07em;
    text-transform: uppercase;
}
.wst-docp-customer-identity h2 {
    margin: 0;
    color: #1d2c24;
    font-size: 21px;
    font-weight: 800;
    line-height: 1.15;
}
.wst-docp-customer-metrics {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 9px;
}
.wst-docp-customer-metrics > div {
    min-width: 0;
    padding: 9px 11px;
    border: 1px solid #d5e0d9;
    border-radius: 9px;
    background: rgba(255,255,255,.75);
}
.wst-docp-customer-metrics span,
.wst-docp-customer-metrics strong { display: block; }
.wst-docp-customer-metrics span { color: #6a776f; font-size: 10px; font-weight: 700; }
.wst-docp-customer-metrics strong { margin-top: 3px; color: #26372e; font-size: 14px; overflow-wrap: anywhere; }
.wst-docp-customer-head > .wst-docp-btn-secondary {
    color: var(--wst-docp-primary-dark);
    background: #fff;
    border-color: #9abda8;
}
.wst-docp-customer-head > .wst-docp-btn-secondary:hover:not(:disabled) {
    color: #fff;
    background: var(--wst-docp-primary);
    border-color: var(--wst-docp-primary);
}

.wst-docp-do-list { padding: 12px; background: #fbfdfc; }
.wst-docp-do + .wst-docp-do { margin-top: 10px; }
.wst-docp-do {
    overflow: hidden;
    border: 1px solid #d3dfd7;
    border-left: 4px solid #8bb59d;
    border-radius: 11px;
    background: #fff;
    box-shadow: none;
}
.wst-docp-do[open] {
    border-color: #b6cdbf;
    border-left-color: var(--wst-docp-primary);
    box-shadow: 0 4px 12px rgba(24, 35, 29, .05);
}
.wst-docp-do-summary {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 14px;
    min-height: 60px;
    margin: 0;
    padding: 12px 14px;
    cursor: pointer;
    list-style: none;
    background: #fff;
}
.wst-docp-do-summary::-webkit-details-marker { display: none; }
.wst-docp-do-summary::before {
    content: '+';
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 27px;
    height: 27px;
    border-radius: 7px;
    background: #edf3ef;
    color: #496057;
    font-size: 16px;
    font-weight: 850;
    line-height: 1;
}
.wst-docp-do[open] > .wst-docp-do-summary::before {
    content: '−';
    background: var(--wst-docp-primary);
    color: #fff;
}
.wst-docp-do-title { display: flex; align-items: center; gap: 10px; margin-right: auto; }
.wst-docp-do-title strong { color: #1f3027; font-size: 16px; font-weight: 800; }
.wst-docp-do-title span { color: var(--wst-docp-muted); font-size: 12px; font-weight: 650; }
.wst-docp-do-summary-right { display: flex; align-items: center; gap: 13px; }
.wst-docp-do-total { min-width: 125px; color: #1f3027; font-size: 16px; text-align: right; }
.wst-docp-price-badge {
    display: inline-flex;
    align-items: center;
    min-height: 27px;
    padding: 4px 9px;
    border: 1px solid transparent;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 800;
}
.wst-docp-price-badge.is-complete { color: #326048; background: #edf7f1; border-color: #bddbc9; }
.wst-docp-price-badge.is-missing { color: #805900; background: var(--wst-docp-warning-soft); border-color: #ead28c; }

.wst-docp-do-body { border-top: 1px solid #e1e8e3; }
.wst-docp-do-actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
    padding: 10px 13px;
    background: #f7faf8;
    border-bottom: 1px solid #dce5df;
}
.wst-docp-do-actions > div:last-child { display: flex; align-items: center; gap: 7px; }
.wst-docp-do-actions a,
.wst-docp-btn-link {
    display: inline-flex;
    align-items: center;
    min-height: 30px;
    margin: 0;
    padding: 5px 9px;
    border: 1px solid #bccfc3;
    border-radius: 7px;
    background: #fff;
    color: #2d5f44 !important;
    font-family: "Segoe UI", Arial, Helvetica, sans-serif;
    font-size: 12px;
    font-weight: 750;
    line-height: 1;
    cursor: pointer;
    text-decoration: none !important;
    appearance: none;
}
.wst-docp-do-actions a:hover,
.wst-docp-btn-link:hover { border-color: #7ea88f; background: #f1f7f3; }

#wst-docp-root button.wst-docp-btn,
#wst-docp-root button.wst-docp-btn-link {
    -webkit-appearance: none !important;
    appearance: none !important;
    text-shadow: none !important;
}
#wst-docp-root button.wst-docp-btn-link:disabled,
#wst-docp-root button.wst-docp-btn-link[disabled] {
    cursor: not-allowed !important;
    opacity: 1 !important;
    color: #7d8a82 !important;
    background: #f0f4f1 !important;
    border-color: #d4ded8 !important;
    box-shadow: none !important;
    filter: none !important;
}
#wst-docp-root button:focus,
#wst-docp-root button:focus-visible {
    outline: 3px solid rgba(23, 107, 70, .14) !important;
    outline-offset: 2px !important;
}
.wst-docp-status-text {
    display: inline-flex;
    align-items: center;
    min-height: 27px;
    padding: 4px 8px;
    border: 1px solid #dce5df;
    border-radius: 7px;
    color: #496057;
    background: #fff;
    font-size: 11px;
    font-weight: 750;
    text-transform: capitalize;
}

.wst-docp-table-wrap { overflow-x: auto; background: #fff; }
.wst-docp-table {
    width: 100%;
    min-width: 880px;
    margin: 0;
    border: 0;
    border-collapse: collapse;
    background: #fff;
}
.wst-docp-table th,
.wst-docp-table td { padding: 11px 12px; border: 0; border-bottom: 1px solid #e4ebe6; vertical-align: middle; }
.wst-docp-table tr:last-child td { border-bottom: 0; }
.wst-docp-table th {
    background: #edf3ef;
    color: #33483c;
    font-size: 10px;
    font-weight: 800;
    text-align: left;
    text-transform: uppercase;
    letter-spacing: .045em;
}
.wst-docp-table tbody tr:nth-child(even):not(.is-missing-price):not(.is-changed) { b7��P���6}��������7�	c
N?�~ackground: #fbfcfb; }
.wst-docp-table td { color: #2d3e35; font-size: 13px; }
.wst-docp-table td strong,
.wst-docp-table td small { display: block; }
.wst-docp-table td strong { color: #1f3027; font-weight: 780; }
.wst-docp-table td small { margin-top: 2px; color: #78837c; font-size: 10px; }
.wst-docp-num { text-align: right !important; font-variant-numeric: tabular-nums; }
.wst-docp-line.is-missing-price { background: #fffaf0; }
.wst-docp-line.is-changed { background: #eef8f2; }
.wst-docp-line.is-changed td { border-bottom-color: #d4e8dc; }
.wst-docp-price-cell { white-space: nowrap; }
.wst-docp-rm { margin-right: 5px; color: #68756e; font-size: 11px; font-weight: 700; }
.wst-docp-price-input {
    width: 120px;
    min-height: 39px;
    margin: 0;
    padding: 7px 10px;
    border: 1px solid #b8c9bf;
    border-radius: 7px;
    outline: none;
    background: #fff;
    color: #1f3027;
    font-size: 14px;
    font-weight: 750;
    line-height: 1.2;
    text-align: right;
    box-shadow: none;
    appearance: auto;
}
.wst-docp-price-input:hover { border-color: #86a997; }
.wst-docp-price-input::placeholder { color: #9a6700; opacity: 1; }
.wst-docp-line-amount { color: #264235 !important; font-weight: 780; }

.wst-docp-save-bar {
    position: sticky;
    z-index: 20;
    bottom: 10px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 18px;
    margin-top: 18px;
    padding: 14px 16px;
    border: 1px solid var(--wst-docp-line-strong);
    border-radius: 13px;
    color: var(--wst-docp-ink);
    background: rgba(255,255,255,.97);
    box-shadow: 0 7px 22px rgba(24, 35, 29, .09);
    backdrop-filter: blur(5px);
}
.wst-docp-save-bar strong,
.wst-docp-save-bar span { display: block; }
.wst-docp-save-bar strong { color: #26372e; font-size: 15px; }
.wst-docp-save-bar span { margin-top: 3px; color: var(--wst-docp-muted); font-size: 12px; }
.wst-docp-save-bar .wst-docp-btn-primary {
    color: #fff;
    background: var(--wst-docp-primary);
    border-color: var(--wst-docp-primary);
}
.wst-docp-save-bar .wst-docp-btn-primary:hover:not(:disabled) {
    background: var(--wst-docp-primary-dark);
    border-color: var(--wst-docp-primary-dark);
}
.wst-docp-hidden-submit { position: absolute; width: 1px; height: 1px; overflow: hidden; opacity: 0; pointer-events: none; }


.wst-docp-filter-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}
.wst-docp-filter-actions .wst-docp-btn { white-space: nowrap; }

.wst-docp-freight-status {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 7px;
    margin: 14px 0 0;
    padding: 10px 13px;
    border: 1px solid #d5e0d9;
    border-radius: 10px;
    background: #f7faf8;
    color: #52635a;
    font-size: 12px;
}
.wst-docp-freight-status > span:first-child {
    color: #6a776f;
    font-weight: 700;
}
.wst-docp-freight-status strong {
    color: var(--wst-docp-primary-dark);
    font-weight: 850;
}
.wst-docp-freight-status small {
    padding: 2px 7px;
    border-radius: 999px;
    background: #e8f1eb;
    color: #496057;
    font-size: 10px;
    font-weight: 800;
}

.wst-docp-freight-panel {
    display: grid;
    grid-template-columns: minmax(280px, 1.4fr) minmax(155px, .55fr) minmax(175px, .65fr) auto;
    gap: 12px;
    align-items: end;
    padding: 14px 18px;
    border-bottom: 1px solid var(--wst-docp-line);
    background: #fbfdfc;
}
.wst-docp-freight-copy {
    min-width: 0;
    padding-bottom: 3px;
}
.wst-docp-freight-copy strong,
.wst-docp-freight-copy span {
    display: block;
}
.wst-docp-freight-copy strong {
    color: #26372e;
    font-size: 14px;
    font-weight: 820;
}
.wst-docp-freight-copy span {
    margin-top: 3px;
    color: var(--wst-docp-muted);
    font-size: 11px;
    line-height: 1.4;
}
.wst-docp-freight-copy b {
    color: #3f574a;
    font-weight: 800;
}
.wst-docp-freight-field {
    display: block;
    margin: 0;
}
.wst-docp-freight-field > span {
    display: block;
    margin: 0 0 5px;
    color: #52635a;
    font-size: 10px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .035em;
}
.wst-docp-money-input {
    display: flex;
    align-items: center;
    min-height: 42px;
    overflow: hidden;
    border: 1px solid #c5d4cb;
    border-radius: 8px;
    background: #fff;
}
.wst-docp-money-input:focus-within {
    outline: 3px solid rgba(23, 107, 70, .12);
    border-color: var(--wst-docp-primary);
}
.wst-docp-money-input > span {
    flex: 0 0 auto;
    padding: 0 0 0 10px;
    color: #68756e;
    font-size: 11px;
    font-weight: 800;
}
.wst-docp-money-input input {
    width: 100%;
    min-width: 0;
    min-height: 40px;
    margin: 0;
    padding: 8px 10px 8px 6px;
    border: 0 !important;
    outline: 0 !important;
    background: transparent !important;
    color: #1f3027;
    font-size: 14px;
    font-weight: 750;
    text-align: right;
    box-shadow: none !important;
}
.wst-docp-freight-panel .wst-docp-add-freight {
    min-width: 170px;
}

.wst-docp-line.is-freight-line {
    background: #f4f8f5;
}
.wst-docp-line.is-freight-line td {
    border-bottom-color: #d8e5dc;
}
.wst-docp-freight-line-badge {
    display: inline-flex;
    align-items: center;
    margin-top: 5px;
    padding: 2px 7px;
    border: 1px solid #bfd6c7;
    border-radius: 999px;
    background: #eaf4ee;
    color: #356047;
    font-size: 9px;
    font-weight: 850;
    line-height: 1.2;
    text-transform: uppercase;
    letter-spacing: .04em;
}

.wst-docp-hidden-form { display: none; }

.wst-docp-modal[hidden] { display: none !important; }
.wst-docp-modal:not([hidden]) {
    position: fixed;
    z-index: 999999;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 18px;
}
.wst-docp-modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(18, 31, 24, .48);
    backdrop-filter: blur(2px);
}
.wst-docp-modal-dialog {
    position: relative;
    z-index: 1;
    width: min(620px, 100%);
    max-height: calc(100vh - 36px);
    overflow: auto;
    border: 1px solid #c6d5cc;
    border-radius: 14px;
    background: #fff;
    box-shadow: 0 24px 70px rgba(15, 34, 23, .24);
}
.wst-docp-settings-form { margin: 0; }
.wst-docp-modal-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 18px;
    padding: 18px 20px;
    border-bottom: 1px solid #dce5df;
    background: #f4f8f5;
}
.wst-docp-modal-head span {
    display: block;
    color: var(--wst-docp-primary);
    font-size: 10px;
    font-weight: 850;
    text-transform: uppercase;
    letter-spacing: .065em;
}
.wst-docp-modal-head h2 {
    margin: 4px 0 0;
    color: #1d2c24;
    font-size: 21px;
    line-height: 1.2;
}
.wst-docp-modal-close {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    margin: 0;
    padding: 0;
    border: 1px solid #cbd8d0;
    border-radius: 8px;
    background: #fff;
    color: #496057;
    font-size: 22px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
}
.wst-docp-modal-body { padding: 20px; }
.wst-docp-modal-body > p {
    margin: 0 0 16px;
    color: #5e6d65;
    font-size: 13px;
    line-height: 1.5;
}
.wst-docp-settings-field {
    display: block;
    margin: 0;
}
.wst-docp-settings-field + .wst-docp-settings-field { margin-top: 17px; }
.wst-docp-settings-field > span {
    display: block;
    margin-bottom: 6px;
    color: #2c3d34;
    font-size: 12px;
    font-weight: 800;
}
.wst-docp-settings-field input {
    width: 100%;
    min-height: 46px;
    margin: 0;
    padding: 10px 12px;
    border: 1px solid #c5d4cb;
    border-radius: 9px;
    background: #fff;
    color: #1f3027;
    font-size: 15px;
    font-weight: 700;
    box-shadow: none;
}
.wst-docp-settings-field input:focus {
    outline: 3px solid rgba(23, 107, 70, .12);
    border-color: var(--wst-docp-primary);
}
.wst-docp-settings-field input[readonly] { cursor: pointer; }
.wst-docp-settings-help {
    display: block;
    margin-top: 6px;
    color: #6d7a72;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.4;
}
.wst-docp-picker-input-wrap { position: relative; }
.wst-docp-picker-input-wrap > input { padding-right: 42px; }
.wst-docp-picker-clear {
    position: absolute;
    top: 50%;
    right: 8px;
    display: grid;
    place-items: center;
    width: 29px;
    height: 29px;
    margin: 0;
    padding: 0;
    transform: translateY(-50%);
    border: 1px solid #d4ded8;
    border-radius: 7px;
    background: #eef3f0;
    color: #496057;
    font-size: 20px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
}
.wst-docp-picker-clear[hidden] { display: none !important; }
.wst-docp-picker-clear:hover { background: #e2ebe5; }
.wst-docp-settings-rate-input input { min-height: 44px; }
.wst-docp-settings-current {
    margin-top: 15px;
    padding: 12px 13px;
    border: 1px solid #d8e2dc;
    border-radius: 9px;
    background: #f8faf9;
}
.wst-docp-settings-current span,
.wst-docp-settings-current strong,
.wst-docp-settings-current small {
    display: block;
}
.wst-docp-settings-current span {
    color: #6b786f;
    font-size: 10px;
    font-weight: 800;
    text-transform: uppercase;
}
.wst-docp-settings-current strong {
    margin-top: 4px;
    color: #26372e;
    font-size: 15px;
}
.wst-docp-settings-current small {
    margin-top: 2px;
    color: #68756e;
    font-size: 11px;
}
.wst-docp-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 9px;
    padding: 14px 20px;
    border-top: 1px solid #dce5df;
    background: #fbfdfc;
}
body.wst-docp-modal-open { overflow: hidden; }
.wst-docp-picker-modal:not([hidden]) { z-index: 1000001; }
.wst-docp-picker-dialog { width: min(680px, 100%); }
.wst-docp-picker-body { padding: 14px 15px 16px; }
.wst-docp-picker-search {
    width: 100%;
    min-height: 48px;
    margin: 0 0 10px;
    padding: 10px 13px;
    border: 1px solid #b8c9bf;
    border-radius: 8px;
    background: #fff;
    color: #1f3027;
    font-size: 15px;
    font-weight: 650;
    box-shadow: none;
}
.wst-docp-picker-search:focus {
    outline: 3px solid rgba(23, 107, 70, .12);
    border-color: var(--wst-docp-primary);
}
.wst-docp-picker-list {
    display: grid;
    gap: 8px;
    max-height: min(56vh, 520px);
    overflow-y: auto;
    padding: 1px 4px 1px 0;
}
.wst-docp-picker-option {
    display: block;
    width: 100%;
    margin: 0;
    padding: 12px 13px;
    border: 1px solid #d2ddd6;
    border-radius: 9px;
    background: #fff;
    color: #1f3027;
    text-align: left;
    cursor: pointer;
    box-shadow: none;
}
.wst-docp-picker-option:hover,
.wst-docp-picker-option:focus {
    outline: none;
    border-color: #8eb39d;
    background: #f3f8f5;
}
.wst-docp-picker-option.is-selected {
    border-color: var(--wst-docp-primary);
    background: var(--wst-docp-primary-soft);
}
.wst-docp-picker-option.is-hidden { display: none !important; }
.wst-docp-picker-option strong,
.wst-docp-picker-option span { display: block; }
.wst-docp-picker-option strong {
    color: #1d2c24;
    font-size: 14px;
    font-weight: 800;
    line-height: 1.3;
}
.wst-docp-picker-option span {
    margin-top: 3px;
    color: #68756e;
    font-size: 11px;
    font-weight: 650;
}
.wst-docp-picker-empty {
    padding: 28px 15px;
    color: #68756e;
    text-align: center;
    font-size: 13px;
    font-weight: 700;
}
.wst-docp-freight-default-rate {
    margin-left: auto;
    padding-left: 12px;
    border-left: 1px solid #d6e1da;
    color: #456354;
    font-size: 11px;
    font-weight: 750;
}
.wst-docp-swal-summary {
    display: grid;
    gap: 8px;
    margin-top: 4px;
    text-align: left;
}
.wst-docp-swal-summary > div {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    padding: 8px 10px;
    border: 1px solid #e0e7e2;
    border-radius: 7px;
    background: #f8faf9;
}
.wst-docp-swal-summary span { color: #657169; }
.wst-docp-swal-summary strong { color: #1f3027; text-align: right; }

@media (max-width: 1120px) {
    .wst-docp-filter-form { grid-template-columns: minmax(170px, .8fr) minmax(260px, 1.4fr) minmax(180px, .8fr); }
    .wst-docp-filter-actions { grid-column: 1 / -1; }
    .wst-docp-customer-head { grid-template-columns: 1fr auto; }
    .wst-docp-customer-metrics { grid-column: 1 / -1; grid-row: 2; }
    .wst-docp-freight-panel { grid-template-columns: 1fr 1fr auto; }
    .wst-docp-freight-copy { grid-column: 1 / -1; }
}

@media (max-width: 820px) {
    .wst-docp-root { padding: 0; border-radius: 0; }
    .wst-docp-filter-form { grid-template-columns: 1fr 1fr; padding: 17px; }
    .wst-docp-filter-search { grid-column: 1 / -1; }
    .wst-docp-filter-actions { justify-content: stretch; }
    .wst-docp-filter-actions .wst-docp-btn { flex: 1 1 auto; }
    .wst-docp-summary-grid { grid-template-columns: 1fr 1fr; }
    .wst-docp-customer-head { grid-template-columns: 1fr; }
    .wst-docp-customer-metrics { grid-column: auto; grid-row: auto; grid-template-columns: 1fr 1fr; }
    .wst-docp-customer-head > .wst-docp-btn { width: 100%; }
    .wst-docp-freight-panel { grid-template-columns: 1fr 1fr; }
    .wst-docp-freight-copy { grid-column: 1 / -1; }
    .wst-docp-freight-panel .wst-docp-add-freight { grid-column: 1 / -1; width: 100%; }
    .wst-docp-do-summary { align-items: flex-start; flex-wrap: wrap; }
    .wst-docp-do-summary-right { width: 100%; padding-left: 41px; justify-content: space-between; }
    .wst-docp-do-total { min-width: 0; }
}

@media (max-width: 620px) {
    .wst-docp-root { margin: 0; padding: 0; border-radius: 0; }
    .wst-docp-filter-form { grid-template-columns: 1fr; gap: 12px; padding: 14px; border-radius: 12px; }
    .wst-docp-filter-search { grid-column: auto; }
    .wst-docp-filter-actions { flex-direction: column; }
    .wst-docp-filter-actions .wst-docp-btn { width: 100%; }
    .wst-docp-summary-grid { grid-template-columns: 1fr 1fr; gap: 9px; margin-bottom: 16px; }
    .wst-docp-summary-card { min-height: 88px; padding: 14px; }
    .wst-docp-summary-card strong { font-size: 21px; }
    .wst-docp-customer { border-radius: 13px; }
    .wst-docp-customer-head { padding: 15px; }
    .wst-docp-customer-metrics { gap: 8px; }
    .wst-docp-customer-metrics > div { padding: 8px; }
    .wst-docp-freight-panel { grid-template-columns: 1fr; padding: 13px 15px; }
    .wst-docp-freight-copy,
    .wst-docp-freight-panel .wst-docp-add-freight { grid-column: auto; }
    .wst-docp-do-list { padding: 8px; }
    .wst-docp-do-actions { align-items: flex-start; flex-direction: column; }
    .wst-docp-do-actions > div:last-child { width: 100%; flex-wrap: wrap; }
    .wst-docp-do-actions a,
    .wst-docp-btn-link { flex: 1 1 auto; justify-content: center; }
    .wst-docp-do-title { align-items: flex-start; flex-direction: column; gap: 2px; }
    .wst-docp-save-bar { bottom: 6px; align-items: stretch; flex-direction: column; }
    .wst-docp-save-bar .wst-docp-btn { width: 100%; }
    .wst-docp-modal { padding: 10px; }
    .wst-docp-modal-dialog { max-height: calc(100vh - 20px); }
    .wst-docp-modal-head,
    .wst-docp-modal-body,
    .wst-docp-modal-actions { padding-left: 15px; padding-right: 15px; }
    .wst-docp-modal-actions { flex-direction: column-reverse; }
    .wst-docp-modal-actions .wst-docp-btn { width: 100%; }
    .wst-docp-freight-default-rate {
        width: 100%;
        margin-left: 0;
        padding: 6px 0 0;
        border-left: 0;
        border-top: 1px solid #d6e1da;
    }
}
</style>

<script>
(function() {
    'use strict';

    const root = document.getElementById('wst-docp-root');
    if (!root) return;

    const money = new Intl.NumberFormat('en-MY', {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2
    });

    function nu7�	c$�~��������7�"�
N?�mericValue(value) {
        const parsed = Number.parseFloat(String(value || '').replace(/,/g, ''));
        return Number.isFinite(parsed) ? parsed : 0;
    }

    function selectorValue(value) {
        if (window.CSS && typeof window.CSS.escape === 'function') {
            return window.CSS.escape(String(value));
        }
        return String(value).replace(/["\\]/g, '\\$&');
    }

    function escapeHtml(value) {
        return String(value == null ? '' : value).replace(/[&<>"']/g, function(character) {
            return {
                '&': '&amp;',
                '<': '&lt;',
                '>': '&gt;',
                '"': '&quot;',
                "'": '&#039;'
            }[character];
        });
    }

    let sweetAlertPromise = null;

    function ensureSweetAlert() {
        if (window.Swal && typeof window.Swal.fire === 'function') {
            return Promise.resolve(window.Swal);
        }

        if (sweetAlertPromise) return sweetAlertPromise;

        sweetAlertPromise = new Promise(function(resolve, reject) {
            const existing = document.querySelector('script[data-wst-docp-sweetalert], script[src*="sweetalert2"]');

            function finish() {
                if (window.Swal && typeof window.Swal.fire === 'function') {
                    resolve(window.Swal);
                }
            }

            if (existing) {
                existing.addEventListener('load', finish, { once: true });
                existing.addEventListener('error', function() {
                    reject(new Error('SweetAlert could not be loaded.'));
                }, { once: true });
                window.setTimeout(function() {
                    if (window.Swal && typeof window.Swal.fire === 'function') {
                        resolve(window.Swal);
                    } else {
                        reject(new Error('SweetAlert could not be loaded.'));
                    }
                }, 5000);
                return;
            }

            const script = document.createElement('script');
            script.src = 'https://cdn.jsdelivr.net/npm/sweetalert2@11';
            script.async = true;
            script.dataset.wstDocpSweetalert = '1';
            script.addEventListener('load', finish, { once: true });
            script.addEventListener('error', function() {
                reject(new Error('SweetAlert could not be loaded.'));
            }, { once: true });
            document.head.appendChild(script);
        });

        return sweetAlertPromise;
    }

    async function showSweetAlert(options) {
        try {
            const SwalRef = await ensureSweetAlert();
            return await SwalRef.fire(Object.assign({
                confirmButtonColor: '#176b46',
                cancelButtonColor: '#6b756f',
                confirmButtonText: 'OK',
                allowOutsideClick: false
            }, options || {}));
        } catch (error) {
            console.error(error);
            const notice = document.createElement('div');
            notice.className = 'wst-docp-alert wst-docp-alert-error';
            notice.textContent = (options && (options.text || options.title)) || 'An unexpected error occurred.';
            root.insertBefore(notice, root.firstChild);
            notice.scrollIntoView({ behavior: 'smooth', block: 'center' });
            return { isConfirmed: false, isDismissed: true };
        }
    }

    /*
     * Administrator-only freight settings and item picker.
     */
    const settingsModal = document.getElementById('wst_docp_settings_modal');
    const itemPickerModal = document.getElementById('wst_docp_item_picker_modal');
    const openSettingsButton = document.getElementById('wst_docp_open_settings');
    const settingsItemCodeInput = document.getElementById('wst_docp_freight_item_code');
    const settingsItemDisplay = document.getElementById('wst_docp_freight_item_display');
    const clearFreightItemButton = document.getElementById('wst_docp_clear_freight_item');
    const currentItemCode = document.getElementById('wst_docp_current_item_code');
    const currentItemDetail = document.getElementById('wst_docp_current_item_detail');
    const itemPickerSearch = document.getElementById('wst_docp_item_picker_search');
    const itemPickerEmpty = document.getElementById('wst_docp_item_picker_empty');
    const itemPickerOptions = Array.from(root.querySelectorAll('.wst-docp-picker-option'));

    function syncModalBodyState() {
        const anyOpen = (settingsModal && !settingsModal.hidden) || (itemPickerModal && !itemPickerModal.hidden);
        document.body.classList.toggle('wst-docp-modal-open', Boolean(anyOpen));
    }

    function openSettings() {
        if (!settingsModal) return;
        settingsModal.hidden = false;
        syncModalBodyState();
        window.setTimeout(function() {
            if (settingsItemDisplay) settingsItemDisplay.focus();
        }, 30);
    }

    function closeSettings() {
        if (!settingsModal) return;
        if (itemPickerModal) itemPickerModal.hidden = true;
        settingsModal.hidden = true;
        syncModalBodyState();
        if (openSettingsButton) openSettingsButton.focus();
    }

    function filterItemPicker() {
        const query = String(itemPickerSearch ? itemPickerSearch.value : '').trim().toLowerCase();
        let visibleCount = 0;

        itemPickerOptions.forEach(function(option) {
            const searchable = String(option.dataset.itemSearch || '').toLowerCase();
            const visible = query === '' || searchable.indexOf(query) !== -1;
            option.classList.toggle('is-hidden', !visible);
            if (visible) visibleCount++;
        });

        if (itemPickerEmpty) itemPickerEmpty.hidden = visibleCount > 0;
    }

    function openItemPicker() {
        if (!itemPickerModal) return;
        itemPickerModal.hidden = false;
        if (itemPickerSearch) itemPickerSearch.value = '';
        filterItemPicker();
        syncModalBodyState();
        window.setTimeout(function() {
            if (itemPickerSearch) itemPickerSearch.focus();
        }, 30);
    }

    function closeItemPicker() {
        if (!itemPickerModal) return;
        itemPickerModal.hidden = true;
        syncModalBodyState();
        if (settingsItemDisplay) settingsItemDisplay.focus();
    }

    function setFreightItem(code, description, uom) {
        const cleanCode = String(code || '').trim();
        const cleanDescription = String(description || '').trim();
        const cleanUom = String(uom || '').trim();

        if (settingsItemCodeInput) settingsItemCodeInput.value = cleanCode;
        if (settingsItemDisplay) {
            settingsItemDisplay.value = cleanCode
                ? (cleanDescription || cleanCode) + ' — ' + cleanCode + (cleanUom ? ' [' + cleanUom + ']' : '')
                : '';
        }
        if (clearFreightItemButton) clearFreightItemButton.hidden = cleanCode === '';
        if (currentItemCode) currentItemCode.textContent = cleanCode || 'No freight item selected';
        if (currentItemDetail) {
            currentItemDetail.textContent = cleanCode
                ? (cleanDescription || cleanCode) + (cleanUom ? ' · ' + cleanUom : '')
                : 'Select an item before staff can add freight charges.';
        }

        itemPickerOptions.forEach(function(option) {
            const selected = String(option.dataset.itemCode || '').toUpperCase() === cleanCode.toUpperCase();
            option.classList.toggle('is-selected', selected);
            option.setAttribute('aria-selected', selected ? 'true' : 'false');
        });
    }

    if (openSettingsButton) openSettingsButton.addEventListener('click', openSettings);
    if (settingsItemDisplay) settingsItemDisplay.addEventListener('click', openItemPicker);
    if (settingsItemDisplay) settingsItemDisplay.addEventListener('keydown', function(event) {
        if (event.key === 'Enter' || event.key === ' ') {
            event.preventDefault();
            openItemPicker();
        }
    });

    if (clearFreightItemButton) {
        clearFreightItemButton.addEventListener('click', function(event) {
            event.preventDefault();
            event.stopPropagation();
            setFreightItem('', '', '');
        });
    }

    if (itemPickerSearch) itemPickerSearch.addEventListener('input', filterItemPicker);

    itemPickerOptions.forEach(function(option) {
        option.addEventListener('click', function() {
            setFreightItem(
                option.dataset.itemCode,
                option.dataset.itemDescription,
                option.dataset.itemUom
            );
            closeItemPicker();
        });
    });

    root.querySelectorAll('[data-wst-docp-close-settings]').forEach(function(button) {
        button.addEventListener('click', closeSettings);
    });
    root.querySelectorAll('[data-wst-docp-close-item-picker]').forEach(function(button) {
        button.addEventListener('click', closeItemPicker);
    });

    document.addEventListener('keydown', function(event) {
        if (event.key !== 'Escape') return;
        if (itemPickerModal && !itemPickerModal.hidden) {
            closeItemPicker();
            return;
        }
        if (settingsModal && !settingsModal.hidden) closeSettings();
    });

    /*
     * Existing bulk price editing workflow.
     */
    const priceForm = document.getElementById('wst-docp-save-form');
    const priceInputs = Array.from(root.querySelectorAll('.wst-docp-price-input'));
    const saveAllButton = document.getElementById('wst_docp_save_all');
    const changedCount = document.getElementById('wst_docp_changed_count');
    const changesField = document.getElementById('wst_docp_changes_json');
    const hiddenSubmit = document.getElementById('wst_docp_hidden_submit');

    function isChanged(input) {
        return Math.abs(numericValue(input.value) - numericValue(input.dataset.original)) > 0.000001;
    }

    function updateLine(input) {
        const itemId = input.dataset.itemId;
        const amountQty = numericValue(input.dataset.amountQty || input.dataset.totalKg);
        const price = numericValue(input.value);
        const amountCell = root.querySelector('[data-line-amount="' + selectorValue(itemId) + '"]');
        const row = input.closest('.wst-docp-line');

        if (amountCell) {
            amountCell.textContent = 'RM ' + money.format(price * amountQty);
        }

        if (row) {
            row.classList.toggle('is-changed', isChanged(input));
            row.classList.toggle('is-missing-price', price <= 0);
        }
    }

    function recalculateDo(doId) {
        const doInputs = priceInputs.filter(function(input) {
            return input.dataset.doId === String(doId);
        });
        let amount = 0;
        let missing = 0;

        doInputs.forEach(function(input) {
            const price = numericValue(input.value);
            amount += price * numericValue(input.dataset.amountQty || input.dataset.totalKg);
            if (price <= 0) missing++;
        });

        const totalNode = root.querySelector('[data-do-total="' + selectorValue(doId) + '"]');
        if (totalNode) totalNode.textContent = 'RM ' + money.format(amount);

        const badge = root.querySelector('[data-do-badge="' + selectorValue(doId) + '"]');
        if (badge) {
            badge.classList.toggle('is-complete', missing === 0);
            badge.classList.toggle('is-missing', missing > 0);
            badge.textContent = missing === 0 ? 'Price complete' : missing + ' missing';
        }
    }

    function recalculateCustomer(customerIndex) {
        const customerInputs = priceInputs.filter(function(input) {
            return input.dataset.customerIndex === String(customerIndex);
        });
        let amount = 0;
        let missing = 0;

        customerInputs.forEach(function(input) {
            const price = numericValue(input.value);
            amount += price * numericValue(input.dataset.amountQty || input.dataset.totalKg);
            if (price <= 0) missing++;
        });

        const totalNode = root.querySelector(
            '.wst-docp-customer-total[data-customer-index="' + selectorValue(customerIndex) + '"]'
        );
        const missingNode = root.querySelector(
            '.wst-docp-customer-missing[data-customer-index="' + selectorValue(customerIndex) + '"]'
        );

        if (totalNode) totalNode.textContent = 'RM ' + money.format(amount);
        if (missingNode) missingNode.textContent = String(missing);
    }

    function currentChangedPriceCount() {
        return priceInputs.filter(isChanged).length;
    }

    function refreshChangedCount() {
        if (!changedCount || !saveAllButton) return;
        const count = currentChangedPriceCount();
        changedCount.textContent = count + (count === 1 ? ' changed price line' : ' changed price lines');
        saveAllButton.disabled = count === 0;
    }

    function collectChanges(allowedDoIds) {
        const allowed = Array.isArray(allowedDoIds) && allowedDoIds.length
            ? new Set(allowedDoIds.map(String))
            : null;

        return priceInputs
            .filter(function(input) {
                return isChanged(input) && (!allowed || allowed.has(String(input.dataset.doId)));
            })
            .map(function(input) {
                return {
                    doId: Number.parseInt(input.dataset.doId, 10),
                    itemId: Number.parseInt(input.dataset.itemId, 10),
                    price: input.value === '' ? '' : String(input.value),
                    original: String(input.dataset.original || '0')
                };
            });
    }

    function submitPriceSelection(doIds) {
        if (!priceForm || !changesField || !hiddenSubmit) return;

        const changes = collectChanges(doIds);
        if (!changes.length) {
            showSweetAlert({
                icon: 'info',
                title: 'No price changes',
                text: 'No changed prices were found in this selection.'
            });
            return;
        }

        changesField.value = JSON.stringify(changes);
        if (saveAllButton) saveAllButton.disabled = true;
        root.querySelectorAll('.wst-docp-save-selection').forEach(function(button) {
            button.disabled = true;
        });
        hiddenSubmit.click();
    }

    priceInputs.forEach(function(input) {
        input.addEventListener('input', function() {
            updateLine(input);
            recalculateDo(input.dataset.doId);
            recalculateCustomer(input.dataset.customerIndex);
            refreshChangedCount();
        });

        input.addEventListener('blur', function() {
            if (input.value !== '' && Number.isFinite(Number.parseFloat(input.value))) {
                input.value = Number.parseFloat(input.value).toFixed(2);
                updateLine(input);
                recalculateDo(input.dataset.doId);
                recalculateCustomer(input.dataset.customerIndex);
                refreshChangedCount();
            }
        });
    });

    root.querySelectorAll('.wst-docp-save-selection').forEach(function(button) {
        button.addEventListener('click', function() {
            const doIds = String(button.dataset.doIds || '')
                .split(',')
                .map(function(value) { return value.trim(); })
                .filter(Boolean);
            submitPriceSelection(doIds);
        });
    });

    if (saveAllButton) {
        saveAllButton.addEventListener('click', function() {
            submitPriceSelection(null);
        });
    }

    if (priceForm) {
        priceForm.addEventListener('submit', function(event) {
            if (!changesField || !changesField.value || changesField.value === '[]') {
                event.preventDefault();
                showSweetAlert({
7�"��zs��������7�"�
N�����
                    icon: 'info',
                    title: 'No price changes',
                    text: 'No changed prices were selected.'
                });
            }
        });
    }

    /*
     * Staff freight calculation and add/update workflow.
     */
    const freightForm = document.getElementById('wst-docp-freight-form');
    const freightDebtorField = document.getElementById('wst_docp_freight_debtor');
    const freightRateField = document.getElementById('wst_docp_freight_rate');
    const freightAmountField = document.getElementById('wst_docp_freight_amount');

    root.querySelectorAll('.wst-docp-freight-panel').forEach(function(panel) {
        const rateInput = panel.querySelector('.wst-docp-freight-rate');
        const amountInput = panel.querySelector('.wst-docp-freight-amount');
        const actionButton = panel.querySelector('.wst-docp-add-freight');
        const dailyKg = numericValue(panel.dataset.dailyKg);

        if (rateInput && amountInput) {
            rateInput.addEventListener('input', function() {
                const rate = numericValue(rateInput.value);
                amountInput.value = rate > 0 && dailyKg > 0
                    ? (rate * dailyKg).toFixed(2)
                    : '';
            });

            rateInput.addEventListener('blur', function() {
                if (rateInput.value !== '' && Number.isFinite(Number.parseFloat(rateInput.value))) {
                    rateInput.value = Number.parseFloat(rateInput.value).toFixed(4);
                }
            });

            amountInput.addEventListener('blur', function() {
                if (amountInput.value !== '' && Number.isFinite(Number.parseFloat(amountInput.value))) {
                    amountInput.value = Number.parseFloat(amountInput.value).toFixed(2);
                }
            });

            [rateInput, amountInput].forEach(function(input) {
                input.addEventListener('keydown', function(event) {
                    if (event.key === 'Enter') {
                        event.preventDefault();
                        if (actionButton && !actionButton.disabled) actionButton.click();
                    }
                });
            });
        }

        if (!actionButton) return;

        actionButton.addEventListener('click', async function() {
            if (!freightForm || !freightDebtorField || !freightRateField || !freightAmountField) {
                await showSweetAlert({
                    icon: 'error',
                    title: 'Freight form unavailable',
                    text: 'Reload the page and try again.'
                });
                return;
            }

            if (currentChangedPriceCount() > 0) {
                await showSweetAlert({
                    icon: 'warning',
                    title: 'Save price changes first',
                    text: 'Save or discard the changed item prices before adding freight. The page reloads after freight is queued.'
                });
                return;
            }

            const debtorCode = String(actionButton.dataset.debtorCode || '').trim();
            const customerName = String(actionButton.dataset.customerName || '').trim();
            const targetDocNo = String(actionButton.dataset.latestDocNo || '').trim();
            const serverDailyKg = numericValue(actionButton.dataset.dailyKg);
            const rate = rateInput ? numericValue(rateInput.value) : 0;
            let amount = amountInput ? numericValue(amountInput.value) : 0;

            if (!debtorCode || !targetDocNo || serverDailyKg <= 0) {
                await showSweetAlert({
                    icon: 'error',
                    title: 'Freight cannot be added',
                    text: 'This customer does not have a valid debtor code, daily KG, or latest Delivery Order.'
                });
                return;
            }

            if (amount <= 0 && rate > 0) {
                amount = rate * serverDailyKg;
                if (amountInput) amountInput.value = amount.toFixed(2);
            }

            if (amount <= 0) {
                await showSweetAlert({
                    icon: 'warning',
                    title: 'Freight amount required',
                    text: 'Enter a freight rate or a final freight amount greater than zero.'
                });
                if (rateInput) rateInput.focus();
                return;
            }

            const confirmation = await showSweetAlert({
                icon: 'question',
                title: 'Add freight charge?',
                html: '<div class="wst-docp-swal-summary">' +
                    '<div><span>Customer</span><strong>' + escapeHtml((customerName || debtorCode) + ' (' + debtorCode + ')') + '</strong></div>' +
                    '<div><span>Daily KG</span><strong>' + escapeHtml(money.format(serverDailyKg) + ' KG') + '</strong></div>' +
                    '<div><span>Rate</span><strong>' + escapeHtml(rate > 0 ? 'RM ' + rate.toFixed(4) + '/KG' : 'Manual amount') + '</strong></div>' +
                    '<div><span>Final freight</span><strong>' + escapeHtml('RM ' + money.format(amount)) + '</strong></div>' +
                    '<div><span>Target latest DO</span><strong>' + escapeHtml(targetDocNo) + '</strong></div>' +
                    '</div>',
                showCancelButton: true,
                confirmButtonText: 'Add freight charge',
                cancelButtonText: 'Cancel',
                reverseButtons: true,
                focusCancel: true
            });

            if (!confirmation.isConfirmed) return;

            freightDebtorField.value = debtorCode;
            freightRateField.value = rate > 0 ? String(rate) : '';
            freightAmountField.value = amount.toFixed(2);

            actionButton.disabled = true;
            actionButton.textContent = 'Adding freight...';
            freightForm.submit();
        });
    });

    refreshChangedCount();
})();
</script>7�"���]����������P`f
N?��<?php
/**
 * RESPONSIVE COMBINED: Delivery Order + Goods Receive + Purchase Invoice + Basket Return
 * - Desktop: two-column layout for customer + add item
 * - Tablet/mobile: stacked cards with mobile chips for items
 * - Avatar header removed
 * - Modal pickers for customers and items (centered on all devices)
 * - Full button styles restored with strong CSS overrides
 * - Form resets only when user clicks "Clear / New DO"
 * - Sticky tab bar, customer dropdown inside Add Item card
 * - Customer sync between Delivery Order and Basket Return
 * - Basket Return keeps customer after successful save
 * - LINKS REDIRECT: Receipt page = /do-receipt/
 * - UPDATED: Delivery Order payload now includes basketQty, cartonQty, unitQty
 * - UPDATED: Bulk-first delivery order entry groups rows by customer + driver
 * - UPDATED (merge): Duplicate rows merge only when customer+driver+item+type+KG are equal.
 * - KG supports decimals (0.01 step), total KG displayed with 2 decimals.
 * - COMPAT: Keeps old ac/v1/job queue flow while sending WPDO/local-DO metadata for the new WordPress-first phase.
 * - PURCHASE INVOICE: Separate creditor-based entry module; Goods Receive remains available during rollout.
 */

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

if (!is_user_logged_in()) {
    echo '<div style="padding:1rem;border:1px solid #fecaca;background:#fff1f2;border-radius:.75rem;color:#991b1b;text-align:center;">
            Please log in to continue.
          </div>';
    return;
}

$current_user = wp_get_current_user();
$current_roles = is_array($current_user->roles ?? null) ? $current_user->roles : [];
$can_create_do = current_user_can('manage_options') || in_array('editor', $current_roles, true);
if (!$can_create_do) {
    echo '<div style="padding:1rem;border:1px solid #fecaca;background:#fff1f2;border-radius:.75rem;color:#991b1b;text-align:center;">
            You do not have permission to create delivery orders.
          </div>';
    return;
}

// -------------------------------------------------------------------
// Shared REST & AJAX data
// -------------------------------------------------------------------
$rest_nonce          = wp_create_nonce('wp_rest');
$ajax_url            = admin_url('admin-ajax.php');
$debtor_nonce        = wp_create_nonce('ac_cs_debtor_search');
$creditor_nonce      = wp_create_nonce('ac_cs_creditor_search');
$item_suggest_nonce  = wp_create_nonce('ac_itemcode_suggest');
$default_location    = 'HQ';
$today_date          = current_time('Y-m-d');

// Delivery Order endpoints
$receipt_page_base   = home_url('/do-receipt/');
$records_page_base   = home_url('/delivery-order-records/');
$REST_JOB_POST       = rest_url('ac/v1/job');
$REST_JOB_BASE       = rest_url('ac/v1/job/');
$REST_RECEIPT_TOKEN  = rest_url('ac/v1/do-receipt-token');

// Basket Return endpoint
$rest_return_post    = rest_url('ac/v1/basket/return');

$show_debtor_code    = false;
$show_creditor_code  = false;
$show_item_code      = false;
// Drivers are WordPress users with role=driver.
$driver_users        = get_users([
    'role'    => 'driver',
    'orderby' => 'display_name',
    'order'   => 'ASC',
]);
$driver_picker_items = array_map(function($driver) {
    $driver_login = trim((string) $driver->user_login);
    $driver_label = strtoupper($driver_login);

    return [
        'id' => (int) $driver->ID,
        'name' => $driver_label,
        'login' => $driver_login,
        'label' => $driver_label,
    ];
}, $driver_users);
?>

<div id="acd-resp-root" class="acd-resp-root"
     data-rest-nonce="<?php echo esc_attr($rest_nonce); ?>"
     data-receipt-base="<?php echo esc_attr($receipt_page_base); ?>"
     data-records-base="<?php echo esc_attr($records_page_base); ?>"
     data-rest-job-post="<?php echo esc_attr($REST_JOB_POST); ?>"
     data-rest-job-base="<?php echo esc_attr($REST_JOB_BASE); ?>"
     data-rest-receipt-token="<?php echo esc_attr($REST_RECEIPT_TOKEN); ?>"
     data-rest-return-post="<?php echo esc_attr($rest_return_post); ?>"
     data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
     data-debtor-nonce="<?php echo esc_attr($debtor_nonce); ?>"
     data-creditor-nonce="<?php echo esc_attr($creditor_nonce); ?>"
     data-item-nonce="<?php echo esc_attr($item_suggest_nonce); ?>"
     data-show-debtor-code="<?php echo $show_debtor_code ? '1' : '0'; ?>"
     data-show-creditor-code="<?php echo $show_creditor_code ? '1' : '0'; ?>"
     data-show-item-code="<?php echo $show_item_code ? '1' : '0'; ?>"
     data-default-location="<?php echo esc_attr($default_location); ?>"
     data-today="<?php echo esc_attr($today_date); ?>"
     data-drivers="<?php echo esc_attr(wp_json_encode($driver_picker_items)); ?>"
     data-local-do-mode="compat-v1"
     data-requested-doc-prefix="WPDO"
     data-grn-mode="compat-v1"
     data-grn-doc-prefix="WPGR"
     data-pi-mode="local-v1"
     data-pi-doc-prefix="WPPI">

    <!-- Tab Bar (Delivery Order | Goods Receive | Purchase Invoice | Basket Return) -->
    <div class="acd-resp-tab-bar">
        <button type="button" class="acd-resp-tab-btn active" data-tab="delivery">Delivery Order</button>
        <button type="button" class="acd-resp-tab-btn" data-tab="goods">Goods Receive</button>
        <button type="button" class="acd-resp-tab-btn" data-tab="purchase">Purchase Invoice</button>
        <button type="button" class="acd-resp-tab-btn" data-tab="basket">Basket Return</button>
    </div>

    <!-- ==================== DELIVERY TAB ==================== -->
    <div id="acd-resp-delivery-tab" class="acd-resp-tab-pane active" data-tab="delivery">
        <!-- Quick entry form -->
        <div class="acd-resp-do-grid">
            <!-- Add Item Card -->
            <div class="acd-resp-card acd-resp-entry-card">
                <div class="acd-resp-card-header">
                    <h3>Add Item</h3>
                </div>
                <div class="acd-resp-card-body">
                    <div class="acd-resp-row-2">
                        <div class="acd-resp-field">
                            <label>Date</label>
                            <input type="date" id="acd_resp_do_date" class="acd-resp-input" value="<?php echo esc_attr($today_date); ?>" required>
                        </div>
                        <div class="acd-resp-field">
                            <label>Customer</label>
                            <div class="acd-resp-search-wrap" id="acdRespDebtorWrapper"
                                 data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
                                 data-nonce="<?php echo esc_attr($debtor_nonce); ?>">
                                <input type="text" id="acdRespDebtorInput" class="acd-resp-input" placeholder="Search customer..." autocomplete="off" readonly required>
                                <button type="button" id="acdRespDebtorClear" class="acd-resp-field-clear" aria-label="Clear customer">×</button>
                                <input type="hidden" id="acd_resp_do_customer" value="">
                                <input type="hidden" id="acd_resp_do_customer_name" value="">
                                <input type="hidden" id="acd_resp_do_sales_agent" value="">
                                <input type="hidden" id="acd_resp_do_location" value="<?php echo esc_attr($default_location); ?>">
                            </div>
                        </div>
                    </div>

                    <div class="acd-resp-row-2">
                        <div class="acd-resp-field">
                            <label>Driver</label>
                            <div class="acd-resp-search-wrap">
                                <input type="text" id="acd_resp_do_driver_name" class="acd-resp-input" placeholder="Select driver..." autocomplete="off" readonly required>
                                <button type="button" id="acdRespDriverClear" class="acd-resp-field-clear" aria-label="Clear driver">×</button>
                                <input type="hidden" id="acd_resp_do_driver" value="">
                                <input type="hidden" id="acd_resp_do_driver_login" value="">
                            </div>
                        </div>
                        <div class="acd-resp-field">
                            <label>Item Name</label>
                            <div class="acd-resp-search-wrap">
                                <input type="text" id="acd_resp_do_item_name" class="acd-resp-input" placeholder="Search item..." autocomplete="off" readonly required>
                                <button type="button" id="acdRespItemClear" class="acd-resp-field-clear" aria-label="Clear item">×</button>
                                <input type="hidden" id="acd_resp_do_item" value="">
                                <input type="hidden" id="acd_resp_do_item_display" value="">
                                <input type="hidden" id="acd_resp_do_item_price" value="0">
                            </div>
                        </div>
                    </div>

                    <div class="acd-resp-row-2">
                        <div class="acd-resp-field">
                            <label>Type</label>
                            <div class="acd-resp-type-toggle" id="acd_resp_do_pack_type_toggle">
                                <button type="button" class="acd-resp-type-btn active" data-pack-type="BASKET">Basket</button>
                                <button type="button" class="acd-resp-type-btn" data-pack-type="CARTON">Carton</button>
                            </div>
                            <select id="acd_resp_do_pack_type" style="display:none;" required>
                                <option value="BASKET" selected>Basket</option>
                                <option value="CARTON">Carton</option>
                            </select>
                        </div>
                        <div class="acd-resp-field">
                            <label>Qty</label>
                            <input type="number" id="acd_resp_do_qty" class="acd-resp-input" value="" step="1" min="0" placeholder="Qty" required>
                        </div>
                    </div>

                    <div class="acd-resp-row-2">
                        <div class="acd-resp-field">
                            <label>Weight (KG)</label>
                            <input type="number" id="acd_resp_do_kg" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Weight (KG)" required>
                        </div>
                        <div class="acd-resp-field">
                            <label>Price</label>
                            <input type="number" id="acd_resp_do_price" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Price">
                        </div>
                    </div>

                    <div class="acd-resp-preview" id="acd_resp_do_line_preview" style="display:none;"></div>
                    <button type="button" id="acd_resp_do_addline" class="acd-resp-btn-primary">Add Item</button>
                </div>
            </div>
        </div>

        <!-- Items Detail Card (full width) -->
        <div class="acd-resp-card acd-resp-items-card">
            <div class="acd-resp-card-header acd-resp-card-header-stack">
                <div class="acd-resp-lines-head">
                    <h3>Items Detail</h3>
                    <span id="acd_resp_do_lines_count_badge" class="acd-resp-lines-badge">0</span>
                </div>

                <button type="button" id="acd_resp_do_submit" class="acd-resp-btn-primary acd-resp-save-btn">Save Delivery Order</button>

                <!-- Success actions panel (hidden initially) -->
                <div id="acd_resp_do_success_actions" class="acd-resp-success-actions" style="display:none;" aria-live="polite">
                    <div class="acd-resp-success-text">
                        <span id="acd_resp_do_success_label">Saved batch</span>:
                        <strong id="acd_resp_do_success_docno">-</strong>
                    </div>
                    <div class="acd-resp-success-btns">
                        <a id="acd_resp_do_receipt_btn"
                           class="acd-resp-action-btn acd-resp-action-soft"
                           href="#"
                           target="_blank"
                           rel="noopener"
                           style="display:none;">
                            View Status
                        </a>
                        <button type="button"
                                id="acd_resp_do_clear_new_btn"
                                class="acd-resp-action-btn acd-resp-action-danger">
                            Clear / New DO
                        </button>
                    </div>
                </div>
            </div>
            <div class="acd-resp-card-body">
                <!-- Item details table header -->
                <div class="acd-resp-lines-header">
                    <span>Item</span>
                    <span>Customer</span>
                    <span>Driver</span>
                    <span>Type</span>
                    <span>Qty</span>
                    <span>KG</span>
                    <span>Total KG</span>
                    <span>Price</span>
                    <span>Total Price</span>
                    <span aria-label="Action">&#9998;</span>
                </div>
                <!-- Lines container -->
                <div id="acd_resp_do_lines" class="acd-resp-lines-container">
                    <div class="acd-resp-empty">No items added</div>
                </div>
            </div>
        </div>
    </div>

    <!-- ==================== GRN FORM ==================== -->
    <div id="acd-resp-grn-tab" class="acd-resp-tab-pane" data-tab="goods">
        <div class="acd-resp-do-grid">
            <!-- Add Item Card -->
            <div class="acd-resp-card acd-resp-entry-card">
                <div class="acd-resp-card-header">
                    <h3>Add Item</h3>
                </div>
                <div class="acd-resp-card-body">
                    <div class="acd-resp-field">
                        <label>Date</label>
                        <input type="date" id="acd_resp_grn_date" class="acd-resp-input" value="<?php echo esc_attr($today_date); ?>">
                    </div>

                    <div class="acd-resp-field">
                        <label>Creditor</label>
                        <div class="acd-resp-search-wrap" id="acdRespCreditorWrapper"
                             data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
                             data-nonce="<?php echo esc_attr($creditor_nonce); ?>">
                            <input type="text" id="acdRespCreditorInput" class="acd-resp-input" placeholder="Search creditor..." autocomplete="off" readonly>
                            <button type="button" id="acdRespCreditorClear" class="acd-resp-field-clear" aria-label="Clear creditor">&times;</button>
                            <input type="hidden" id="acd_resp_grn_creditor" value="">
                            <input type="hidden" id="acd_resp_grn_creditor_name" value="">
                            <input type="hidden" id="acd_resp_grn_location" value="<?php echo esc_attr($default_location); ?>">
                        </div>
                    </div>

                    <div class="acd-resp-field acd-resp-grn-type-field">
                        <label>Type</label>
                        <div class="acd-resp-type-toggle" id="acd_resp_grn_pack_type_toggle">
                            <button type="button" class="acd-resp-type-btn active" data-pack-type="BASKET">Basket</button>
                            <button type="button" class="acd-resp-type-btn" data-pack-type="CARTON">Carton</button>
                        </div>
                        <select id="acd_resp_grn_pack_type" style="display:none;">
                            <option value="BASKET" selected>Basket</option>
 P`f浲���������P�}
N?��                           <option value="CARTON">Carton</option>
                        </select>
                    </div>

                    <div class="acd-resp-field">
                        <label>Item Name</label>
                        <div class="acd-resp-search-wrap">
                            <input type="text" id="acd_resp_grn_item_name" class="acd-resp-input" placeholder="Search item..." autocomplete="off" readonly>
                            <button type="button" id="acdRespGrnItemClear" class="acd-resp-field-clear" aria-label="Clear item">&times;</button>
                            <input type="hidden" id="acd_resp_grn_item" value="">
                            <input type="hidden" id="acd_resp_grn_item_display" value="">
                        </div>
                    </div>

                    <div class="acd-resp-field">
                        <label>Qty</label>
                        <input type="number" id="acd_resp_grn_qty" class="acd-resp-input" value="" step="1" min="0" placeholder="Qty">
                    </div>

                    <div class="acd-resp-field">
                        <label>Weight (KG)</label>
                        <input type="number" id="acd_resp_grn_kg" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Weight (KG)">
                    </div>

                    <div class="acd-resp-field">
                        <label>Price</label>
                        <input type="number" id="acd_resp_grn_price" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Price">
                    </div>

                    <div class="acd-resp-preview" id="acd_resp_grn_line_preview" style="display:none;"></div>
                    <button type="button" id="acd_resp_grn_addline" class="acd-resp-btn-primary">Add Item</button>
                </div>
            </div>
        </div>

        <!-- Items Detail Card -->
        <div class="acd-resp-card acd-resp-items-card">
            <div class="acd-resp-card-header acd-resp-card-header-stack">
                <div class="acd-resp-lines-head">
                    <h3>Items Detail</h3>
                    <span id="acd_resp_grn_lines_count_badge" class="acd-resp-lines-badge">0</span>
                </div>

                <button type="button" id="acd_resp_grn_submit" class="acd-resp-btn-primary acd-resp-save-btn">Save Goods Receive Note</button>

                <div id="acd_resp_grn_success_actions" class="acd-resp-success-actions" style="display:none;" aria-live="polite">
                    <div class="acd-resp-success-text">
                        <span id="acd_resp_grn_success_label">Saved batch</span>:
                        <strong id="acd_resp_grn_success_docno">-</strong>
                    </div>
                    <div class="acd-resp-success-btns">
                        <button type="button"
                                id="acd_resp_grn_clear_new_btn"
                                class="acd-resp-action-btn acd-resp-action-danger">
                            Clear / New GRN
                        </button>
                    </div>
                </div>
            </div>
            <div class="acd-resp-card-body">
                <div class="acd-resp-lines-header">
                    <span>Item</span>
                    <span>Creditor</span>
                    <span>Type</span>
                    <span>Qty</span>
                    <span>KG</span>
                    <span>Total KG</span>
                    <span>Price</span>
                    <span>Total Price</span>
                    <span aria-label="Action">&#9998;</span>
                </div>
                <div id="acd_resp_grn_lines" class="acd-resp-lines-container">
                    <div class="acd-resp-empty">No items added</div>
                </div>
            </div>
        </div>
    </div>

    <!-- Shared Picker Modal -->
    <div class="acd-resp-picker-modal" id="acd_resp_grn_picker_modal" aria-hidden="true">
        <div class="acd-resp-picker-backdrop" id="acd_resp_grn_picker_backdrop"></div>
        <div class="acd-resp-picker-sheet">
            <div class="acd-resp-picker-head">
                <div class="acd-resp-picker-title" id="acd_resp_grn_picker_title">Search</div>
                <button type="button" class="acd-resp-picker-close" id="acd_resp_grn_picker_close" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="acd-resp-picker-body">
                <input type="text" id="acd_resp_grn_picker_search" class="acd-resp-input acd-resp-picker-search" placeholder="Type to search..." autocomplete="off">
                <div class="acd-resp-picker-results" id="acd_resp_grn_picker_results"></div>
            </div>
        </div>
    </div>


    <!-- ==================== PURCHASE INVOICE FORM ==================== -->
    <div id="acd-resp-pi-tab" class="acd-resp-tab-pane" data-tab="purchase">
        <div class="acd-resp-do-grid">
            <!-- Add Item Card -->
            <div class="acd-resp-card acd-resp-entry-card">
                <div class="acd-resp-card-header">
                    <h3>Add Item</h3>
                </div>
                <div class="acd-resp-card-body">
                    <div class="acd-resp-warning" role="alert">
                        <strong>Transition warning:</strong> Use either Goods Receive or Purchase Invoice for the same incoming goods, never both. A direct Purchase Invoice increases stock; submitting both can increase stock twice.
                    </div>
                    <div class="acd-resp-field">
                        <label>Date</label>
                        <input type="date" id="acd_resp_pi_date" class="acd-resp-input" value="<?php echo esc_attr($today_date); ?>">
                    </div>

                    <div class="acd-resp-field">
                        <label>Supplier Invoice No.</label>
                        <input type="text" id="acd_resp_pi_supplier_invoice_no" class="acd-resp-input" maxlength="100" placeholder="Supplier invoice reference">
                    </div>

                    <div class="acd-resp-field">
                        <label>Creditor</label>
                        <div class="acd-resp-search-wrap" id="acdRespPiCreditorWrapper"
                             data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
                             data-nonce="<?php echo esc_attr($creditor_nonce); ?>">
                            <input type="text" id="acdRespPiCreditorInput" class="acd-resp-input" placeholder="Search creditor..." autocomplete="off" readonly>
                            <button type="button" id="acdRespPiCreditorClear" class="acd-resp-field-clear" aria-label="Clear creditor">&times;</button>
                            <input type="hidden" id="acd_resp_pi_creditor" value="">
                            <input type="hidden" id="acd_resp_pi_creditor_name" value="">
                            <input type="hidden" id="acd_resp_pi_location" value="<?php echo esc_attr($default_location); ?>">
                        </div>
                    </div>

                    <div class="acd-resp-field acd-resp-pi-type-field">
                        <label>Type</label>
                        <div class="acd-resp-type-toggle" id="acd_resp_pi_pack_type_toggle">
                            <button type="button" class="acd-resp-type-btn active" data-pack-type="BASKET">Basket</button>
                            <button type="button" class="acd-resp-type-btn" data-pack-type="CARTON">Carton</button>
                        </div>
                        <select id="acd_resp_pi_pack_type" style="display:none;">
                            <option value="BASKET" selected>Basket</option>
                            <option value="CARTON">Carton</option>
                        </select>
                    </div>

                    <div class="acd-resp-field">
                        <label>Item Name</label>
                        <div class="acd-resp-search-wrap">
                            <input type="text" id="acd_resp_pi_item_name" class="acd-resp-input" placeholder="Search item..." autocomplete="off" readonly>
                            <button type="button" id="acdRespPiItemClear" class="acd-resp-field-clear" aria-label="Clear item">&times;</button>
                            <input type="hidden" id="acd_resp_pi_item" value="">
                            <input type="hidden" id="acd_resp_pi_item_display" value="">
                        </div>
                    </div>

                    <div class="acd-resp-field">
                        <label>Qty</label>
                        <input type="number" id="acd_resp_pi_qty" class="acd-resp-input" value="" step="1" min="0" placeholder="Qty">
                    </div>

                    <div class="acd-resp-field">
                        <label>Weight (KG)</label>
                        <input type="number" id="acd_resp_pi_kg" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Weight (KG)">
                    </div>

                    <div class="acd-resp-field">
                        <label>Price</label>
                        <input type="number" id="acd_resp_pi_price" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Price">
                    </div>

                    <div class="acd-resp-preview" id="acd_resp_pi_line_preview" style="display:none;"></div>
                    <button type="button" id="acd_resp_pi_addline" class="acd-resp-btn-primary">Add Item</button>
                </div>
            </div>
        </div>

        <!-- Items Detail Card -->
        <div class="acd-resp-card acd-resp-items-card">
            <div class="acd-resp-card-header acd-resp-card-header-stack">
                <div class="acd-resp-lines-head">
                    <h3>Items Detail</h3>
                    <span id="acd_resp_pi_lines_count_badge" class="acd-resp-lines-badge">0</span>
                </div>

                <button type="button" id="acd_resp_pi_submit" class="acd-resp-btn-primary acd-resp-save-btn">Save Purchase Invoice</button>

                <div id="acd_resp_pi_success_actions" class="acd-resp-success-actions" style="display:none;" aria-live="polite">
                    <div class="acd-resp-success-text">
                        <span id="acd_resp_pi_success_label">Saved batch</span>:
                        <strong id="acd_resp_pi_success_docno">-</strong>
                    </div>
                    <div class="acd-resp-success-btns">
                        <button type="button"
                                id="acd_resp_pi_clear_new_btn"
                                class="acd-resp-action-btn acd-resp-action-danger">
                            Clear / New PI
                        </button>
                    </div>
                </div>
            </div>
            <div class="acd-resp-card-body">
                <div class="acd-resp-lines-header">
                    <span>Item</span>
                    <span>Creditor</span>
                    <span>Type</span>
                    <span>Qty</span>
                    <span>KG</span>
                    <span>Total KG</span>
                    <span>Price</span>
                    <span>Total Price</span>
                    <span aria-label="Action">&#9998;</span>
                </div>
                <div id="acd_resp_pi_lines" class="acd-resp-lines-container">
                    <div class="acd-resp-empty">No items added</div>
                </div>
            </div>
        </div>
    </div>

    <!-- Purchase Invoice Picker Modal -->
    <div class="acd-resp-picker-modal" id="acd_resp_pi_picker_modal" aria-hidden="true">
        <div class="acd-resp-picker-backdrop" id="acd_resp_pi_picker_backdrop"></div>
        <div class="acd-resp-picker-sheet">
            <div class="acd-resp-picker-head">
                <div class="acd-resp-picker-title" id="acd_resp_pi_picker_title">Search</div>
                <button type="button" class="acd-resp-picker-close" id="acd_resp_pi_picker_close" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="acd-resp-picker-body">
                <input type="text" id="acd_resp_pi_picker_search" class="acd-resp-input acd-resp-picker-search" placeholder="Type to search..." autocomplete="off">
                <div class="acd-resp-picker-results" id="acd_resp_pi_picker_results"></div>
            </div>
        </div>
    </div>


    <!-- ==================== BASKET TAB ==================== -->
    <div id="acd-resp-basket-tab" class="acd-resp-tab-pane" data-tab="basket">
        <div class="acd-resp-stack">
            <div class="acd-resp-card acd-resp-basket-card">
                <div class="acd-resp-card-header">
                    <h3 id="acd_resp_br_title">Customer Basket Return</h3>
                </div>
                <div class="acd-resp-card-body">
                    <input type="hidden" id="acd_resp_br_date" value="<?php echo esc_attr($today_date); ?>">

                    <div class="acd-resp-field">
                        <label>Return Account</label>
                        <div class="acd-resp-type-toggle" id="acd_resp_br_account_type_toggle">
                            <button type="button" class="acd-resp-type-btn active" data-account-type="CUSTOMER">Customer</button>
                            <button type="button" class="acd-resp-type-btn" data-account-type="CREDITOR">Creditor</button>
                        </div>
                        <select id="acd_resp_br_account_type" style="display:none;">
                            <option value="CUSTOMER" selected>Customer</option>
                            <option value="CREDITOR">Creditor</option>
                        </select>
                    </div>

                    <div class="acd-resp-field">
                        <label id="acd_resp_br_account_label">Customer</label>
                        <div class="acd-resp-search-wrap" id="acdRespBrDebtorWrapper"
                             data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
                             data-debtor-nonce="<?php echo esc_attr($debtor_nonce); ?>"
                             data-creditor-nonce="<?php echo esc_attr($creditor_nonce); ?>">
                            <input type="text" id="acdRespBrDebtorInput" class="acd-resp-input" placeholder="Search customer..." autocomplete="off" readonly>
                            <button type="button" id="acdRespBrDebtorClear" class="acd-resp-field-clear" aria-label="Clear selected account">&times;</button>
                            <input type="hidden" id="acd_resp_br_debtor_code" value="">
                            <input type="hidden" id="acd_resp_br_debtor_name" value="">
                            <input type="hidden" id="acd_resp_br_creditor_code" value="">
                            <input type="hidden" id="acd_resp_br_creditor_name" value="">
                        </div>
                    </div>

                    <div class="acd-resp-field">
                        <label id="acd_resp_br_qty_label">Basket Returned by Customer</label>
                        <input type="number" id="acd_resp_br_qty" class="acd-resp-input" value="" min="1" step="1" placeholder="Basket Return Qty">
                    </div>

                    <div class="acd-resp-field">
                        <label>Proof Image <span class="acd-resp-label-note">Optional</span></label>
                        <input type="file" id="acd_resp_br_proof" class="acd-resp-file-input" accept="image/jpeg,image/png,image/webp" capture="environment">
                    </div>

                    <button type="button" id="acd_resp_br_submit" class="acd-resp-btn-primary">Save Customer Basket Return</button>
          P�}I^H���������P�
N?��          <div id="acd_resp_br_status" class="acd-resp-status" aria-live="polite"></div>
                </div>
            </div>
        </div>

        <!-- Basket Account Picker Modal -->
        <div class="acd-resp-picker-modal" id="acd_resp_br_picker_modal" aria-hidden="true">
            <div class="acd-resp-picker-backdrop" id="acd_resp_br_picker_backdrop"></div>
            <div class="acd-resp-picker-sheet">
                <div class="acd-resp-picker-head">
                    <div class="acd-resp-picker-title" id="acd_resp_br_picker_title">Select Customer</div>
                    <button type="button" class="acd-resp-picker-close" id="acd_resp_br_picker_close" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="acd-resp-picker-body">
                    <input type="text" id="acd_resp_br_picker_search" class="acd-resp-input acd-resp-picker-search" placeholder="Type to search..." autocomplete="off">
                    <div class="acd-resp-picker-results" id="acd_resp_br_picker_results"></div>
                </div>
            </div>
        </div>
    </div>

    <!-- Shared Picker Modal for Delivery Order -->
    <div class="acd-resp-picker-modal" id="acd_resp_do_picker_modal" aria-hidden="true">
        <div class="acd-resp-picker-backdrop" id="acd_resp_do_picker_backdrop"></div>
        <div class="acd-resp-picker-sheet">
            <div class="acd-resp-picker-head">
                <div class="acd-resp-picker-title" id="acd_resp_do_picker_title">Search</div>
                <button type="button" class="acd-resp-picker-close" id="acd_resp_do_picker_close" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="acd-resp-picker-body">
                <input type="text" id="acd_resp_do_picker_search" class="acd-resp-input acd-resp-picker-search" placeholder="Type to search..." autocomplete="off">
                <div class="acd-resp-picker-results" id="acd_resp_do_picker_results"></div>
            </div>
        </div>
    </div>
</div>

<style>
/* ----------------------------------------------
   RESPONSIVE STYLES (full original button effects + mobile chips)
   With stronger CSS overrides to beat theme styles
---------------------------------------------- */
#acd-resp-root {
    --acd-bg: #f8fafc;
    --acd-card-bg: #ffffff;
    --acd-border: #dbe4ee;
    --acd-border-strong: #c4d0dd;
    --acd-text: #0f172a;
    --acd-muted: #475569;
    --acd-green: #166534;
    --acd-green-light: #dcfce7;
    --acd-green-soft: #f0fdf4;
    --acd-green-dark: #14532d;
    --acd-danger: #dc2626;
    --acd-radius: 0.75rem;
    --acd-shadow: 0 0.75rem 1.75rem rgba(15, 23, 42, 0.08);
    font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
    color: var(--acd-text);
    background: var(--acd-bg);
    font-size: 1rem;
    margin: 0;
    padding: 0;
    max-width: none;
}

#acd-resp-root * {
    box-sizing: border-box;
}

/* Sticky tab bar */
#acd-resp-root .acd-resp-tab-bar {
    position: sticky;
    top: 0;
    z-index: 80;
    background: #ffffff !important;
    box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
}

/* Remove original two-column grid because customer moved into Add Item card */
#acd-resp-root .acd-resp-do-grid {
    display: block;
}

@media (min-width: 1024px) {
    #acd-resp-root .acd-resp-do-grid {
        display: block;
    }
}

/* Tab Bar - four tabs: Delivery Order | Goods Receive | Purchase Invoice | Basket Return */
.acd-resp-tab-bar {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0.45rem;
    background: #fff;
    border: 1px solid var(--acd-border);
    border-radius: 0.9rem;
    padding: 0.45rem;
    margin: 0 0 0.8rem;
}
@media (min-width: 1024px) {
    .acd-resp-tab-bar {
        display: grid;
        grid-template-columns: repeat(4, max-content);
        justify-content: start;
        background: #fff;
        border: 1px solid var(--acd-border);
        border-radius: 0.9rem;
        padding: 0.45rem;
        margin-bottom: 0.8rem;
    }
}
.acd-resp-tab-btn {
    min-width: 0;
    min-height: 3rem;
    padding: 0.65rem 0.75rem;
    font-size: 0.92rem;
    font-weight: 700;
    background: #f8fafc;
    border: 1px solid var(--acd-border);
    border-radius: 0.7rem;
    color: var(--acd-muted);
    cursor: pointer;
    transition: all 0.18s ease;
    text-align: center;
    white-space: normal;
}
.acd-resp-tab-btn:hover {
    background: var(--acd-green-soft);
    color: var(--acd-green);
}
.acd-resp-tab-btn.active {
    background: var(--acd-green-soft);
    border-color: #86efac;
    color: var(--acd-green);
}
@media (min-width: 1024px) {
    .acd-resp-tab-btn:hover {
        background: var(--acd-green-soft);
    }
}

/* Tab Panes */
.acd-resp-tab-pane {
    display: none;
    padding: 0;
}
.acd-resp-tab-pane.active {
    display: block;
}

/* Cards */
.acd-resp-card {
    background: var(--acd-card-bg);
    border: 1px solid var(--acd-border);
    border-radius: var(--acd-radius);
    box-shadow: var(--acd-shadow);
    overflow: hidden;
}
.acd-resp-items-card {
    margin-top: 0.9rem;
}
.acd-resp-stack {
    max-width: 42rem;
}
#acd-resp-basket-tab .acd-resp-stack {
    width: 100%;
    max-width: 64rem;
    margin-left: auto;
    margin-right: auto;
}
.acd-resp-basket-card {
    width: 100%;
}
.acd-resp-card-header {
    padding: 0.85rem 0.9rem;
    border-bottom: 1px solid var(--acd-border);
    background: #fcfdff;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
}
.acd-resp-card-header-stack {
    flex-direction: column;
    align-items: stretch;
}
.acd-resp-card-header h3 {
    margin: 0;
    font-size: 1rem;
    font-weight: 800;
}
.acd-resp-lines-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
}
.acd-resp-lines-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.8rem;
    min-height: 1.8rem;
    padding: 0 0.45rem;
    border-radius: 999px;
    background: var(--acd-green-soft);
    border: 1px solid #bbf7d0;
    color: var(--acd-green);
    font-size: 0.82rem;
    font-weight: 800;
}
.acd-resp-card-body {
    padding: 0.9rem;
}

@media (min-width: 768px) {
    #acd-resp-root .acd-resp-card-body {
        padding: 1rem;
    }

    #acd-resp-root .acd-resp-entry-card .acd-resp-card-body {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.9rem 1rem;
        align-items: end;
    }

    #acd-resp-root .acd-resp-entry-card .acd-resp-field,
    #acd-resp-root .acd-resp-entry-card .acd-resp-row-2,
    #acd-resp-root .acd-resp-entry-card .acd-resp-preview,
    #acd-resp-root .acd-resp-entry-card .acd-resp-warning,
    #acd-resp-root .acd-resp-entry-card #acd_resp_do_addline,
    #acd-resp-root .acd-resp-entry-card #acd_resp_grn_addline,
    #acd-resp-root .acd-resp-entry-card #acd_resp_pi_addline {
        margin-bottom: 0;
    }

    /* Date field spans full width; Customer/Driver and Item/Type pair naturally */
    #acd-resp-root .acd-resp-entry-card .acd-resp-card-body > .acd-resp-field:first-child {
        grid-column: 1 / -1;
    }

    /* Creditor document field positioning (Goods Receive and Purchase Invoice):
       Date | Creditor
       Type | Type
       Item | Qty
       Weight | Price */
    #acd-resp-root #acd-resp-grn-tab .acd-resp-entry-card .acd-resp-card-body > .acd-resp-field:first-child,
    #acd-resp-root #acd-resp-pi-tab .acd-resp-entry-card .acd-resp-card-body > .acd-resp-field:first-child {
        grid-column: auto;
    }

    #acd-resp-root #acd-resp-grn-tab .acd-resp-grn-type-field,
    #acd-resp-root #acd-resp-pi-tab .acd-resp-pi-type-field {
        grid-column: 1 / -1;
    }

    #acd-resp-root .acd-resp-entry-card .acd-resp-row-2,
    #acd-resp-root .acd-resp-entry-card .acd-resp-preview,
    #acd-resp-root .acd-resp-entry-card .acd-resp-warning,
    #acd-resp-root .acd-resp-entry-card #acd_resp_do_addline,
    #acd-resp-root .acd-resp-entry-card #acd_resp_grn_addline,
    #acd-resp-root .acd-resp-entry-card #acd_resp_pi_addline {
        grid-column: 1 / -1;
    }
}

/* Purchase Invoice transition warning */
.acd-resp-warning {
    margin-bottom: 0.85rem;
    padding: 0.8rem 0.9rem;
    border: 1px solid #fbbf24;
    border-radius: 0.65rem;
    background: #fffbeb;
    color: #92400e;
    font-size: 0.9rem;
    line-height: 1.45;
}

/* Fields */
.acd-resp-field {
    margin-bottom: 0.85rem;
}
.acd-resp-field label {
    display: block;
    font-size: 0.88rem;
    font-weight: 700;
    color: var(--acd-muted);
    margin-bottom: 0.35rem;
}
.acd-resp-label-note {
    color: #64748b;
    font-size: 0.78rem;
    font-weight: 700;
}
.acd-resp-input {
    width: 100%;
    min-height: 3rem;
    padding: 0.72rem 0.85rem;
    border: 1px solid var(--acd-border-strong);
    border-radius: 0.65rem;
    background: #fff;
    font-size: 1rem;
    transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.acd-resp-file-input {
    width: 100%;
    min-height: 3rem;
    padding: 0.66rem 0.75rem;
    border: 1px dashed var(--acd-border-strong);
    border-radius: 0.65rem;
    background: #fff;
    font-size: 0.95rem;
}
.acd-resp-file-input:focus {
    outline: none;
    border-color: var(--acd-green);
    box-shadow: 0 0 0 0.2rem rgba(22, 101, 52, 0.10);
}
.acd-resp-input:focus {
    outline: none;
    border-color: var(--acd-green);
    box-shadow: 0 0 0 0.2rem rgba(22, 101, 52, 0.10);
}

/* Make date input fully clickable — expand the native calendar picker to full width */
#acd-resp-root input[type="date"].acd-resp-input,
#acd-resp-root input[type="date"] {
    position: relative;
    cursor: pointer;
}
#acd-resp-root input[type="date"].acd-resp-input::-webkit-calendar-picker-indicator,
#acd-resp-root input[type="date"]::-webkit-calendar-picker-indicator {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}
/* Firefox fallback */
#acd-resp-root input[type="date"].acd-resp-input::-moz-calendar-picker-indicator,
#acd-resp-root input[type="date"]::-moz-calendar-picker-indicator {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

.acd-resp-search-wrap {
    position: relative;
}
.acd-resp-search-wrap .acd-resp-input {
    padding-right: 3.1rem;
    cursor: pointer;
}
.acd-resp-field-clear {
    position: absolute;
    top: 50%;
    right: 0.5rem;
    transform: translateY(-50%);
    width: 2.15rem;
    height: 2.15rem;
    border: 1px solid var(--acd-border);
    background: #fff;
    color: #64748b;
    border-radius: 0.5rem;
    display: none;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
    cursor: pointer;
    transition: all 0.18s ease;
}
.acd-resp-field-clear.show {
    display: inline-flex;
}
.acd-resp-field-clear:hover {
    background: var(--acd-green-soft);
    border-color: #bbf7d0;
    color: var(--acd-green);
}

/* Type Toggle Buttons */
.acd-resp-type-toggle {
    display: flex;
    gap: 0.55rem;
}
.acd-resp-type-btn {
    flex: 1;
    min-height: 3rem;
    padding: 0.7rem 0.8rem;
    border: 1px solid var(--acd-border-strong);
    background: #f8fafc;
    color: #334155;
    border-radius: 0.65rem;
    font-weight: 700;
    font-size: 1rem;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}
.acd-resp-type-btn:hover {
    background: #ecfdf3;
    border-color: #86efac;
    color: var(--acd-green);
}
.acd-resp-type-btn.active {
    background: var(--acd-green-light);
    border-color: #16a34a;
    color: var(--acd-green);
    box-shadow: 0 0 0 1px rgba(22, 101, 52, 0.05) inset;
}
.acd-resp-row-2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.8rem;
    margin-bottom: 0.5rem;
}
@media (max-width: 480px) {
    .acd-resp-row-2 {
        grid-template-columns: 1fr;
        gap: 0;
    }
}
.acd-resp-preview {
    background: var(--acd-green-soft);
    border: 1px solid #bbf7d0;
    border-radius: 0.65rem;
    padding: 0.7rem 0.8rem;
    margin: 0.6rem 0;
    font-size: 0.95rem;
}

/* ========== PRIMARY BUTTONS - STRONG OVERRIDES ========== */
#acd-resp-root .acd-resp-btn-primary,
#acd-resp-root button.acd-resp-btn-primary {
    width: 100%;
    min-height: 3.05rem;
    padding: 0.78rem 1rem;
    border: 1px solid var(--acd-green);
    border-radius: 0.7rem;
    background: var(--acd-green);
    color: #ffffff;
    font-weight: 800;
    font-size: 1rem;
    line-height: 1.2;
    font-family: inherit;
    text-align: center;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
    appearance: none;
    -webkit-appearance: none;
    box-shadow: none;
}

#acd-resp-root .acd-resp-btn-primary:hover,
#acd-resp-root button.acd-resp-btn-primary:hover {
    background: var(--acd-green-dark);
    border-color: var(--acd-green-dark);
    color: #ffffff;
    box-shadow: 0 4px 12px rgba(22, 101, 52, 0.14);
}

#acd-resp-root .acd-resp-btn-primary:focus,
#acd-resp-root button.acd-resp-btn-primary:focus {
    outline: none;
    box-shadow: 0 0 0 0.2rem rgba(22, 101, 52, 0.12);
}

#acd-resp-root .acd-resp-btn-primary:disabled,
#acd-resp-root button.acd-resp-btn-primary:disabled {
    background: #94a3b8;
    border-color: #94a3b8;
    color: #ffffff;
    cursor: not-allowed;
    opacity: 1;
    box-shadow: none;
}

/* Save button inside items card */
#acd-resp-root .acd-resp-save-btn {
    width: 100%;
}

/* Item details table */
.acd-resp-lines-header {
    display: grid;
    grid-template-columns: minmax(12rem, 2fr) 1fr 0.8fr 0.8fr 0.55fr 0.55fr 0.75fr 0.85fr 0.9fr 3rem;
    gap: 0.45rem;
    align-items: center;
    text-align: center;
    background: #f1f5f9;
    border: 1px solid var(--acd-border);
    border-radius: 0.65rem 0.65rem 0 0;
    padding: 0.72rem 0.8rem;
    font-size: 0.85rem;
    font-weight: 800;
    margin-bottom: 0.25rem;
}
.acd-resp-lines-header span:first-child {
    text-align: left;
}

.acd-resp-line {
    display: grid;
    grid-template-columns: minmax(12rem, 2fr) 1fr 0.8fr 0.8fr 0.55fr 0.55fr 0.75fr 0.85fr 0.9fr 3rem;
    gap: 0.45rem;
    align-items: center;
    text-align: center;
    padding: 0.7rem 0.8rem;
    border-right: 1px solid #eef2f6;
    border-left: 1px solid #eef2f6;
    border-bottom: 1px solid #eef2f6;
    font-size: 0.95rem;
}
.acd-resp-line > div:first-child {
    text-align: left;
}

.acd-resp-price-input {
    width: 100%;
    min-height: 2.35rem;
    padding: 0.45rem 0.55rem;
    border: 1px solid var(--acd-border-strong);
    border-radius: 0.55rem;
    background: #fff;
    color: var(--acd-text);
    font: inherit;
    font-weight: 700;
    text-align: center;
}
.acd-resp-price-input:focus {
    outline: none;
    border-color: var(--acd-green);
    box-shadow: 0 0 0 0.18rem rgba(22, 101, 52, 0.10);
}
.acd-resp-money-cell,
.acd-resp-number-cell {
    font-variant-numeric: tabular-nums;
}
.acd-resp-money-cell,
.acd-resp-price-cell,
.acd-resp-number-cell {
    text-align: center;
}
.acd-resp-type-pill {
    display: inline-flex;
    padding: 0.3rem 0.7rem;
    border-radius: 999px;
    background: var(--acd-green-soft);
    border: 1px solid #bbf7d0;
    color: var(--acd-green);
    font-size: 0.8rem;
    font-weight: 800;
}

/* ========== DESKTOP DELETE BUTTON STYLES ========== */
#acd-resp-root .acd-resp-delete-btn,
#acd-resp-root button.acd-resp-delete-btn {
    display: inline-flex;
    align-items: center;
    justify-P�F�(����������P&�
N?��content: center;
    width: 2.4rem;
    min-width: 2.4rem;
    min-height: 2.35rem;
    padding: 0.45rem;
    border: 1px solid #fecaca;
    background: #fff5f5;
    color: #dc2626;
    border-radius: 0.65rem;
    font-size: 1rem;
    font-weight: 700;
    line-height: 1.2;
    font-family: inherit;
    cursor: pointer;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
    appearance: none;
    -webkit-appearance: none;
    box-shadow: none;
}

#acd-resp-root .acd-resp-delete-btn:hover,
#acd-resp-root button.acd-resp-delete-btn:hover {
    background: #fee2e2;
    border-color: #fca5a5;
    color: #b91c1c;
}

#acd-resp-root .acd-resp-delete-btn:focus,
#acd-resp-root button.acd-resp-delete-btn:focus {
    outline: none;
    box-shadow: 0 0 0 0.2rem rgba(220, 38, 38, 0.12);
}

.acd-resp-lines-container {
    max-height: min(32rem, 64vh);
    overflow: auto;
    padding: 0.15rem;
}
.acd-resp-lines-header,
.acd-resp-line {
    min-width: 88rem;
}
.acd-resp-empty {
    padding: 1.2rem;
    text-align: center;
    color: var(--acd-muted);
    font-style: italic;
}
.acd-resp-status {
    margin-top: 0.8rem;
    font-size: 0.9rem;
    text-align: center;
}

/* ========== SUCCESS ACTIONS PANEL ========== */
#acd-resp-root .acd-resp-success-actions {
    margin-top: 0.75rem;
    padding: 0.85rem;
    border: 1px solid #bbf7d0;
    background: var(--acd-green-soft);
    border-radius: 0.75rem;
}

#acd-resp-root .acd-resp-success-text {
    font-size: 0.9rem;
    font-weight: 700;
    color: var(--acd-green-dark);
    margin-bottom: 0.55rem;
}

#acd-resp-root .acd-resp-success-btns {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.5rem;
}

@media (min-width: 768px) {
    #acd-resp-root .acd-resp-success-btns {
        grid-template-columns: repeat(2, 1fr);
    }
}

#acd-resp-root .acd-resp-action-btn,
#acd-resp-root a.acd-resp-action-btn,
#acd-resp-root button.acd-resp-action-btn {
    min-height: 2.8rem;
    padding: 0.7rem 0.8rem;
    border-radius: 0.65rem;
    font-size: 0.92rem;
    font-weight: 800;
    line-height: 1.2;
    font-family: inherit;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    appearance: none;
    -webkit-appearance: none;
    box-shadow: none;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}

#acd-resp-root .acd-resp-action-green {
    background: var(--acd-green);
    border: 1px solid var(--acd-green);
    color: #ffffff;
}

#acd-resp-root .acd-resp-action-green:hover {
    background: var(--acd-green-dark);
    border-color: var(--acd-green-dark);
    color: #ffffff;
}

#acd-resp-root .acd-resp-action-soft {
    background: #ffffff;
    border: 1px solid #86efac;
    color: var(--acd-green);
}

#acd-resp-root .acd-resp-action-soft:hover {
    background: #dcfce7;
    border-color: #22c55e;
    color: var(--acd-green-dark);
}

#acd-resp-root .acd-resp-action-danger {
    background: #fff5f5;
    border: 1px solid #fecaca;
    color: var(--acd-danger);
}

#acd-resp-root .acd-resp-action-danger:hover {
    background: #fee2e2;
    border-color: #fca5a5;
    color: #b91c1c;
}


/* Picker Modal - Base styles (centered) */
.acd-resp-picker-modal {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 0.75rem;
}
.acd-resp-picker-modal.active {
    display: flex;
}
.acd-resp-picker-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
}
.acd-resp-picker-sheet {
    position: relative;
    width: 100%;
    max-width: 42rem;
    background: #fff;
    border-radius: 0.9rem;
    box-shadow: 0 1.4rem 2.4rem rgba(0, 0, 0, 0.18);
    overflow: hidden;
}
.acd-resp-picker-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.85rem 0.95rem;
    border-bottom: 1px solid var(--acd-border);
}
.acd-resp-picker-title {
    font-size: 1.05rem;
    font-weight: 800;
}
/* Picker close button - fixed alignment */
.acd-resp-picker-close {
    flex: 0 0 auto;
    width: 2.35rem;
    height: 2.35rem;
    padding: 0;
    border: 1px solid var(--acd-border-strong);
    background: #fff;
    color: var(--acd-text);
    border-radius: 0.55rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    font-size: 1.35rem;
    font-weight: 500;
    font-family: Arial, sans-serif;
    cursor: pointer;
    transition: all 0.18s ease;
    appearance: none;
    -webkit-appearance: none;
}
.acd-resp-picker-close span {
    display: block;
    line-height: 1;
    transform: translateY(-1px);
}
.acd-resp-picker-close:hover {
    background: var(--acd-green-soft);
    border-color: #bbf7d0;
    color: var(--acd-green);
}
.acd-resp-picker-body {
    padding: 0.85rem 0.95rem 0.95rem;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.acd-resp-picker-results {
    max-height: min(24rem, calc(86vh - 9rem));
    overflow-y: auto;
}
/* Override modal text colours to ensure dark text on white background */
.acd-resp-picker-title,
.acd-resp-picker-search,
.acd-resp-picker-results,
.acd-resp-picker-item,
.acd-resp-picker-item-main {
    color: var(--acd-text);
}
.acd-resp-picker-note,
.acd-resp-picker-item-sub {
    color: var(--acd-muted);
}
.acd-resp-picker-item {
    color: var(--acd-text);
}
.acd-resp-picker-item {
    display: block;
    width: 100%;
    text-align: left;
    min-height: 3rem;
    padding: 0.78rem 0.85rem;
    border: 1px solid var(--acd-border);
    border-radius: 0.65rem;
    background: #fff;
    margin-bottom: 0.5rem;
    cursor: pointer;
    transition: all 0.18s ease;
}
.acd-resp-picker-item:hover {
    background: var(--acd-green-soft);
    border-color: #bbf7d0;
}
.acd-resp-picker-item-main {
    font-weight: 800;
}
.acd-resp-picker-item-sub {
    font-size: 0.8rem;
    color: var(--acd-muted);
}

/* Force picker modal to stay centered on desktop, tablet, and mobile (overrides previous bottom-sheet behavior) */
#acd-resp-root .acd-resp-picker-modal {
    align-items: center !important;
    justify-content: center !important;
    padding: 0.75rem !important;
}

#acd-resp-root .acd-resp-picker-sheet {
    width: 100% !important;
    max-width: min(42rem, calc(100vw - 2rem)) !important;
    border-radius: 0.9rem !important;
    max-height: 86vh !important;
    overflow: hidden !important;
}
</style>

<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>

<script>
(function(){
    // --------------------------------------------------------------
    // TAB SWITCHING
    // --------------------------------------------------------------
    const tabs = document.querySelectorAll('#acd-resp-root .acd-resp-tab-btn');
    const panes = {
        delivery: document.getElementById('acd-resp-delivery-tab'),
        goods: document.getElementById('acd-resp-grn-tab'),
        purchase: document.getElementById('acd-resp-pi-tab'),
        basket: document.getElementById('acd-resp-basket-tab')
    };
    function activateTab(tabId) {
        tabs.forEach(btn => btn.classList.toggle('active', btn.dataset.tab === tabId));
        Object.keys(panes).forEach(id => panes[id].classList.toggle('active', id === tabId));
    }
    tabs.forEach(btn => btn.addEventListener('click', () => {
        const tabId = btn.dataset.tab;
        if (tabId && panes[tabId]) activateTab(tabId);
    }));

    // --------------------------------------------------------------
    // DELIVERY ORDER MODULE (with customer moved into Add Item)
    // --------------------------------------------------------------
    const root = document.getElementById('acd-resp-root');

    // Shared state between Delivery Order and Basket Return for customer sync
    const sharedCustomerState = {
        basketCustomerManuallyCleared: false,
        deliveryCustomer: { name: '', code: '' },
        basketCustomer: { name: '', code: '' }
    };

    const doContainer = document.getElementById('acd-resp-delivery-tab');
    if (doContainer && !doContainer.dataset.doInit) {
        doContainer.dataset.doInit = '1';

        const REST_NONCE    = root.dataset.restNonce;
        const RECEIPT_BASE  = root.dataset.receiptBase;
        const RECORDS_BASE  = root.dataset.recordsBase || '';
        const REST_JOB_POST = root.dataset.restJobPost;
        const REST_JOB_BASE = root.dataset.restJobBase;
        const REST_RECEIPT_TOKEN = root.dataset.restReceiptToken;
        const LOCAL_DO_MODE = root.dataset.localDoMode || 'legacy';
        const REQUESTED_DOC_PREFIX = root.dataset.requestedDocPrefix || 'WPDO';
        const AJAX_URL      = root.dataset.ajaxUrl;
        const DEBTOR_NONCE  = root.dataset.debtorNonce;
        const ITEM_NONCE    = root.dataset.itemNonce;
        let DRIVER_ITEMS = [];
        try {
            DRIVER_ITEMS = JSON.parse(root.dataset.drivers || '[]');
        } catch(e) {
            DRIVER_ITEMS = [];
        }
        const DROPDOWN_META = {
            showDebtorCode: root.dataset.showDebtorCode === '1',
            showItemCode: root.dataset.showItemCode === '1',
            showSalesAgent: false
        };

        const state = {
            lines: [],
            jobFinished: false,
            isSubmitting: false,
            savedPendingClear: false,
        };
        const pickerState = { items: [], defaultItems: [], fetchFn: null, onPick: null };
        let pickerTimer = null;

        function $(id) { return document.getElementById(id); }
        function submitIdleText() { return 'Save Delivery Order'; }
        function submitDoneText() { return 'Saved - Ready for Next Batch'; }
        function submitProgressText() { return 'Queuing...'; }
        function successToastText(count = 1) { return count === 1 ? 'Delivery Order queued' : `${count} Delivery Orders queued`; }

        function escapeHtml(s) {
            if (!s) return '';
            return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));
        }

        // NEW helper functions for quantity and KG (decimal support)
        function fmtQty(n) {
            const x = Number(n);
            return (!isFinite(x)) ? '0' : String(Math.round(x));
        }

        function fmtKg(n) {
            const x = Number(n);
            return (!isFinite(x)) ? '0.00' : x.toFixed(2);
        }

        function fmtMoney(n) {
            const x = Number(n);
            return (!isFinite(x)) ? '0.00' : x.toFixed(2);
        }

        function parseQty(n) {
            const x = Number(n);
            return (!isFinite(x) || x < 0) ? 0 : Math.round(x);
        }

        function parseKg(n) {
            const x = Number(n);
            return (!isFinite(x) || x < 0) ? 0 : Number(x.toFixed(2));
        }

        function parseMoney(n) {
            const x = Number(n);
            return (!isFinite(x) || x < 0) ? 0 : x;
        }

        function roundMoney(n) {
            return Number(parseMoney(n).toFixed(2));
        }

        function calcTotalKg(qty, kg) {
            return Number(((parseQty(qty) || 0) * (parseKg(kg) || 0)).toFixed(2));
        }

        function kgKey(n) {
            return fmtKg(parseKg(n));
        }

        function calcTotalPrice(line) {
            return parseMoney(line?.price) * (parseFloat(line?.total) || 0);
        }

        function normalizeBatchId(value) {
            return String(value || '').replace(/[^a-zA-Z0-9\-_.:]/g, '').slice(0, 80);
        }
        function makeBulkBatchId() {
            return normalizeBatchId(`DOBULK-${Date.now()}-${Math.random().toString(36).slice(2)}`);
        }
        function buildRecordsUrl(bulkBatchId) {
            if (!RECORDS_BASE) {
                throw new Error('Delivery Order records page URL is missing.');
            }

            const base = RECORDS_BASE;
            const url = new URL(base, window.location.origin);
            url.searchParams.set('bulkBatchId', normalizeBatchId(bulkBatchId));
            url.searchParams.set('print', '1');
            return url.toString();
        }

        function extractReturnedDocNo(response) {
            return response?.localDocNo
                || response?.local_doc_no
                || response?.sourceDocNo
                || response?.source_doc_no
                || response?.docNo
                || response?.doc_no
                || '';
        }

        function buildLocalDoCompatMeta(group, bulkBatchId, groupIndex) {
            return {
                mode: LOCAL_DO_MODE,
                schemaVersion: 'wpdo-local-v1',
                legacyQueueCompatible: true,
                sourceType: 'DELIVERY_ORDER',
                sourceSystem: 'WORDPRESS',
                requestedDocPrefix: REQUESTED_DOC_PREFIX,
                requestedDocNoMode: 'SERVER_GENERATED',
                requestedDocNoFormat: `${REQUESTED_DOC_PREFIX}{yyMM}/0000`,
                localDocNo: '',
                localDoId: null,
                bulkBatchId,
                groupIndex,
                customerCode: group?.customerCode || '',
                assignedDriverId: group?.assignedDriverId || 0
            };
        }

        function showToast(icon, title, text='') {
            if (window.Swal) Swal.fire({ toast: true, position: 'center', icon, title, text, showConfirmButton: false, timer: 2600, timerProgressBar: true });
        }
        function showModal(icon, title, html) {
            if (window.Swal) Swal.fire({ icon, title, html, confirmButtonText: 'OK' });
        }
        function showBulkSuccessModal(result) {
            const count = result?.count || 0;
            const recordsUrl = result?.recordsUrl || '#';
            const label = count === 1 ? '1 delivery order' : `${count} delivery orders`;

            if (window.Swal) {
                Swal.fire({
                    icon: 'success',
                    title: 'Delivery Orders Queued',
                    html: `<p>${escapeHtml(label)} queued for AutoCount.</p><p>DO numbers are still generating. Use the status page to print when ready.</p>`,
                    showCancelButton: true,
                    confirmButtonText: 'View Status / Print When Ready',
                    cancelButtonText: 'Close'
                }).then(res => {
                    if (res.isConfirmed && recordsUrl !== '#') {
                        window.open(recordsUrl, '_blank', 'noopener');
                    }
                });
            } else if (recordsUrl !== '#') {
                window.open(recordsUrl, '_blank', 'noopener');
            }
        }

        function savedJobListHtml(savedJobs) {
            if (!savedJobs.length) return '<p>No delivery orders were queued.</p>';

            const rows = savedJobs.map(job => {
                const customer = escapeHtml(job.customerName || job.customerCode || '-');
                const driver = escapeHtml(job.assignedDriverLabel || '-');
                const docNo = escapeHtml(job.docNo || 'Queued');
                const jobId = escapeHtml(job.jobId || '-');

                return `<li><strong>${docNo}</strong> | ${customer} | Driver: ${driver} | Job #${jobId}</li>`;
            }).join('');

            return `<ul style="text-align:left;margin:.75rem 0 0;padding-left:1.25rem;">${rows}</ul>`;
        }

        function showBulkPartialFailureModal(result) {
            const savedJobs = result?.savedJobs || [];
            const recordsUrl = result?.recordsUrl || '#';
            const errorMessage = result?.errorMessage || 'Submit failed';
            const savedCount = savedJobs.length;
            const titleP&�m8'����������Ph�
N?�� = savedCount
                ? `${savedCount} DO${savedCount === 1 ? '' : 's'} already queued`
                : 'Delivery Order submit failed';
            const html = `
                <p>${escapeHtml(errorMessage)}</p>
                ${savedCount ? '<p><strong>Do not resubmit these queued DOs.</strong> They were removed from the form, so retry will submit only the remaining unsaved items.</p>' : ''}
                ${savedJobListHtml(savedJobs)}
            `;

            if (window.Swal) {
                Swal.fire({
                    icon: savedCount ? 'warning' : 'error',
                    title,
                    html,
                    showCancelButton: savedCount && recordsUrl !== '#',
                    confirmButtonText: 'OK',
                    cancelButtonText: 'View Queued DOs'
                }).then(res => {
                    if (res.dismiss === Swal.DismissReason.cancel && recordsUrl !== '#') {
                        window.open(recordsUrl, '_blank', 'noopener');
                    }
                });
                return;
            }

            showModal(savedCount ? 'warning' : 'error', title, html);
        }

        function updateEntryTotal() {
            const itemCode = ($('acd_resp_do_item').value || '').trim();
            const itemName = ($('acd_resp_do_item_display').value || '').trim() || itemCode;
            const packType = ($('acd_resp_do_pack_type').value || '').trim();
            const qtyRaw = ($('acd_resp_do_qty').value || '').trim();
            const kgRaw = ($('acd_resp_do_kg').value || '').trim();
            const priceRaw = ($('acd_resp_do_price').value || '').trim();
            const qty = parseQty(qtyRaw || '0');
            const kg = parseKg(kgRaw || '0');
            const total = calcTotalKg(qty, kg);
            const defaultItemPrice = parseMoney($('acd_resp_do_item_price').value || '0');
            const enteredPrice = parseMoney(priceRaw);
            const price = enteredPrice > 0 ? enteredPrice : defaultItemPrice;
            const lineTotal = roundMoney(price * total);
            const pv = $('acd_resp_do_line_preview');
            if (!itemCode || qtyRaw === '' || kgRaw === '') {
                pv.style.display = 'none';
                pv.innerHTML = '';
                return;
            }
            pv.style.display = 'block';
            pv.innerHTML = `<div><b>${escapeHtml(itemName)}</b></div>
                            <div>Type: ${escapeHtml(packType)} | Qty: ${fmtQty(qty)} | KG: ${fmtKg(kg)} | Total KG: ${fmtKg(total)} | Price: ${fmtMoney(price)} | Line Total: ${fmtMoney(lineTotal)}</div>`;
        }

        function setPackType(type) {
            const nextType = String(type || '').toUpperCase() === 'BASKET' ? 'BASKET' : 'CARTON';
            $('acd_resp_do_pack_type').value = nextType;
            document.querySelectorAll('#acd_resp_do_pack_type_toggle .acd-resp-type-btn').forEach(btn => {
                btn.classList.toggle('active', (btn.dataset.packType || '').toUpperCase() === nextType);
            });
            updateEntryTotal();
        }

        function updateUI() {
            const lines = state.lines;
            const badge = document.getElementById('acd_resp_do_lines_count_badge');
            if (badge) badge.innerText = lines.length;

            const container = $('acd_resp_do_lines');
            if (!lines.length) {
                container.innerHTML = '<div class="acd-resp-empty">No items added</div>';
                return;
            }

            container.innerHTML = lines.map((l, idx) => `
                <div class="acd-resp-line" data-idx="${idx}">
                    <div><strong>${escapeHtml(l.itemName || l.itemCode)}</strong></div>
                    <div><strong>${escapeHtml(l.customerName || l.customerCode)}</strong></div>
                    <div>${escapeHtml(l.assignedDriverLabel || l.assignedDriverLogin)}</div>
                    <div><span class="acd-resp-type-pill">${l.packType}</span></div>
                    <div class="acd-resp-number-cell">${fmtQty(l.qty)}</div>
                    <div class="acd-resp-number-cell">${fmtKg(l.kg)}</div>
                    <div class="acd-resp-number-cell">${fmtKg(l.total)}</div>
                    <div class="acd-resp-price-cell"><input type="number" class="acd-resp-price-input" data-price-idx="${idx}" value="${fmtMoney(l.price)}" min="0" step="0.01" inputmode="decimal" aria-label="Price for ${escapeHtml(l.itemName || l.itemCode)}"></div>
                    <div class="acd-resp-money-cell" data-total-price-idx="${idx}">${fmtMoney(calcTotalPrice(l))}</div>
                    <div><button type="button" class="acd-resp-delete-btn" data-idx="${idx}" aria-label="Delete ${escapeHtml(l.itemName || l.itemCode)}" title="Delete">&#128465;</button></div>
                </div>
            `).join('');
        }

        function updateLinePrice(idx, value, shouldFormatInput = false) {
            if (isNaN(idx) || !state.lines[idx]) return;
            state.lines[idx].price = parseMoney(value);
            const nextTotal = fmtMoney(calcTotalPrice(state.lines[idx]));
            document.querySelectorAll(`[data-total-price-idx="${idx}"]`).forEach(el => {
                el.textContent = nextTotal;
            });
            if (shouldFormatInput) {
                document.querySelectorAll(`[data-price-idx="${idx}"]`).forEach(input => {
                    input.value = fmtMoney(state.lines[idx].price);
                });
            }
        }

        async function apiGet(url) {
            const res = await fetch(url, { method: 'GET', credentials: 'same-origin', headers: { 'Accept': 'application/json', 'X-WP-Nonce': REST_NONCE }, cache: 'no-store' });
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            const text = await res.text();
            return text ? JSON.parse(text) : null;
        }
        async function apiPost(url, body) {
            const res = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': REST_NONCE }, body: JSON.stringify(body) });
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            return await res.json();
        }

        async function createReceiptToken(jobId) {
            if (!REST_RECEIPT_TOKEN) {
                throw new Error('Receipt token endpoint missing');
            }
            return await apiPost(REST_RECEIPT_TOKEN, { job_id: jobId });
        }

        function buildPayloadLine(l, location) {
            const displayName = String(l.itemName || l.itemCode || '').trim();
            const isBasket = (l.packType === 'BASKET');
            const count = l.qty;
            const weightPerUnit = l.kg;
            const totalWeight = l.total;
            const unitPrice = roundMoney(l.price || 0);
            const amount = roundMoney(unitPrice * totalWeight);

            return {
                itemCode: l.itemCode,
                description: displayName,
                itemName: displayName,
                ItemName: displayName,
                itemDesc: displayName,
                uom: 'KG',
                unitPrice,
                amount,
                taxCode: 'SR-0',
                taxRate: 0,
                packType: l.packType,
                qty: totalWeight,
                kg: weightPerUnit,
                totalKg: totalWeight,
                unitQty: count,
                basketQty: isBasket ? count : null,
                cartonQty: !isBasket ? count : null,
                location
            };
        }

        function groupKey(customerCode, assignedDriverId) {
            return `${customerCode}::${assignedDriverId}`;
        }

        function groupLinesByCustomerDriver(lines) {
            const groups = new Map();
            lines.forEach(line => {
                const key = groupKey(line.customerCode, line.assignedDriverId);
                if (!groups.has(key)) {
                    groups.set(key, {
                        key,
                        customerCode: line.customerCode,
                        customerName: line.customerName,
                        salesAgent: line.salesAgent || '',
                        assignedDriverId: line.assignedDriverId,
                        assignedDriverLabel: line.assignedDriverLabel,
                        assignedDriverLogin: line.assignedDriverLogin,
                        lines: []
                    });
                }
                groups.get(key).lines.push(line);
            });
            return Array.from(groups.values());
        }

        function removeSavedGroupsFromForm(savedJobs) {
            const savedKeys = new Set(savedJobs.map(job => job.groupKey).filter(Boolean));
            if (!savedKeys.size) return;

            state.lines = state.lines.filter(line => !savedKeys.has(groupKey(line.customerCode, line.assignedDriverId)));
            updateUI();
        }

        function clearDeliveryFormAfterSave() {
            clearCustomerSelection();
            clearDriverSelection();
            clearLineEntry();
            state.lines = [];
            state.jobFinished = false;
            state.savedPendingClear = false;
            const submitBtn = $('acd_resp_do_submit');
            if (submitBtn) {
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            }
            updateUI();
            updateClearButtons();
        }

        async function searchItemsLive(q) {
            if (!AJAX_URL || !ITEM_NONCE) {
                console.warn('[Item Search] Missing AJAX_URL or ITEM_NONCE', { AJAX_URL, ITEM_NONCE });
                return [];
            }

            const url =
                `${AJAX_URL}?action=ac_itemcode_suggest` +
                `&nonce=${encodeURIComponent(ITEM_NONCE)}` +
                `&_ajax_nonce=${encodeURIComponent(ITEM_NONCE)}` +
                `&term=${encodeURIComponent(q)}` +
                `&q=${encodeURIComponent(q)}` +
                `&keyword=${encodeURIComponent(q)}`;

            const res = await fetch(url, {
                method: 'GET',
                credentials: 'same-origin',
                cache: 'no-store'
            });

            const text = await res.text();
            let data = null;

            try {
                data = text ? JSON.parse(text) : null;
            } catch (e) {
                console.error('[Item Search] Non-JSON response:', text);
                throw new Error('Item search returned invalid response.');
            }

            console.log('[Item Search] Response:', data);

            if (!data) {
                return [];
            }

            let rows = [];

            if (Array.isArray(data)) {
                rows = data;
            } else if (Array.isArray(data.items)) {
                rows = data.items;
            } else if (Array.isArray(data.data)) {
                rows = data.data;
            } else if (Array.isArray(data.data?.items)) {
                rows = data.data.items;
            } else if (Array.isArray(data.results)) {
                rows = data.results;
            } else if (Array.isArray(data.data?.results)) {
                rows = data.data.results;
            }

            return rows.map(it => {
                const code =
                    it.code ||
                    it.itemCode ||
                    it.ItemCode ||
                    it.item_code ||
                    it.value ||
                    '';

                const name =
                    it.desc ||
                    it.description ||
                    it.Description ||
                    it.name ||
                    it.itemName ||
                    it.ItemName ||
                    it.label ||
                    code;

                const price =
                    it.price ??
                    it.Price ??
                    it.unitPrice ??
                    it.UnitPrice ??
                    it.salesPrice ??
                    it.SalesPrice ??
                    0;

                return {
                    code: String(code || '').trim(),
                    name: String(name || code || '').trim(),
                    price: parseMoney(price)
                };
            }).filter(it => it.code || it.name);
        }

        async function searchDebtorsLive(q) {
            const wrapper = $('acdRespDebtorWrapper');
            const url = `${wrapper.dataset.ajaxUrl}?action=ac_cs_debtor_search&nonce=${encodeURIComponent(wrapper.dataset.nonce)}&q=${encodeURIComponent(q)}`;
            const res = await fetch(url, { credentials: 'same-origin' });
            const data = await res.json();
            if (!data.success) throw new Error(data.data?.error || 'Search failed');
            const items = data.data?.items || [];
            return items.map(it => {
                const name = it.name || it.debtorName || '';
                const code = it.code || it.debtorCode || '';
                const sa = (it.salesAgent || it.sales_agent || '').trim();
                const meta = [];
                if (DROPDOWN_META.showDebtorCode && code) meta.push(code);
                if (DROPDOWN_META.showSalesAgent && sa) meta.push('SA: ' + sa);
                return { label: name || code, meta: meta.join('  |  '), raw: { name, code, salesAgent: sa } };
            });
        }

        function renderPickerNote(msg) { $('acd_resp_do_picker_results').innerHTML = `<div class="acd-resp-picker-note">${escapeHtml(msg)}</div>`; }
        function renderPickerItems(items) {
            const box = $('acd_resp_do_picker_results');
            if (!items.length) { renderPickerNote('No result found'); return; }
            box.innerHTML = items.map((it, idx) => `<button type="button" class="acd-resp-picker-item" data-picker-idx="${idx}"><span class="acd-resp-picker-item-main">${escapeHtml(it.label || '')}</span>${it.meta ? `<span class="acd-resp-picker-item-sub">${escapeHtml(it.meta)}</span>` : ''}</button>`).join('');
        }
        async function runPickerSearch(q) {
            const query = (q || '').trim();
            clearTimeout(pickerTimer);
            if (query.length < 1) {
                pickerState.items = pickerState.defaultItems || [];
                if (pickerState.items.length) {
                    renderPickerItems(pickerState.items);
                } else {
                    renderPickerNote('Type to search');
                }
                return;
            }
            pickerTimer = setTimeout(async () => {
                renderPickerNote('Searching...');
                try {
                    const items = await pickerState.fetchFn(query);
                    pickerState.items = items || [];
                    renderPickerItems(pickerState.items);
                } catch(e) { pickerState.items = []; renderPickerNote('Failed to load'); }
            }, 220);
        }
        function openPicker(opts) {
            pickerState.defaultItems = opts.initialItems || [];
            pickerState.items = pickerState.defaultItems;
            pickerState.fetchFn = opts.fetchFn;
            pickerState.onPick = opts.onPick;
            $('acd_resp_do_picker_title').textContent = opts.title || 'Search';
            $('acd_resp_do_picker_search').placeholder = opts.placeholder || 'Type to search...';
            $('acd_resp_do_picker_search').value = '';
            $('acd_resp_do_picker_modal').classList.add('active');
            if (pickerState.items.length) {
                renderPickerItems(pickerState.items);
            } else {
                renderPickerNote('Type to search');
            }
            setTimeout(() => $('acd_resp_do_picker_search').focus(), 80);
        }
        function closePicker() {
            $('acd_resp_do_picker_modal').classList.remove('active');
      Ph�~�����������P��
N?��      $('acd_resp_do_picker_search').value = '';
            $('acd_resp_do_picker_results').innerHTML = '';
            pickerState.items = []; pickerState.defaultItems = []; pickerState.fetchFn = null; pickerState.onPick = null;
        }
        function updateClearButtons() {
            const debtorHas = !!($('acdRespDebtorInput')?.value.trim());
            const driverHas = !!($('acd_resp_do_driver_name')?.value.trim());
            const itemHas = !!($('acd_resp_do_item_name')?.value.trim());
            $('acdRespDebtorClear')?.classList.toggle('show', debtorHas);
            $('acdRespDriverClear')?.classList.toggle('show', driverHas);
            $('acdRespItemClear')?.classList.toggle('show', itemHas);
        }

        // ---- Customer sync functions ----
        function setBasketCustomerFromDelivery(customer) {
            const basketAccountType = String($('acd_resp_br_account_type')?.value || 'CUSTOMER').toUpperCase();
            if (basketAccountType !== 'CUSTOMER' || sharedCustomerState.basketCustomerManuallyCleared) {
                return;
            }
            const brInput = $('acdRespBrDebtorInput');
            const brCode = $('acd_resp_br_debtor_code');
            const brName = $('acd_resp_br_debtor_name');
            const brClear = $('acdRespBrDebtorClear');

            const name = customer?.name || '';
            const code = customer?.code || '';

            if (brInput) brInput.value = name || code || '';
            if (brCode) brCode.value = code;
            if (brName) brName.value = name;
            sharedCustomerState.basketCustomer = { name, code };
            if (brClear) {
                brClear.classList.toggle('show', !!(name || code));
            }
        }

        function clearBasketCustomerFromDelivery() {
            const basketAccountType = String($('acd_resp_br_account_type')?.value || 'CUSTOMER').toUpperCase();
            if (basketAccountType !== 'CUSTOMER') {
                return;
            }
            const brInput = $('acdRespBrDebtorInput');
            const brCode = $('acd_resp_br_debtor_code');
            const brName = $('acd_resp_br_debtor_name');
            const brClear = $('acdRespBrDebtorClear');

            if (brInput) brInput.value = '';
            if (brCode) brCode.value = '';
            if (brName) brName.value = '';
            sharedCustomerState.basketCustomer = { name: '', code: '' };
            if (brClear) brClear.classList.remove('show');
        }

        function setDeliveryCustomer(picked) {
            const name = picked?.name || '';
            const code = picked?.code || '';
            const salesAgent = picked?.salesAgent || '';

            $('acdRespDebtorInput').value = name || code || '';
            $('acd_resp_do_customer').value = code;
            $('acd_resp_do_customer_name').value = name;
            $('acd_resp_do_sales_agent').value = salesAgent;

            sharedCustomerState.basketCustomerManuallyCleared = false;
            sharedCustomerState.deliveryCustomer = { name, code };

            setBasketCustomerFromDelivery({
                name,
                code
            });

            updateClearButtons();
        }

        function clearCustomerSelection() {
            $('acdRespDebtorInput').value = '';
            $('acd_resp_do_customer').value = '';
            $('acd_resp_do_customer_name').value = '';
            $('acd_resp_do_sales_agent').value = '';

            sharedCustomerState.basketCustomerManuallyCleared = true;
            sharedCustomerState.deliveryCustomer = { name: '', code: '' };
            clearBasketCustomerFromDelivery();

            updateClearButtons();
        }

        function searchDriversLive(q) {
            const query = String(q || '').trim().toLowerCase();
            if (!query) return Promise.resolve(DRIVER_ITEMS);
            return Promise.resolve(DRIVER_ITEMS.filter(driver => {
                const haystack = [
                    String(driver.label || '').toUpperCase(),
                    String(driver.name || '').toUpperCase(),
                    driver.login || ''
                ].join(' ').toLowerCase();
                return haystack.includes(query);
            }));
        }

        function setDeliveryDriver(picked) {
            const id = parseInt(picked?.id || 0, 10) || 0;
            const label = String(picked?.login || picked?.label || picked?.name || '').toUpperCase();
            const login = picked?.login || '';
            $('acd_resp_do_driver_name').value = label;
            $('acd_resp_do_driver').value = id ? String(id) : '';
            $('acd_resp_do_driver_login').value = login;
            updateClearButtons();
        }

        function clearDriverSelection() {
            $('acd_resp_do_driver_name').value = '';
            $('acd_resp_do_driver').value = '';
            $('acd_resp_do_driver_login').value = '';
            updateClearButtons();
        }

        function openDebtorPicker() {
            openPicker({
                title: 'Select Customer',
                placeholder: 'Search customer...',
                fetchFn: searchDebtorsLive,
                onPick: (picked) => {
                    if (!picked) return;
                    setDeliveryCustomer(picked);
                    closePicker();
                }
            });
        }

        function openDriverPicker() {
            const driverOptions = DRIVER_ITEMS.map(driver => ({
                label: String(driver.login || '').toUpperCase(),
                meta: '',
                raw: driver
            }));
            openPicker({
                title: 'Select Driver',
                placeholder: 'Search driver...',
                initialItems: driverOptions,
                fetchFn: async (q) => {
                    const drivers = await searchDriversLive(q);
                    return drivers.map(driver => ({
                        label: String(driver.login || '').toUpperCase(),
                        meta: '',
                        raw: driver
                    }));
                },
                onPick: (picked) => {
                    if (!picked) return;
                    setDeliveryDriver(picked);
                    closePicker();
                }
            });
        }

        function clearItemSelection() {
            $('acd_resp_do_item_name').value = '';
            $('acd_resp_do_item').value = '';
            $('acd_resp_do_item_display').value = '';
            $('acd_resp_do_item_price').value = '0';
            updateEntryTotal();
            updateClearButtons();
        }

        function openItemPicker() {
            openPicker({
                title: 'Select Item',
                placeholder: 'Search item...',
                fetchFn: async (q) => {
                    const items = await searchItemsLive(q);
                    return items.map(it => ({
                        label: it.name || it.code,
                        meta: (DROPDOWN_META.showItemCode && it.code) ? it.code : '',
                        raw: { code: it.code, name: it.name || it.code, price: it.price || 0 }
                    }));
                },
                onPick: (picked) => {
                    if (!picked) return;
                    $('acd_resp_do_item_name').value = picked.name || picked.code || '';
                    $('acd_resp_do_item').value = picked.code || '';
                    $('acd_resp_do_item_display').value = picked.name || picked.code || '';
                    const rawPrice = Number(picked.price || 0);
                    $('acd_resp_do_item_price').value = fmtMoney(rawPrice);
                    $('acd_resp_do_price').value = rawPrice > 0 ? String(rawPrice.toFixed(2)) : '';
                    console.log('[DO item pick]', picked.code, 'price', rawPrice, 'field value', $('acd_resp_do_price').value);
                    updateEntryTotal();
                    updateClearButtons();
                    closePicker();
                }
            });
        }

        function initPickerModal() {
            $('acd_resp_do_picker_close').addEventListener('click', closePicker);
            $('acd_resp_do_picker_backdrop').addEventListener('click', closePicker);
            $('acd_resp_do_picker_search').addEventListener('input', function() { runPickerSearch(this.value); });
            $('acd_resp_do_picker_results').addEventListener('click', (e) => {
                const btn = e.target.closest('[data-picker-idx]');
                if (!btn) return;
                const idx = parseInt(btn.dataset.pickerIdx);
                if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) pickerState.onPick(pickerState.items[idx].raw);
            });
        }
        function initPickerTriggers() {
            $('acdRespDebtorInput').setAttribute('readonly', 'readonly');
            $('acd_resp_do_driver_name').setAttribute('readonly', 'readonly');
            $('acd_resp_do_item_name').setAttribute('readonly', 'readonly');
            $('acdRespDebtorInput').addEventListener('click', openDebtorPicker);
            $('acd_resp_do_driver_name').addEventListener('click', openDriverPicker);
            $('acd_resp_do_item_name').addEventListener('click', openItemPicker);
            $('acdRespDebtorClear')?.addEventListener('click', (e) => { e.preventDefault(); clearCustomerSelection(); });
            $('acdRespDriverClear')?.addEventListener('click', (e) => { e.preventDefault(); clearDriverSelection(); });
            $('acdRespItemClear')?.addEventListener('click', (e) => { e.preventDefault(); clearItemSelection(); });
        }
        function makeClientRequestId(prefix='DO') { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`; }

        function clearLineEntry() {
            $('acd_resp_do_qty').value = '';
            $('acd_resp_do_kg').value = '';
            $('acd_resp_do_price').value = '';
            $('acd_resp_do_item_name').value = '';
            $('acd_resp_do_item').value = '';
            $('acd_resp_do_item_display').value = '';
            $('acd_resp_do_item_price').value = '0';
            updateEntryTotal();
            updateClearButtons();
        }

        // ---- MERGE LOGIC (same customer+driver+item+type+KG) ----
        function findMergeableLineIndex(nextLine) {
            return state.lines.findIndex(line => {
                return String(line.customerCode || '') === String(nextLine.customerCode || '')
                    && String(line.assignedDriverId || '') === String(nextLine.assignedDriverId || '')
                    && String(line.itemCode || '').toUpperCase() === String(nextLine.itemCode || '').toUpperCase()
                    && String(line.packType || '').toUpperCase() === String(nextLine.packType || '').toUpperCase()
                    && kgKey(line.kg) === kgKey(nextLine.kg);
            });
        }

        function mergeLine(existingLine, nextLine) {
            const mergedQty = parseQty(existingLine.qty || 0) + parseQty(nextLine.qty || 0);
            const sameKg = parseKg(existingLine.kg || 0);

            existingLine.qty = mergedQty;
            existingLine.kg = sameKg;
            existingLine.total = calcTotalKg(mergedQty, sameKg);

            if (parseMoney(existingLine.price || 0) <= 0 && parseMoney(nextLine.price || 0) > 0) {
                existingLine.price = parseMoney(nextLine.price || 0);
            }

            return existingLine;
        }

        // ---- Success actions panel ----
        function hideDeliverySuccessActions() {
            const box = $('acd_resp_do_success_actions');
            const docNoEl = $('acd_resp_do_success_docno');
            const receiptBtn = $('acd_resp_do_receipt_btn');

            if (box) box.style.display = 'none';
            if (docNoEl) docNoEl.textContent = '-';
            if (receiptBtn) {
                receiptBtn.href = '#';
                receiptBtn.style.display = 'none';
            }
        }

        function showDeliverySuccessActions(data) {
            const box = $('acd_resp_do_success_actions');
            const docNoEl = $('acd_resp_do_success_docno');
            const receiptBtn = $('acd_resp_do_receipt_btn');

            const docNo = data?.docNo || data?.batchLabel || '-';
            const receiptUrl = data?.receiptUrl || '';

            if (docNoEl) docNoEl.textContent = docNo;
            if (receiptBtn && receiptUrl) {
                receiptBtn.href = receiptUrl;
                receiptBtn.style.display = 'inline-flex';
            }
            if (box) box.style.display = 'block';
        }

        function resetDeliveryOrderForm() {
            clearCustomerSelection();
            clearLineEntry();

            state.lines = [];
            state.jobFinished = false;
            state.savedPendingClear = false;

            const dateField = $('acd_resp_do_date');
            if (dateField) dateField.value = root.dataset.today || '';
            const driverSelect = $('acd_resp_do_driver');
            if (driverSelect) clearDriverSelection();

            const submitBtn = $('acd_resp_do_submit');
            if (submitBtn) {
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            }

            hideDeliverySuccessActions();
            updateUI();
            updateClearButtons();
        }

        // Init
        initPickerModal();
        initPickerTriggers();
        $('acd_resp_do_qty').addEventListener('input', updateEntryTotal);
        $('acd_resp_do_kg').addEventListener('input', updateEntryTotal);
        $('acd_resp_do_price').addEventListener('input', updateEntryTotal);
        $('acd_resp_do_pack_type').addEventListener('change', () => setPackType($('acd_resp_do_pack_type').value));
        document.querySelectorAll('#acd_resp_do_pack_type_toggle .acd-resp-type-btn').forEach(btn => btn.addEventListener('click', () => setPackType(btn.dataset.packType)));
        setPackType('BASKET');
        updateUI();

        // Add Item click with merge
        $('acd_resp_do_addline').addEventListener('click', () => {
            const itemCode = ($('acd_resp_do_item').value || '').trim();
            const itemName = ($('acd_resp_do_item_display').value || '').trim() || itemCode;
            const packType = ($('acd_resp_do_pack_type').value || '').trim();
            const qtyRaw = ($('acd_resp_do_qty').value || '').trim();
            const kgRaw = ($('acd_resp_do_kg').value || '').trim();
            const qty = parseQty(qtyRaw || '0');
            const kg = parseKg(kgRaw || '0');
            const defaultItemPrice = parseMoney($('acd_resp_do_item_price').value || '0');
            const enteredPrice = parseMoney($('acd_resp_do_price').value || '');
            const price = enteredPrice > 0 ? enteredPrice : defaultItemPrice;
            const customerCode = ($('acd_resp_do_customer').value || '').trim();
            const customerName = ($('acd_resp_do_customer_name').value || '').trim();
            const salesAgent = ($('acd_resp_do_sales_agent').value || '').trim();
            const assignedDriverId = parseInt($('acd_resp_do_driver')?.value || '0', 10) || 0;
            const assignedDriverLabel = ($('acd_resp_do_driver_name')?.value || '').trim();
            const assignedDriverLogin = ($('acd_resp_do_driver_login')?.value || '').trim();
            if (!customerCode) { showToast('error', 'Select customer'); return; }
            if (!assignedDriverId) { showToast('error', 'Select driver'); return; }
            if (!itemCode) { showToast('error', 'Select an item'); return; }
            if (qty <= 0) { showToast('error', 'Qty must be >0'); return; }
            if (kg <= 0) { showToast('error', 'KG must be >0'); return; }

            const nextLine = {
                customerCode,
                customerName,
                salesAgent,
             P���7f���������P��
N?��   assignedDriverId,
                assignedDriverLabel,
                assignedDriverLogin,
                itemCode,
                itemName,
                packType,
                qty,
                kg,
                total: calcTotalKg(qty, kg),
                price
            };

            const existingIdx = findMergeableLineIndex(nextLine);

            if (existingIdx >= 0) {
                mergeLine(state.lines[existingIdx], nextLine);
                updateUI();
                clearLineEntry();
                showToast(
                    'warning',
                    'Same item + KG merged',
                    `${itemName} ${fmtKg(kg)}KG already exists for ${customerName || customerCode}. Quantity has been added into the same row.`
                );
                return;
            }

            state.lines.push(nextLine);
            updateUI();
            clearLineEntry();
            showToast('success', 'Item added');
        });

        // Item detail events for editable price and delete buttons.
        document.getElementById('acd_resp_do_lines').addEventListener('click', (e) => {
            const btn = e.target.closest('.acd-resp-delete-btn');
            if (!btn) return;
            const idx = parseInt(btn.dataset.idx);
            if (!isNaN(idx)) {
                state.lines.splice(idx, 1);
                updateUI();
                showToast('info', 'Item removed');
            }
        });
        document.getElementById('acd_resp_do_lines').addEventListener('input', (e) => {
            const input = e.target.closest('.acd-resp-price-input');
            if (!input) return;
            updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, false);
        });
        document.getElementById('acd_resp_do_lines').addEventListener('change', (e) => {
            const input = e.target.closest('.acd-resp-price-input');
            if (!input) return;
            updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, true);
        });

        const clearNewBtn = $('acd_resp_do_clear_new_btn');
        if (clearNewBtn) {
            clearNewBtn.addEventListener('click', () => {
                resetDeliveryOrderForm();
                showToast('info', 'Ready for new DO');
            });
        }

        $('acd_resp_do_submit').addEventListener('click', async () => {
            if (state.isSubmitting) { showToast('warning', 'Already submitting'); return; }

            const submitBtn = $('acd_resp_do_submit');
            let saveSucceeded = false;
            state.isSubmitting = true;
            state.jobFinished = false;
            submitBtn.disabled = true;
            submitBtn.textContent = submitProgressText();

            const savedJobs = [];
            let recordsUrl = '#';

            try {
                const location = ($('acd_resp_do_location').value || '').trim();
                const docDate = ($('acd_resp_do_date').value || '').trim();
                if (!state.lines.length) throw new Error('Add at least one item');

                const groups = groupLinesByCustomerDriver(state.lines);
                if (!groups.length) throw new Error('Add at least one valid item');

                groups.forEach((group, groupIdx) => {
                    if (!group.customerCode) throw new Error(`Group ${groupIdx + 1}: customer missing`);
                    if (!group.assignedDriverId) throw new Error(`Group ${groupIdx + 1}: driver missing`);
                    if (!group.lines.length) throw new Error(`Group ${groupIdx + 1}: item lines missing`);
                    group.lines.forEach((line, lineIdx) => {
                        if (!line.itemCode) throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: item missing`);
                        if (line.qty <= 0 || line.kg <= 0 || line.total <= 0) {
                            throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: quantity and KG must be >0`);
                        }
                    });
                });

                const bulkBatchId = makeBulkBatchId();
                recordsUrl = buildRecordsUrl(bulkBatchId);

                for (let i = 0; i < groups.length; i++) {
                    const group = groups[i];
                    const payloadLines = group.lines.map(line => buildPayloadLine(line, location));
                    const payload = {
                        bulkBatchId,
                        customerCode: group.customerCode,
                        customerName: group.customerName,
                        debtorCode: group.customerCode,
                        DebtorCode: group.customerCode,
                        debtorName: group.customerName,
                        DebtorName: group.customerName,
                        location,
                        Location: location,
                        docDate,
                        remark: '',
                        assignedDriverId: group.assignedDriverId,
                        assignedDriverName: group.assignedDriverLabel,
                        assignedDriverLogin: group.assignedDriverLogin,
                        driverId: group.assignedDriverId,
                        driverName: group.assignedDriverLabel,
                        driverLogin: group.assignedDriverLogin,

                        // Compatibility metadata for the new WordPress-first DO structure.
                        // Current/old endpoint and bridge can safely ignore this.
                        // New endpoint will use it to create wp_vege_ac_do + wp_vege_ac_do_items first,
                        // then keep wp_vege_ac_jobs as the sync queue.
                        localDoCompat: buildLocalDoCompatMeta(group, bulkBatchId, i + 1),

                        // Server must generate WPDO number. Do not generate DocNo in browser.
                        localDocNo: '',
                        sourceType: 'DELIVERY_ORDER',
                        sourceSystem: 'WORDPRESS',
                        requestedDocPrefix: REQUESTED_DOC_PREFIX,
                        requestedDocNoMode: 'SERVER_GENERATED',

                        lines: payloadLines
                    };

                    const salesAgent = String(group.salesAgent || '').trim();
                    if (salesAgent) {
                        payload.salesAgent = salesAgent;
                        payload.SalesAgent = salesAgent;
                    }

                    const body = {
                        type: 'DELIVERY_ORDER',
                        bulkBatchId,
                        client_request_id: normalizeBatchId(`${bulkBatchId}-${i + 1}`),
                        source: 'wp-ui',
                        assignedDriverId: group.assignedDriverId,
                        payload
                    };
                    const r = await apiPost(REST_JOB_POST, body);
                    const jobId = r.jobId || r.id;
                    const returnedDocNo = extractReturnedDocNo(r);
                    if (!jobId) throw new Error(`No job ID returned for ${group.customerName || group.customerCode}`);
                    showToast('info', 'Job queued', `${group.customerName || group.customerCode}${returnedDocNo ? ' | ' + returnedDocNo : ' | Job #' + jobId}`);

                    savedJobs.push({
                        jobId,
                        groupKey: group.key,
                        docNo: returnedDocNo,
                        customerCode: group.customerCode,
                        customerName: group.customerName,
                        assignedDriverLabel: group.assignedDriverLabel
                    });
                }

                showDeliverySuccessActions({
                    batchLabel: `${savedJobs.length} DO${savedJobs.length === 1 ? '' : 's'} | ${bulkBatchId}`,
                    receiptUrl: recordsUrl
                });
                showBulkSuccessModal({ count: savedJobs.length, recordsUrl });
                clearDeliveryFormAfterSave();
                saveSucceeded = true;
                submitBtn.textContent = submitDoneText();
            } catch(err) {
                if (savedJobs.length) {
                    removeSavedGroupsFromForm(savedJobs);
                }
                showBulkPartialFailureModal({
                    savedJobs,
                    recordsUrl,
                    errorMessage: err.message
                });
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            } finally {
                state.isSubmitting = false;
                if (!saveSucceeded) {
                    submitBtn.disabled = false;
                    submitBtn.textContent = submitIdleText();
                }
            }
        });
    }

    // --------------------------------------------------------------
    // BASKET RETURN MODULE (customer + creditor)
    // --------------------------------------------------------------
    const basketContainer = document.getElementById('acd-resp-basket-tab');
    if (basketContainer && !basketContainer.dataset.brInit) {
        basketContainer.dataset.brInit = '1';

        function $(id) { return document.getElementById(id); }

        const REST_NONCE = root.dataset.restNonce;
        const REST_RETURN_URL = root.dataset.restReturnPost;
        const SHOW_DEBTOR_CODE = root.dataset.showDebtorCode === '1';
        const SHOW_CREDITOR_CODE = root.dataset.showCreditorCode === '1';
        const AJAX_URL = root.dataset.ajaxUrl;
        const DEBTOR_NONCE = root.dataset.debtorNonce;
        const CREDITOR_NONCE = root.dataset.creditorNonce;

        let isSubmitting = false;
        const pickerState = { items: [], fetchFn: null, onPick: null };
        let pickerTimer = null;

        function esc(s) {
            return s ? String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c])) : '';
        }

        function toast(icon, title, text='') {
            if (window.Swal) {
                Swal.fire({
                    toast: true,
                    position: 'center',
                    icon,
                    title,
                    text,
                    showConfirmButton: false,
                    timer: 2400,
                    timerProgressBar: true
                });
            }
        }

        function whole(n) {
            const x = Number(n);
            return (!isFinite(x) || x < 0) ? 0 : Math.round(x);
        }

        function currentAccountType() {
            return String($('acd_resp_br_account_type')?.value || 'CUSTOMER').toUpperCase() === 'CREDITOR'
                ? 'CREDITOR'
                : 'CUSTOMER';
        }

        function accountConfig() {
            const creditor = currentAccountType() === 'CREDITOR';
            return {
                creditor,
                type: creditor ? 'CREDITOR' : 'CUSTOMER',
                typeApi: creditor ? 'creditor' : 'customer',
                label: creditor ? 'Creditor' : 'Customer',
                title: creditor ? 'Creditor Basket Return' : 'Customer Basket Return',
                pickerTitle: creditor ? 'Select Creditor' : 'Select Customer',
                searchPlaceholder: creditor ? 'Search creditor...' : 'Search customer...',
                qtyLabel: creditor ? 'Baskets Returned to Creditor' : 'Basket Returned by Customer',
                buttonText: creditor ? 'Save Creditor Basket Return' : 'Save Customer Basket Return',
                savingText: creditor ? 'Saving Creditor Return...' : 'Saving Customer Return...',
                ajaxAction: creditor ? 'ac_cs_creditor_search' : 'ac_cs_debtor_search',
                nonce: creditor ? CREDITOR_NONCE : DEBTOR_NONCE,
                showCode: creditor ? SHOW_CREDITOR_CODE : SHOW_DEBTOR_CODE
            };
        }

        function renderPickerNote(msg) {
            const div = $('acd_resp_br_picker_results');
            if (div) div.innerHTML = `<div class="acd-resp-picker-note">${esc(msg)}</div>`;
        }

        function renderPickerItems(items) {
            const box = $('acd_resp_br_picker_results');
            if (!box) return;
            if (!items.length) {
                renderPickerNote('No result found');
                return;
            }
            box.innerHTML = items.map((it, idx) =>
                `<button type="button" class="acd-resp-picker-item" data-picker-idx="${idx}">
                    <span class="acd-resp-picker-item-main">${esc(it.label || '')}</span>
                    ${it.meta ? `<span class="acd-resp-picker-item-sub">${esc(it.meta)}</span>` : ''}
                </button>`
            ).join('');
        }

        async function runPickerSearch(q) {
            const query = (q || '').trim();
            clearTimeout(pickerTimer);
            if (query.length < 1) {
                pickerState.items = [];
                renderPickerNote('Type to search');
                return;
            }

            pickerTimer = setTimeout(async () => {
                renderPickerNote('Searching...');
                try {
                    const items = await pickerState.fetchFn(query);
                    pickerState.items = items || [];
                    renderPickerItems(pickerState.items);
                } catch (e) {
                    pickerState.items = [];
                    renderPickerNote('Failed to load');
                }
            }, 220);
        }

        function openBasketPicker(opts) {
            pickerState.items = [];
            pickerState.fetchFn = opts.fetchFn;
            pickerState.onPick = opts.onPick;

            const titleEl = $('acd_resp_br_picker_title');
            const searchInput = $('acd_resp_br_picker_search');
            const modal = $('acd_resp_br_picker_modal');

            if (titleEl) titleEl.textContent = opts.title || 'Search';
            if (searchInput) {
                searchInput.placeholder = opts.placeholder || 'Type to search...';
                searchInput.value = '';
            }
            if (modal) {
                modal.classList.add('active');
                renderPickerNote('Type to search');
                setTimeout(() => searchInput?.focus(), 80);
            }
        }

        function closeBasketPicker() {
            const modal = $('acd_resp_br_picker_modal');
            const searchInput = $('acd_resp_br_picker_search');
            const resultsDiv = $('acd_resp_br_picker_results');

            if (modal) modal.classList.remove('active');
            if (searchInput) searchInput.value = '';
            if (resultsDiv) resultsDiv.innerHTML = '';

            pickerState.items = [];
            pickerState.fetchFn = null;
            pickerState.onPick = null;
        }

        async function searchBasketAccountsLive(q) {
            const cfg = accountConfig();
            if (!AJAX_URL || !cfg.nonce) return [];

            const url = `${AJAX_URL}?action=${encodeURIComponent(cfg.ajaxAction)}&nonce=${encodeURIComponent(cfg.nonce)}&q=${encodeURIComponent(q)}`;
            const res = await fetch(url, { credentials: 'same-origin', cache: 'no-store' });
            const data = await res.json();

            if (!data.success) {
                throw new Error(data.data?.error || 'Search failed');
            }

            const items = data.data?.items || [];
            return items.map(it => {
                const name = cfg.creditor
                    ? (it.name || it.creditorName || it.companyName || '')
                    : (it.name || it.debtorName || it.companyName || '');
                const code = cfg.creditor
                    ? (it.code || it.creditorCode || it.accNo || '')
                    : (it.code || it.debtorCode || it.accNo || '');

                return {
                    label: name || code,
                    meta: (cfg.showCoP��-o"����������P/	
N?��de && code) ? code : '',
                    raw: { name, code }
                };
            });
        }

        function initBasketPickerModal() {
            const closeBtn = $('acd_resp_br_picker_close');
            const backdrop = $('acd_resp_br_picker_backdrop');
            const searchInput = $('acd_resp_br_picker_search');
            const resultsDiv = $('acd_resp_br_picker_results');

            if (closeBtn) closeBtn.addEventListener('click', closeBasketPicker);
            if (backdrop) backdrop.addEventListener('click', closeBasketPicker);
            if (searchInput) searchInput.addEventListener('input', function() {
                runPickerSearch(this.value);
            });
            if (resultsDiv) {
                resultsDiv.addEventListener('click', e => {
                    const btn = e.target.closest('[data-picker-idx]');
                    if (!btn) return;
                    const idx = parseInt(btn.dataset.pickerIdx, 10);
                    if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) {
                        pickerState.onPick(pickerState.items[idx].raw);
                    }
                });
            }
        }

        function updateBasketClearButton() {
            const input = $('acdRespBrDebtorInput');
            const clearBtn = $('acdRespBrDebtorClear');
            if (clearBtn) clearBtn.classList.toggle('show', !!(input?.value.trim()));
        }

        function clearBasketAccount(manual = false) {
            const input = $('acdRespBrDebtorInput');
            if (input) input.value = '';

            ['acd_resp_br_debtor_code', 'acd_resp_br_debtor_name', 'acd_resp_br_creditor_code', 'acd_resp_br_creditor_name']
                .forEach(id => {
                    const field = $(id);
                    if (field) field.value = '';
                });

            if (manual && currentAccountType() === 'CUSTOMER') {
                sharedCustomerState.basketCustomerManuallyCleared = true;
                sharedCustomerState.basketCustomer = { name: '', code: '' };
            }

            updateBasketClearButton();
        }

        function setBasketAccount(picked) {
            if (!picked) return;

            const cfg = accountConfig();
            const name = String(picked.name || '').trim();
            const code = String(picked.code || '').trim();
            const input = $('acdRespBrDebtorInput');

            clearBasketAccount(false);
            if (input) input.value = name || code;

            if (cfg.creditor) {
                $('acd_resp_br_creditor_code').value = code;
                $('acd_resp_br_creditor_name').value = name;
            } else {
                $('acd_resp_br_debtor_code').value = code;
                $('acd_resp_br_debtor_name').value = name;
                sharedCustomerState.basketCustomerManuallyCleared = false;
                sharedCustomerState.basketCustomer = { name, code };
            }

            updateBasketClearButton();
        }

        function applyBasketAccountType(type) {
            const nextType = String(type || '').toUpperCase() === 'CREDITOR' ? 'CREDITOR' : 'CUSTOMER';
            const select = $('acd_resp_br_account_type');
            if (select) select.value = nextType;

            document.querySelectorAll('#acd_resp_br_account_type_toggle .acd-resp-type-btn').forEach(btn => {
                btn.classList.toggle('active', String(btn.dataset.accountType || '').toUpperCase() === nextType);
            });

            clearBasketAccount(false);
            const cfg = accountConfig();

            if ($('acd_resp_br_title')) $('acd_resp_br_title').textContent = cfg.title;
            if ($('acd_resp_br_account_label')) $('acd_resp_br_account_label').textContent = cfg.label;
            if ($('acd_resp_br_qty_label')) $('acd_resp_br_qty_label').textContent = cfg.qtyLabel;
            if ($('acdRespBrDebtorInput')) $('acdRespBrDebtorInput').placeholder = cfg.searchPlaceholder;
            if ($('acd_resp_br_submit')) $('acd_resp_br_submit').textContent = cfg.buttonText;

            if (!cfg.creditor && !sharedCustomerState.basketCustomerManuallyCleared) {
                const savedCustomer = sharedCustomerState.basketCustomer || {};
                const deliveryCustomer = sharedCustomerState.deliveryCustomer || {};
                const restoreCustomer = (savedCustomer.code || savedCustomer.name) ? savedCustomer : deliveryCustomer;
                if (restoreCustomer.code || restoreCustomer.name) {
                    setBasketAccount(restoreCustomer);
                }
            }
        }

        function openBasketAccountPicker() {
            const cfg = accountConfig();
            openBasketPicker({
                title: cfg.pickerTitle,
                placeholder: cfg.searchPlaceholder,
                fetchFn: searchBasketAccountsLive,
                onPick: picked => {
                    setBasketAccount(picked);
                    closeBasketPicker();
                }
            });
        }

        initBasketPickerModal();

        const input = $('acdRespBrDebtorInput');
        const clearBtn = $('acdRespBrDebtorClear');

        if (input) {
            input.setAttribute('readonly', 'readonly');
            input.addEventListener('click', openBasketAccountPicker);
        }

        if (clearBtn) {
            clearBtn.addEventListener('click', e => {
                e.preventDefault();
                clearBasketAccount(true);
            });
        }

        document.querySelectorAll('#acd_resp_br_account_type_toggle .acd-resp-type-btn').forEach(btn => {
            btn.addEventListener('click', () => applyBasketAccountType(btn.dataset.accountType));
        });

        $('acd_resp_br_account_type')?.addEventListener('change', e => {
            applyBasketAccountType(e.target.value);
        });

        applyBasketAccountType('CUSTOMER');
        updateBasketClearButton();

        $('acd_resp_br_submit').addEventListener('click', async () => {
            if (isSubmitting) return;

            const cfg = accountConfig();
            const code = cfg.creditor
                ? ($('acd_resp_br_creditor_code').value || '').trim()
                : ($('acd_resp_br_debtor_code').value || '').trim();
            const name = cfg.creditor
                ? ($('acd_resp_br_creditor_name').value || '').trim()
                : ($('acd_resp_br_debtor_name').value || '').trim();

            const docDate = $('acd_resp_br_date').value;
            const basketQty = whole($('acd_resp_br_qty').value);
            const proofInput = $('acd_resp_br_proof');
            const proofFile = proofInput?.files?.[0] || null;

            if (!code) {
                toast('error', `Select ${cfg.label.toLowerCase()}`);
                return;
            }
            if (basketQty <= 0) {
                toast('error', 'Quantity must be >0');
                return;
            }

            isSubmitting = true;
            const btn = $('acd_resp_br_submit');
            btn.disabled = true;
            btn.textContent = cfg.savingText;

            try {
                let body;
                const headers = { 'X-WP-Nonce': REST_NONCE };

                if (proofFile) {
                    body = new FormData();
                    body.append('accountType', cfg.typeApi);
                    body.append('docDate', docDate);
                    body.append('basketQty', String(basketQty));
                    body.append('basketReturnProof', proofFile);

                    if (cfg.creditor) {
                        body.append('creditorCode', code);
                        body.append('creditorName', name);
                    } else {
                        body.append('debtorCode', code);
                        body.append('debtorName', name);
                    }
                } else {
                    const payload = {
                        accountType: cfg.typeApi,
                        docDate,
                        basketQty
                    };

                    if (cfg.creditor) {
                        payload.creditorCode = code;
                        payload.creditorName = name;
                    } else {
                        payload.debtorCode = code;
                        payload.debtorName = name;
                    }

                    body = JSON.stringify(payload);
                    headers['Content-Type'] = 'application/json';
                }

                const res = await fetch(REST_RETURN_URL, {
                    method: 'POST',
                    headers,
                    body,
                    credentials: 'same-origin'
                });

                const text = await res.text();
                let data = {};
                try {
                    data = text ? JSON.parse(text) : {};
                } catch (e) {
                    data = { message: text };
                }

                if (!res.ok || data?.ok === false) {
                    throw new Error(data?.message || `HTTP ${res.status}`);
                }

                const proofText = data?.proofSaved
                    ? ' | Proof saved'
                    : (proofFile && data?.proofMessage ? ' | Return saved, proof failed' : '');

                toast(
                    'success',
                    cfg.creditor ? 'Creditor basket return saved' : 'Customer basket return saved',
                    `${name || code} | Qty ${basketQty}${proofText}`
                );

                $('acd_resp_br_qty').value = '';
                if (proofInput) proofInput.value = '';

                const dateField = $('acd_resp_br_date');
                if (dateField) dateField.value = root.dataset.today || '';
            } catch (err) {
                toast('error', 'Save failed', err.message);
            } finally {
                isSubmitting = false;
                btn.disabled = false;
                btn.textContent = accountConfig().buttonText;
            }
        });
    }
})();
</script>

<script>
(function(){
    // --------------------------------------------------------------
    // GOODS RECEIVE NOTE MODULE
    // --------------------------------------------------------------
    const root = document.getElementById('acd-resp-root');
    const grnContainer = document.getElementById('acd-resp-grn-tab');
    if (!grnContainer || grnContainer.dataset.grnInit) return;
    grnContainer.dataset.grnInit = '1';

    const REST_NONCE    = root.dataset.restNonce;
    const REST_JOB_POST = root.dataset.restJobPost;
    const REST_JOB_BASE = root.dataset.restJobBase;
    const GRN_MODE      = root.dataset.grnMode || 'compat-v1';
    const REQUESTED_DOC_PREFIX = root.dataset.grnDocPrefix || 'WPGR';
    const AJAX_URL      = root.dataset.ajaxUrl;
    const CREDITOR_NONCE = root.dataset.creditorNonce;
    const ITEM_NONCE    = root.dataset.itemNonce;

    const DROPDOWN_META = {
        showCreditorCode: root.dataset.showCreditorCode === '1',
        showItemCode: root.dataset.showItemCode === '1'
    };

    const state = {
        lines: [],
        jobFinished: false,
        isSubmitting: false,
        savedPendingClear: false,
    };
    const pickerState = { items: [], defaultItems: [], fetchFn: null, onPick: null };
    let pickerTimer = null;

    function $(id) { return document.getElementById(id); }
    function submitIdleText() { return 'Save Goods Receive Note'; }
    function submitDoneText() { return 'Saved - Ready for Next Batch'; }
    function submitProgressText() { return 'Queuing...'; }
    function successToastText(count = 1) { return count === 1 ? 'Goods Receive Note queued' : `${count} Goods Receive Notes queued`; }

    function escapeHtml(s) {
        if (!s) return '';
        return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));
    }

    function fmtQty(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0' : String(Math.round(x));
    }

    function fmtKg(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0.00' : x.toFixed(2);
    }

    function fmtMoney(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0.00' : x.toFixed(2);
    }

    function parseQty(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : Math.round(x);
    }

    function parseKg(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : Number(x.toFixed(2));
    }

    function parseMoney(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : x;
    }

    function roundMoney(n) {
        return Number(parseMoney(n).toFixed(2));
    }

    function calcTotalKg(qty, kg) {
        return Number(((parseQty(qty) || 0) * (parseKg(kg) || 0)).toFixed(2));
    }

    function kgKey(n) {
        return fmtKg(parseKg(n));
    }

    function moneyKey(n) {
        return fmtMoney(parseMoney(n));
    }

    function calcTotalPrice(line) {
        return roundMoney(parseMoney(line?.price || 0) * (parseFloat(line?.total) || 0));
    }

    function normalizeBatchId(value) {
        return String(value || '').replace(/[^a-zA-Z0-9\-_.:]/g, '').slice(0, 80);
    }
    function makeBulkBatchId() {
        return normalizeBatchId(`GRNBULK-${Date.now()}-${Math.random().toString(36).slice(2)}`);
    }

    function extractReturnedDocNo(response) {
        return response?.localDocNo
            || response?.local_doc_no
            || response?.sourceDocNo
            || response?.source_doc_no
            || response?.docNo
            || response?.doc_no
            || '';
    }

    function buildGrnCompatMeta(group, bulkBatchId, groupIndex) {
        return {
            mode: GRN_MODE,
            schemaVersion: 'wpgrn-local-v1',
            legacyQueueCompatible: true,
            sourceType: 'GOODS_RECEIVE_NOTE',
            sourceSystem: 'WORDPRESS',
            requestedDocPrefix: REQUESTED_DOC_PREFIX,
            requestedDocNoMode: 'SERVER_GENERATED',
            requestedDocNoFormat: `${REQUESTED_DOC_PREFIX}{yyMM}/0000`,
            localDocNo: '',
            deliveryStatus: '',
            delivery_status: '',
            localGrnId: null,
            bulkBatchId,
            groupIndex,
            creditorCode: group?.creditorCode || ''
        };
    }

    function showToast(icon, title, text='') {
        if (window.Swal) {
            Swal.fire({ toast: true, position: 'center', icon, title, text, showConfirmButton: false, timer: 2600, timerProgressBar: true });
        } else {
            alert(title + (text ? '\n' + text : ''));
        }
    }
    function showModal(icon, title, html) {
        if (window.Swal) {
            Swal.fire({ icon, title, html, confirmButtonText: 'OK' });
        } else {
            alert(title + '\n' + html);
        }
    }

    function savedJobListHtml(savedJobs) {
        if (!savedJobs.length) return '<p>No Goods Receive Notes were queued.</p>';
        const rows = savedJobs.map(job => {
            const creditor = escapeHtml(job.creditorName || job.creditorCode || '-');
            const docNo = escapeHtml(job.docNo || 'Queued');
            const jobId = escapeHtml(job.jobId || '-');
            return `<li><strong>${docNo}</strong> | ${creditor} | Job #${jobId}</li>`;
        }).join('');
        return `<ul style="text-align:left;margin:.75rem 0 0;padding-left:1.25rem;">${rows}</ul>`;
    }

    function showBulkSuccessModal(result) {
        const count = result?.count || 0;
        const label = count === 1 ? '1 Goods Receive Note' : `${count} Goods Receive Notes`;
        if (window.Swal) {
            Swal.fire({
                icon: 'success',
                title: 'Goods Receive Notes Queued',
                html: `<p>${escapeHtml(labelP/	쟏���������Pq 
N?��)} queued for AutoCount.</p><p>GRN numbers are still generating.</p>`,
                confirmButtonText: 'OK'
            });
        } else {
            alert(label + ' queued for AutoCount. GRN numbers are still generating.');
        }
    }

    function showBulkPartialFailureModal(result) {
        const savedJobs = result?.savedJobs || [];
        const errorMessage = result?.errorMessage || 'Submit failed';
        const savedCount = savedJobs.length;
        const title = savedCount
            ? `${savedCount} GRN${savedCount === 1 ? '' : 's'} already queued`
            : 'Goods Receive Note submit failed';
        const html = `
            <p>${escapeHtml(errorMessage)}</p>
            ${savedCount ? '<p><strong>Do not resubmit these queued GRNs.</strong> They were removed from the form, so retry will submit only the remaining unsaved items.</p>' : ''}
            ${savedJobListHtml(savedJobs)}
        `;
        showModal(savedCount ? 'warning' : 'error', title, html);
    }

    function updateEntryTotal() {
        const itemCode = ($('acd_resp_grn_item').value || '').trim();
        const itemName = ($('acd_resp_grn_item_display').value || '').trim() || itemCode;
        const packType = ($('acd_resp_grn_pack_type').value || '').trim();
        const qtyRaw = ($('acd_resp_grn_qty').value || '').trim();
        const kgRaw = ($('acd_resp_grn_kg').value || '').trim();
        const priceRaw = ($('acd_resp_grn_price').value || '').trim();
        const qty = parseQty(qtyRaw || '0');
        const kg = parseKg(kgRaw || '0');
        const price = parseMoney(priceRaw || '0');
        const total = calcTotalKg(qty, kg);
        const totalPrice = roundMoney(price * total);
        const pv = $('acd_resp_grn_line_preview');
        if (!itemCode || qtyRaw === '' || kgRaw === '') {
            pv.style.display = 'none';
            pv.innerHTML = '';
            return;
        }
        pv.style.display = 'block';
        pv.innerHTML = `<div><b>${escapeHtml(itemName)}</b></div>
                        <div>Type: ${escapeHtml(packType)} | Qty: ${fmtQty(qty)} | KG: ${fmtKg(kg)} | Total KG: ${fmtKg(total)} | Price: ${fmtMoney(price)} | Total: ${fmtMoney(totalPrice)}</div>`;
    }

    function setPackType(type) {
        const nextType = String(type || '').toUpperCase() === 'BASKET' ? 'BASKET' : 'CARTON';
        $('acd_resp_grn_pack_type').value = nextType;
        document.querySelectorAll('#acd_resp_grn_pack_type_toggle .acd-resp-type-btn').forEach(btn => {
            btn.classList.toggle('active', (btn.dataset.packType || '').toUpperCase() === nextType);
        });
        updateEntryTotal();
    }

    function updateUI() {
        const lines = state.lines;
        const badge = document.getElementById('acd_resp_grn_lines_count_badge');
        if (badge) badge.innerText = lines.length;

        const container = $('acd_resp_grn_lines');
        if (!lines.length) {
            container.innerHTML = '<div class="acd-resp-empty">No items added</div>';
            return;
        }

        container.innerHTML = lines.map((l, idx) => `
            <div class="acd-resp-line" data-idx="${idx}">
                <div><strong>${escapeHtml(l.itemName || l.itemCode)}</strong></div>
                <div><strong>${escapeHtml(l.creditorName || l.creditorCode)}</strong></div>
                <div><span class="acd-resp-type-pill">${l.packType}</span></div>
                <div class="acd-resp-number-cell">${fmtQty(l.qty)}</div>
                <div class="acd-resp-number-cell">${fmtKg(l.kg)}</div>
                <div class="acd-resp-number-cell">${fmtKg(l.total)}</div>
                <div class="acd-resp-price-cell"><input type="number" class="acd-resp-price-input" data-price-idx="${idx}" value="${fmtMoney(l.price)}" min="0" step="0.01" inputmode="decimal" aria-label="Price for ${escapeHtml(l.itemName || l.itemCode)}"></div>
                <div class="acd-resp-money-cell" data-total-price-idx="${idx}">${fmtMoney(calcTotalPrice(l))}</div>
                <div><button type="button" class="acd-resp-delete-btn" data-idx="${idx}" aria-label="Delete ${escapeHtml(l.itemName || l.itemCode)}" title="Delete">&#128465;</button></div>
            </div>
        `).join('');
    }

    function updateLinePrice(idx, value, shouldFormatInput = false) {
        if (isNaN(idx) || !state.lines[idx]) return;
        state.lines[idx].price = parseMoney(value);
        const nextTotal = fmtMoney(calcTotalPrice(state.lines[idx]));
        document.querySelectorAll(`[data-total-price-idx="${idx}"]`).forEach(el => {
            el.textContent = nextTotal;
        });
        if (shouldFormatInput) {
            document.querySelectorAll(`[data-price-idx="${idx}"]`).forEach(input => {
                input.value = fmtMoney(state.lines[idx].price);
            });
        }
    }

    async function apiGet(url) {
        const res = await fetch(url, { method: 'GET', credentials: 'same-origin', headers: { 'Accept': 'application/json', 'X-WP-Nonce': REST_NONCE }, cache: 'no-store' });
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const text = await res.text();
        return text ? JSON.parse(text) : null;
    }
    async function apiPost(url, body) {
        const res = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': REST_NONCE }, body: JSON.stringify(body) });
        let data = null;
        const text = await res.text();
        try { data = text ? JSON.parse(text) : null; } catch (e) { data = { raw: text }; }
        if (!res.ok) {
            const message = data?.message || data?.error || `HTTP ${res.status}`;
            const err = new Error(message);
            err.status = res.status;
            err.data = data;
            throw err;
        }
        return data;
    }

    function buildPayloadLine(l, location) {
        const displayName = String(l.itemName || l.itemCode || '').trim();
        const isBasket = (l.packType === 'BASKET');
        const count = l.qty;
        const weightPerUnit = l.kg;
        const totalWeight = l.total;
        const unitPrice = roundMoney(l.price || 0);
        const amount = roundMoney(unitPrice * totalWeight);

        return {
            itemCode: l.itemCode,
            description: displayName,
            itemName: displayName,
            ItemName: displayName,
            itemDesc: displayName,
            uom: 'KG',
            unitPrice,
            amount,
            taxCode: 'SR-0',
            taxRate: 0,
            packType: l.packType,
            qty: totalWeight,
            kg: weightPerUnit,
            totalKg: totalWeight,
            unitQty: count,
            basketQty: isBasket ? count : null,
            cartonQty: !isBasket ? count : null,
            location
        };
    }

    function groupKey(creditorCode) {
        return `${creditorCode}`;
    }

    function groupLinesByCreditor(lines) {
        const groups = new Map();
        lines.forEach(line => {
            const key = groupKey(line.creditorCode);
            if (!groups.has(key)) {
                groups.set(key, {
                    key,
                    creditorCode: line.creditorCode,
                    creditorName: line.creditorName,
                    lines: []
                });
            }
            groups.get(key).lines.push(line);
        });
        return Array.from(groups.values());
    }

    function removeSavedGroupsFromForm(savedJobs) {
        const savedKeys = new Set(savedJobs.map(job => job.groupKey).filter(Boolean));
        if (!savedKeys.size) return;
        state.lines = state.lines.filter(line => !savedKeys.has(groupKey(line.creditorCode)));
        updateUI();
    }

    function clearGrnFormAfterSave() {
        clearCreditorSelection();
        clearLineEntry();
        state.lines = [];
        state.jobFinished = false;
        state.savedPendingClear = false;
        const submitBtn = $('acd_resp_grn_submit');
        if (submitBtn) {
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        }
        updateUI();
        updateClearButtons();
    }

    async function searchItemsLive(q) {
        if (!AJAX_URL || !ITEM_NONCE) {
            console.warn('[Item Search] Missing AJAX_URL or ITEM_NONCE', { AJAX_URL, ITEM_NONCE });
            return [];
        }

        const url =
            `${AJAX_URL}?action=ac_itemcode_suggest` +
            `&nonce=${encodeURIComponent(ITEM_NONCE)}` +
            `&_ajax_nonce=${encodeURIComponent(ITEM_NONCE)}` +
            `&term=${encodeURIComponent(q)}` +
            `&q=${encodeURIComponent(q)}` +
            `&keyword=${encodeURIComponent(q)}`;

        const res = await fetch(url, {
            method: 'GET',
            credentials: 'same-origin',
            cache: 'no-store'
        });

        const text = await res.text();
        let data = null;

        try {
            data = text ? JSON.parse(text) : null;
        } catch (e) {
            console.error('[Item Search] Non-JSON response:', text);
            throw new Error('Item search returned invalid response.');
        }

        console.log('[Item Search] Response:', data);

        if (!data) {
            return [];
        }

        let rows = [];

        if (Array.isArray(data)) {
            rows = data;
        } else if (Array.isArray(data.items)) {
            rows = data.items;
        } else if (Array.isArray(data.data)) {
            rows = data.data;
        } else if (Array.isArray(data.data?.items)) {
            rows = data.data.items;
        } else if (Array.isArray(data.results)) {
            rows = data.results;
        } else if (Array.isArray(data.data?.results)) {
            rows = data.data.results;
        }

        return rows.map(it => {
            const code =
                it.code ||
                it.itemCode ||
                it.ItemCode ||
                it.item_code ||
                it.value ||
                '';

            const name =
                it.desc ||
                it.description ||
                it.Description ||
                it.name ||
                it.itemName ||
                it.ItemName ||
                it.label ||
                code;

            const price =
                it.price ??
                it.Price ??
                it.unitPrice ??
                it.UnitPrice ??
                it.salesPrice ??
                it.SalesPrice ??
                0;

            return {
                code: String(code || '').trim(),
                name: String(name || code || '').trim(),
                price: parseMoney(price)
            };
        }).filter(it => it.code || it.name);
    }

    async function searchCreditorsLive(q) {
        const wrapper = $('acdRespCreditorWrapper');
        const url = `${wrapper.dataset.ajaxUrl}?action=ac_cs_creditor_search&nonce=${encodeURIComponent(wrapper.dataset.nonce)}&q=${encodeURIComponent(q)}`;
        const res = await fetch(url, { credentials: 'same-origin' });
        const data = await res.json();
        if (!data.success) throw new Error(data.data?.error || 'Search failed');
        const items = data.data?.items || [];
        return items.map(it => {
            const name = it.name || it.creditorName || '';
            const code = it.code || it.creditorCode || '';
            const meta = [];
            if (DROPDOWN_META.showCreditorCode && code) meta.push(code);
            return { label: name || code, meta: meta.join('  |  '), raw: { name, code } };
        });
    }

    function renderPickerNote(msg) { $('acd_resp_grn_picker_results').innerHTML = `<div class="acd-resp-picker-note">${escapeHtml(msg)}</div>`; }
    function renderPickerItems(items) {
        const box = $('acd_resp_grn_picker_results');
        if (!items.length) { renderPickerNote('No result found'); return; }
        box.innerHTML = items.map((it, idx) => `<button type="button" class="acd-resp-picker-item" data-picker-idx="${idx}"><span class="acd-resp-picker-item-main">${escapeHtml(it.label || '')}</span>${it.meta ? `<span class="acd-resp-picker-item-sub">${escapeHtml(it.meta)}</span>` : ''}</button>`).join('');
    }
    async function runPickerSearch(q) {
        const query = (q || '').trim();
        clearTimeout(pickerTimer);
        if (query.length < 1) {
            pickerState.items = pickerState.defaultItems || [];
            if (pickerState.items.length) {
                renderPickerItems(pickerState.items);
            } else {
                renderPickerNote('Type to search');
            }
            return;
        }
        pickerTimer = setTimeout(async () => {
            renderPickerNote('Searching...');
            try {
                const items = await pickerState.fetchFn(query);
                pickerState.items = items || [];
                renderPickerItems(pickerState.items);
            } catch(e) { pickerState.items = []; renderPickerNote('Failed to load'); }
        }, 220);
    }
    function openPicker(opts) {
        pickerState.defaultItems = opts.initialItems || [];
        pickerState.items = pickerState.defaultItems;
        pickerState.fetchFn = opts.fetchFn;
        pickerState.onPick = opts.onPick;
        $('acd_resp_grn_picker_title').textContent = opts.title || 'Search';
        $('acd_resp_grn_picker_search').placeholder = opts.placeholder || 'Type to search...';
        $('acd_resp_grn_picker_search').value = '';
        $('acd_resp_grn_picker_modal').classList.add('active');
        if (pickerState.items.length) {
            renderPickerItems(pickerState.items);
        } else {
            renderPickerNote('Type to search');
        }
        setTimeout(() => $('acd_resp_grn_picker_search').focus(), 80);
    }
    function closePicker() {
        $('acd_resp_grn_picker_modal').classList.remove('active');
        $('acd_resp_grn_picker_search').value = '';
        $('acd_resp_grn_picker_results').innerHTML = '';
        pickerState.items = []; pickerState.defaultItems = []; pickerState.fetchFn = null; pickerState.onPick = null;
    }
    function updateClearButtons() {
        const creditorHas = !!($('acdRespCreditorInput')?.value.trim());
        const itemHas = !!($('acd_resp_grn_item_name')?.value.trim());
        $('acdRespCreditorClear')?.classList.toggle('show', creditorHas);
        $('acdRespGrnItemClear')?.classList.toggle('show', itemHas);
    }

    function setCreditor(picked) {
        const name = picked?.name || '';
        const code = picked?.code || '';
        $('acdRespCreditorInput').value = name || code || '';
        $('acd_resp_grn_creditor').value = code;
        $('acd_resp_grn_creditor_name').value = name;
        updateClearButtons();
    }

    function clearCreditorSelection() {
        $('acdRespCreditorInput').value = '';
        $('acd_resp_grn_creditor').value = '';
        $('acd_resp_grn_creditor_name').value = '';
        updateClearButtons();
    }

    function clearItemSelection() {
        $('acd_resp_grn_item_name').value = '';
        $('acd_resp_grn_item').value = '';
        $('acd_resp_grn_item_display').value = '';
        $('acd_resp_grn_price').value = '';
        updateEntryTotal();
        updateClearButtons();
    }

    function openCreditorPicker() {
        openPicker({
            title: 'Select Creditor',
            placeholder: 'Search creditor...',
            fetchFn: searchCreditorsLive,
            onPick: (picked) => {
                if (!picked) return;
                setCreditor(picked);
                closePicker();
            }
        });
    }

    function openItemPicker() {
        openPicker({
            title: 'Select Item',
            placeholder: 'Search item...',
            fetchFn: async (q) => {
                const items = await searchItemsLive(q);
           Pq ��gt���������P�7
N?��     return items.map(it => ({
                    label: it.name || it.code,
                    meta: (DROPDOWN_META.showItemCode && it.code) ? it.code : '',
                    raw: { code: it.code, name: it.name || it.code, price: it.price || 0 }
                }));
            },
            onPick: (picked) => {
                if (!picked) return;
                $('acd_resp_grn_item_name').value = picked.name || picked.code || '';
                $('acd_resp_grn_item').value = picked.code || '';
                $('acd_resp_grn_item_display').value = picked.name || picked.code || '';
                const rawPrice = Number(picked.price || 0);
                $('acd_resp_grn_price').value = rawPrice > 0 ? String(rawPrice.toFixed(2)) : '';
                updateEntryTotal();
                updateClearButtons();
                closePicker();
            }
        });
    }

    function initPickerModal() {
        $('acd_resp_grn_picker_close').addEventListener('click', closePicker);
        $('acd_resp_grn_picker_backdrop').addEventListener('click', closePicker);
        $('acd_resp_grn_picker_search').addEventListener('input', function() { runPickerSearch(this.value); });
        $('acd_resp_grn_picker_results').addEventListener('click', (e) => {
            const btn = e.target.closest('[data-picker-idx]');
            if (!btn) return;
            const idx = parseInt(btn.dataset.pickerIdx);
            if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) pickerState.onPick(pickerState.items[idx].raw);
        });
    }
    function initPickerTriggers() {
        $('acdRespCreditorInput').setAttribute('readonly', 'readonly');
        $('acd_resp_grn_item_name').setAttribute('readonly', 'readonly');
        $('acdRespCreditorInput').addEventListener('click', openCreditorPicker);
        $('acd_resp_grn_item_name').addEventListener('click', openItemPicker);
        $('acdRespCreditorClear')?.addEventListener('click', (e) => { e.preventDefault(); clearCreditorSelection(); });
        $('acdRespGrnItemClear')?.addEventListener('click', (e) => { e.preventDefault(); clearItemSelection(); });
    }
    function makeClientRequestId(prefix='GRN') { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`; }

    function clearLineEntry() {
        $('acd_resp_grn_qty').value = '';
        $('acd_resp_grn_kg').value = '';
        $('acd_resp_grn_price').value = '';
        $('acd_resp_grn_item_name').value = '';
        $('acd_resp_grn_item').value = '';
        $('acd_resp_grn_item_display').value = '';
        updateEntryTotal();
        updateClearButtons();
    }

    // ---- MERGE LOGIC (same creditor + item + type + KG + price) ----
    function findMergeableLineIndex(nextLine) {
        return state.lines.findIndex(line => {
            return String(line.creditorCode || '') === String(nextLine.creditorCode || '')
                && String(line.itemCode || '').toUpperCase() === String(nextLine.itemCode || '').toUpperCase()
                && String(line.packType || '').toUpperCase() === String(nextLine.packType || '').toUpperCase()
                && kgKey(line.kg) === kgKey(nextLine.kg)
                && moneyKey(line.price) === moneyKey(nextLine.price);
        });
    }

    function mergeLine(existingLine, nextLine) {
        const mergedQty = parseQty(existingLine.qty || 0) + parseQty(nextLine.qty || 0);
        const sameKg = parseKg(existingLine.kg || 0);
        existingLine.qty = mergedQty;
        existingLine.kg = sameKg;
        existingLine.total = calcTotalKg(mergedQty, sameKg);
        return existingLine;
    }

    function hideGrnSuccessActions() {
        const box = $('acd_resp_grn_success_actions');
        const docNoEl = $('acd_resp_grn_success_docno');
        if (box) box.style.display = 'none';
        if (docNoEl) docNoEl.textContent = '-';
    }

    function showGrnSuccessActions(data) {
        const box = $('acd_resp_grn_success_actions');
        const docNoEl = $('acd_resp_grn_success_docno');
        const docNo = data?.docNo || data?.batchLabel || '-';
        if (docNoEl) docNoEl.textContent = docNo;
        if (box) box.style.display = 'block';
    }

    function resetGrnForm() {
        clearCreditorSelection();
        clearLineEntry();
        state.lines = [];
        state.jobFinished = false;
        state.savedPendingClear = false;
        const dateField = $('acd_resp_grn_date');
        if (dateField) dateField.value = root.dataset.today || '';
        const submitBtn = $('acd_resp_grn_submit');
        if (submitBtn) {
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        }
        hideGrnSuccessActions();
        updateUI();
        updateClearButtons();
    }

    initPickerModal();
    initPickerTriggers();
    $('acd_resp_grn_qty').addEventListener('input', updateEntryTotal);
    $('acd_resp_grn_kg').addEventListener('input', updateEntryTotal);
    $('acd_resp_grn_price').addEventListener('input', updateEntryTotal);
    $('acd_resp_grn_pack_type').addEventListener('change', () => setPackType($('acd_resp_grn_pack_type').value));
    document.querySelectorAll('#acd_resp_grn_pack_type_toggle .acd-resp-type-btn').forEach(btn => btn.addEventListener('click', () => setPackType(btn.dataset.packType)));
    setPackType('BASKET');
    updateUI();

    $('acd_resp_grn_addline').addEventListener('click', () => {
        const itemCode = ($('acd_resp_grn_item').value || '').trim();
        const itemName = ($('acd_resp_grn_item_display').value || '').trim() || itemCode;
        const packType = ($('acd_resp_grn_pack_type').value || '').trim();
        const qtyRaw = ($('acd_resp_grn_qty').value || '').trim();
        const kgRaw = ($('acd_resp_grn_kg').value || '').trim();
        const priceRaw = ($('acd_resp_grn_price').value || '').trim();
        const qty = parseQty(qtyRaw || '0');
        const kg = parseKg(kgRaw || '0');
        const price = parseMoney(priceRaw || '0');
        const creditorCode = ($('acd_resp_grn_creditor').value || '').trim();
        const creditorName = ($('acd_resp_grn_creditor_name').value || '').trim();

        if (!creditorCode) { showToast('error', 'Select creditor'); return; }
        if (!itemCode) { showToast('error', 'Select an item'); return; }
        if (qty <= 0) { showToast('error', 'Qty must be >0'); return; }
        if (kg <= 0) { showToast('error', 'KG must be >0'); return; }

        const nextLine = {
            creditorCode,
            creditorName,
            itemCode,
            itemName,
            packType,
            qty,
            kg,
            total: calcTotalKg(qty, kg),
            price
        };

        const existingIdx = findMergeableLineIndex(nextLine);
        if (existingIdx >= 0) {
            mergeLine(state.lines[existingIdx], nextLine);
            updateUI();
            clearLineEntry();
            showToast(
                'warning',
                'Same item + KG + price merged',
                `${itemName} ${fmtKg(kg)}KG @ ${fmtMoney(price)} already exists for ${creditorName || creditorCode}. Quantity has been added into the same row.`
            );
            return;
        }

        state.lines.push(nextLine);
        updateUI();
        clearLineEntry();
        showToast('success', 'Item added');
    });

    document.getElementById('acd_resp_grn_lines').addEventListener('click', (e) => {
        const btn = e.target.closest('.acd-resp-delete-btn');
        if (!btn) return;
        const idx = parseInt(btn.dataset.idx);
        if (!isNaN(idx)) {
            state.lines.splice(idx, 1);
            updateUI();
            showToast('info', 'Item removed');
        }
    });
    document.getElementById('acd_resp_grn_lines').addEventListener('input', (e) => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, false);
    });
    document.getElementById('acd_resp_grn_lines').addEventListener('change', (e) => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, true);
    });

    const clearNewBtn = $('acd_resp_grn_clear_new_btn');
    if (clearNewBtn) {
        clearNewBtn.addEventListener('click', () => {
            resetGrnForm();
            showToast('info', 'Ready for new GRN');
        });
    }

    $('acd_resp_grn_submit').addEventListener('click', async () => {
        if (state.isSubmitting) { showToast('warning', 'Already submitting'); return; }

        const submitBtn = $('acd_resp_grn_submit');
        let saveSucceeded = false;
        state.isSubmitting = true;
        state.jobFinished = false;
        submitBtn.disabled = true;
        submitBtn.textContent = submitProgressText();

        const savedJobs = [];

        try {
            const location = ($('acd_resp_grn_location').value || '').trim();
            const docDate = ($('acd_resp_grn_date').value || '').trim();
            if (!state.lines.length) throw new Error('Add at least one item');

            const groups = groupLinesByCreditor(state.lines);
            if (!groups.length) throw new Error('Add at least one valid item');

            groups.forEach((group, groupIdx) => {
                if (!group.creditorCode) throw new Error(`Group ${groupIdx + 1}: creditor missing`);
                if (!group.lines.length) throw new Error(`Group ${groupIdx + 1}: item lines missing`);
                group.lines.forEach((line, lineIdx) => {
                    if (!line.itemCode) throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: item missing`);
                    if (line.qty <= 0 || line.kg <= 0 || line.total <= 0) {
                        throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: quantity and KG must be >0`);
                    }
                });
            });

            const bulkBatchId = makeBulkBatchId();

            for (let i = 0; i < groups.length; i++) {
                const group = groups[i];
                const payloadLines = group.lines.map(line => buildPayloadLine(line, location));
                const payload = {
                    bulkBatchId,
                    creditorCode: group.creditorCode,
                    creditorName: group.creditorName,
                    CreditorCode: group.creditorCode,
                    CreditorName: group.creditorName,
                    location,
                    Location: location,
                    docDate,
                    supplierInvoiceNo,
                    SupplierInvoiceNo: supplierInvoiceNo,
                    ref: supplierInvoiceNo,
                    remark: '',

                    localGrnCompat: buildGrnCompatMeta(group, bulkBatchId, i + 1),

                    localDocNo: '',
                    deliveryStatus: '',
                    delivery_status: '',
                    sourceType: 'GOODS_RECEIVE_NOTE',
                    sourceSystem: 'WORDPRESS',
                    requestedDocPrefix: REQUESTED_DOC_PREFIX,
                    requestedDocNoMode: 'SERVER_GENERATED',

                    lines: payloadLines
                };
                const body = {
                    type: 'GOODS_RECEIVE_NOTE',
                    bulkBatchId,
                    client_request_id: normalizeBatchId(`${bulkBatchId}-${i + 1}`),
                    source: 'wp-ui',
                    payload
                };
                const r = await apiPost(REST_JOB_POST, body);
                const jobId = r.jobId || r.id;
                const returnedDocNo = extractReturnedDocNo(r);
                if (!jobId) throw new Error(`No job ID returned for ${group.creditorName || group.creditorCode}`);
                showToast('info', 'Job queued', `${group.creditorName || group.creditorCode}${returnedDocNo ? ' | ' + returnedDocNo : ' | Job #' + jobId}`);

                savedJobs.push({
                    jobId,
                    groupKey: group.key,
                    docNo: returnedDocNo,
                    creditorCode: group.creditorCode,
                    creditorName: group.creditorName
                });
            }

            showGrnSuccessActions({
                batchLabel: `${savedJobs.length} GRN${savedJobs.length === 1 ? '' : 's'} | ${bulkBatchId}`
            });
            showBulkSuccessModal({ count: savedJobs.length });
            clearGrnFormAfterSave();
            saveSucceeded = true;
            submitBtn.textContent = submitDoneText();
        } catch(err) {
            if (savedJobs.length) {
                removeSavedGroupsFromForm(savedJobs);
            }
            showBulkPartialFailureModal({
                savedJobs,
                errorMessage: err.message
            });
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        } finally {
            state.isSubmitting = false;
            if (!saveSucceeded) {
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            }
        }
    });
})();
</script>

<script>
(function(){
    // --------------------------------------------------------------
    // PURCHASE INVOICE MODULE
    // --------------------------------------------------------------
    const root = document.getElementById('acd-resp-root');
    const piContainer = document.getElementById('acd-resp-pi-tab');
    if (!piContainer || piContainer.dataset.piInit) return;
    piContainer.dataset.piInit = '1';

    const REST_NONCE    = root.dataset.restNonce;
    const REST_JOB_POST = root.dataset.restJobPost;
    const REST_JOB_BASE = root.dataset.restJobBase;
    const PI_MODE      = root.dataset.piMode || 'compat-v1';
    const REQUESTED_DOC_PREFIX = root.dataset.piDocPrefix || 'WPPI';
    const AJAX_URL      = root.dataset.ajaxUrl;
    const CREDITOR_NONCE = root.dataset.creditorNonce;
    const ITEM_NONCE    = root.dataset.itemNonce;

    const DROPDOWN_META = {
        showCreditorCode: root.dataset.showCreditorCode === '1',
        showItemCode: root.dataset.showItemCode === '1'
    };

    const state = {
        lines: [],
        jobFinished: false,
        isSubmitting: false,
        savedPendingClear: false,
    };
    const pickerState = { items: [], defaultItems: [], fetchFn: null, onPick: null };
    let pickerTimer = null;

    function $(id) { return document.getElementById(id); }
    function submitIdleText() { return 'Save Purchase Invoice'; }
    function submitDoneText() { return 'Saved - Ready for Next Batch'; }
    function submitProgressText() { return 'Queuing...'; }
    function successToastText(count = 1) { return count === 1 ? 'Purchase Invoice queued' : `${count} Purchase Invoices queued`; }

    function escapeHtml(s) {
        if (!s) return '';
        return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));
    }

    function fmtQty(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0' : String(Math.round(x));
    }

    function fmtKg(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0.00' : x.toFixed(2);
    }

    function fmtMoney(n) {
        const x = Number(n);
        return (!isFinite(x)) ? '0.00' : x.toFixed(2);
    }

    function parseQty(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : Math.round(x);
    }

    function parseKg(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : Number(x.toFixed(2));
    }

    function parseMoney(n) {
        const x = Number(n);
        return (!isFinite(x) || x < 0) ? 0 : x;
    }

    function roundMoney(n) {
        return Number(parseMoney(n).toFixed(2));
    }

    function calcTotalKg(qty, kg) {
        return NumbP�7�Г/���������P�N
N?��er(((parseQty(qty) || 0) * (parseKg(kg) || 0)).toFixed(2));
    }

    function kgKey(n) {
        return fmtKg(parseKg(n));
    }

    function moneyKey(n) {
        return fmtMoney(parseMoney(n));
    }

    function calcTotalPrice(line) {
        return roundMoney(parseMoney(line?.price || 0) * (parseFloat(line?.total) || 0));
    }

    function normalizeBatchId(value) {
        return String(value || '').replace(/[^a-zA-Z0-9\-_.:]/g, '').slice(0, 80);
    }
    function makeBulkBatchId() {
        return normalizeBatchId(`PIBULK-${Date.now()}-${Math.random().toString(36).slice(2)}`);
    }

    function extractReturnedDocNo(response) {
        return response?.localDocNo
            || response?.local_doc_no
            || response?.sourceDocNo
            || response?.source_doc_no
            || response?.docNo
            || response?.doc_no
            || '';
    }

    function buildPiCompatMeta(group, bulkBatchId, groupIndex) {
        return {
            mode: PI_MODE,
            schemaVersion: 'wppi-local-v1',
            legacyQueueCompatible: true,
            localTableBacked: true,
            sourceType: 'PURCHASE_INVOICE',
            sourceSystem: 'WORDPRESS',
            requestedDocPrefix: REQUESTED_DOC_PREFIX,
            requestedDocNoMode: 'SERVER_GENERATED',
            requestedDocNoFormat: `${REQUESTED_DOC_PREFIX}{yyMM}/000`,
            localDocNo: '',
            localPurchaseInvoiceId: null,
            bulkBatchId,
            groupIndex,
            creditorCode: group?.creditorCode || ''
        };
    }

    function showToast(icon, title, text='') {
        if (window.Swal) {
            Swal.fire({ toast: true, position: 'center', icon, title, text, showConfirmButton: false, timer: 2600, timerProgressBar: true });
        } else {
            alert(title + (text ? '\n' + text : ''));
        }
    }
    function showModal(icon, title, html) {
        if (window.Swal) {
            Swal.fire({ icon, title, html, confirmButtonText: 'OK' });
        } else {
            alert(title + '\n' + html);
        }
    }

    function savedJobListHtml(savedJobs) {
        if (!savedJobs.length) return '<p>No Purchase Invoices were queued.</p>';
        const rows = savedJobs.map(job => {
            const creditor = escapeHtml(job.creditorName || job.creditorCode || '-');
            const docNo = escapeHtml(job.docNo || 'Queued');
            const jobId = escapeHtml(job.jobId || '-');
            return `<li><strong>${docNo}</strong> | ${creditor} | Job #${jobId}</li>`;
        }).join('');
        return `<ul style="text-align:left;margin:.75rem 0 0;padding-left:1.25rem;">${rows}</ul>`;
    }

    function showBulkSuccessModal(result) {
        const count = result?.count || 0;
        const label = count === 1 ? '1 Purchase Invoice' : `${count} Purchase Invoices`;
        if (window.Swal) {
            Swal.fire({
                icon: 'success',
                title: 'Purchase Invoices Queued',
                html: `<p>${escapeHtml(label)} queued for AutoCount.</p><p>WordPress Purchase Invoice numbers were generated before queueing.</p>`,
                confirmButtonText: 'OK'
            });
        } else {
            alert(label + ' queued for AutoCount with WordPress Purchase Invoice numbers.');
        }
    }

    function showBulkPartialFailureModal(result) {
        const savedJobs = result?.savedJobs || [];
        const errorMessage = result?.errorMessage || 'Submit failed';
        const savedCount = savedJobs.length;
        const title = savedCount
            ? `${savedCount} PI${savedCount === 1 ? '' : 's'} already queued`
            : 'Purchase Invoice submit failed';
        const html = `
            <p>${escapeHtml(errorMessage)}</p>
            ${savedCount ? '<p><strong>Do not resubmit these queued Purchase Invoices.</strong> They were removed from the form, so retry will submit only the remaining unsaved items.</p>' : ''}
            ${savedJobListHtml(savedJobs)}
        `;
        showModal(savedCount ? 'warning' : 'error', title, html);
    }

    function updateEntryTotal() {
        const itemCode = ($('acd_resp_pi_item').value || '').trim();
        const itemName = ($('acd_resp_pi_item_display').value || '').trim() || itemCode;
        const packType = ($('acd_resp_pi_pack_type').value || '').trim();
        const qtyRaw = ($('acd_resp_pi_qty').value || '').trim();
        const kgRaw = ($('acd_resp_pi_kg').value || '').trim();
        const priceRaw = ($('acd_resp_pi_price').value || '').trim();
        const qty = parseQty(qtyRaw || '0');
        const kg = parseKg(kgRaw || '0');
        const price = parseMoney(priceRaw || '0');
        const total = calcTotalKg(qty, kg);
        const totalPrice = roundMoney(price * total);
        const pv = $('acd_resp_pi_line_preview');
        if (!itemCode || qtyRaw === '' || kgRaw === '') {
            pv.style.display = 'none';
            pv.innerHTML = '';
            return;
        }
        pv.style.display = 'block';
        pv.innerHTML = `<div><b>${escapeHtml(itemName)}</b></div>
                        <div>Type: ${escapeHtml(packType)} | Qty: ${fmtQty(qty)} | KG: ${fmtKg(kg)} | Total KG: ${fmtKg(total)} | Price: ${fmtMoney(price)} | Total: ${fmtMoney(totalPrice)}</div>`;
    }

    function setPackType(type) {
        const nextType = String(type || '').toUpperCase() === 'BASKET' ? 'BASKET' : 'CARTON';
        $('acd_resp_pi_pack_type').value = nextType;
        document.querySelectorAll('#acd_resp_pi_pack_type_toggle .acd-resp-type-btn').forEach(btn => {
            btn.classList.toggle('active', (btn.dataset.packType || '').toUpperCase() === nextType);
        });
        updateEntryTotal();
    }

    function updateUI() {
        const lines = state.lines;
        const badge = document.getElementById('acd_resp_pi_lines_count_badge');
        if (badge) badge.innerText = lines.length;

        const container = $('acd_resp_pi_lines');
        if (!lines.length) {
            container.innerHTML = '<div class="acd-resp-empty">No items added</div>';
            return;
        }

        container.innerHTML = lines.map((l, idx) => `
            <div class="acd-resp-line" data-idx="${idx}">
                <div><strong>${escapeHtml(l.itemName || l.itemCode)}</strong></div>
                <div><strong>${escapeHtml(l.creditorName || l.creditorCode)}</strong></div>
                <div><span class="acd-resp-type-pill">${l.packType}</span></div>
                <div class="acd-resp-number-cell">${fmtQty(l.qty)}</div>
                <div class="acd-resp-number-cell">${fmtKg(l.kg)}</div>
                <div class="acd-resp-number-cell">${fmtKg(l.total)}</div>
                <div class="acd-resp-price-cell"><input type="number" class="acd-resp-price-input" data-price-idx="${idx}" value="${fmtMoney(l.price)}" min="0" step="0.01" inputmode="decimal" aria-label="Price for ${escapeHtml(l.itemName || l.itemCode)}"></div>
                <div class="acd-resp-money-cell" data-total-price-idx="${idx}">${fmtMoney(calcTotalPrice(l))}</div>
                <div><button type="button" class="acd-resp-delete-btn" data-idx="${idx}" aria-label="Delete ${escapeHtml(l.itemName || l.itemCode)}" title="Delete">&#128465;</button></div>
            </div>
        `).join('');
    }

    function updateLinePrice(idx, value, shouldFormatInput = false) {
        if (isNaN(idx) || !state.lines[idx]) return;
        state.lines[idx].price = parseMoney(value);
        const nextTotal = fmtMoney(calcTotalPrice(state.lines[idx]));
        document.querySelectorAll(`[data-total-price-idx="${idx}"]`).forEach(el => {
            el.textContent = nextTotal;
        });
        if (shouldFormatInput) {
            document.querySelectorAll(`[data-price-idx="${idx}"]`).forEach(input => {
                input.value = fmtMoney(state.lines[idx].price);
            });
        }
    }

    async function apiGet(url) {
        const res = await fetch(url, { method: 'GET', credentials: 'same-origin', headers: { 'Accept': 'application/json', 'X-WP-Nonce': REST_NONCE }, cache: 'no-store' });
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const text = await res.text();
        return text ? JSON.parse(text) : null;
    }
    async function apiPost(url, body) {
        const res = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': REST_NONCE }, body: JSON.stringify(body) });
        let data = null;
        const text = await res.text();
        try { data = text ? JSON.parse(text) : null; } catch (e) { data = { raw: text }; }
        if (!res.ok) {
            const message = data?.message || data?.error || `HTTP ${res.status}`;
            const err = new Error(message);
            err.status = res.status;
            err.data = data;
            throw err;
        }
        return data;
    }

    function buildPayloadLine(l, location) {
        const displayName = String(l.itemName || l.itemCode || '').trim();
        const isBasket = (l.packType === 'BASKET');
        const count = l.qty;
        const weightPerUnit = l.kg;
        const totalWeight = l.total;
        const unitPrice = roundMoney(l.price || 0);
        const amount = roundMoney(unitPrice * totalWeight);

        return {
            itemCode: l.itemCode,
            description: displayName,
            itemName: displayName,
            ItemName: displayName,
            itemDesc: displayName,
            uom: 'KG',
            unitPrice,
            amount,
            packType: l.packType,
            qty: totalWeight,
            kg: weightPerUnit,
            totalKg: totalWeight,
            unitQty: count,
            basketQty: isBasket ? count : null,
            cartonQty: !isBasket ? count : null,
            location
        };
    }

    function groupKey(creditorCode) {
        return `${creditorCode}`;
    }

    function groupLinesByCreditor(lines) {
        const groups = new Map();
        lines.forEach(line => {
            const key = groupKey(line.creditorCode);
            if (!groups.has(key)) {
                groups.set(key, {
                    key,
                    creditorCode: line.creditorCode,
                    creditorName: line.creditorName,
                    lines: []
                });
            }
            groups.get(key).lines.push(line);
        });
        return Array.from(groups.values());
    }

    function removeSavedGroupsFromForm(savedJobs) {
        const savedKeys = new Set(savedJobs.map(job => job.groupKey).filter(Boolean));
        if (!savedKeys.size) return;
        state.lines = state.lines.filter(line => !savedKeys.has(groupKey(line.creditorCode)));
        updateUI();
    }

    function clearPiFormAfterSave() {
        clearCreditorSelection();
        clearLineEntry();
        state.lines = [];
        state.jobFinished = false;
        state.savedPendingClear = false;
        const submitBtn = $('acd_resp_pi_submit');
        if (submitBtn) {
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        }
        updateUI();
        updateClearButtons();
    }

    async function searchItemsLive(q) {
        if (!AJAX_URL || !ITEM_NONCE) {
            console.warn('[Item Search] Missing AJAX_URL or ITEM_NONCE', { AJAX_URL, ITEM_NONCE });
            return [];
        }

        const url =
            `${AJAX_URL}?action=ac_itemcode_suggest` +
            `&nonce=${encodeURIComponent(ITEM_NONCE)}` +
            `&_ajax_nonce=${encodeURIComponent(ITEM_NONCE)}` +
            `&term=${encodeURIComponent(q)}` +
            `&q=${encodeURIComponent(q)}` +
            `&keyword=${encodeURIComponent(q)}`;

        const res = await fetch(url, {
            method: 'GET',
            credentials: 'same-origin',
            cache: 'no-store'
        });

        const text = await res.text();
        let data = null;

        try {
            data = text ? JSON.parse(text) : null;
        } catch (e) {
            console.error('[Item Search] Non-JSON response:', text);
            throw new Error('Item search returned invalid response.');
        }

        console.log('[Item Search] Response:', data);

        if (!data) {
            return [];
        }

        let rows = [];

        if (Array.isArray(data)) {
            rows = data;
        } else if (Array.isArray(data.items)) {
            rows = data.items;
        } else if (Array.isArray(data.data)) {
            rows = data.data;
        } else if (Array.isArray(data.data?.items)) {
            rows = data.data.items;
        } else if (Array.isArray(data.results)) {
            rows = data.results;
        } else if (Array.isArray(data.data?.results)) {
            rows = data.data.results;
        }

        return rows.map(it => {
            const code =
                it.code ||
                it.itemCode ||
                it.ItemCode ||
                it.item_code ||
                it.value ||
                '';

            const name =
                it.desc ||
                it.description ||
                it.Description ||
                it.name ||
                it.itemName ||
                it.ItemName ||
                it.label ||
                code;

            const price =
                it.price ??
                it.Price ??
                it.unitPrice ??
                it.UnitPrice ??
                it.salesPrice ??
                it.SalesPrice ??
                0;

            return {
                code: String(code || '').trim(),
                name: String(name || code || '').trim(),
                price: parseMoney(price)
            };
        }).filter(it => it.code || it.name);
    }

    async function searchCreditorsLive(q) {
        const wrapper = $('acdRespPiCreditorWrapper');
        const url = `${wrapper.dataset.ajaxUrl}?action=ac_cs_creditor_search&nonce=${encodeURIComponent(wrapper.dataset.nonce)}&q=${encodeURIComponent(q)}`;
        const res = await fetch(url, { credentials: 'same-origin' });
        const data = await res.json();
        if (!data.success) throw new Error(data.data?.error || 'Search failed');
        const items = data.data?.items || [];
        return items.map(it => {
            const name = it.name || it.creditorName || '';
            const code = it.code || it.creditorCode || '';
            const meta = [];
            if (DROPDOWN_META.showCreditorCode && code) meta.push(code);
            return { label: name || code, meta: meta.join('  |  '), raw: { name, code } };
        });
    }

    function renderPickerNote(msg) { $('acd_resp_pi_picker_results').innerHTML = `<div class="acd-resp-picker-note">${escapeHtml(msg)}</div>`; }
    function renderPickerItems(items) {
        const box = $('acd_resp_pi_picker_results');
        if (!items.length) { renderPickerNote('No result found'); return; }
        box.innerHTML = items.map((it, idx) => `<button type="button" class="acd-resp-picker-item" data-picker-idx="${idx}"><span class="acd-resp-picker-item-main">${escapeHtml(it.label || '')}</span>${it.meta ? `<span class="acd-resp-picker-item-sub">${escapeHtml(it.meta)}</span>` : ''}</button>`).join('');
    }
    async function runPickerSearch(q) {
        const query = (q || '').trim();
        clearTimeout(pickerTimer);
        if (query.length < 1) {
            pickerState.items = pickerState.defaultItems || [];
            if (pickerState.items.length) {
                renderPickerItems(pickerState.items);
            } else {
                renderPickerNote('Type to search');
            }
            return;
        }
        pickerTimer = setTimeout(async () => {
            renderPickerNote('Searching...');
            try {
                const items = await pickerState.fetchFn(query);
                pickerState.items = itP�N�Q�0���������S,��
N?��
            && $job_error_column !== ''
            && $job_updated_column !== ''
        ) {
            $doc_select = $job_doc_column !== ''
                ? "`{$job_doc_column}`"
                : "''";

            $recent_failed_jobs = wst_ops_query_rows(
                "SELECT
                    `{$job_id_column}` AS job_id,
                    `{$job_type_column}` AS job_type,
                    {$doc_select} AS document_no,
                    `{$job_status_column}` AS job_status,
                    `{$job_error_column}` AS error_message,
                    `{$job_updated_column}` AS updated_at
                 FROM `{$jobs_table}`
                 WHERE UPPER(COALESCE(`{$job_status_column}`, '')) IN ('FAILED', 'FAILED_FINAL')
                 ORDER BY `{$job_updated_column}` DESC
                 LIMIT 6"
            );
        }
    }
}

/*
|--------------------------------------------------------------------------
| Delivery Order statistics
|--------------------------------------------------------------------------
*/

$do_stats = array(
    'total'             => 0,
    'delivered'         => 0,
    'open_delivery'     => 0,
    'sync_failed'       => 0,
    'hidden'            => 0,
    'proof_count'       => 0,
);

$recent_delivery_orders = array();

if ($table_availability['delivery_orders']) {
    $do_table = preg_replace(
        '/[^A-Za-z0-9_]/',
        '',
        $tables['delivery_orders']
    );

    $do_date_column = wst_ops_first_column(
        $tables['delivery_orders'],
        array('doc_date', 'delivery_date', 'created_at')
    );

    $do_status_column = wst_ops_first_column(
        $tables['delivery_orders'],
        array('delivery_status', 'status')
    );

    $do_sync_column = wst_ops_first_column(
        $tables['delivery_orders'],
        array('sync_status', 'job_status', 'status')
    );

    $do_hidden_column = wst_ops_first_column(
        $tables['delivery_orders'],
        array('hidden_from_staff_list')
    );

    if ($do_date_column !== '') {
        list($do_date_sql, $do_date_params) = wst_ops_date_condition(
            $do_date_column,
            $start_date,
            $end_date
        );

        $do_stats['total'] = wst_ops_safe_int(
            wst_ops_query_value(
                "SELECT COUNT(*) FROM `{$do_table}` WHERE {$do_date_sql}",
                $do_date_params
            )
        );

        if ($do_status_column !== '') {
            $do_stats['delivered'] = wst_ops_safe_int(
                wst_ops_query_value(
                    "SELECT COUNT(*)
                     FROM `{$do_table}`
                     WHERE {$do_date_sql}
                       AND UPPER(COALESCE(`{$do_status_column}`, '')) = 'DELIVERED'",
                    $do_date_params
                )
            );

            $do_stats['open_delivery'] = wst_ops_safe_int(
                wst_ops_query_value(
                    "SELECT COUNT(*)
                     FROM `{$do_table}`
                     WHERE {$do_date_sql}
                       AND UPPER(COALESCE(`{$do_status_column}`, '')) IN (
                           'PENDING_DELIVERY',
                           'ASSIGNED',
                           'SCHEDULED',
                           'DRIVER_ACKNOWLEDGED',
                           'RECEIVED',
                           'OUT_FOR_DELIVERY'
                       )",
                    $do_date_params
                )
            );

        }

        if ($do_sync_column !== '') {
            $do_stats['sync_failed'] = wst_ops_safe_int(
                wst_ops_query_value(
                    "SELECT COUNT(*)
                     FROM `{$do_table}`
                     WHERE {$do_date_sql}
                       AND UPPER(COALESCE(`{$do_sync_column}`, '')) IN (
                           'FAILED',
                           'FAILED_FINAL',
                           'VOID_FAILED'
                       )",
                    $do_date_params
                )
            );
        }

        if ($do_hidden_column !== '') {
            $do_stats['hidden'] = wst_ops_safe_int(
                wst_ops_query_value(
                    "SELECT COUNT(*)
                     FROM `{$do_table}`
                     WHERE {$do_date_sql}
                       AND COALESCE(`{$do_hidden_column}`, 0) = 1",
                    $do_date_params
                )
            );
        }

        $do_id_column = wst_ops_first_column(
            $tables['delivery_orders'],
            array('id')
        );

        $do_doc_column = wst_ops_first_column(
            $tables['delivery_orders'],
            array('local_doc_no', 'doc_no', 'source_doc_no')
        );

        $do_customer_code_column = wst_ops_first_column(
            $tables['delivery_orders'],
            array('debtor_code', 'customer_code')
        );

        $do_customer_name_column = wst_ops_first_column(
            $tables['delivery_orders'],
            array('debtor_name', 'customer_name')
        );

        $do_updated_column = wst_ops_first_column(
            $tables['delivery_orders'],
            array('updated_at', 'created_at', 'doc_date')
        );

        if (
            $do_id_column !== ''
            && $do_doc_column !== ''
            && $do_updated_column !== ''
        ) {
            $customer_code_select = $do_customer_code_column !== ''
                ? "`{$do_customer_code_column}`"
                : "''";

            $customer_name_select = $do_customer_name_column !== ''
                ? "`{$do_customer_name_column}`"
                : "''";

            $delivery_status_select = $do_status_column !== ''
                ? "`{$do_status_column}`"
                : "''";

            $sync_status_select = $do_sync_column !== ''
                ? "`{$do_sync_column}`"
                : "''";

            $recent_delivery_orders = wst_ops_query_rows(
                "SELECT
                    `{$do_id_column}` AS record_id,
                    `{$do_doc_column}` AS document_no,
                    `{$do_date_column}` AS document_date,
                    {$customer_code_select} AS party_code,
                    {$customer_name_select} AS party_name,
                    {$delivery_status_select} AS delivery_status,
                    {$sync_status_select} AS sync_status
                 FROM `{$do_table}`
                 WHERE {$do_date_sql}
                 ORDER BY `{$do_updated_column}` DESC
                 LIMIT 8",
                $do_date_params
            );
        }
    }
}

if ($table_availability['delivery_proofs']) {
    $proof_table = preg_replace(
        '/[^A-Za-z0-9_]/',
        '',
        $tables['delivery_proofs']
    );

    $proof_date_column = wst_ops_first_column(
        $tables['delivery_proofs'],
        array('captured_at', 'created_at', 'uploaded_at')
    );

    if ($proof_date_column !== '') {
        list($proof_date_sql, $proof_date_params) = wst_ops_date_condition(
            $proof_date_column,
            $start_date,
            $end_date
        );

        $do_stats['proof_count'] = wst_ops_safe_int(
            wst_ops_query_value(
                "SELECT COUNT(*) FROM `{$proof_table}` WHERE {$proof_date_sql}",
                $proof_date_params
            )
        );
    }
}

/*
|--------------------------------------------------------------------------
| Purchase Invoice statistics
|--------------------------------------------------------------------------
*/

$pi_stats = array(
    'total'        => 0,
    'amount'       => 0.0,
    'pending'      => 0,
    'failed'       => 0,
    'successful'   => 0,
);

$recent_purchase_invoices = array();

if ($table_availability['purchase_invoices']) {
    $pi_table = preg_replace(
        '/[^A-Za-z0-9_]/',
        '',
        $tables['purchase_invoices']
    );

    $pi_date_column = wst_ops_first_column(
        $tables['purchase_invoices'],
        array('doc_date', 'created_at')
    );

    $pi_status_column = wst_ops_first_column(
        $tables['purchase_invoices'],
        array('sync_status', 'status')
    );

    $pi_amount_column = wst_ops_first_column(
        $tables['purchase_invoices'],
        array('total_amount', 'grand_total')
    );

    if ($pi_date_column !== '') {
        list($pi_date_sql, $pi_date_params) = wst_ops_date_condition(
            $pi_date_column,
            $start_date,
            $end_date
        );

        $pi_stats['total'] = wst_ops_safe_int(
            wst_ops_query_value(
                "SELECT COUNT(*) FROM `{$pi_table}` WHERE {$pi_date_sql}",
                $pi_date_params
            )
        );

        if ($pi_amount_column !== '') {
            $pi_stats['amount'] = wst_ops_safe_float(
                wst_ops_query_value(
                    "SELECT COALESCE(SUM(`{$pi_amount_column}`), 0)
                     FROM `{$pi_table}`
                     WHERE {$pi_date_sql}",
                    $pi_date_params,
                    0
                )
            );
        }

        if ($pi_status_column !== '') {
            $pi_stats['pending'] = wst_ops_safe_int(
                wst_ops_query_value(
                    "SELECT COUNT(*)
                     FROM `{$pi_table}`
                     WHERE {$pi_date_sql}
                       AND UPPER(COALESCE(`{$pi_status_column}`, '')) IN ('PENDING', 'PROCESSING')",
                    $pi_date_params
                )
            );

            $pi_stats['failed'] = wst_ops_safe_int(
                wst_ops_query_value(
                    "SELECT COUNT(*)
                     FROM `{$pi_table}`
                     WHERE {$pi_date_sql}
                       AND UPPER(COALESCE(`{$pi_status_column}`, '')) IN ('FAILED', 'FAILED_FINAL')",
                    $pi_date_params
                )
            );

            $pi_stats['successful'] = wst_ops_safe_int(
                wst_ops_query_value(
                    "SELECT COUNT(*)
                     FROM `{$pi_table}`
                     WHERE {$pi_date_sql}
                       AND UPPER(COALESCE(`{$pi_status_column}`, '')) IN (
                           'SUCCESS',
                           'COMPLETED',
                           'SYNCED'
                       )",
                    $pi_date_params
                )
            );
        }

        $pi_id_column = wst_ops_first_column(
            $tables['purchase_invoices'],
            array('id')
        );

        $pi_doc_column = wst_ops_first_column(
            $tables['purchase_invoices'],
            array('local_doc_no', 'doc_no')
        );

        $pi_creditor_code_column = wst_ops_first_column(
            $tables['purchase_invoices'],
            array('creditor_code', 'supplier_code')
        );

        $pi_creditor_name_column = wst_ops_first_column(
            $tables['purchase_invoices'],
            array('creditor_name', 'supplier_name')
        );

        $pi_supplier_invoice_column = wst_ops_first_column(
            $tables['purchase_invoices'],
            array('supplier_invoice_no', 'supplier_ref')
        );

        $pi_updated_column = wst_ops_first_column(
            $tables['purchase_invoices'],
            array('updated_at', 'created_at', 'doc_date')
        );

        if (
            $pi_id_column !== ''
            && $pi_doc_column !== ''
            && $pi_updated_column !== ''
        ) {
            $creditor_code_select = $pi_creditor_code_column !== ''
                ? "`{$pi_creditor_code_column}`"
                : "''";

            $creditor_name_select = $pi_creditor_name_column !== ''
                ? "`{$pi_creditor_name_column}`"
                : "''";

            $supplier_invoice_select = $pi_supplier_invoice_column !== ''
                ? "`{$pi_supplier_invoice_column}`"
                : "''";

            $status_select = $pi_status_column !== ''
                ? "`{$pi_status_column}`"
                : "''";

            $amount_select = $pi_amount_column !== ''
                ? "`{$pi_amount_column}`"
                : '0';

            $recent_purchase_invoices = wst_ops_query_rows(
                "SELECT
                    `{$pi_id_column}` AS record_id,
                    `{$pi_doc_column}` AS document_no,
                    `{$pi_date_column}` AS document_date,
                    {$creditor_code_select} AS party_code,
                    {$creditor_name_select} AS party_name,
                    {$supplier_invoice_select} AS supplier_invoice_no,
                    {$status_select} AS sync_status,
                    {$amount_select} AS total_amount
                 FROM `{$pi_table}`
                 WHERE {$pi_date_sql}
                 ORDER BY `{$pi_updated_column}` DESC
                 LIMIT 8",
                $pi_date_params
            );
        }
    }
}

/*
|--------------------------------------------------------------------------
| Goods Receive statistics
|--------------------------------------------------------------------------
*/

$gr_stats = array(
    'total'      => 0,
    'pending'    => 0,
    'failed'     => 0,
    'successful' => 0,
);

if ($table_availability['goods_receive']) {
    $gr_table = preg_replace(
        '/[^A-Za-z0-9_]/',
        '',
        $tables['goods_receive']
    );

    $gr_date_column = wst_ops_first_column(
        $tables['goods_receive'],
        array('doc_date', 'created_at')
    );

    $gr_status_column = wst_ops_first_column(
        $tables['goods_receive'],
        array('sync_status', 'status')
    );

    if ($gr_date_column !== '') {
        list($gr_date_sql, $gr_date_params) = wst_ops_date_condition(
            $gr_date_column,
            $start_date,
            $end_date
        );

        $gr_stats['total'] = wst_ops_safe_int(
            wst_ops_query_value(
                "SELECT COUNT(*) FROM `{$gr_table}` WHERE {$gr_date_sql}",
                $gr_date_params
            )
        );

        if ($gr_status_column !== '') {
            $gr_stats['pending'] = wst_ops_safe_int(
                wst_ops_query_value(
                    "SELECT COUNT(*)
                     FROM `{$gr_table}`
                     WHERE {$gr_date_sql}
                       AND UPPER(COALESCE(`{$gr_status_column}`, '')) IN ('PENDING', 'PROCESSING')",
                    $gr_date_params
                )
            );

            $gr_stats['failed'] = wst_ops_safe_int(
                wst_ops_query_value(
                    "SELECT COUNT(*)
                     FROM `{$gr_table}`
                     WHERE {$gr_date_sql}
                       AND UPPER(COALESCE(`{$gr_status_column}`, '')) IN ('FAILED', 'FAILED_FINAL')",
                    $gr_date_params
                )
            );

            $gr_stats['successful'] = wst_ops_safe_int(
                wst_ops_query_value(
                    "SELECT COUNT(*)
                     FROM `{$gr_table}`
                     WHERE {$gr_date_sql}
                       AND UPPER(COALESCE(`{$gr_status_column}`, '')) IN (
                           'SUCCESS',
                           'COMPLETED',
                           'SYNCED'
                       )",
                    $gr_date_params
                )
            );
        }
    }
}

/*
|--------------------------------------------------------------------------
| Basket balances
|--------------------------------------------------------------------------
*/

$debtor_basket_summary = array(
    'issued'             => 0.0,
    'returned'           => 0.0,
    'outstanding'        => 0.0,
    'customers_positive' => 0,
);

$creditor_basket_summary = array(
    'received'              => 0.0,
    'returned'              => 0.0,
    'outstanding'           => 0.0,
    'creditors_positive'    => 0,
);

$top_debtor_baS,��h�����������S-A�
N?��lances = array();
$top_creditor_balances = array();

if ($table_availability['debtor_baskets']) {
    $basket_table = preg_replace(
        '/[^A-Za-z0-9_]/',
        '',
        $tables['debtor_baskets']
    );

    $basket_code_column = wst_ops_first_column(
        $tables['debtor_baskets'],
        array('debtor_code')
    );

    $basket_name_column = wst_ops_first_column(
        $tables['debtor_baskets'],
        array('debtor_name')
    );

    $basket_type_column = wst_ops_first_column(
        $tables['debtor_baskets'],
        array('txn_type', 'transaction_type')
    );

    $basket_qty_column = wst_ops_first_column(
        $tables['debtor_baskets'],
        array('qty', 'quantity')
    );

    if (
        $basket_code_column !== ''
        && $basket_type_column !== ''
        && $basket_qty_column !== ''
    ) {
        $name_select = $basket_name_column !== ''
            ? "MAX(`{$basket_name_column}`)"
            : "''";

        $debtor_balance_expression = "
            SUM(
                CASE
                    WHEN UPPER(COALESCE(`{$basket_type_column}`, '')) IN (
                        'OUT',
                        'ISSUE',
                        'ISSUED',
                        'DELIVERY',
                        'DELIVERED',
                        'DEBIT'
                    )
                    THEN ABS(COALESCE(`{$basket_qty_column}`, 0))

                    WHEN UPPER(COALESCE(`{$basket_type_column}`, '')) IN (
                        'IN',
                        'RETURN',
                        'RETURNED',
                        'CREDIT'
                    )
                    THEN -ABS(COALESCE(`{$basket_qty_column}`, 0))

                    ELSE COALESCE(`{$basket_qty_column}`, 0)
                END
            )
        ";

        $debtor_basket_summary['issued'] = wst_ops_safe_float(
            wst_ops_query_value(
                "SELECT COALESCE(SUM(ABS(`{$basket_qty_column}`)), 0)
                 FROM `{$basket_table}`
                 WHERE UPPER(COALESCE(`{$basket_type_column}`, '')) IN (
                    'OUT',
                    'ISSUE',
                    'ISSUED',
                    'DELIVERY',
                    'DELIVERED',
                    'DEBIT'
                 )"
            )
        );

        $debtor_basket_summary['returned'] = wst_ops_safe_float(
            wst_ops_query_value(
                "SELECT COALESCE(SUM(ABS(`{$basket_qty_column}`)), 0)
                 FROM `{$basket_table}`
                 WHERE UPPER(COALESCE(`{$basket_type_column}`, '')) IN (
                    'IN',
                    'RETURN',
                    'RETURNED',
                    'CREDIT'
                 )"
            )
        );

        $debtor_basket_summary['outstanding'] = wst_ops_safe_float(
            wst_ops_query_value(
                "SELECT COALESCE(SUM(balance_qty), 0)
                 FROM (
                    SELECT {$debtor_balance_expression} AS balance_qty
                    FROM `{$basket_table}`
                    GROUP BY `{$basket_code_column}`
                 ) debtor_balances
                 WHERE balance_qty > 0"
            )
        );

        $debtor_basket_summary['customers_positive'] = wst_ops_safe_int(
            wst_ops_query_value(
                "SELECT COUNT(*)
                 FROM (
                    SELECT {$debtor_balance_expression} AS balance_qty
                    FROM `{$basket_table}`
                    GROUP BY `{$basket_code_column}`
                 ) debtor_balances
                 WHERE balance_qty > 0"
            )
        );

        $top_debtor_balances = wst_ops_query_rows(
            "SELECT
                `{$basket_code_column}` AS party_code,
                {$name_select} AS party_name,
                {$debtor_balance_expression} AS balance_qty
             FROM `{$basket_table}`
             GROUP BY `{$basket_code_column}`
             HAVING balance_qty > 0
             ORDER BY balance_qty DESC
             LIMIT 8"
        );
    }
}

if ($table_availability['creditor_baskets']) {
    $creditor_basket_table = preg_replace(
        '/[^A-Za-z0-9_]/',
        '',
        $tables['creditor_baskets']
    );

    $creditor_code_column = wst_ops_first_column(
        $tables['creditor_baskets'],
        array('creditor_code')
    );

    $creditor_name_column = wst_ops_first_column(
        $tables['creditor_baskets'],
        array('creditor_name')
    );

    $creditor_type_column = wst_ops_first_column(
        $tables['creditor_baskets'],
        array('txn_type', 'transaction_type')
    );

    $creditor_qty_column = wst_ops_first_column(
        $tables['creditor_baskets'],
        array('qty', 'quantity')
    );

    if (
        $creditor_code_column !== ''
        && $creditor_type_column !== ''
        && $creditor_qty_column !== ''
    ) {
        $creditor_name_select = $creditor_name_column !== ''
            ? "MAX(`{$creditor_name_column}`)"
            : "''";

        /*
         * Creditor basket logic:
         * - Receipt from supplier increases baskets held by WST.
         * - Return to supplier reduces baskets held by WST.
         */
        $creditor_balance_expression = "
            SUM(
                CASE
                    WHEN UPPER(COALESCE(`{$creditor_type_column}`, '')) IN (
                        'IN',
                        'RECEIVE',
                        'RECEIVED',
                        'ISSUE',
                        'ISSUED',
                        'DEBIT'
                    )
                    THEN ABS(COALESCE(`{$creditor_qty_column}`, 0))

                    WHEN UPPER(COALESCE(`{$creditor_type_column}`, '')) IN (
                        'OUT',
                        'RETURN',
                        'RETURNED',
                        'CREDIT'
                    )
                    THEN -ABS(COALESCE(`{$creditor_qty_column}`, 0))

                    ELSE COALESCE(`{$creditor_qty_column}`, 0)
                END
            )
        ";

        $creditor_basket_summary['received'] = wst_ops_safe_float(
            wst_ops_query_value(
                "SELECT COALESCE(SUM(ABS(`{$creditor_qty_column}`)), 0)
                 FROM `{$creditor_basket_table}`
                 WHERE UPPER(COALESCE(`{$creditor_type_column}`, '')) IN (
                    'IN',
                    'RECEIVE',
                    'RECEIVED',
                    'ISSUE',
                    'ISSUED',
                    'DEBIT'
                 )"
            )
        );

        $creditor_basket_summary['returned'] = wst_ops_safe_float(
            wst_ops_query_value(
                "SELECT COALESCE(SUM(ABS(`{$creditor_qty_column}`)), 0)
                 FROM `{$creditor_basket_table}`
                 WHERE UPPER(COALESCE(`{$creditor_type_column}`, '')) IN (
                    'OUT',
                    'RETURN',
                    'RETURNED',
                    'CREDIT'
                 )"
            )
        );

        $creditor_basket_summary['outstanding'] = wst_ops_safe_float(
            wst_ops_query_value(
                "SELECT COALESCE(SUM(balance_qty), 0)
                 FROM (
                    SELECT {$creditor_balance_expression} AS balance_qty
                    FROM `{$creditor_basket_table}`
                    GROUP BY `{$creditor_code_column}`
                 ) creditor_balances
                 WHERE balance_qty > 0"
            )
        );

        $creditor_basket_summary['creditors_positive'] = wst_ops_safe_int(
            wst_ops_query_value(
                "SELECT COUNT(*)
                 FROM (
                    SELECT {$creditor_balance_expression} AS balance_qty
                    FROM `{$creditor_basket_table}`
                    GROUP BY `{$creditor_code_column}`
                 ) creditor_balances
                 WHERE balance_qty > 0"
            )
        );

        $top_creditor_balances = wst_ops_query_rows(
            "SELECT
                `{$creditor_code_column}` AS party_code,
                {$creditor_name_select} AS party_name,
                {$creditor_balance_expression} AS balance_qty
             FROM `{$creditor_basket_table}`
             GROUP BY `{$creditor_code_column}`
             HAVING balance_qty > 0
             ORDER BY balance_qty DESC
             LIMIT 8"
        );
    }
}

/*
|--------------------------------------------------------------------------
| Operational health
|--------------------------------------------------------------------------
*/

$attention_count =
    $job_stats['failed']
    + $do_stats['sync_failed']
    + $pi_stats['failed']
    + $gr_stats['failed'];

$open_work_count =
    $job_stats['pending']
    + $job_stats['processing']
    + $do_stats['open_delivery']
    + $pi_stats['pending']
    + $gr_stats['pending'];

$job_success_rate = $job_stats['total'] > 0
    ? round(($job_stats['success'] / $job_stats['total']) * 100, 1)
    : 100;

if ($attention_count > 0) {
    $system_health_label = 'Action Required';
    $system_health_class = 'wst-ops-health-danger';
    $system_health_text = sprintf(
        '%s issue%s require review.',
        number_format_i18n($attention_count),
        $attention_count === 1 ? '' : 's'
    );
} elseif ($open_work_count > 0) {
    $system_health_label = 'Work in Progress';
    $system_health_class = 'wst-ops-health-warning';
    $system_health_text = sprintf(
        '%s item%s are still pending or in progress.',
        number_format_i18n($open_work_count),
        $open_work_count === 1 ? '' : 's'
    );
} else {
    $system_health_label = 'Operational';
    $system_health_class = 'wst-ops-health-good';
    $system_health_text = 'No failed or pending operational items were detected.';
}

/*
|--------------------------------------------------------------------------
| Page links
|--------------------------------------------------------------------------
|
| Change these slugs if your WordPress page slugs are different.
|
*/

$page_links = array(
    'create_document' => wst_ops_build_page_url('create-delivery-order'),
    'do_list'         => wst_ops_build_page_url('delivery-order-staff-list'),
    'pi_list'         => wst_ops_build_page_url('purchase-invoice-staff-list'),
    'basket_summary'  => wst_ops_build_page_url('basket-summary'),
    'driver_dashboard'=> wst_ops_build_page_url('assigned-driver-dashboard'),
    'daily_summary'   => wst_ops_build_page_url('delivery-order-daily-customer-summary'),
);

$dashboard_url = remove_query_arg(
    array('wst_start', 'wst_end'),
    wp_unslash($_SERVER['REQUEST_URI'] ?? '')
);

$display_period = wst_ops_format_date($start_date)
    . ' – '
    . wst_ops_format_date($end_date);

/*
|--------------------------------------------------------------------------
| Output
|--------------------------------------------------------------------------
*/
?>

<div class="wst-ops-dashboard">
    <div class="wst-ops-heading">
        <div>
            <span class="wst-ops-eyebrow">WST Excellent Vege</span>
            <h1>Operations Dashboard</h1>
            <p>
                Monitor document activity, AutoCount processing, deliveries,
                basket balances and operational exceptions.
            </p>
        </div>

        <div class="wst-ops-health <?php echo esc_attr($system_health_class); ?>">
            <span class="wst-ops-health-dot"></span>

            <div>
                <strong><?php echo esc_html($system_health_label); ?></strong>
                <span><?php echo esc_html($system_health_text); ?></span>
            </div>
        </div>
    </div>

    <form method="get" class="wst-ops-filter">
        <div class="wst-ops-filter-heading">
            <div>
                <strong>Reporting Period</strong>
                <span>
                    <?php echo esc_html($display_period); ?>
                    · <?php echo esc_html(number_format_i18n($range_days)); ?>
                    day<?php echo $range_days === 1 ? '' : 's'; ?>
                </span>
            </div>
        </div>

        <div class="wst-ops-filter-fields">
            <label>
                <span>Start date</span>
                <input
                    type="date"
                    name="wst_start"
                    value="<?php echo esc_attr($start_date); ?>"
                >
            </label>

            <label>
                <span>End date</span>
                <input
                    type="date"
                    name="wst_end"
                    value="<?php echo esc_attr($end_date); ?>"
                >
            </label>

            <button type="submit" class="wst-ops-button wst-ops-button-primary">
                Apply
            </button>

            <a
                href="<?php echo esc_url($dashboard_url); ?>"
                class="wst-ops-button wst-ops-button-secondary"
            >
                Last 7 Days
            </a>
        </div>
    </form>

    <div class="wst-ops-primary-metrics">
        <article class="wst-ops-metric">
            <div class="wst-ops-metric-top">
                <span>Delivery Orders</span>
                <span class="wst-ops-metric-icon">DO</span>
            </div>

            <strong><?php echo esc_html(wst_ops_format_number($do_stats['total'])); ?></strong>

            <div class="wst-ops-metric-foot">
                <span>
                    <?php echo esc_html(wst_ops_format_number($do_stats['delivered'])); ?>
                    delivered
                </span>

                <?php if ($do_stats['open_delivery'] > 0) : ?>
                    <span class="wst-ops-text-warning">
                        <?php echo esc_html(wst_ops_format_number($do_stats['open_delivery'])); ?>
                        open
                    </span>
                <?php else : ?>
                    <span class="wst-ops-text-good">No open deliveries</span>
                <?php endif; ?>
            </div>
        </article>

        <article class="wst-ops-metric">
            <div class="wst-ops-metric-top">
                <span>Purchase Invoices</span>
                <span class="wst-ops-metric-icon">PI</span>
            </div>

            <strong><?php echo esc_html(wst_ops_format_number($pi_stats['total'])); ?></strong>

            <div class="wst-ops-metric-foot">
                <span>
                    <?php echo $is_administrator
                        ? esc_html(wst_ops_format_money($pi_stats['amount']))
                        : esc_html(wst_ops_format_number($pi_stats['pending'])) . ' pending'; ?>
                </span>

                <?php if ($pi_stats['failed'] > 0) : ?>
                    <span class="wst-ops-text-danger">
                        <?php echo esc_html(wst_ops_format_number($pi_stats['failed'])); ?>
                        failed
                    </span>
                <?php else : ?>
                    <span class="wst-ops-text-good">No failures</span>
                <?php endif; ?>
            </div>
        </article>

        <article class="wst-ops-metric">
            <div class="wst-ops-metric-top">
                <span>AutoCount Jobs</span>
                <span class="wst-ops-metric-icon">AC</span>
            </div>

            <strong><?php echo esc_html(wst_ops_format_number($job_stats['total'])); ?></strong>

            <div class="wst-ops-metric-foot">
                <span><?php echo esc_html($job_success_rate); ?>% successful</span>

                <?php if ($job_stats['failed'] > 0) : ?>
                    <span class="wst-ops-text-danger">
                        <?php echo esc_html(wst_ops_format_number($job_stats['failed'])); ?>
                        failed
                    </span>
                <?php else : ?>
              S-A�<W����������S-��
N?��      <span class="wst-ops-text-good">Healthy</span>
                <?php endif; ?>
            </div>
        </article>

        <?php if ($is_administrator) : ?>
            <article class="wst-ops-metric">
                <div class="wst-ops-metric-top">
                    <span>Customer Baskets Out</span>
                    <span class="wst-ops-metric-icon">BSK</span>
                </div>

                <strong>
                    <?php echo esc_html(
                        wst_ops_format_number($debtor_basket_summary['outstanding'], 2)
                    ); ?>
                </strong>

                <div class="wst-ops-metric-foot">
                    <span>
                        <?php echo esc_html(
                            wst_ops_format_number($debtor_basket_summary['customers_positive'])
                        ); ?>
                        customers
                    </span>

                    <span>Current balance</span>
                </div>
            </article>
        <?php else : ?>
            <article class="wst-ops-metric">
                <div class="wst-ops-metric-top">
                    <span>Goods Receive Notes</span>
                    <span class="wst-ops-metric-icon">GRN</span>
                </div>

                <strong><?php echo esc_html(wst_ops_format_number($gr_stats['total'])); ?></strong>

                <div class="wst-ops-metric-foot">
                    <span>
                        <?php echo esc_html(wst_ops_format_number($gr_stats['pending'])); ?>
                        pending
                    </span>

                    <?php if ($gr_stats['failed'] > 0) : ?>
                        <span class="wst-ops-text-danger">
                            <?php echo esc_html(wst_ops_format_number($gr_stats['failed'])); ?>
                            failed
                        </span>
                    <?php else : ?>
                        <span class="wst-ops-text-good">No failures</span>
                    <?php endif; ?>
                </div>
            </article>
        <?php endif; ?>
    </div>

    <?php if ($attention_count > 0 || $open_work_count > 0) : ?>
        <section class="wst-ops-section">
            <div class="wst-ops-section-heading">
                <div>
                    <span class="wst-ops-section-eyebrow">Priority</span>
                    <h2>Needs Attention</h2>
                </div>
            </div>

            <div class="wst-ops-alert-grid">
                <?php if ($job_stats['failed'] > 0) : ?>
                    <article class="wst-ops-alert-card wst-ops-alert-danger">
                        <span class="wst-ops-alert-value">
                            <?php echo esc_html(wst_ops_format_number($job_stats['failed'])); ?>
                        </span>

                        <div>
                            <strong>Failed AutoCount jobs</strong>
                            <p><?php echo $is_administrator
                                ? 'Review the bridge error messages and retry only after correcting the cause.'
                                : 'These documents did not complete AutoCount synchronization. Ask an administrator to review them.'; ?></p>
                        </div>
                    </article>
                <?php endif; ?>

                <?php if ($job_stats['pending'] + $job_stats['processing'] > 0) : ?>
                    <article class="wst-ops-alert-card wst-ops-alert-warning">
                        <span class="wst-ops-alert-value">
                            <?php echo esc_html(
                                wst_ops_format_number(
                                    $job_stats['pending'] + $job_stats['processing']
                                )
                            ); ?>
                        </span>

                        <div>
                            <strong>Jobs waiting for AutoCount</strong>
                            <p>These jobs have not finished processing through the connector.</p>
                        </div>
                    </article>
                <?php endif; ?>



                <?php if ($do_stats['open_delivery'] > 0) : ?>
                    <article class="wst-ops-alert-card wst-ops-alert-warning">
                        <span class="wst-ops-alert-value">
                            <?php echo esc_html(
                                wst_ops_format_number($do_stats['open_delivery'])
                            ); ?>
                        </span>

                        <div>
                            <strong>Open deliveries</strong>
                            <p>These orders are assigned, scheduled, received or still out for delivery.</p>
                        </div>
                    </article>
                <?php endif; ?>

                <?php if ($pi_stats['failed'] > 0) : ?>
                    <article class="wst-ops-alert-card wst-ops-alert-danger">
                        <span class="wst-ops-alert-value">
                            <?php echo esc_html(wst_ops_format_number($pi_stats['failed'])); ?>
                        </span>

                        <div>
                            <strong>Purchase Invoice failures</strong>
                            <p>These invoices were stored in WordPress but failed during AutoCount processing.</p>
                        </div>
                    </article>
                <?php endif; ?>

                <?php if ($gr_stats['failed'] > 0) : ?>
                    <article class="wst-ops-alert-card wst-ops-alert-danger">
                        <span class="wst-ops-alert-value">
                            <?php echo esc_html(wst_ops_format_number($gr_stats['failed'])); ?>
                        </span>

                        <div>
                            <strong>Goods Receive failures</strong>
                            <p>Review the affected GRN jobs and their AutoCount error messages.</p>
                        </div>
                    </article>
                <?php endif; ?>
            </div>
        </section>
    <?php endif; ?>

    <section class="wst-ops-section">
        <div class="wst-ops-section-heading">
            <div>
                <span class="wst-ops-section-eyebrow">Workflow</span>
                <h2>Document Overview</h2>
            </div>

            <span class="wst-ops-section-note">
                <?php echo esc_html($display_period); ?>
            </span>
        </div>

        <div class="wst-ops-document-grid">
            <article class="wst-ops-document-card">
                <div class="wst-ops-document-title">
                    <div>
                        <span class="wst-ops-document-code">DO</span>
                        <div>
                            <strong>Delivery Orders</strong>
                            <span>Customer deliveries</span>
                        </div>
                    </div>

                    <span class="wst-ops-document-total">
                        <?php echo esc_html(wst_ops_format_number($do_stats['total'])); ?>
                    </span>
                </div>

                <dl class="wst-ops-breakdown">
                    <div>
                        <dt>Delivered</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($do_stats['delivered'])); ?></dd>
                    </div>
                    <div>
                        <dt>Open delivery</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($do_stats['open_delivery'])); ?></dd>
                    </div>
                    <div>
                        <dt>Sync failed</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($do_stats['sync_failed'])); ?></dd>
                    </div>
                    <div>
                        <dt>Proof images</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($do_stats['proof_count'])); ?></dd>
                    </div>
                    <?php if ($is_administrator) : ?>
                        <div>
                            <dt>Hidden records</dt>
                            <dd><?php echo esc_html(wst_ops_format_number($do_stats['hidden'])); ?></dd>
                        </div>
                    <?php endif; ?>
                </dl>
            </article>

            <article class="wst-ops-document-card">
                <div class="wst-ops-document-title">
                    <div>
                        <span class="wst-ops-document-code">PI</span>
                        <div>
                            <strong>Purchase Invoices</strong>
                            <span>Supplier invoices</span>
                        </div>
                    </div>

                    <span class="wst-ops-document-total">
                        <?php echo esc_html(wst_ops_format_number($pi_stats['total'])); ?>
                    </span>
                </div>

                <dl class="wst-ops-breakdown">
                    <?php if ($is_administrator) : ?>
                        <div>
                            <dt>Total amount</dt>
                            <dd><?php echo esc_html(wst_ops_format_money($pi_stats['amount'])); ?></dd>
                        </div>
                    <?php endif; ?>
                    <div>
                        <dt>Successful</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($pi_stats['successful'])); ?></dd>
                    </div>
                    <div>
                        <dt>Pending</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($pi_stats['pending'])); ?></dd>
                    </div>
                    <div>
                        <dt>Failed</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($pi_stats['failed'])); ?></dd>
                    </div>
                </dl>
            </article>

            <article class="wst-ops-document-card">
                <div class="wst-ops-document-title">
                    <div>
                        <span class="wst-ops-document-code">GRN</span>
                        <div>
                            <strong>Goods Receive Notes</strong>
                            <span>Supplier goods receipt</span>
                        </div>
                    </div>

                    <span class="wst-ops-document-total">
                        <?php echo esc_html(wst_ops_format_number($gr_stats['total'])); ?>
                    </span>
                </div>

                <dl class="wst-ops-breakdown">
                    <div>
                        <dt>Successful</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($gr_stats['successful'])); ?></dd>
                    </div>
                    <div>
                        <dt>Pending</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($gr_stats['pending'])); ?></dd>
                    </div>
                    <div>
                        <dt>Failed</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($gr_stats['failed'])); ?></dd>
                    </div>
                </dl>
            </article>

            <article class="wst-ops-document-card">
                <div class="wst-ops-document-title">
                    <div>
                        <span class="wst-ops-document-code">JOB</span>
                        <div>
                            <strong>Bridge Queue</strong>
                            <span>WordPress to AutoCount</span>
                        </div>
                    </div>

                    <span class="wst-ops-document-total">
                        <?php echo esc_html(wst_ops_format_number($job_stats['total'])); ?>
                    </span>
                </div>

                <dl class="wst-ops-breakdown">
                    <div>
                        <dt>Successful</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($job_stats['success'])); ?></dd>
                    </div>
                    <div>
                        <dt>Pending</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($job_stats['pending'])); ?></dd>
                    </div>
                    <div>
                        <dt>Processing</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($job_stats['processing'])); ?></dd>
                    </div>
                    <div>
                        <dt>Failed</dt>
                        <dd><?php echo esc_html(wst_ops_format_number($job_stats['failed'])); ?></dd>
                    </div>
                    <?php if ($is_administrator) : ?>
                        <div>
                            <dt>Retried</dt>
                            <dd><?php echo esc_html(wst_ops_format_number($job_stats['retrying'])); ?></dd>
                        </div>
                    <?php endif; ?>
                </dl>
            </article>
        </div>
    </section>

    <?php if ($is_administrator) : ?>
    <section class="wst-ops-section">
        <div class="wst-ops-section-heading">
            <div>
                <span class="wst-ops-section-eyebrow">Containers</span>
                <h2>Basket Position</h2>
            </div>

            <span class="wst-ops-section-note">Current ledger balance</span>
        </div>

        <div class="wst-ops-basket-grid">
            <article class="wst-ops-basket-card">
                <div class="wst-ops-basket-heading">
                    <div>
                        <strong>Customer Baskets</strong>
                        <span>Baskets currently held by debtors</span>
                    </div>

                    <span class="wst-ops-basket-total">
                        <?php echo esc_html(
                            wst_ops_format_number(
                                $debtor_basket_summary['outstanding'],
                                2
                            )
                        ); ?>
                    </span>
                </div>

                <div class="wst-ops-basket-stats">
                    <div>
                        <span>Total issued</span>
                        <strong>
                            <?php echo esc_html(
                                wst_ops_format_number(
                                    $debtor_basket_summary['issued'],
                                    2
                                )
                            ); ?>
                        </strong>
                    </div>

                    <div>
                        <span>Total returned</span>
                        <strong>
                            <?php echo esc_html(
                                wst_ops_format_number(
                                    $debtor_basket_summary['returned'],
                                    2
                                )
                            ); ?>
                        </strong>
                    </div>

                    <div>
                        <span>Customers owing</span>
                        <strong>
                            <?php echo esc_html(
                                wst_ops_format_number(
                                    $debtor_basket_summary['customers_positive']
                                )
                            ); ?>
                        </strong>
                    </div>
                </div>

                <?php if ($top_debtor_balances) : ?>
                    <div class="wst-ops-mini-table">
                        <div class="wst-ops-mini-table-head">
                            <span>Customer</span>
                            <span>Balance</span>
                        </div>

                        <?php foreachS-��Y'+����������S-��
N?�� ($top_debtor_balances as $balance) : ?>
                            <div class="wst-ops-mini-table-row">
                                <span>
                                    <strong>
                                        <?php echo esc_html($balance['party_code'] ?? '—'); ?>
                                    </strong>

                                    <?php if (!empty($balance['party_name'])) : ?>
                                        <small>
                                            <?php echo esc_html($balance['party_name']); ?>
                                        </small>
                                    <?php endif; ?>
                                </span>

                                <strong>
                                    <?php echo esc_html(
                                        wst_ops_format_number(
                                            $balance['balance_qty'] ?? 0,
                                            2
                                        )
                                    ); ?>
                                </strong>
                            </div>
                        <?php endforeach; ?>
                    </div>
                <?php else : ?>
                    <div class="wst-ops-empty">No outstanding customer baskets.</div>
                <?php endif; ?>
            </article>

            <article class="wst-ops-basket-card">
                <div class="wst-ops-basket-heading">
                    <div>
                        <strong>Supplier Baskets</strong>
                        <span>Baskets held by WST for creditors</span>
                    </div>

                    <span class="wst-ops-basket-total">
                        <?php echo esc_html(
                            wst_ops_format_number(
                                $creditor_basket_summary['outstanding'],
                                2
                            )
                        ); ?>
                    </span>
                </div>

                <div class="wst-ops-basket-stats">
                    <div>
                        <span>Total received</span>
                        <strong>
                            <?php echo esc_html(
                                wst_ops_format_number(
                                    $creditor_basket_summary['received'],
                                    2
                                )
                            ); ?>
                        </strong>
                    </div>

                    <div>
                        <span>Total returned</span>
                        <strong>
                            <?php echo esc_html(
                                wst_ops_format_number(
                                    $creditor_basket_summary['returned'],
                                    2
                                )
                            ); ?>
                        </strong>
                    </div>

                    <div>
                        <span>Suppliers outstanding</span>
                        <strong>
                            <?php echo esc_html(
                                wst_ops_format_number(
                                    $creditor_basket_summary['creditors_positive']
                                )
                            ); ?>
                        </strong>
                    </div>
                </div>

                <?php if ($top_creditor_balances) : ?>
                    <div class="wst-ops-mini-table">
                        <div class="wst-ops-mini-table-head">
                            <span>Supplier</span>
                            <span>Balance</span>
                        </div>

                        <?php foreach ($top_creditor_balances as $balance) : ?>
                            <div class="wst-ops-mini-table-row">
                                <span>
                                    <strong>
                                        <?php echo esc_html($balance['party_code'] ?? '—'); ?>
                                    </strong>

                                    <?php if (!empty($balance['party_name'])) : ?>
                                        <small>
                                            <?php echo esc_html($balance['party_name']); ?>
                                        </small>
                                    <?php endif; ?>
                                </span>

                                <strong>
                                    <?php echo esc_html(
                                        wst_ops_format_number(
                                            $balance['balance_qty'] ?? 0,
                                            2
                                        )
                                    ); ?>
                                </strong>
                            </div>
                        <?php endforeach; ?>
                    </div>
                <?php else : ?>
                    <div class="wst-ops-empty">No outstanding supplier baskets.</div>
                <?php endif; ?>
            </article>
        </div>
    </section>

    <?php endif; ?>

    <section class="wst-ops-section">
        <div class="wst-ops-section-heading">
            <div>
                <span class="wst-ops-section-eyebrow">Recent Records</span>
                <h2>Latest Activity</h2>
            </div>
        </div>

        <div class="wst-ops-activity-grid">
            <article class="wst-ops-panel">
                <div class="wst-ops-panel-heading">
                    <div>
                        <strong>Recent Delivery Orders</strong>
                        <span>Latest records in the selected period</span>
                    </div>

                    <a href="<?php echo esc_url($page_links['do_list']); ?>">
                        View all
                    </a>
                </div>

                <?php if ($recent_delivery_orders) : ?>
                    <div class="wst-ops-table-scroll">
                        <table class="wst-ops-table">
                            <thead>
                                <tr>
                                    <th>Document</th>
                                    <th>Customer</th>
                                    <th>Delivery</th>
                                    <th>AutoCount</th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php foreach ($recent_delivery_orders as $order) : ?>
                                    <?php
                                    $document_no = trim((string)($order['document_no'] ?? ''));
                                    $record_id = wst_ops_safe_int($order['record_id'] ?? 0);

                                    $do_view_url = wst_ops_build_page_url(
                                        'view-delivery-order',
                                        array_filter(
                                            array(
                                                'docNo' => $document_no,
                                                'id'    => $record_id,
                                            )
                                        )
                                    );
                                    ?>
                                    <tr>
                                        <td>
                                            <a href="<?php echo esc_url($do_view_url); ?>">
                                                <strong>
                                                    <?php echo esc_html($document_no ?: '—'); ?>
                                                </strong>
                                            </a>

                                            <small>
                                                <?php echo esc_html(
                                                    wst_ops_format_date(
                                                        $order['document_date'] ?? ''
                                                    )
                                                ); ?>
                                            </small>
                                        </td>

                                        <td>
                                            <strong>
                                                <?php echo esc_html($order['party_code'] ?? '—'); ?>
                                            </strong>

                                            <?php if (!empty($order['party_name'])) : ?>
                                                <small>
                                                    <?php echo esc_html($order['party_name']); ?>
                                                </small>
                                            <?php endif; ?>
                                        </td>

                                        <td>
                                            <?php
                                            $delivery_status = $order['delivery_status'] ?? '';
                                            ?>
                                            <span class="wst-ops-status <?php echo esc_attr(wst_ops_status_class($delivery_status)); ?>">
                                                <?php echo esc_html(wst_ops_status_label($delivery_status)); ?>
                                            </span>
                                        </td>

                                        <td>
                                            <?php
                                            $sync_status = $order['sync_status'] ?? '';
                                            ?>
                                            <span class="wst-ops-status <?php echo esc_attr(wst_ops_status_class($sync_status)); ?>">
                                                <?php echo esc_html(wst_ops_status_label($sync_status)); ?>
                                            </span>
                                        </td>
                                    </tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>
                <?php else : ?>
                    <div class="wst-ops-empty">No Delivery Orders found for this period.</div>
                <?php endif; ?>
            </article>

            <article class="wst-ops-panel">
                <div class="wst-ops-panel-heading">
                    <div>
                        <strong>Recent Purchase Invoices</strong>
                        <span>Latest supplier invoices</span>
                    </div>

                    <a href="<?php echo esc_url($page_links['pi_list']); ?>">
                        View all
                    </a>
                </div>

                <?php if ($recent_purchase_invoices) : ?>
                    <div class="wst-ops-table-scroll">
                        <table class="wst-ops-table">
                            <thead>
                                <tr>
                                    <th>Document</th>
                                    <th>Supplier</th>
                                    <th>Amount</th>
                                    <th>Status</th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php foreach ($recent_purchase_invoices as $invoice) : ?>
                                    <?php
                                    $invoice_no = trim((string)($invoice['document_no'] ?? ''));
                                    $invoice_id = wst_ops_safe_int($invoice['record_id'] ?? 0);

                                    $pi_view_url = wst_ops_build_page_url(
                                        'purchase-invoice-view',
                                        array_filter(
                                            array(
                                                'docNo' => $invoice_no,
                                                'id'    => $invoice_id,
                                            )
                                        )
                                    );
                                    ?>
                                    <tr>
                                        <td>
                                            <a href="<?php echo esc_url($pi_view_url); ?>">
                                                <strong>
                                                    <?php echo esc_html($invoice_no ?: '—'); ?>
                                                </strong>
                                            </a>

                                            <small>
                                                <?php echo esc_html(
                                                    wst_ops_format_date(
                                                        $invoice['document_date'] ?? ''
                                                    )
                                                ); ?>
                                            </small>
                                        </td>

                                        <td>
                                            <strong>
                                                <?php echo esc_html($invoice['party_code'] ?? '—'); ?>
                                            </strong>

                                            <?php if (!empty($invoice['party_name'])) : ?>
                                                <small>
                                                    <?php echo esc_html($invoice['party_name']); ?>
                                                </small>
                                            <?php endif; ?>

                                            <?php if (!empty($invoice['supplier_invoice_no'])) : ?>
                                                <small>
                                                    Ref:
                                                    <?php echo esc_html($invoice['supplier_invoice_no']); ?>
                                                </small>
                                            <?php endif; ?>
                                        </td>

                                        <td>
                                            <strong>
                                                <?php echo esc_html(
                                                    wst_ops_format_money(
                                                        $invoice['total_amount'] ?? 0
                                                    )
                                                ); ?>
                                            </strong>
                                        </td>

                                        <td>
                                            <?php $invoice_status = $invoice['sync_status'] ?? ''; ?>

                                            <span class="wst-ops-status <?php echo esc_attr(wst_ops_status_class($invoice_status)); ?>">
                                                <?php echo esc_html(wst_ops_status_label($invoice_status)); ?>
                                            </span>
                                        </td>
                                    </tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>
                <?php else : ?>
                    <div class="wst-ops-empty">No Purchase Invoices found for this period.</div>
                <?php endif; ?>
            </article>
        </div>
    </section>

    <?php if ($is_administrator) : ?>
    <section class="wst-ops-section">
    S-��RI�F���������S.�
N?��    <div class="wst-ops-section-heading">
            <div>
                <span class="wst-ops-section-eyebrow">Integration</span>
                <h2>AutoCount Bridge Health</h2>
            </div>

            <span class="wst-ops-section-note">
                <?php echo esc_html($job_success_rate); ?>% success rate
            </span>
        </div>

        <div class="wst-ops-bridge-grid">
            <article class="wst-ops-panel">
                <div class="wst-ops-panel-heading">
                    <div>
                        <strong>Jobs by Document Type</strong>
                        <span>Selected reporting period</span>
                    </div>
                </div>

                <?php if ($job_type_breakdown) : ?>
                    <div class="wst-ops-job-types">
                        <?php foreach ($job_type_breakdown as $type_row) : ?>
                            <?php
                            $type_total = max(
                                1,
                                wst_ops_safe_int($type_row['total_count'] ?? 0)
                            );

                            $type_failed = wst_ops_safe_int(
                                $type_row['failed_count'] ?? 0
                            );

                            $type_open = wst_ops_safe_int(
                                $type_row['open_count'] ?? 0
                            );

                            $completed_percent = max(
                                0,
                                min(
                                    100,
                                    (($type_total - $type_failed - $type_open) / $type_total) * 100
                                )
                            );
                            ?>
                            <div class="wst-ops-job-type">
                                <div class="wst-ops-job-type-head">
                                    <div>
                                        <strong>
                                            <?php echo esc_html(
                                                wst_ops_status_label(
                                                    $type_row['document_type'] ?? 'UNKNOWN'
                                                )
                                            ); ?>
                                        </strong>

                                        <span>
                                            <?php echo esc_html(
                                                wst_ops_format_number($type_total)
                                            ); ?>
                                            jobs
                                        </span>
                                    </div>

                                    <div>
                                        <?php if ($type_failed > 0) : ?>
                                            <span class="wst-ops-text-danger">
                                                <?php echo esc_html(
                                                    wst_ops_format_number($type_failed)
                                                ); ?>
                                                failed
                                            </span>
                                        <?php elseif ($type_open > 0) : ?>
                                            <span class="wst-ops-text-warning">
                                                <?php echo esc_html(
                                                    wst_ops_format_number($type_open)
                                                ); ?>
                                                open
                                            </span>
                                        <?php else : ?>
                                            <span class="wst-ops-text-good">Completed</span>
                                        <?php endif; ?>
                                    </div>
                                </div>

                                <div class="wst-ops-progress">
                                    <span style="width: <?php echo esc_attr($completed_percent); ?>%;"></span>
                                </div>
                            </div>
                        <?php endforeach; ?>
                    </div>
                <?php else : ?>
                    <div class="wst-ops-empty">No AutoCount jobs found for this period.</div>
                <?php endif; ?>
            </article>

            <article class="wst-ops-panel">
                <div class="wst-ops-panel-heading">
                    <div>
                        <strong>Latest Failed Jobs</strong>
                        <span>Newest bridge failures across all dates</span>
                    </div>
                </div>

                <?php if ($recent_failed_jobs) : ?>
                    <div class="wst-ops-failure-list">
                        <?php foreach ($recent_failed_jobs as $failed_job) : ?>
                            <div class="wst-ops-failure">
                                <div class="wst-ops-failure-top">
                                    <strong>
                                        <?php echo esc_html(
                                            $failed_job['document_no']
                                            ?: 'Job #' . wst_ops_safe_int($failed_job['job_id'] ?? 0)
                                        ); ?>
                                    </strong>

                                    <span class="wst-ops-status wst-ops-status-danger">
                                        <?php echo esc_html(
                                            wst_ops_status_label(
                                                $failed_job['job_status'] ?? 'FAILED'
                                            )
                                        ); ?>
                                    </span>
                                </div>

                                <span class="wst-ops-failure-type">
                                    <?php echo esc_html(
                                        wst_ops_status_label(
                                            $failed_job['job_type'] ?? 'Unknown'
                                        )
                                    ); ?>
                                    ·
                                    <?php echo esc_html(
                                        wst_ops_format_date(
                                            $failed_job['updated_at'] ?? '',
                                            true
                                        )
                                    ); ?>
                                </span>

                                <p>
                                    <?php
                                    echo esc_html(
                                        wp_trim_words(
                                            (string)($failed_job['error_message'] ?? 'Unknown AutoCount processing error.'),
                                            24,
                                            '…'
                                        )
                                    );
                                    ?>
                                </p>
                            </div>
                        <?php endforeach; ?>
                    </div>
                <?php else : ?>
                    <div class="wst-ops-empty wst-ops-empty-good">
                        No failed AutoCount jobs were found.
                    </div>
                <?php endif; ?>
            </article>
        </div>
    </section>

    <?php endif; ?>

    <section class="wst-ops-section">
        <div class="wst-ops-section-heading">
            <div>
                <span class="wst-ops-section-eyebrow">Navigation</span>
                <h2>Quick Actions</h2>
            </div>
        </div>

        <div class="wst-ops-actions">
            <a href="<?php echo esc_url($page_links['create_document']); ?>">
                <span class="wst-ops-action-icon">+</span>
                <div>
                    <strong>Create Document</strong>
                    <span>Create DO, GRN or Purchase Invoice</span>
                </div>
            </a>

            <a href="<?php echo esc_url($page_links['do_list']); ?>">
                <span class="wst-ops-action-icon">DO</span>
                <div>
                    <strong>Delivery Order List</strong>
                    <span>Search, view, edit and print Delivery Orders</span>
                </div>
            </a>

            <a href="<?php echo esc_url($page_links['pi_list']); ?>">
                <span class="wst-ops-action-icon">PI</span>
                <div>
                    <strong>Purchase Invoice List</strong>
                    <span>Review Purchase Invoice records and sync status</span>
                </div>
            </a>

            <a href="<?php echo esc_url($page_links['driver_dashboard']); ?>">
                <span class="wst-ops-action-icon">DRV</span>
                <div>
                    <strong>Driver Dashboard</strong>
                    <span>Manage assigned and active deliveries</span>
                </div>
            </a>

            <a href="<?php echo esc_url($page_links['basket_summary']); ?>">
                <span class="wst-ops-action-icon">BSK</span>
                <div>
                    <strong>Basket Summary</strong>
                    <span>Review debtor and creditor basket positions</span>
                </div>
            </a>

            <a href="<?php echo esc_url($page_links['daily_summary']); ?>">
                <span class="wst-ops-action-icon">KG</span>
                <div>
                    <strong>Daily Customer Summary</strong>
                    <span>Review quantity, weight and freight calculation</span>
                </div>
            </a>
        </div>
    </section>

    <?php if ($is_administrator) : ?>
        <section class="wst-ops-section">
            <details class="wst-ops-system-details">
                <summary>Administrator system information</summary>

                <div class="wst-ops-table-status">
                    <?php foreach ($tables as $table_key => $table_name) : ?>
                        <div>
                            <span>
                                <?php echo esc_html(
                                    ucwords(str_replace('_', ' ', $table_key))
                                ); ?>
                            </span>

                            <?php if ($table_availability[$table_key]) : ?>
                                <strong class="wst-ops-text-good">Available</strong>
                            <?php else : ?>
                                <strong class="wst-ops-text-danger">Missing</strong>
                            <?php endif; ?>
                        </div>
                    <?php endforeach; ?>
                </div>
            </details>
        </section>
    <?php endif; ?>
</div>

<style>
.wst-ops-dashboard,
.wst-ops-dashboard * {
    box-sizing: border-box;
}

.wst-ops-dashboard {
    --wst-ops-bg: #f4f7f5;
    --wst-ops-card: #ffffff;
    --wst-ops-text: #17231b;
    --wst-ops-muted: #66736b;
    --wst-ops-border: #dce5df;
    --wst-ops-primary: #176b3a;
    --wst-ops-primary-dark: #0f4f2b;
    --wst-ops-primary-soft: #eaf5ee;
    --wst-ops-good: #177245;
    --wst-ops-good-bg: #e8f6ee;
    --wst-ops-warning: #9a6300;
    --wst-ops-warning-bg: #fff4d9;
    --wst-ops-danger: #b42318;
    --wst-ops-danger-bg: #ffebe9;
    --wst-ops-neutral: #496157;
    --wst-ops-neutral-bg: #eef3f0;
    width: 100%;
    max-width: 1540px;
    margin: 0 auto;
    padding: 28px;
    color: var(--wst-ops-text);
    font-family:
        Inter,
        -apple-system,
        BlinkMacSystemFont,
        "Segoe UI",
        sans-serif;
}

.wst-ops-heading {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 24px;
    margin-bottom: 22px;
}

.wst-ops-eyebrow,
.wst-ops-section-eyebrow {
    display: block;
    margin-bottom: 6px;
    color: var(--wst-ops-primary);
    font-size: 12px;
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
}

.wst-ops-heading h1 {
    margin: 0;
    color: var(--wst-ops-text);
    font-size: clamp(28px, 3vw, 42px);
    line-height: 1.08;
}

.wst-ops-heading p {
    max-width: 760px;
    margin: 10px 0 0;
    color: var(--wst-ops-muted);
    font-size: 15px;
    line-height: 1.65;
}

.wst-ops-health {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 250px;
    padding: 15px 17px;
    border: 1px solid;
    border-radius: 15px;
}

.wst-ops-health > div {
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.wst-ops-health strong {
    font-size: 14px;
}

.wst-ops-health span:not(.wst-ops-health-dot) {
    font-size: 12px;
    line-height: 1.4;
}

.wst-ops-health-dot {
    width: 11px;
    height: 11px;
    flex: 0 0 11px;
    border-radius: 50%;
    box-shadow: 0 0 0 5px rgba(255,255,255,.45);
}

.wst-ops-health-good {
    color: var(--wst-ops-good);
    background: var(--wst-ops-good-bg);
    border-color: #b9dfc9;
}

.wst-ops-health-good .wst-ops-health-dot {
    background: var(--wst-ops-good);
}

.wst-ops-health-warning {
    color: var(--wst-ops-warning);
    background: var(--wst-ops-warning-bg);
    border-color: #efd28c;
}

.wst-ops-health-warning .wst-ops-health-dot {
    background: var(--wst-ops-warning);
}

.wst-ops-health-danger {
    color: var(--wst-ops-danger);
    background: var(--wst-ops-danger-bg);
    border-color: #efb9b4;
}

.wst-ops-health-danger .wst-ops-health-dot {
    background: var(--wst-ops-danger);
}

.wst-ops-filter {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 20px;
    margin-bottom: 22px;
    padding: 18px;
    background: var(--wst-ops-card);
    border: 1px solid var(--wst-ops-border);
    border-radius: 16px;
    box-shadow: 0 7px 24px rgba(21, 53, 35, .05);
}

.wst-ops-filter-heading > div,
.wst-ops-filter-fields label {
    display: flex;
    flex-direction: column;
}

.wst-ops-filter-heading strong {
    font-size: 15px;
}

.wst-ops-filter-heading span {
    margin-top: 4px;
    color: var(--wst-ops-muted);
    font-size: 12px;
}

.wst-ops-filter-fields {
    display: flex;
    align-items: flex-end;
    gap: 10px;
}

.wst-ops-filter-fields label {
    gap: 5px;
}

.wst-ops-filter-fields label span {
    color: var(--wst-ops-muted);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
}

.wst-ops-filter input {
    min-height: 42px;
    padding: 8px 11px;
    color: var(--wst-ops-text);
    background: #fff;
    border: 1px solid #cfd9d3;
    border-radius: 9px;
    font: inherit;
}

.wst-ops-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 42px;
    padding: 9px 16px;
    border: 1px solid transparent;
    border-radius: 9px;
    font-size: 13px;
    font-weight: 750;
    line-height: 1;
    text-decoration: none !important;
    cursor: pointer;
}

.wst-ops-button-primary {
    color: #fff !important;
    background: var(--wst-ops-primary);
}

.wst-ops-button-primary:hover {
    background: var(--wst-ops-primary-dark);
}

.wst-ops-button-secondary {
    color: var(--wst-ops-text) !important;
    background: #fff;
    border-color: var(--wst-ops-border);
}

.wst-ops-primary-metrics {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 15px;
}

.wst-ops-metric {
    min-width: 0;
    padding: 19px;
    background: var(--wst-ops-card);
    border: 1px solid var(--wst-ops-border);
    border-radius: 16px;
    box-sS.�T����������S.�
N;���hadow: 0 8px 26px rgba(22, 54, 36, .055);
}

.wst-ops-metric-top,
.wst-ops-metric-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.wst-ops-metric-top > span:first-child {
    color: var(--wst-ops-muted);
    font-size: 13px;
    font-weight: 700;
}

.wst-ops-metric-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 36px;
    height: 30px;
    padding: 0 8px;
    color: var(--wst-ops-primary);
    background: var(--wst-ops-primary-soft);
    border-radius: 9px;
    font-size: 11px;
    font-weight: 850;
}

.wst-ops-metric > strong {
    display: block;
    margin: 15px 0 12px;
    font-size: clamp(27px, 3vw, 38px);
    line-height: 1;
}

.wst-ops-metric-foot {
    align-items: flex-start;
    color: var(--wst-ops-muted);
    font-size: 11px;
}

.wst-ops-section {
    margin-top: 25px;
}

.wst-ops-section-heading {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 15px;
    margin-bottom: 12px;
}

.wst-ops-section-heading h2 {
    margin: 0;
    color: var(--wst-ops-text);
    font-size: 21px;
}

.wst-ops-section-note {
    color: var(--wst-ops-muted);
    font-size: 12px;
}

.wst-ops-alert-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
}

.wst-ops-alert-card {
    display: flex;
    align-items: flex-start;
    gap: 15px;
    min-width: 0;
    padding: 17px;
    border: 1px solid;
    border-radius: 14px;
}

.wst-ops-alert-card .wst-ops-alert-value {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 48px;
    height: 48px;
    padding: 0 8px;
    background: rgba(255,255,255,.65);
    border-radius: 12px;
    font-size: 21px;
    font-weight: 850;
}

.wst-ops-alert-card strong {
    display: block;
    margin-bottom: 5px;
    font-size: 14px;
}

.wst-ops-alert-card p {
    margin: 0;
    font-size: 12px;
    line-height: 1.55;
}

.wst-ops-alert-danger {
    color: var(--wst-ops-danger);
    background: var(--wst-ops-danger-bg);
    border-color: #efb9b4;
}

.wst-ops-alert-warning {
    color: var(--wst-ops-warning);
    background: var(--wst-ops-warning-bg);
    border-color: #efd28c;
}

.wst-ops-document-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 15px;
}

.wst-ops-document-card,
.wst-ops-basket-card,
.wst-ops-panel {
    min-width: 0;
    background: var(--wst-ops-card);
    border: 1px solid var(--wst-ops-border);
    border-radius: 16px;
    box-shadow: 0 8px 26px rgba(22, 54, 36, .05);
}

.wst-ops-document-card {
    padding: 18px;
}

.wst-ops-document-title,
.wst-ops-document-title > div {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 11px;
}

.wst-ops-document-title > div {
    justify-content: flex-start;
    min-width: 0;
}

.wst-ops-document-title > div > div {
    min-width: 0;
}

.wst-ops-document-title strong,
.wst-ops-document-title span {
    display: block;
}

.wst-ops-document-title strong {
    font-size: 14px;
}

.wst-ops-document-title > div > div > span {
    margin-top: 3px;
    color: var(--wst-ops-muted);
    font-size: 11px;
}

.wst-ops-document-code {
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    min-width: 42px;
    height: 42px;
    padding: 0 7px;
    color: var(--wst-ops-primary);
    background: var(--wst-ops-primary-soft);
    border-radius: 11px;
    font-size: 11px;
    font-weight: 850;
}

.wst-ops-document-total {
    font-size: 24px;
    font-weight: 850;
}

.wst-ops-breakdown {
    margin: 17px 0 0;
}

.wst-ops-breakdown > div {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 0;
    border-top: 1px solid #edf1ef;
}

.wst-ops-breakdown dt {
    color: var(--wst-ops-muted);
    font-size: 12px;
}

.wst-ops-breakdown dd {
    margin: 0;
    font-size: 12px;
    font-weight: 800;
    text-align: right;
}

.wst-ops-basket-grid,
.wst-ops-activity-grid,
.wst-ops-bridge-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 15px;
}

.wst-ops-basket-card {
    padding: 19px;
}

.wst-ops-basket-heading {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 15px;
}

.wst-ops-basket-heading strong,
.wst-ops-basket-heading span {
    display: block;
}

.wst-ops-basket-heading > div > strong {
    font-size: 16px;
}

.wst-ops-basket-heading > div > span {
    margin-top: 4px;
    color: var(--wst-ops-muted);
    font-size: 11px;
}

.wst-ops-basket-total {
    color: var(--wst-ops-primary);
    font-size: 27px;
    font-weight: 850;
}

.wst-ops-basket-stats {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 9px;
    margin: 17px 0;
}

.wst-ops-basket-stats > div {
    padding: 12px;
    background: #f7faf8;
    border: 1px solid #e5ece8;
    border-radius: 11px;
}

.wst-ops-basket-stats span,
.wst-ops-basket-stats strong {
    display: block;
}

.wst-ops-basket-stats span {
    margin-bottom: 7px;
    color: var(--wst-ops-muted);
    font-size: 10px;
    line-height: 1.35;
}

.wst-ops-basket-stats strong {
    font-size: 16px;
}

.wst-ops-mini-table {
    overflow: hidden;
    border: 1px solid var(--wst-ops-border);
    border-radius: 11px;
}

.wst-ops-mini-table-head,
.wst-ops-mini-table-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 14px;
    align-items: center;
    padding: 10px 12px;
}

.wst-ops-mini-table-head {
    color: var(--wst-ops-muted);
    background: #f3f7f4;
    font-size: 10px;
    font-weight: 800;
    text-transform: uppercase;
}

.wst-ops-mini-table-row {
    border-top: 1px solid #edf1ef;
    font-size: 12px;
}

.wst-ops-mini-table-row > span {
    min-width: 0;
}

.wst-ops-mini-table-row strong,
.wst-ops-mini-table-row small {
    display: block;
}

.wst-ops-mini-table-row small {
    overflow: hidden;
    margin-top: 2px;
    color: var(--wst-ops-muted);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wst-ops-panel {
    overflow: hidden;
}

.wst-ops-panel-heading {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 16px 18px;
    border-bottom: 1px solid var(--wst-ops-border);
}

.wst-ops-panel-heading strong,
.wst-ops-panel-heading span {
    display: block;
}

.wst-ops-panel-heading strong {
    font-size: 15px;
}

.wst-ops-panel-heading span {
    margin-top: 3px;
    color: var(--wst-ops-muted);
    font-size: 11px;
}

.wst-ops-panel-heading a {
    color: var(--wst-ops-primary);
    font-size: 12px;
    font-weight: 750;
    text-decoration: none;
}

.wst-ops-table-scroll {
    overflow-x: auto;
}

.wst-ops-table {
    width: 100%;
    min-width: 580px;
    border: 0;
    border-collapse: collapse;
}

.wst-ops-table th,
.wst-ops-table td {
    padding: 12px 14px;
    border: 0;
    border-bottom: 1px solid #edf1ef;
    text-align: left;
    vertical-align: top;
}

.wst-ops-table th {
    color: var(--wst-ops-muted);
    background: #fafcfb;
    font-size: 10px;
    font-weight: 800;
    text-transform: uppercase;
}

.wst-ops-table td {
    font-size: 12px;
}

.wst-ops-table td strong,
.wst-ops-table td small {
    display: block;
}

.wst-ops-table td small {
    max-width: 220px;
    overflow: hidden;
    margin-top: 3px;
    color: var(--wst-ops-muted);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wst-ops-table a {
    color: var(--wst-ops-primary-dark);
    text-decoration: none;
}

.wst-ops-status {
    display: inline-flex;
    align-items: center;
    min-height: 24px;
    padding: 4px 8px;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 800;
    line-height: 1.2;
    white-space: nowrap;
}

.wst-ops-status-good {
    color: var(--wst-ops-good);
    background: var(--wst-ops-good-bg);
}

.wst-ops-status-warning {
    color: var(--wst-ops-warning);
    background: var(--wst-ops-warning-bg);
}

.wst-ops-status-danger {
    color: var(--wst-ops-danger);
    background: var(--wst-ops-danger-bg);
}

.wst-ops-status-neutral {
    color: var(--wst-ops-neutral);
    background: var(--wst-ops-neutral-bg);
}

.wst-ops-text-good {
    color: var(--wst-ops-good) !important;
    font-weight: 750;
}

.wst-ops-text-warning {
    color: var(--wst-ops-warning) !important;
    font-weight: 750;
}

.wst-ops-text-danger {
    color: var(--wst-ops-danger) !important;
    font-weight: 750;
}

.wst-ops-job-types {
    padding: 7px 18px 16px;
}

.wst-ops-job-type {
    padding: 12px 0;
    border-bottom: 1px solid #edf1ef;
}

.wst-ops-job-type:last-child {
    border-bottom: 0;
}

.wst-ops-job-type-head {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 8px;
}

.wst-ops-job-type-head strong,
.wst-ops-job-type-head span {
    display: block;
}

.wst-ops-job-type-head strong {
    font-size: 12px;
}

.wst-ops-job-type-head span {
    margin-top: 2px;
    color: var(--wst-ops-muted);
    font-size: 10px;
}

.wst-ops-progress {
    height: 7px;
    overflow: hidden;
    background: #e8eeea;
    border-radius: 999px;
}

.wst-ops-progress span {
    display: block;
    height: 100%;
    background: var(--wst-ops-primary);
    border-radius: inherit;
}

.wst-ops-failure-list {
    max-height: 440px;
    overflow-y: auto;
    padding: 3px 18px 15px;
}

.wst-ops-failure {
    padding: 13px 0;
    border-bottom: 1px solid #edf1ef;
}

.wst-ops-failure:last-child {
    border-bottom: 0;
}

.wst-ops-failure-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.wst-ops-failure-top > strong {
    font-size: 13px;
}

.wst-ops-failure-type {
    display: block;
    margin-top: 4px;
    color: var(--wst-ops-muted);
    font-size: 10px;
}

.wst-ops-failure p {
    margin: 8px 0 0;
    color: #4d5b53;
    font-size: 11px;
    line-height: 1.5;
}

.wst-ops-actions {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
}

.wst-ops-actions > a {
    display: flex;
    align-items: center;
    gap: 13px;
    min-width: 0;
    padding: 16px;
    color: var(--wst-ops-text);
    background: var(--wst-ops-card);
    border: 1px solid var(--wst-ops-border);
    border-radius: 14px;
    box-shadow: 0 7px 22px rgba(22, 54, 36, .045);
    text-decoration: none !important;
    transition:
        border-color .18s ease,
        transform .18s ease,
        box-shadow .18s ease;
}

.wst-ops-actions > a:hover {
    border-color: #a8c8b5;
    box-shadow: 0 9px 27px rgba(22, 54, 36, .09);
    transform: translateY(-1px);
}

.wst-ops-action-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    height: 44px;
    padding: 0 6px;
    color: var(--wst-ops-primary);
    background: var(--wst-ops-primary-soft);
    border-radius: 11px;
    font-size: 11px;
    font-weight: 850;
}

.wst-ops-actions strong,
.wst-ops-actions span {
    display: block;
}

.wst-ops-actions strong {
    font-size: 13px;
}

.wst-ops-actions div > span {
    overflow: hidden;
    margin-top: 4px;
    color: var(--wst-ops-muted);
    font-size: 10px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wst-ops-empty {
    padding: 24px 18px;
    color: var(--wst-ops-muted);
    font-size: 12px;
    text-align: center;
}

.wst-ops-empty-good {
    color: var(--wst-ops-good);
}

.wst-ops-system-details {
    padding: 17px;
    background: var(--wst-ops-card);
    border: 1px solid var(--wst-ops-border);
    border-radius: 14px;
}

.wst-ops-system-details summary {
    font-size: 13px;
    font-weight: 750;
    cursor: pointer;
}

.wst-ops-table-status {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 8px;
    margin-top: 14px;
}

.wst-ops-table-status > div {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    padding: 10px;
    background: #f7faf8;
    border-radius: 8px;
    font-size: 11px;
}

.wst-ops-message {
    max-width: 900px;
    margin: 20px auto;
    padding: 15px;
    border-radius: 10px;
    font-family:
        Inter,
        -apple-system,
        BlinkMacSystemFont,
        "Segoe UI",
        sans-serif;
}

.wst-ops-message-error {
    color: #9f1d15;
    background: #ffebe9;
    border: 1px solid #efb9b4;
}

@media (max-width: 1180px) {
    .wst-ops-primary-metrics,
    .wst-ops-document-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .wst-ops-alert-grid,
    .wst-ops-actions {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 850px) {
    .wst-ops-dashboard {
        padding: 18px;
    }

    .wst-ops-heading,
    .wst-ops-filter {
        align-items: stretch;
        flex-direction: column;
    }

    .wst-ops-health {
        min-width: 0;
    }

    .wst-ops-filter-fields {
        flex-wrap: wrap;
    }

    .wst-ops-filter-fields label {
        flex: 1 1 180px;
    }

    .wst-ops-filter input {
        width: 100%;
    }

    .wst-ops-basket-grid,
    .wst-ops-activity-grid,
    .wst-ops-bridge-grid {
        grid-template-columns: 1fr;
    }

    .wst-ops-table-status {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 620px) {
    .wst-ops-dashboard {
        padding: 12px;
    }

    .wst-ops-heading {
        margin-bottom: 16px;
    }

    .wst-ops-primary-metrics,
    .wst-ops-document-grid,
    .wst-ops-alert-grid,
    .wst-ops-actions {
        grid-template-columns: 1fr;
    }

    .wst-ops-filter-fields {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        width: 100%;
    }

    .wst-ops-filter-fields label {
        grid-column: span 1;
    }

    .wst-ops-filter-fields .wst-ops-button {
        width: 100%;
    }

    .wst-ops-basket-stats {
        grid-template-columns: 1fr;
    }

    .wst-ops-section-heading {
        align-items: flex-start;
        flex-direction: column;
    }

    .wst-ops-table-status {
        grid-template-columns: 1fr;
    }
}

@media print {
    .wst-ops-dashboard {
        max-width: none;
        padding: 0;
    }

    .wst-ops-filter,
    .wst-ops-actions,
    .wst-ops-system-details {
        display: none !important;
    }

    .wst-ops-document-card,
    .wst-ops-basket-card,
    .wst-ops-panel,
    .wst-ops-metric {
        break-inside: avoid;
        box-shadow: none;
    }
}
</style>S.�"lMS���������S;��
N?��<?php
if (!defined('ABSPATH')) exit;

/*
 * VegeBasketDO staff Delivery Order list - MySQL-only version.
 *
 * Keeps the original staff-list style and action buttons:
 * Print | Edit | View | Delete
 * Hidden-row show toggle is available only for Administrator users.
 *
 * Data source:
 * WordPress MySQL tables: {$wpdb->prefix}ac_do + {$wpdb->prefix}ac_do_items
 * ac_jobs is only the bridge queue/history and is not used as the list source.
 *
 * Page URLs:
 * Edit: /edit-delivery-order/?docNo=DO-0001&docKey=123
 * View: /view-delivery-order/?docNo=DO-0001&docKey=123
 */

if (!is_user_logged_in()) {
    echo '<div class="wst-dod-alert wst-dod-alert-error">Please log in to view Delivery Order records.</div>';
    return;
}

if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
    echo '<div class="wst-dod-alert wst-dod-alert-error">You do not have permission to view Delivery Order records.</div>';
    return;
}

global $wpdb;

if (!$wpdb) {
    echo '<div class="wst-dod-alert wst-dod-alert-error">WordPress database connection is not available.</div>';
    return;
}

$edit_page_url = home_url('/edit-delivery-order/');
$view_page_url = home_url('/view-delivery-order/');
$show_technical_errors = current_user_can('manage_options') && defined('WP_DEBUG') && WP_DEBUG;

if (!function_exists('wst_dod_log_error')) {
    function wst_dod_log_error($message) {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('[VegeBasketDO DO List] ' . $message);
        }
    }
}

if (!function_exists('wst_dod_valid_date')) {
    function wst_dod_valid_date($value, $fallback) {
        $value = trim((string)$value);
        if ($value === '') return $fallback;

        $dt = DateTime::createFromFormat('Y-m-d', $value, wp_timezone());
        if (!$dt || $dt->format('Y-m-d') !== $value) return $fallback;

        return $value;
    }
}

if (!function_exists('wst_dod_date')) {
    function wst_dod_date($v) {
        if ($v instanceof DateTime) return $v->format('Y-m-d');

        if (is_string($v) && $v !== '') {
            $ts = strtotime($v);
            if ($ts) return date('Y-m-d', $ts);
            return $v;
        }

        return '';
    }
}

if (!function_exists('wst_dod_datetime')) {
    function wst_dod_datetime($v) {
        if ($v instanceof DateTime) return $v->format('Y-m-d H:i:s');

        if (is_string($v) && $v !== '') {
            $v = trim($v);
            if ($v === '' || $v === '0000-00-00 00:00:00') return '';
            $ts = strtotime($v);
            if ($ts) return date('Y-m-d H:i:s', $ts);
            return $v;
        }

        return '';
    }
}

if (!function_exists('wst_dod_fmt_qty')) {
    function wst_dod_fmt_qty($v, $decimals = 2) {
        $n = (float)$v;

        if (abs($n - round($n)) < 0.00001) {
            return number_format_i18n($n, 0);
        }

        return number_format_i18n($n, $decimals);
    }
}

if (!function_exists('wst_dod_fmt_weight')) {
    function wst_dod_fmt_weight($v) {
        return number_format_i18n((float)$v, 2);
    }
}

if (!function_exists('wst_dod_read_json_array')) {
    function wst_dod_read_json_array($json) {
        $data = json_decode((string)$json, true);
        return is_array($data) ? $data : array();
    }
}

if (!function_exists('wst_dod_pick_payload_value')) {
    function wst_dod_pick_payload_value($payload, $keys, $fallback = '') {
        if (!is_array($payload)) return $fallback;

        foreach ((array)$keys as $key) {
            if (isset($payload[$key]) && trim((string)$payload[$key]) !== '') {
                return trim((string)$payload[$key]);
            }
        }

        return $fallback;
    }
}

if (!function_exists('wst_dod_pick_payload_any')) {
    function wst_dod_pick_payload_any($payload, $keys, $fallback = null) {
        if (!is_array($payload)) return $fallback;

        foreach ((array)$keys as $key) {
            if (array_key_exists($key, $payload) && $payload[$key] !== '' && $payload[$key] !== null) {
                return $payload[$key];
            }
        }

        return $fallback;
    }
}

if (!function_exists('wst_dod_doc_key_from_data')) {
    function wst_dod_doc_key_from_data($data) {
        if (!is_array($data)) return 0;

        foreach (array('docKey', 'DocKey', 'dockey', 'doc_key', 'sourceDocKey') as $key) {
            if (isset($data[$key]) && is_numeric($data[$key])) {
                return (int)$data[$key];
            }
        }

        return 0;
    }
}

if (!function_exists('wst_dod_doc_no_from_data')) {
    function wst_dod_doc_no_from_data($data) {
        if (!is_array($data)) return '';

        foreach (array('docNo', 'DocNo', 'docno', 'doc_no', 'sourceDocNo', 'oldDocNo', 'originalDocNo') as $key) {
            if (!empty($data[$key])) {
                return strtoupper(trim((string)$data[$key]));
            }
        }

        return '';
    }
}

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

        $labels = array(
            'PENDING' => 'Pending AutoCount',
            'PROCESSING' => 'Processing AutoCount',
            'SUCCESS' => 'Created',
            'FAILED' => 'AutoCount Failed',
            'FAILED_FINAL' => 'AutoCount Failed',
            'PENDING_DELIVERY' => 'Pending Delivery',
            'ASSIGNED' => 'Assigned',
            'SCHEDULED' => 'Scheduled',
            'DRIVER_ACKNOWLEDGED' => 'Driver Received',
            'RECEIVED' => 'Driver Received',
            'OUT_FOR_DELIVERY' => 'Out for Delivery',
            'DELIVERED' => 'Delivered',
            'EDIT_PENDING_AUTOCOUNT' => 'Edit Pending',
            'EDITED_IN_AUTOCOUNT' => 'Edited',
            'VOID_PENDING_AUTOCOUNT' => 'Void Pending',
            'VOID_FAILED' => 'Void Failed',
            'VOIDED_IN_AUTOCOUNT' => 'Voided',
            'CANCELLED' => 'Cancelled',
            'ACTIVE' => 'Active',
            'HIDDEN' => 'Hidden',
            'UNASSIGNED' => 'Unassigned',
        );

        return $labels[$value] ?? ($value !== '' ? ucwords(strtolower(str_replace('_', ' ', $value))) : '-');
    }
}

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

        if (in_array($value, array('DELIVERED', 'SUCCESS', 'EDITED_IN_AUTOCOUNT', 'ACTIVE'), true)) {
            return 'wst-dod-badge-good';
        }

        if (in_array($value, array('FAILED', 'FAILED_FINAL', 'VOID_FAILED', 'CANCELLED', 'HIDDEN'), true)) {
            return 'wst-dod-badge-danger';
        }

        if (in_array($value, array('PENDING', 'PROCESSING', 'EDIT_PENDING_AUTOCOUNT', 'VOID_PENDING_AUTOCOUNT', 'SCHEDULED'), true)) {
            return 'wst-dod-badge-warn';
        }

        return 'wst-dod-badge-info';
    }
}

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

        $help = array(
            'PENDING' => 'Order is waiting for AutoCount bridge processing.',
            'PROCESSING' => 'AutoCount bridge is currently processing this order.',
            'SUCCESS' => 'Order was created successfully in AutoCount.',
            'FAILED' => 'AutoCount bridge failed to create or update this order.',
            'FAILED_FINAL' => 'AutoCount bridge failed after all retries.',
            'PENDING_DELIVERY' => 'Order exists but has not been assigned to a driver yet.',
            'ASSIGNED' => 'Order has been assigned to a driver.',
            'SCHEDULED' => 'This delivery order is scheduled for a future date.',
            'DRIVER_ACKNOWLEDGED' => 'Driver confirmed receiving the delivery list or goods.',
            'RECEIVED' => 'Driver confirmed receiving the delivery list or goods.',
            'OUT_FOR_DELIVERY' => 'Driver is currently delivering this order.',
            'DELIVERED' => 'Driver marked this order as delivered.',
            'EDIT_PENDING_AUTOCOUNT' => 'Staff edited this order and the AutoCount update is still pending.',
            'EDITED_IN_AUTOCOUNT' => 'The edited order was updated successfully in AutoCount.',
            'VOID_PENDING_AUTOCOUNT' => 'This record was deleted from the normal staff list and its AutoCount void request is waiting for the bridge.',
            'VOID_FAILED' => 'AutoCount did not confirm the void. The record remains hidden from normal staff and can be reviewed from the hidden-record recovery view.',
            'VOIDED_IN_AUTOCOUNT' => 'AutoCount confirmed that this delivery order was voided.',
            'CANCELLED' => 'This delivery order was cancelled.',
            'ACTIVE' => 'This delivery order is active in AutoCount.',
            'HIDDEN' => 'This row is hidden from normal staff.',
        );

        return $help[$value] ?? 'Current delivery order status.';
    }
}

if (!function_exists('wst_dod_wp_table_exists')) {
    function wst_dod_wp_table_exists($table_name) {
        global $wpdb;
        if (!$wpdb) return false;

        return $wpdb->get_var(
            $wpdb->prepare('SHOW TABLES LIKE %s', $table_name)
        ) === $table_name;
    }
}

if (!function_exists('wst_dod_wp_table_columns')) {
    function wst_dod_wp_table_columns($table_name) {
        global $wpdb;

        static $cache = array();
        if (!$wpdb) return array();

        $refresh = false;
        if (substr($table_name, -9) === '__refresh') {
            $refresh = true;
            $table_name = substr($table_name, 0, -9);
        }

        if (!$refresh && isset($cache[$table_name])) {
            return $cache[$table_name];
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table_name);
        $cols = $wpdb->get_col("SHOW COLUMNS FROM `{$safe_table}`", 0);

        $cache[$table_name] = is_array($cols) ? array_flip($cols) : array();

        return $cache[$table_name];
    }
}

if (!function_exists('wst_dod_is_administrator')) {
    function wst_dod_is_administrator() {
        $user = wp_get_current_user();

        return in_array('administrator', (array)($user->roles ?? array()), true);
    }
}

if (!function_exists('wst_dod_job_soft_delete_available')) {
    function wst_dod_job_soft_delete_available() {
        global $wpdb;
        if (!$wpdb) return false;

        $table = $wpdb->prefix . 'ac_do';
        if (!wst_dod_wp_table_exists($table)) return false;

        $cols = wst_dod_wp_table_columns($table);

        return isset($cols['hidden_from_staff_list']);
    }
}

if (!function_exists('wst_dod_redirect_with_notice')) {
    function wst_dod_redirect_with_notice($type, $message) {
        $request_uri = isset($_SERVER['REQUEST_URI'])
            ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI']))
            : '/';

        $redirect_url = home_url($request_uri);

        $redirect_url = remove_query_arg(
            array('wst_dod_notice_type', 'wst_dod_notice', 'wst_dod_row_action', 'job_id', 'do_id', 'wst_dod_row_nonce'),
            $redirect_url
        );

        $redirect_url = add_query_arg(
            array(
                'wst_dod_notice_type' => sanitize_key($type),
                'wst_dod_notice' => (string)$message,
            ),
            $redirect_url
        );

        wp_safe_redirect($redirect_url);
        exit;
    }
}

if (!function_exists('wst_dod_notice_from_query')) {
    function wst_dod_notice_from_query() {
        $type = isset($_GET['wst_dod_notice_type'])
            ? sanitize_key(wp_unslash($_GET['wst_dod_notice_type']))
            : '';

        $message = isset($_GET['wst_dod_notice'])
            ? rawurldecode((string)wp_unslash($_GET['wst_dod_notice']))
            : '';

        $message = trim($message);

        if ($message === '') {
            return '';
        }

        $class = $type === 'error'
            ? 'wst-dod-alert-error'
            : ($type === 'warning' ? 'wst-dod-alert-warning' : 'wst-dod-alert-success');

        return '<div class="wst-dod-alert ' . esc_attr($class) . '">' . esc_html($message) . '</div>';
    }
}

if (!function_exists('wst_dod_get_job_label')) {
    function wst_dod_get_job_label($job_id) {
        global $wpdb;

        $do_id = (int)$job_id;
        if (!$wpdb || $do_id <= 0) return 'DO-' . $do_id;

        $table = $wpdb->prefix . 'ac_do';
        if (!wst_dod_wp_table_exists($table)) return 'DO-' . $do_id;

        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT local_doc_no, autocount_doc_no
                 FROM `{$table}`
                 WHERE id = %d
                 LIMIT 1",
                $do_id
            ),
            ARRAY_A
        );

        if (!$row) return 'DO-' . $do_id;

        $doc_label = strtoupper(trim((string)($row['local_doc_no'] ?? '')));
        if ($doc_label === '') {
            $doc_label = strtoupper(trim((string)($row['autocount_doc_no'] ?? '')));
        }

        return $doc_label !== '' ? $doc_label : 'DO-' . $do_id;
    }
}

if (!function_exists('wst_dod_handle_soft_delete_action')) {
    function wst_dod_handle_soft_delete_action() {
        global $wpdb;

        if (!$wpdb || strtoupper((string)($_SERVER['REQUEST_METHOD'] ?? '')) !== 'POST') {
            return '';
        }

        $posted_action = isset($_POST['wst_dod_row_action'])
            ? sanitize_key(wp_unslash($_POST['wst_dod_row_action']))
            : '';

        if ($posted_action === '') {
            return '';
        }

        if (!current_user_can('edit_posts') && !current_user_can('manage_options')) {
            wst_dod_redirect_with_notice('error', 'You do not have permission to update this row.');
        }

        $do_id = isset($_POST['do_id']) ? absint($_POST['do_id']) : (isset($_POST['job_id']) ? absint($_POST['job_id']) : 0);

        if ($do_id <= 0) {
            wst_dod_redirect_with_notice('error', 'This row cannot be updated because it has no local Delivery Order record.');
        }

        if (
            !isset($_POST['wst_dod_row_nonce'])
            || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['wst_dod_row_nonce'])), 'wst_dod_row_action_' . $do_id)
        ) {
            wst_dod_redirect_with_notice('error', 'Security check failed. Please refresh and try again.');
        }

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

        if (!wst_dod_wp_table_exists($table)) {
            wst_dod_redirect_with_notice('error', 'Local Delivery Order table is not available.');
        }

        if (!wst_dod_job_soft_delete_available()) {
            wst_dod_redirect_with_notice('error', 'Soft delete columns are not available on the local Delivery Order table. Please add the hidden_from_staff_list column first.');
        }

        $cols = wst_dod_wp_table_columns($table);
        $row = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT *
                 FROM `{$table}`
                 WHERE id = %d
                 LIMIT 1",
                $do_id
            ),
            ARRAY_A
        );

        if (!$row) {
            wst_dod_redirect_with_notice('error', 'The local Delivery Order record could not be found.');
        }

        $job_label = wst_dod_get_job_label($do_id);

        if ($posted_action === 'delete' || $posted_action === 'void') {
            if (!empty($row['hidden_from_staff_list'])) {
                wst_dod_redirect_with_notice('warning', $job_label . ' is already hidden.');
            }

            $sync_status = strtoupper(trim((string)($row['sync_status'] ?? '')));
            if ($sync_status === 'VOID_PENDING_AUTOCOUNT') {
                wst_dod_redirect_with_notice('warning', $job_label . ' already has a pending AutoCount void request.');
            }

            $autocount_doc_no = strtoupper(trim((string)($row['autocount_doc_no'] ?? '')));
            $autocount_S;�����-���������S;�
N?��doc_key = (int)($row['autocount_doc_key'] ?? 0);
            $local_doc_no = strtoupper(trim((string)($row['local_doc_no'] ?? '')));

            if ($autocount_doc_no === '' && $autocount_doc_key <= 0) {
                wst_dod_redirect_with_notice(
                    'error',
                    $job_label . ' cannot be deleted because it has no confirmed AutoCount document number or key.'
                );
            }

            $payload = array(
                'action' => 'void',
                'documentType' => 'DELIVERY_ORDER',
                'manifestVersion' => 1,
                'docNo' => $autocount_doc_no,
                'docKey' => $autocount_doc_key,
                'localDoId' => $do_id,
                'localDocNo' => $local_doc_no,
                'previousDeliveryStatus' => strtoupper(trim((string)($row['delivery_status'] ?? ''))),
                'previousSyncStatus' => $sync_status,
                'hideReason' => 'Deleted from staff list; AutoCount void confirmed',
                'hideImmediately' => true,
            );

            $request = new WP_REST_Request('POST', '/ac/v1/job');
            $request->set_header('Content-Type', 'application/json');
            $request->set_body(wp_json_encode(array(
                'type' => 'DELIVERY_ORDER',
                'subtype' => 'VOID',
                'priority' => 10,
                'source' => 'staff_do_delete',
                'client_request_id' => 'do-delete-' . $do_id . '-' . wp_generate_uuid4(),
                'payload' => $payload,
            )));

            $response = rest_do_request($request);
            if (is_wp_error($response)) {
                wst_dod_log_error('Void enqueue failed for local DO ' . $do_id . ': ' . $response->get_error_message());
                wst_dod_redirect_with_notice('error', 'Could not delete this record because the AutoCount void request was not queued: ' . $response->get_error_message());
            }

            $response_data = $response->get_data();
            $response_code = (int)$response->get_status();

            if (
                $response_code < 200
                || $response_code >= 300
                || !is_array($response_data)
                || empty($response_data['ok'])
            ) {
                $message = is_array($response_data) && !empty($response_data['message'])
                    ? (string)$response_data['message']
                    : 'Unknown bridge queue error.';

                wst_dod_log_error('Void enqueue failed for local DO ' . $do_id . ': ' . $message);
                wst_dod_redirect_with_notice('error', 'Could not delete this record because the AutoCount void request was not queued: ' . $message);
            }

            $queued_job_id = (int)($response_data['jobId'] ?? 0);
            $suffix = $queued_job_id > 0 ? ' as bridge job #' . $queued_job_id : '';

            /*
             * The bridge job is safely queued first. Only after the queue
             * accepts it do we hide the WordPress row immediately.
             *
             * This is a soft delete: the database record remains available to
             * recovery users and the AutoCount action remains VOID/CANCEL.
             */
            $delete_update = array(
                'hidden_from_staff_list' => 1,
            );
            $delete_formats = array('%d');

            if (isset($cols['hidden_reason'])) {
                $delete_update['hidden_reason'] = 'Deleted from staff list; AutoCount void queued';
                $delete_formats[] = '%s';
            }

            if (isset($cols['hidden_at'])) {
                $delete_update['hidden_at'] = current_time('mysql');
                $delete_formats[] = '%s';
            }

            if (isset($cols['hidden_by'])) {
                $delete_update['hidden_by'] = get_current_user_id();
                $delete_formats[] = '%d';
            }

            if (isset($cols['updated_at'])) {
                $delete_update['updated_at'] = current_time('mysql');
                $delete_formats[] = '%s';
            }

            $deleted = $wpdb->update(
                $table,
                $delete_update,
                array('id' => $do_id),
                $delete_formats,
                array('%d')
            );

            if ($deleted === false) {
                wst_dod_log_error(
                    'Delete hide failed after AutoCount void job was queued for local DO ' .
                    $do_id . ': ' . $wpdb->last_error
                );

                wst_dod_redirect_with_notice(
                    'error',
                    $job_label . ' AutoCount void was queued' . $suffix .
                    ', but WordPress could not hide the row. Please contact an administrator.'
                );
            }

            wst_dod_redirect_with_notice(
                'success',
                $job_label . ' was deleted from the staff list. AutoCount void was queued' . $suffix . '.'
            );
        }

        if ($posted_action === 'activate') {
            if (!wst_dod_is_administrator()) {
                wst_dod_redirect_with_notice('error', 'Only an Administrator can show hidden rows.');
            }

            $update = array('hidden_from_staff_list' => 0);
            $formats = array('%d');

            if (isset($cols['hidden_reason'])) {
                $update['hidden_reason'] = null;
                $formats[] = '%s';
            }

            if (isset($cols['hidden_at'])) {
                $update['hidden_at'] = null;
                $formats[] = '%s';
            }

            if (isset($cols['hidden_by'])) {
                $update['hidden_by'] = null;
                $formats[] = '%d';
            }

            if (isset($cols['updated_at'])) {
                $update['updated_at'] = current_time('mysql');
                $formats[] = '%s';
            }

            $ok = $wpdb->update(
                $table,
                $update,
                array('id' => $do_id),
                $formats,
                array('%d')
            );

            if ($ok === false) {
                wst_dod_log_error('Show hidden row failed for local DO ' . $do_id . ': ' . $wpdb->last_error);
                wst_dod_redirect_with_notice('error', 'Could not show this row. Please try again.');
            }

            wst_dod_redirect_with_notice(
                'success',
                $job_label . ' is shown again in WordPress. This does not reactivate a document that is already voided in AutoCount.'
            );
        }

        wst_dod_redirect_with_notice('error', 'Unknown row action.');
    }
}

if (!function_exists('wst_dod_get_proof_image_by_doc')) {
    function wst_dod_get_proof_image_by_doc($docNo, $docKey) {
        global $wpdb;

        if (!$wpdb) return '';

        $table = $wpdb->prefix . 'ac_do_proof_images';
        if (!wst_dod_wp_table_exists($table)) return '';

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $cols = wst_dod_wp_table_columns($table);

        $orWhere = array();
        $args = array();

        $docNo = trim((string)$docNo);
        $docKey = (int)$docKey;

        if ($docNo !== '' && isset($cols['doc_no'])) {
            $orWhere[] = 'doc_no = %s';
            $args[] = $docNo;
        }

        if ($docKey > 0 && isset($cols['doc_key'])) {
            $orWhere[] = 'doc_key = %d';
            $args[] = $docKey;
        }

        if (empty($orWhere) || !isset($cols['image_url'])) return '';

        $whereSql = '(' . implode(' OR ', $orWhere) . ')';

        if (isset($cols['proof_type'])) {
            $whereSql .= ' AND proof_type = %s';
            $args[] = 'DELIVERY_PROOF';
        }

        if (isset($cols['deleted_at'])) {
            $whereSql .= ' AND deleted_at IS NULL';
        }

        $orderCol = isset($cols['id']) ? 'id' : (isset($cols['captured_at']) ? 'captured_at' : 'image_url');

        $sql = "
            SELECT image_url
            FROM `{$safe_table}`
            WHERE {$whereSql}
            ORDER BY `{$orderCol}` DESC
            LIMIT 1
        ";

        $url = $wpdb->get_var($wpdb->prepare($sql, $args));

        return $url ? esc_url_raw((string)$url) : '';
    }
}

if (!function_exists('wst_dod_driver_label_from_job')) {
    function wst_dod_driver_label_from_job($job, $payload) {
        $driver_id = (int)($job['assigned_driver_id'] ?? 0);

        if ($driver_id > 0) {
            $user = get_userdata($driver_id);
            if ($user) {
                $display = trim((string)$user->display_name);
                return $display !== '' ? $display : (string)$user->user_login;
            }
        }

        foreach (array('assignedDriverName', 'driverName', 'driver_name', 'assignedDriverLogin', 'driverLogin', 'driver_login', 'assignedDriver', 'assigned_driver', 'driver') as $key) {
            if (!empty($payload[$key])) {
                return trim((string)$payload[$key]);
            }
        }

        if (!empty($job['assigned_driver'])) {
            return trim((string)$job['assigned_driver']);
        }

        return '';
    }
}

if (!function_exists('wst_dod_is_goods_receive_row')) {
    function wst_dod_is_goods_receive_row($row) {
        $doc_values = array(
            $row['local_doc_no'] ?? '',
            $row['autocount_doc_no'] ?? '',
        );

        foreach ($doc_values as $doc_no) {
            $doc_no = strtoupper(trim((string)$doc_no));
            if ($doc_no !== '' && (strpos($doc_no, 'WPGR') === 0 || strpos($doc_no, 'GRN') === 0)) {
                return true;
            }
        }

        return false;
    }
}

if (!function_exists('wst_dod_calc_status')) {
    function wst_dod_calc_status($syncStatus, $deliveryStatus, $docDate, $hidden = false, $isGoodsReceive = false) {
        $syncStatus = strtoupper(trim((string)$syncStatus));
        $deliveryStatus = strtoupper(trim((string)$deliveryStatus));
        $docDate = trim((string)$docDate);

        if ($hidden) return 'HIDDEN';

        if (!$isGoodsReceive) {
            if ($syncStatus === 'VOID_PENDING_AUTOCOUNT') return 'VOID_PENDING_AUTOCOUNT';
            if ($syncStatus === 'VOID_FAILED') return 'VOID_FAILED';
            if ($syncStatus === 'VOIDED_IN_AUTOCOUNT') return 'CANCELLED';
        }

        if ($isGoodsReceive) {
            return $syncStatus !== '' ? $syncStatus : 'ACTIVE';
        }

        if ($deliveryStatus === 'DELIVERED') return 'DELIVERED';
        if ($deliveryStatus === 'CANCELLED') return 'CANCELLED';

        $today = current_time('Y-m-d');
        if ($docDate !== '' && $docDate > $today) return 'SCHEDULED';

        return 'OUT_FOR_DELIVERY';
    }
}

if (!function_exists('wst_dod_item_from_payload_line')) {
    function wst_dod_item_from_payload_line($line) {
        $line = is_array($line) ? $line : array();

        return array(
            'itemCode' => wst_dod_pick_payload_value($line, array('itemCode', 'ItemCode', 'item_code', 'code'), ''),
            'name' => wst_dod_pick_payload_value($line, array('description', 'Description', 'description1', 'itemName', 'item_name', 'name'), ''),
            'qty' => (float)wst_dod_pick_payload_any($line, array('qty', 'Qty', 'quantity'), 0),
            'basket' => (float)wst_dod_pick_payload_any($line, array('basketQty', 'basket_qty', 'basket', 'Basket', 'bsk', 'UDF_BASKET'), 0),
            'carton' => (float)wst_dod_pick_payload_any($line, array('cartonQty', 'carton_qty', 'carton', 'Carton', 'ctn', 'UDF_CARTON'), 0),
            'weightKg' => (float)wst_dod_pick_payload_any($line, array('kg', 'weight', 'weightKg', 'WeightKG', 'weight_kg', 'UDF_WEIGHTKG'), 0),
        );
    }
}

if (!function_exists('wst_dod_can_staff_edit_row')) {
    function wst_dod_can_staff_edit_row($row) {
        if (!empty($row['isGoodsReceive'])) return false;
        if (trim((string)($row['docNo'] ?? '')) === '') return false;

        return true;
    }
}

if (!function_exists('wst_dod_staff_edit_disabled_reason')) {
    function wst_dod_staff_edit_disabled_reason($row) {
        if (trim((string)($row['docNo'] ?? '')) === '') {
            return 'This order is missing a document number.';
        }

        return 'This order cannot be edited because it is a Goods Receive record.';
    }
}

if (!function_exists('wst_dod_load_mysql_rows')) {
    function wst_dod_load_mysql_rows($customer = '', $status = 'ALL', $dateFrom = '', $dateTo = '', $limit = 25, $includeHidden = false) {
        global $wpdb;

        $doTable = $wpdb->prefix . 'ac_do';
        $itemTable = $wpdb->prefix . 'ac_do_items';

        if (!wst_dod_wp_table_exists($doTable)) {
            return array('rows' => array(), 'error' => 'Local Delivery Order table is not available: ' . $doTable);
        }
        if (!wst_dod_wp_table_exists($itemTable)) {
            return array('rows' => array(), 'error' => 'Local Delivery Order item table is not available: ' . $itemTable);
        }

        $safeDoTable = preg_replace('/[^A-Za-z0-9_]/', '', $doTable);
        $safeItemTable = preg_replace('/[^A-Za-z0-9_]/', '', $itemTable);
        $doCols = wst_dod_wp_table_columns($doTable);

        $where = array('1=1');
        $params = array();

        if (isset($doCols['deleted_at'])) {
            $where[] = 'deleted_at IS NULL';
        }

        if (!$includeHidden && isset($doCols['hidden_from_staff_list'])) {
            $where[] = 'hidden_from_staff_list = 0';
        }

        if ($customer !== '') {
            $like = '%' . $wpdb->esc_like($customer) . '%';
            $customerParts = array('local_doc_no LIKE %s', 'debtor_code LIKE %s', 'debtor_name LIKE %s');
            $params[] = $like;
            $params[] = $like;
            $params[] = $like;

            if (isset($doCols['autocount_doc_no'])) {
                $customerParts[] = 'autocount_doc_no LIKE %s';
                $params[] = $like;
            }

            $where[] = '(' . implode(' OR ', $customerParts) . ')';
        }

        if ($dateFrom !== '') {
            $where[] = 'doc_date >= %s';
            $params[] = $dateFrom;
        }
        if ($dateTo !== '') {
            $where[] = 'doc_date <= %s';
            $params[] = $dateTo;
        }

        $sql = "SELECT *
                FROM `{$safeDoTable}`
                WHERE " . implode(' AND ', $where) . "
                ORDER BY doc_date DESC, updated_at DESC, id DESC
                LIMIT 1000";

        if (!empty($params)) {
            $sql = $wpdb->prepare($sql, $params);
        }

        $doRows = $wpdb->get_results($sql, ARRAY_A);
        if ($wpdb->last_error) {
            wst_dod_log_error('Local DO load failed: ' . $wpdb->last_error);
            return array('rows' => array(), 'error' => $wpdb->last_error);
        }

        $ids = array();
        foreach ((array)$doRows as $row) {
            $id = (int)($row['id'] ?? 0);
            if ($id > 0) $ids[] = $id;
        }

        $itemsByDo = array();
        if (!empty($ids)) {
            $placeholders = implode(',', array_fill(0, count($ids), '%d'));
            $itemSql = "SELECT * FROM `{$safeItemTable}` WHERE do_id IN ({$placeholders}) ORDER BY do_id ASC, line_no ASC, id ASC";
            $itemRows = $wpdb->get_results($wpdb->prepare($itemSql, $ids), ARRAY_A);

            if ($wpdb->last_error) {
                wst_dod_log_error('Local DO item load failed: ' . $wpdb->last_error);
                return array('rows' => array(), 'error' => $wpdb->last_error);
            }

            foreach ((array)$itemRows as $item) {
                $doId = (int)($item['do_id'] ?? 0);
                if ($doId <= 0) continue;

                $itemsByDo[$doId][] = array(
                    'itemCode' => (string)($item['item_code'] ?? ''),
                    'name' => (string)($item['description'] ?? ''),
       S;�Sc6���������S<'
N?��             'qty' => (float)($item['qty'] ?? 0),
                    'basket' => (float)($item['basket_qty'] ?? 0),
                    'carton' => (float)($item['carton_qty'] ?? 0),
                    'weightKg' => (float)($item['weight_kg'] ?? 0),
                );
            }
        }

        $out = array();
        $wantedStatus = strtoupper(trim((string)$status));

        foreach ((array)$doRows as $do) {
            $doId = (int)($do['id'] ?? 0);
            if ($doId <= 0) continue;

            $docDate = wst_dod_date($do['doc_date'] ?? '');
            $hidden = !empty($do['hidden_from_staff_list']);
            $syncStatus = strtoupper(trim((string)($do['sync_status'] ?? '')));
            $deliveryStatus = strtoupper(trim((string)($do['delivery_status'] ?? '')));
            $isGoodsReceive = wst_dod_is_goods_receive_row($do);
            $displayStatus = wst_dod_calc_status($syncStatus, $deliveryStatus, $docDate, $hidden, $isGoodsReceive);

            if ($wantedStatus !== 'ALL' && $displayStatus !== $wantedStatus) continue;

            $driver = '';
            $driverId = (int)($do['assigned_driver_id'] ?? 0);
            if ($driverId > 0) {
                $driverUser = get_userdata($driverId);
                if ($driverUser) {
                    $driver = trim((string)$driverUser->display_name);
                    if ($driver === '') $driver = trim((string)$driverUser->user_login);
                }
            }
            if ($driver === '') $driver = $isGoodsReceive ? '-' : 'UNASSIGNED';

            $items = $itemsByDo[$doId] ?? array();
            $totalBasket = 0.0;
            $totalCarton = 0.0;
            foreach ($items as $line) {
                $totalBasket += (float)($line['basket'] ?? 0);
                $totalCarton += (float)($line['carton'] ?? 0);
            }

            $docNo = strtoupper(trim((string)($do['local_doc_no'] ?? '')));
            $autoDocNo = strtoupper(trim((string)($do['autocount_doc_no'] ?? '')));
            $docKey = (int)($do['autocount_doc_key'] ?? 0);

            $out[] = array(
                'docKey' => $docKey,
                'docNo' => $docNo !== '' ? $docNo : $autoDocNo,
                'autoCountDocNo' => $autoDocNo,
                'docDate' => $docDate,
                'debtorCode' => (string)($do['debtor_code'] ?? ''),
                'debtorName' => (string)($do['debtor_name'] ?? ''),
                'autoCountStatus' => $syncStatus,
                'displayStatus' => $displayStatus,
                'driver' => $driver,
                'isGoodsReceive' => $isGoodsReceive,
                'documentTypeLabel' => $isGoodsReceive ? 'Goods Receive' : 'Delivery Order',
                'partyLabel' => $isGoodsReceive ? 'Supplier' : 'Customer',
                'jobId' => $doId,
                'doId' => $doId,
                'jobSubtype' => '',
                'syncStatus' => $syncStatus,
                'deliveryStatus' => $deliveryStatus,
                'jobError' => (string)($do['last_sync_error'] ?? ''),
                'createdAt' => wst_dod_datetime($do['created_at'] ?? ''),
                'lastModified' => wst_dod_datetime($do['updated_at'] ?? ''),
                'totalBasket' => $totalBasket,
                'totalCarton' => $totalCarton,
                'items' => $items,
                'proofImage' => wst_dod_get_proof_image_by_doc(($autoDocNo !== '' ? $autoDocNo : $docNo), $docKey),
                'hasAuthoritativeJob' => true,
                'isPendingJob' => false,
                'hiddenFromStaffList' => (int)($do['hidden_from_staff_list'] ?? 0),
                'hiddenReason' => (string)($do['hidden_reason'] ?? ''),
                'hiddenAt' => (string)($do['hidden_at'] ?? ''),
                'hiddenBy' => (int)($do['hidden_by'] ?? 0),
            );
        }

        $limit = (int)$limit;
        if ($limit > 0) {
            $out = array_slice($out, 0, $limit);
        }

        return array('rows' => $out, 'error' => '');
    }
}

$isAdministrator = wst_dod_is_administrator();
wst_dod_handle_soft_delete_action();
$wst_dod_action_notice = wst_dod_notice_from_query();

$customer = isset($_GET['customer']) ? trim(sanitize_text_field(wp_unslash($_GET['customer']))) : '';
$status = isset($_GET['status']) ? strtoupper(trim(sanitize_text_field(wp_unslash($_GET['status'])))) : 'ALL';
$limit_input = isset($_GET['limit']) ? (int)$_GET['limit'] : 25;
$allowed_limits = array(25, 50, 100);
$limit = in_array($limit_input, $allowed_limits, true) ? $limit_input : 25;

$status_options = array(
    'ALL' => 'All',
    'SCHEDULED' => 'Scheduled',
    'OUT_FOR_DELIVERY' => 'Out for Delivery',
    'DELIVERED' => 'Delivered',
);

if ($isAdministrator) {
    $status_options['HIDDEN'] = 'Hidden';
}

if (!isset($status_options[$status])) {
    $status = 'ALL';
}

$todayObj = new DateTime('now', wp_timezone());
$defaultDateTo = $todayObj->format('Y-m-d');

$fromObj = clone $todayObj;
$fromObj->modify('-1 month');
$defaultDateFrom = $fromObj->format('Y-m-d');

$dateFromRaw = isset($_GET['dateFrom']) ? wp_unslash($_GET['dateFrom']) : '';
$dateToRaw = isset($_GET['dateTo']) ? wp_unslash($_GET['dateTo']) : '';
$dateFrom = wst_dod_valid_date(sanitize_text_field($dateFromRaw), $defaultDateFrom);
$dateTo = wst_dod_valid_date(sanitize_text_field($dateToRaw), $defaultDateTo);

/*
 * Hidden / inactive row visibility:
 * - Non-administrators never see hidden rows, the Hidden status option, or the toggle.
 * - Administrators see hidden rows by default.
 * - Administrators can toggle hidden rows off using show_hidden=0.
 */
$showHiddenRows = false;
if ($isAdministrator) {
    $showHiddenRaw = isset($_GET['show_hidden'])
        ? sanitize_text_field(wp_unslash($_GET['show_hidden']))
        : '1';

    $showHiddenRows = ($showHiddenRaw !== '0');
}

$loadResult = wst_dod_load_mysql_rows($customer, $status, $dateFrom, $dateTo, $limit, $showHiddenRows);
$rows = $loadResult['rows'];
$loadWarning = $loadResult['error'];

$hiddenToggleUrl = '';
$hiddenToggleLabel = '';
if ($isAdministrator) {
    $hiddenToggleUrl = add_query_arg(
        array(
            'customer' => $customer,
            'status' => $status,
            'dateFrom' => $dateFrom,
            'dateTo' => $dateTo,
            'limit' => $limit,
            'show_hidden' => $showHiddenRows ? '0' : '1',
        ),
        get_permalink()
    );

    $hiddenToggleLabel = $showHiddenRows ? 'Hide Hidden' : 'Show Hidden';
}

$clearFilterUrl = get_permalink();
if ($isAdministrator) {
    $clearFilterUrl = add_query_arg(
        'show_hidden',
        $showHiddenRows ? '1' : '0',
        $clearFilterUrl
    );
}
?>

<div class="wst-dod-wrap">
    <?php echo $wst_dod_action_notice; ?>

    <?php if ($loadWarning !== ''): ?>
        <div class="wst-dod-alert wst-dod-alert-error">
            Failed to load Delivery Order records.
            <?php if ($show_technical_errors): ?>
                <?php echo esc_html($loadWarning); ?>
            <?php endif; ?>
        </div>
    <?php endif; ?>

    <div class="wst-dod-filter-card">
        <form method="get" class="wst-dod-form">
            <?php if ($isAdministrator): ?>
                <input type="hidden" name="show_hidden" value="<?php echo esc_attr($showHiddenRows ? '1' : '0'); ?>">
            <?php endif; ?>

            <div class="wst-dod-filter-main">
                <div class="wst-dod-field wst-dod-search-field">
                    <label class="wst-dod-label" for="wstDodCustomer">Customer / Supplier / Doc No</label>
                    <input id="wstDodCustomer" name="customer" class="wst-dod-input" type="search" value="<?php echo esc_attr($customer); ?>" placeholder="Search customer, supplier, DO or GRN no..." autocomplete="off">
                </div>

                <div class="wst-dod-filter-actions">
                    <button type="submit" class="wst-dod-btn wst-dod-btn-primary">Search</button>
                    <a class="wst-dod-btn wst-dod-btn-secondary" href="<?php echo esc_url($clearFilterUrl); ?>" aria-label="Clear search filters">Clear</a>
                </div>
            </div>

            <div class="wst-dod-filter-grid">
                <div class="wst-dod-field">
                    <label class="wst-dod-label" for="wstDodStatus">Status</label>
                    <select id="wstDodStatus" name="status" class="wst-dod-input">
                        <?php foreach ($status_options as $status_value => $status_label): ?>
                            <option value="<?php echo esc_attr($status_value); ?>" <?php selected($status, $status_value); ?>>
                                <?php echo esc_html($status_label); ?>
                            </option>
                        <?php endforeach; ?>
                    </select>
                </div>

                <div class="wst-dod-field">
                    <label class="wst-dod-label" for="wstDodDateFrom">From</label>
                    <input id="wstDodDateFrom" name="dateFrom" class="wst-dod-input" type="date" value="<?php echo esc_attr($dateFrom); ?>">
                </div>

                <div class="wst-dod-field">
                    <label class="wst-dod-label" for="wstDodDateTo">To</label>
                    <input id="wstDodDateTo" name="dateTo" class="wst-dod-input" type="date" value="<?php echo esc_attr($dateTo); ?>">
                </div>

                <div class="wst-dod-field">
                    <label class="wst-dod-label" for="wstDodLimit">Rows</label>
                    <select id="wstDodLimit" name="limit" class="wst-dod-input">
                        <?php foreach ($allowed_limits as $allowed_limit): ?>
                            <option value="<?php echo esc_attr($allowed_limit); ?>" <?php selected($limit, $allowed_limit); ?>>
                                <?php echo esc_html($allowed_limit); ?>
                            </option>
                        <?php endforeach; ?>
                    </select>
                </div>
            </div>
        </form>
    </div>

    <div class="wst-dod-summary">
        <span>Showing <?php echo esc_html(number_format_i18n(count($rows))); ?> rows</span>

        <?php if ($isAdministrator): ?>
            <a class="wst-dod-hidden-toggle" href="<?php echo esc_url($hiddenToggleUrl); ?>">
                <?php echo esc_html($hiddenToggleLabel); ?>
            </a>
        <?php endif; ?>
    </div>

    <div class="wst-dod-table-card">
        <div class="wst-dod-table-scroll">
            <table class="wst-dod-table">
                <thead>
                    <tr>
                        <th class="wst-dod-col-date">Date</th>
                        <th class="wst-dod-col-doc">Doc No</th>
                        <th class="wst-dod-col-customer">Customer / Supplier</th>
                        <th class="wst-dod-col-driver">Driver</th>
                        <th class="wst-dod-col-status">Status</th>
                        <th class="wst-dod-col-summary">Bsk / Ctn</th>
                        <th class="wst-dod-col-items">Items</th>
                        <th class="wst-dod-col-action">Actions</th>
                    </tr>
                </thead>

                <tbody>
                    <?php if (empty($rows)): ?>
                        <tr>
                            <td colspan="8" class="wst-dod-empty">No matching delivery order records.</td>
                        </tr>
                    <?php else: ?>
                        <?php foreach ($rows as $r): ?>
                            <?php
                            $isHidden = !empty($r['hiddenFromStaffList']);
                            $rowJobId = (int)($r['jobId'] ?? 0);
                            $rowActionNonce = $rowJobId > 0 ? wp_create_nonce('wst_dod_row_action_' . $rowJobId) : '';

                            $view_args = !empty($r['isPendingJob'])
                                ? array('job_id' => (int)$r['jobId'])
                                : array('docNo' => $r['docNo'], 'docKey' => (int)$r['docKey']);

                            $view_url = add_query_arg($view_args, $view_page_url);

                            $edit_url = add_query_arg(
                                array(
                                    'docNo' => (string)($r['docNo'] ?? ''),
                                    'docKey' => (int)($r['docKey'] ?? 0),
                                ),
                                $edit_page_url
                            );

                            $can_edit_row = wst_dod_can_staff_edit_row($r);
                            $edit_disabled_reason = $can_edit_row ? '' : wst_dod_staff_edit_disabled_reason($r);

                            $row_display_status_key = strtoupper(trim((string)($r['displayStatus'] ?? '')));
                            $row_delivery_status_key = strtoupper(trim((string)($r['deliveryStatus'] ?? '')));
                            $is_delivered_row = ($row_display_status_key === 'DELIVERED' || $row_delivery_status_key === 'DELIVERED');
                            $row_sync_status_key = strtoupper(trim((string)($r['syncStatus'] ?? '')));
                            $is_void_pending = ($row_sync_status_key === 'VOID_PENDING_AUTOCOUNT');
                            $has_autocount_reference =
                                trim((string)($r['autoCountDocNo'] ?? '')) !== ''
                                || (int)($r['docKey'] ?? 0) > 0;

                            // WordPress-first: allow opening any row that has a real local DO record.
                            // docKey is not required when the order was created in WordPress and not yet synced to AutoCount.
                            $can_open_document = !empty($r['isPendingJob']) || ((int)($r['jobId'] ?? 0) > 0 && trim((string)($r['docNo'] ?? '')) !== '');

                            $print_url = add_query_arg(
                                array(
                                    'autoPrint' => '1',
                                    'printPage' => 'do',
                                ),
                                $view_url
                            );

                            $badgeClass = wst_dod_status_class($r['displayStatus']);
                            ?>

                            <tr class="wst-dod-main-row <?php echo $isHidden ? 'wst-dod-row-hidden' : ''; ?>">
                                <td class="wst-dod-date">
                                    <div class="wst-dod-date-main"><?php echo esc_html($r['docDate'] ?: '-'); ?></div>
                                    <div class="wst-dod-date-sub">Created: <?php echo esc_html($r['createdAt'] ?: '-'); ?></div>
                                    <div class="wst-dod-date-sub">Updated: <?php echo esc_html($r['lastModified'] ?: '-'); ?></div>
                                </td>

                                <td class="wst-dod-docno">
                                    <?php if (!empty($r['isPendingJob'])): ?>
                                        <span class="wst-dod-muted">JOB-<?php echo esc_html((int)$r['jobId']); ?></span>
                                    <?php else: ?>
                                        <?php echo esc_html($r['docNo'] ?: '-'); ?>
                                        <div class="wst-dod-date-sub"><?php echo esc_html($r['documentTypeLabel'] ?? 'Delivery Order'); ?></div>
                                    <?php endif; ?>
                                </td>

                                <td class="wst-dod-customer">
                                    <div class="wst-dod-customer-name"><?php echo esc_html($r['debtorName'] ?: '-'); ?></div>
                                    <div class="wst-dod-customer-code"><?php echo esc_html(($r['partyLabel'] ?? 'Customer') . ': ' . ($r['debtorCode'] ?: '-')); ?></div>
                                </td>

                                <td class="wst-dod-driver">
                                   S<'��j���������S<:�
N?�� <?php echo esc_html(strtoupper($r['driver'] ?: 'Unassigned')); ?>
                                </td>

                                <td class="wst-dod-status">
                                    <span
                                        class="wst-dod-badge <?php echo esc_attr($badgeClass); ?>"
                                        data-status-help="<?php echo esc_attr(wst_dod_status_help($r['displayStatus'])); ?>"
                                    >
                                        <?php echo esc_html(wst_dod_label_status($r['displayStatus'])); ?>
                                    </span>

                                    <?php if ($isHidden && $isAdministrator): ?>
                                        <span
                                            class="wst-dod-badge wst-dod-badge-hidden"
                                            data-status-help="<?php echo esc_attr($r['hiddenReason'] ?: 'This row is hidden from normal staff.'); ?>"
                                        >
                                            Hidden
                                        </span>
                                    <?php endif; ?>
                                </td>

                                <td class="wst-dod-summary-cell">
                                    <div>Bsk <strong><?php echo esc_html(wst_dod_fmt_qty($r['totalBasket'])); ?></strong></div>
                                    <div>Ctn <strong><?php echo esc_html(wst_dod_fmt_qty($r['totalCarton'])); ?></strong></div>
                                </td>

                                <td class="wst-dod-items">
                                    <?php if (empty($r['items'])): ?>
                                        <div class="wst-dod-muted">No item detail.</div>
                                    <?php else: ?>
                                        <?php foreach ($r['items'] as $item): ?>
                                            <div class="wst-dod-item">
                                                <div class="wst-dod-item-name">
                                                    <?php echo esc_html($item['name'] !== '' ? $item['name'] : ($item['itemCode'] ?: '-')); ?>
                                                </div>
                                                <div class="wst-dod-item-meta">
                                                    <?php echo esc_html($item['itemCode'] ?: '-'); ?>
                                                    | Qty <?php echo esc_html(wst_dod_fmt_qty($item['qty'])); ?>
                                                    | Basket <?php echo esc_html(wst_dod_fmt_qty($item['basket'])); ?>
                                                    | Carton <?php echo esc_html(wst_dod_fmt_qty($item['carton'])); ?>
                                                    | KG <?php echo esc_html(wst_dod_fmt_weight($item['weightKg'])); ?>
                                                </div>
                                            </div>
                                        <?php endforeach; ?>
                                    <?php endif; ?>
                                </td>

                                <td class="wst-dod-action">
                                    <?php if ($can_open_document): ?>
                                        <a
                                            class="wst-dod-action-btn wst-dod-action-print"
                                            href="<?php echo esc_url($print_url); ?>"
                                            onclick="return wstDodOpenPrintPopup(this.href);"
                                        >
                                            Print
                                        </a>
                                    <?php else: ?>
                                        <span
                                            class="wst-dod-action-btn wst-dod-action-disabled"
                                            title="Cannot print because this row has no document reference."
                                            aria-label="Cannot print because this row has no document reference."
                                        >Print</span>
                                    <?php endif; ?>

                                    <?php if ($can_edit_row): ?>
                                        <a class="wst-dod-action-btn wst-dod-action-edit" href="<?php echo esc_url($edit_url); ?>">
                                            Edit
                                        </a>
                                    <?php else: ?>
                                        <span
                                            class="wst-dod-action-btn wst-dod-action-disabled"
                                            title="<?php echo esc_attr($edit_disabled_reason); ?>"
                                            aria-label="<?php echo esc_attr($edit_disabled_reason); ?>"
                                        >Edit</span>
                                    <?php endif; ?>

                                    <?php if ($can_open_document): ?>
                                        <a class="wst-dod-action-btn wst-dod-action-view" href="<?php echo esc_url($view_url); ?>">
                                            View
                                        </a>
                                    <?php else: ?>
                                        <span
                                            class="wst-dod-action-btn wst-dod-action-disabled"
                                            title="Cannot view because this row has no document reference."
                                            aria-label="Cannot view because this row has no document reference."
                                        >View</span>
                                    <?php endif; ?>

                                    <?php if (!$is_delivered_row || $isAdministrator): ?>
                                        <?php if ($rowJobId > 0): ?>
                                            <?php if ($isHidden && $isAdministrator): ?>
                                                <form method="post" class="wst-dod-inline-form" onsubmit="return wstDodConfirmSoftAction(this, 'activate');">
                                                    <input type="hidden" name="do_id" value="<?php echo esc_attr($rowJobId); ?>">
                                                    <input type="hidden" name="job_id" value="<?php echo esc_attr($rowJobId); ?>">
                                                    <input type="hidden" name="wst_dod_row_nonce" value="<?php echo esc_attr($rowActionNonce); ?>">
                                                    <input type="hidden" name="wst_dod_row_action" value="activate">
                                                    <button type="submit" class="wst-dod-action-btn wst-dod-action-active">Show</button>
                                                </form>
                                            <?php elseif ($is_void_pending): ?>
                                                <span
                                                    class="wst-dod-action-btn wst-dod-action-disabled"
                                                    title="This record is already deleted from the normal staff list and its AutoCount void request is pending."
                                                    aria-label="This record is already deleted from the normal staff list and its AutoCount void request is pending."
                                                >Delete Pending</span>
                                            <?php elseif (!$has_autocount_reference): ?>
                                                <span
                                                    class="wst-dod-action-btn wst-dod-action-disabled"
                                                    title="Cannot delete because this row has no confirmed AutoCount document number or key."
                                                    aria-label="Cannot delete because this row has no confirmed AutoCount document number or key."
                                                >Delete</span>
                                            <?php else: ?>
                                                <form method="post" class="wst-dod-inline-form" onsubmit="return wstDodConfirmSoftAction(this, 'delete');">
                                                    <input type="hidden" name="do_id" value="<?php echo esc_attr($rowJobId); ?>">
                                                    <input type="hidden" name="job_id" value="<?php echo esc_attr($rowJobId); ?>">
                                                    <input type="hidden" name="wst_dod_row_nonce" value="<?php echo esc_attr($rowActionNonce); ?>">
                                                    <input type="hidden" name="wst_dod_row_action" value="delete">
                                                    <button type="submit" class="wst-dod-action-btn wst-dod-action-delete">Delete</button>
                                                </form>
                                            <?php endif; ?>
                                        <?php else: ?>
                                            <span
                                                class="wst-dod-action-btn wst-dod-action-disabled"
                                                title="Cannot delete because this row has no local Delivery Order record."
                                                aria-label="Cannot delete because this row has no local Delivery Order record."
                                            >Delete</span>
                                        <?php endif; ?>
                                    <?php endif; ?>
                                </td>
                            </tr>
                        <?php endforeach; ?>
                    <?php endif; ?>
                </tbody>
            </table>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
function wstDodConfirmSoftAction(form, actionType) {
    var isActivate = actionType === 'activate';
    var title = isActivate
        ? 'Show this hidden record?'
        : 'Delete this Delivery Order?';

    var text = isActivate
        ? 'This only shows the row again in WordPress. It does not reactivate a voided AutoCount document.'
        : 'This will immediately delete the Delivery Order from the staff list and cancel it in AutoCount.';

    var confirmText = isActivate ? 'Yes, show' : 'Yes, delete';
    var confirmColor = isActivate ? '#166534' : '#dc2626';
    var cancelColor = '#64748b';

    if (window.Swal && typeof window.Swal.fire === 'function') {
        window.Swal.fire({
            title: title,
            text: text,
            icon: isActivate ? 'question' : 'warning',
            showCancelButton: true,
            confirmButtonText: confirmText,
            cancelButtonText: 'Cancel',
            confirmButtonColor: confirmColor,
            cancelButtonColor: cancelColor,
            reverseButtons: true,
            focusCancel: true
        }).then(function(result) {
            if (result && result.isConfirmed) {
                form.submit();
            }
        });

        return false;
    }

    if (window.confirm(text)) {
        form.submit();
    }

    return false;
}

function wstDodOpenPrintPopup(url) {
    var width = 920;
    var height = 760;
    var left = Math.max(0, Math.round((window.screen.width - width) / 2));
    var top = Math.max(0, Math.round((window.screen.height - height) / 2));
    var features = [
        'popup=yes',
        'width=' + width,
        'height=' + height,
        'left=' + left,
        'top=' + top,
        'resizable=yes',
        'scrollbars=yes',
        'noopener=yes'
    ].join(',');

    var popup = window.open(url, 'wstDodPrintWindow', features);

    if (!popup) {
        window.open(url, '_blank', 'noopener=yes');
        return false;
    }

    try {
        popup.focus();
    } catch (error) {}

    return false;
}

var wstDodStatusTooltip = null;

function wstDodGetStatusTooltip() {
    if (wstDodStatusTooltip) {
        return wstDodStatusTooltip;
    }

    wstDodStatusTooltip = document.createElement('div');
    wstDodStatusTooltip.className = 'wst-dod-status-tooltip';
    wstDodStatusTooltip.setAttribute('role', 'tooltip');
    document.body.appendChild(wstDodStatusTooltip);

    return wstDodStatusTooltip;
}

function wstDodPositionStatusTooltip(target) {
    var tooltip = wstDodGetStatusTooltip();
    var targetRect = target.getBoundingClientRect();
    var tooltipRect = tooltip.getBoundingClientRect();
    var gap = 8;
    var viewportPadding = 10;
    var left = targetRect.left;
    var top;

    if (left + tooltipRect.width > window.innerWidth - viewportPadding) {
        left = window.innerWidth - tooltipRect.width - viewportPadding;
    }

    left = Math.max(viewportPadding, left);

    var spaceBelow = window.innerHeight - targetRect.bottom;
    var spaceAbove = targetRect.top;

    if (spaceBelow >= tooltipRect.height + gap || spaceBelow >= spaceAbove) {
        top = targetRect.bottom + gap;
    } else {
        top = targetRect.top - tooltipRect.height - gap;
    }

    top = Math.max(
        viewportPadding,
        Math.min(top, window.innerHeight - tooltipRect.height - viewportPadding)
    );

    tooltip.style.left = Math.round(left) + 'px';
    tooltip.style.top = Math.round(top) + 'px';
}

function wstDodShowStatusTooltip(target) {
    var message = target.getAttribute('data-status-help');
    if (!message) {
        return;
    }

    var tooltip = wstDodGetStatusTooltip();
    tooltip.textContent = message;
    tooltip.classList.add('is-visible');
    wstDodPositionStatusTooltip(target);
}

function wstDodHideStatusTooltip() {
    if (wstDodStatusTooltip) {
        wstDodStatusTooltip.classList.remove('is-visible');
    }
}

document.querySelectorAll('.wst-dod-badge[data-status-help]').forEach(function(badge) {
    badge.setAttribute('tabindex', '0');
    badge.addEventListener('mouseenter', function() {
        wstDodShowStatusTooltip(badge);
    });
    badge.addEventListener('mouseleave', wstDodHideStatusTooltip);
    badge.addEventListener('focus', function() {
        wstDodShowStatusTooltip(badge);
    });
    badge.addEventListener('blur', wstDodHideStatusTooltip);
});

window.addEventListener('resize', wstDodHideStatusTooltip);
window.addEventListener('scroll', wstDodHideStatusTooltip, true);
</script>

<style>
.wst-dod-wrap{
    --dod-green:#166534;
    --dod-green-dark:#14532d;
    --dod-line:#e5e7eb;
    --dod-text:#0f172a;
    --dod-muted:#64748b;
    width:100%;
    max-width:100%;
    margin:0 auto;
    padding:6px;
    box-sizing:border-box;
    font-family:"Segoe UI", Roboto, Arial, sans-serif;
    color:var(--dod-text);
    background:#f4faf5;
}

.wst-dod-alert{
    padding:12px 14px;
    border-radius:8px;
    margin:8px 0;
    font-size:14px;
    font-weight:700;
}

.wst-dod-alert-error{
    border:1px solid #fecaca;
    background:#fff1f2;
    color:#991b1b;
}

.wst-dod-alert-warning{
    border:1px solid #fed7aa;
    background:#fff7ed;
    color:#9a3412;
}

.wst-dod-alert-success{
    border:1px solid #86efac;
    background:#f0fdf4;
    color:#166534;
}

.wst-dod-filter-card,
.wst-dod-table-card{
    background:#fff;
    border:1px solid var(--dod-line);
    border-radius:8px;
    padding:8px;
    margin-bottom:8px;
    box-sizing:border-box;
}

.wst-dod-form{
    display:flex;
    flex-direction:column;
    gap:10px;
}

.wst-dod-filter-main{
    display:grid;
    grid-template-columns:minmax(280px, 1fr) auto;
    gap:10px;
    align-items:end;
}

.wst-dod-filter-actions{
    display:flex;
    align-items:center;
    justify-content:flex-eS<:�������������S<:�
N(D����nd;
    gap:8px;
}

.wst-dod-filter-grid{
    display:grid;
    grid-template-columns:minmax(150px, 1fr) repeat(2, minmax(170px, 1fr)) minmax(90px, .55fr);
    gap:8px;
}

.wst-dod-field{
    min-width:0;
    display:flex;
    flex-direction:column;
    gap:4px;
}

.wst-dod-label{
    font-size:13px;
    line-height:1.1;
    font-weight:800;
    color:#334155;
}

.wst-dod-input{
    width:100%;
    min-height:38px;
    border:1px solid #cbd5e1;
    border-radius:6px;
    padding:7px 9px;
    font-size:14px;
    color:var(--dod-text);
    background:#fff;
    box-sizing:border-box;
}

.wst-dod-input:focus{
    outline:none;
    border-color:var(--dod-green);
    box-shadow:0 0 0 3px rgba(22,101,52,.12);
}

.wst-dod-btn{
    min-height:38px;
    min-width:92px;
    border:1px solid transparent;
    border-radius:7px;
    padding:8px 16px;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    box-sizing:border-box;
    font-size:14px;
    font-weight:900;
    line-height:1;
    text-decoration:none !important;
    cursor:pointer;
    white-space:nowrap;
}

.wst-dod-btn-primary{
    background:var(--dod-green);
    border-color:var(--dod-green);
    color:#fff !important;
}

.wst-dod-btn-primary:hover,
.wst-dod-btn-primary:focus{
    background:var(--dod-green-dark);
    border-color:var(--dod-green-dark);
    color:#fff !important;
}

.wst-dod-btn-secondary{
    background:#fff;
    border-color:#cbd5e1;
    color:#334155 !important;
}

.wst-dod-btn-secondary:hover,
.wst-dod-btn-secondary:focus{
    background:#f8fafc;
    border-color:#94a3b8;
    color:#0f172a !important;
}

.wst-dod-summary{
    display:flex;
    justify-content:space-between;
    align-items:center;
    gap:10px;
    margin:0 0 8px;
    color:#334155;
    font-size:13px;
    font-weight:800;
}

.wst-dod-hidden-toggle{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    min-height:30px;
    padding:7px 12px;
    border:1px solid #cbd5e1;
    border-radius:999px;
    background:#ffffff;
    color:#334155 !important;
    font-size:12px;
    font-weight:900;
    line-height:1;
    text-decoration:none !important;
    white-space:nowrap;
}

.wst-dod-hidden-toggle:hover,
.wst-dod-hidden-toggle:focus{
    background:#f8fafc;
    border-color:#94a3b8;
    color:#0f172a !important;
    text-decoration:none !important;
}

.wst-dod-table-card{
    padding:0;
    overflow:hidden;
}

.wst-dod-table-scroll{
    display:block;
    width:100%;
    max-width:100%;
    overflow-x:auto;
    overflow-y:hidden;
    -webkit-overflow-scrolling:touch;
    scrollbar-width:thin;
    scrollbar-color:#94a3b8 #e5e7eb;
}

.wst-dod-table-scroll::-webkit-scrollbar{
    height:12px;
}

.wst-dod-table-scroll::-webkit-scrollbar-thumb{
    background:#94a3b8;
    border-radius:999px;
}

.wst-dod-table-scroll::-webkit-scrollbar-track{
    background:#e5e7eb;
    border-radius:999px;
}

.wst-dod-table{
    width:100%;
    min-width:1160px;
    border-collapse:collapse;
    table-layout:fixed;
    background:#fff;
}

.wst-dod-table th{
    background:#f8fafc;
    color:#334155;
    font-size:12px;
    font-weight:900;
    text-align:left;
    padding:8px 7px;
    border-bottom:1px solid var(--dod-line);
    white-space:nowrap;
}

.wst-dod-table td{
    padding:8px 7px;
    vertical-align:top;
    color:var(--dod-text);
    font-size:13px;
    line-height:1.25;
}

.wst-dod-table tbody tr{
    box-shadow:inset 0 -1px 0 #edf2f7;
}

.wst-dod-table tbody tr:nth-child(odd){
    background:#ffffff;
}

.wst-dod-table tbody tr:nth-child(even){
    background:#f1f8f3;
}

.wst-dod-table tbody tr:hover{
    background:#e8f5ec;
}

.wst-dod-table tbody tr.wst-dod-row-hidden{
    background:#f8fafc;
    opacity:.78;
}

.wst-dod-table tbody tr.wst-dod-row-hidden:hover{
    background:#eef2f7;
    opacity:1;
}

.wst-dod-col-date{width:12%;}
.wst-dod-col-doc{width:9%;}
.wst-dod-col-customer{width:19%;}
.wst-dod-col-driver{width:10%;}
.wst-dod-col-status{width:10%;}
.wst-dod-col-summary{width:7%;}
.wst-dod-col-items{width:18%;}
.wst-dod-col-action{width:15%;}

.wst-dod-docno{
    font-weight:900;
    white-space:nowrap;
}

.wst-dod-date{
    white-space:normal;
}

.wst-dod-date-main{
    font-weight:900;
    white-space:nowrap;
}

.wst-dod-date-sub{
    margin-top:2px;
    color:#64748b;
    font-size:11px;
    font-weight:800;
    line-height:1.25;
    word-break:break-word;
}

.wst-dod-customer-name{
    font-size:14px;
    font-weight:900;
    line-height:1.15;
    color:#020617;
    word-break:break-word;
}

.wst-dod-customer-code{
    margin-top:2px;
    color:#64748b;
    font-size:11px;
    font-weight:800;
    word-break:break-word;
}

.wst-dod-driver{
    font-weight:800;
    word-break:break-word;
    text-transform:uppercase;
}

.wst-dod-summary-cell{
    white-space:nowrap;
    font-size:12px;
}

.wst-dod-summary-cell strong{
    font-weight:900;
}

.wst-dod-badge{
    position:relative;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    border-radius:999px;
    border:1px solid;
    padding:3px 7px;
    max-width:100%;
    font-size:10px;
    line-height:1.1;
    font-weight:900;
    text-transform:uppercase;
    white-space:normal;
}

.wst-dod-status-tooltip{
    position:fixed;
    z-index:999999;
    width:220px;
    max-width:calc(100vw - 20px);
    padding:8px 10px;
    border:1px solid #cbd5e1;
    border-radius:8px;
    background:#0f172a;
    color:#fff;
    box-sizing:border-box;
    font-family:"Segoe UI", Roboto, Arial, sans-serif;
    font-size:12px;
    font-weight:800;
    line-height:1.35;
    text-transform:none;
    white-space:normal;
    overflow-wrap:anywhere;
    box-shadow:0 12px 24px rgba(15,23,42,.2);
    pointer-events:none;
    visibility:hidden;
    opacity:0;
}

.wst-dod-status-tooltip.is-visible{
    visibility:visible;
    opacity:1;
}

.wst-dod-badge-good{
    color:#166534;
    background:#dcfce7;
    border-color:#86efac;
}

.wst-dod-badge-info{
    color:#075985;
    background:#e0f2fe;
    border-color:#7dd3fc;
}

.wst-dod-badge-warn{
    color:#92400e;
    background:#fef3c7;
    border-color:#fbbf24;
}

.wst-dod-badge-danger{
    color:#9f1239;
    background:#ffe4e6;
    border-color:#fda4af;
}

.wst-dod-badge-hidden{
    margin-top:4px;
    color:#475569;
    background:#f1f5f9;
    border-color:#cbd5e1;
}

.wst-dod-action{
    display:flex;
    align-items:flex-start;
    gap:5px;
    flex-wrap:wrap;
    vertical-align:top;
}

.wst-dod-action-btn{
    appearance:none;
    -webkit-appearance:none;
    display:inline-flex !important;
    align-items:center;
    justify-content:center;
    min-height:30px;
    min-width:54px;
    padding:7px 8px;
    border-radius:999px;
    border:1px solid;
    font-size:10.5px;
    font-weight:900;
    line-height:1;
    text-decoration:none !important;
    box-shadow:none !important;
    cursor:pointer;
    flex:0 0 auto;
    transition:background .15s ease, border-color .15s ease, color .15s ease, transform .15s ease;
}

.wst-dod-action-print{
    background:var(--dod-green);
    border-color:var(--dod-green);
    color:#ffffff !important;
}

.wst-dod-action-edit{
    background:#eff6ff;
    border-color:#93c5fd;
    color:#1d4ed8 !important;
}

.wst-dod-action-view{
    background:#ffffff;
    border-color:#cbd5e1;
    color:#334155 !important;
}

.wst-dod-inline-form{
    display:inline-flex;
    margin:0;
    padding:0;
}

.wst-dod-inline-form button{
    font-family:inherit;
}

.wst-dod-action-delete{
    background:#fff1f2;
    border-color:#fda4af;
    color:#9f1239 !important;
}

.wst-dod-action-active{
    background:#f0fdf4;
    border-color:#86efac;
    color:#166534 !important;
}

.wst-dod-action-disabled{
    background:#f8fafc;
    border-color:#e2e8f0;
    color:#94a3b8 !important;
    cursor:not-allowed;
}

.wst-dod-action-btn:hover{
    filter:none;
    transform:translateY(-1px);
}

.wst-dod-action-print:hover{
    background:var(--dod-green-dark);
    border-color:var(--dod-green-dark);
}

.wst-dod-action-edit:hover{
    background:#dbeafe;
    border-color:#60a5fa;
}

.wst-dod-action-view:hover{
    background:#f8fafc;
    border-color:#94a3b8;
}

.wst-dod-action-delete:hover{
    background:#ffe4e6;
    border-color:#fb7185;
}

.wst-dod-action-active:hover{
    background:#dcfce7;
    border-color:#4ade80;
}

.wst-dod-item{
    padding:0 0 6px;
    margin-bottom:6px;
}

.wst-dod-item:last-child{
    border-bottom:0;
    margin-bottom:0;
    padding-bottom:0;
}

.wst-dod-item-name{
    font-size:13px;
    font-weight:900;
    line-height:1.2;
    color:#020617;
    text-transform:uppercase;
}

.wst-dod-item-meta{
    margin-top:2px;
    color:#64748b;
    font-size:11px;
    font-weight:800;
    word-break:break-word;
}

.wst-dod-muted,
.wst-dod-empty{
    color:var(--dod-muted);
    font-weight:800;
}

.wst-dod-empty{
    text-align:center;
    padding:22px 12px !important;
}

@media (max-width:900px){
    .wst-dod-filter-main{
        grid-template-columns:1fr;
    }

    .wst-dod-filter-actions{
        justify-content:flex-end;
    }

    .wst-dod-filter-grid{
        grid-template-columns:1fr 1fr;
    }
}

@media (max-width:760px){
    .wst-dod-wrap{
        padding:6px;
    }

    .wst-dod-summary{
        align-items:flex-start;
        flex-direction:column;
    }

    .wst-dod-table{
        min-width:1160px;
    }
}

@media (max-width:480px){
    .wst-dod-filter-actions{
        display:grid;
        grid-template-columns:1fr 1fr;
        width:100%;
    }

    .wst-dod-filter-actions .wst-dod-btn{
        width:100%;
        min-width:0;
    }

    .wst-dod-filter-grid{
        grid-template-columns:1fr;
    }

    .wst-dod-table{
        min-width:1120px;
    }
}
</style>S<:�+tm���������I���
N?��_title').textContent = opts.title || 'Search';
        $('acd_resp_pi_picker_search').placeholder = opts.placeholder || 'Type to search...';
        $('acd_resp_pi_picker_search').value = '';
        $('acd_resp_pi_picker_modal').classList.add('active');
        if (pickerState.items.length) {
            renderPickerItems(pickerState.items);
        } else {
            renderPickerNote('Type to search');
        }
        setTimeout(() => $('acd_resp_pi_picker_search').focus(), 80);
    }
    function closePicker() {
        $('acd_resp_pi_picker_modal').classList.remove('active');
        $('acd_resp_pi_picker_search').value = '';
        $('acd_resp_pi_picker_results').innerHTML = '';
        pickerState.items = []; pickerState.defaultItems = []; pickerState.fetchFn = null; pickerState.onPick = null;
    }
    function updateClearButtons() {
        const creditorHas = !!($('acdRespPiCreditorInput')?.value.trim());
        const itemHas = !!($('acd_resp_pi_item_name')?.value.trim());
        $('acdRespPiCreditorClear')?.classList.toggle('show', creditorHas);
        $('acdRespPiItemClear')?.classList.toggle('show', itemHas);
    }

    function setCreditor(picked) {
        const name = picked?.name || '';
        const code = picked?.code || '';
        $('acdRespPiCreditorInput').value = name || code || '';
        $('acd_resp_pi_creditor').value = code;
        $('acd_resp_pi_creditor_name').value = name;
        updateClearButtons();
    }

    function clearCreditorSelection() {
        $('acdRespPiCreditorInput').value = '';
        $('acd_resp_pi_creditor').value = '';
        $('acd_resp_pi_creditor_name').value = '';
        updateClearButtons();
    }

    function clearItemSelection() {
        $('acd_resp_pi_item_name').value = '';
        $('acd_resp_pi_item').value = '';
        $('acd_resp_pi_item_display').value = '';
        $('acd_resp_pi_price').value = '';
        updateEntryTotal();
        updateClearButtons();
    }

    function openCreditorPicker() {
        openPicker({
            title: 'Select Creditor',
            placeholder: 'Search creditor...',
            fetchFn: searchCreditorsLive,
            onPick: (picked) => {
                if (!picked) return;
                setCreditor(picked);
                closePicker();
            }
        });
    }

    function openItemPicker() {
        openPicker({
            title: 'Select Item',
            placeholder: 'Search item...',
            fetchFn: async (q) => {
                const items = await searchItemsLive(q);
                return items.map(it => ({
                    label: it.name || it.code,
                    meta: (DROPDOWN_META.showItemCode && it.code) ? it.code : '',
                    raw: { code: it.code, name: it.name || it.code, price: it.price || 0 }
                }));
            },
            onPick: (picked) => {
                if (!picked) return;
                $('acd_resp_pi_item_name').value = picked.name || picked.code || '';
                $('acd_resp_pi_item').value = picked.code || '';
                $('acd_resp_pi_item_display').value = picked.name || picked.code || '';
                const rawPrice = Number(picked.price || 0);
                $('acd_resp_pi_price').value = rawPrice > 0 ? String(rawPrice.toFixed(2)) : '';
                updateEntryTotal();
                updateClearButtons();
                closePicker();
            }
        });
    }

    function initPickerModal() {
        $('acd_resp_pi_picker_close').addEventListener('click', closePicker);
        $('acd_resp_pi_picker_backdrop').addEventListener('click', closePicker);
        $('acd_resp_pi_picker_search').addEventListener('input', function() { runPickerSearch(this.value); });
        $('acd_resp_pi_picker_results').addEventListener('click', (e) => {
            const btn = e.target.closest('[data-picker-idx]');
            if (!btn) return;
            const idx = parseInt(btn.dataset.pickerIdx);
            if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) pickerState.onPick(pickerState.items[idx].raw);
        });
    }
    function initPickerTriggers() {
        $('acdRespPiCreditorInput').setAttribute('readonly', 'readonly');
        $('acd_resp_pi_item_name').setAttribute('readonly', 'readonly');
        $('acdRespPiCreditorInput').addEventListener('click', openCreditorPicker);
        $('acd_resp_pi_item_name').addEventListener('click', openItemPicker);
        $('acdRespPiCreditorClear')?.addEventListener('click', (e) => { e.preventDefault(); clearCreditorSelection(); });
        $('acdRespPiItemClear')?.addEventListener('click', (e) => { e.preventDefault(); clearItemSelection(); });
    }
    function makeClientRequestId(prefix='PI') { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`; }

    function clearLineEntry() {
        $('acd_resp_pi_qty').value = '';
        $('acd_resp_pi_kg').value = '';
        $('acd_resp_pi_price').value = '';
        $('acd_resp_pi_item_name').value = '';
        $('acd_resp_pi_item').value = '';
        $('acd_resp_pi_item_display').value = '';
        updateEntryTotal();
        updateClearButtons();
    }

    // ---- MERGE LOGIC (same creditor + item + type + KG + price) ----
    function findMergeableLineIndex(nextLine) {
        return state.lines.findIndex(line => {
            return String(line.creditorCode || '') === String(nextLine.creditorCode || '')
                && String(line.itemCode || '').toUpperCase() === String(nextLine.itemCode || '').toUpperCase()
                && String(line.packType || '').toUpperCase() === String(nextLine.packType || '').toUpperCase()
                && kgKey(line.kg) === kgKey(nextLine.kg)
                && moneyKey(line.price) === moneyKey(nextLine.price);
        });
    }

    function mergeLine(existingLine, nextLine) {
        const mergedQty = parseQty(existingLine.qty || 0) + parseQty(nextLine.qty || 0);
        const sameKg = parseKg(existingLine.kg || 0);
        existingLine.qty = mergedQty;
        existingLine.kg = sameKg;
        existingLine.total = calcTotalKg(mergedQty, sameKg);
        return existingLine;
    }

    function hidePiSuccessActions() {
        const box = $('acd_resp_pi_success_actions');
        const docNoEl = $('acd_resp_pi_success_docno');
        if (box) box.style.display = 'none';
        if (docNoEl) docNoEl.textContent = '-';
    }

    function showPiSuccessActions(data) {
        const box = $('acd_resp_pi_success_actions');
        const docNoEl = $('acd_resp_pi_success_docno');
        const docNo = data?.docNo || data?.batchLabel || '-';
        if (docNoEl) docNoEl.textContent = docNo;
        if (box) box.style.display = 'block';
    }

    function resetPiForm() {
        clearCreditorSelection();
        clearLineEntry();
        state.lines = [];
        state.jobFinished = false;
        state.savedPendingClear = false;
        const dateField = $('acd_resp_pi_date');
        if (dateField) dateField.value = root.dataset.today || '';
        const submitBtn = $('acd_resp_pi_submit');
        if (submitBtn) {
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        }
        hidePiSuccessActions();
        updateUI();
        updateClearButtons();
    }

    initPickerModal();
    initPickerTriggers();
    $('acd_resp_pi_qty').addEventListener('input', updateEntryTotal);
    $('acd_resp_pi_kg').addEventListener('input', updateEntryTotal);
    $('acd_resp_pi_price').addEventListener('input', updateEntryTotal);
    $('acd_resp_pi_pack_type').addEventListener('change', () => setPackType($('acd_resp_pi_pack_type').value));
    document.querySelectorAll('#acd_resp_pi_pack_type_toggle .acd-resp-type-btn').forEach(btn => btn.addEventListener('click', () => setPackType(btn.dataset.packType)));
    setPackType('BASKET');
    updateUI();

    $('acd_resp_pi_addline').addEventListener('click', () => {
        const itemCode = ($('acd_resp_pi_item').value || '').trim();
        const itemName = ($('acd_resp_pi_item_display').value || '').trim() || itemCode;
        const packType = ($('acd_resp_pi_pack_type').value || '').trim();
        const qtyRaw = ($('acd_resp_pi_qty').value || '').trim();
        const kgRaw = ($('acd_resp_pi_kg').value || '').trim();
        const priceRaw = ($('acd_resp_pi_price').value || '').trim();
        const qty = parseQty(qtyRaw || '0');
        const kg = parseKg(kgRaw || '0');
        const price = parseMoney(priceRaw || '0');
        const creditorCode = ($('acd_resp_pi_creditor').value || '').trim();
        const creditorName = ($('acd_resp_pi_creditor_name').value || '').trim();

        if (!creditorCode) { showToast('error', 'Select creditor'); return; }
        if (!itemCode) { showToast('error', 'Select an item'); return; }
        if (qty <= 0) { showToast('error', 'Qty must be >0'); return; }
        if (kg <= 0) { showToast('error', 'KG must be >0'); return; }

        const nextLine = {
            creditorCode,
            creditorName,
            itemCode,
            itemName,
            packType,
            qty,
            kg,
            total: calcTotalKg(qty, kg),
            price
        };

        const existingIdx = findMergeableLineIndex(nextLine);
        if (existingIdx >= 0) {
            mergeLine(state.lines[existingIdx], nextLine);
            updateUI();
            clearLineEntry();
            showToast(
                'warning',
                'Same item + KG + price merged',
                `${itemName} ${fmtKg(kg)}KG @ ${fmtMoney(price)} already exists for ${creditorName || creditorCode}. Quantity has been added into the same row.`
            );
            return;
        }

        state.lines.push(nextLine);
        updateUI();
        clearLineEntry();
        showToast('success', 'Item added');
    });

    document.getElementById('acd_resp_pi_lines').addEventListener('click', (e) => {
        const btn = e.target.closest('.acd-resp-delete-btn');
        if (!btn) return;
        const idx = parseInt(btn.dataset.idx);
        if (!isNaN(idx)) {
            state.lines.splice(idx, 1);
            updateUI();
            showToast('info', 'Item removed');
        }
    });
    document.getElementById('acd_resp_pi_lines').addEventListener('input', (e) => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, false);
    });
    document.getElementById('acd_resp_pi_lines').addEventListener('change', (e) => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, true);
    });

    const clearNewBtn = $('acd_resp_pi_clear_new_btn');
    if (clearNewBtn) {
        clearNewBtn.addEventListener('click', () => {
            resetPiForm();
            showToast('info', 'Ready for new PI');
        });
    }

    $('acd_resp_pi_submit').addEventListener('click', async () => {
        if (state.isSubmitting) { showToast('warning', 'Already submitting'); return; }

        let confirmed = true;
        if (window.Swal) {
            const confirmation = await Swal.fire({
                icon: 'warning',
                title: 'Create direct Purchase Invoice?',
                html: '<p>This directly created Purchase Invoice will increase stock.</p><p><strong>Do not continue if a Goods Receive Note was already created for the same goods.</strong></p>',
                showCancelButton: true,
                confirmButtonText: 'Yes, Queue Purchase Invoice',
                cancelButtonText: 'Cancel',
                focusCancel: true
            });
            confirmed = confirmation.isConfirmed;
        } else {
            confirmed = window.confirm('This direct Purchase Invoice will increase stock. Do not continue if a Goods Receive Note already exists for the same goods. Continue?');
        }
        if (!confirmed) return;

        const submitBtn = $('acd_resp_pi_submit');
        let saveSucceeded = false;
        state.isSubmitting = true;
        state.jobFinished = false;
        submitBtn.disabled = true;
        submitBtn.textContent = submitProgressText();

        const savedJobs = [];

        try {
            const location = ($('acd_resp_pi_location').value || '').trim();
            const docDate = ($('acd_resp_pi_date').value || '').trim();
            if (!state.lines.length) throw new Error('Add at least one item');

            const groups = groupLinesByCreditor(state.lines);
            if (!groups.length) throw new Error('Add at least one valid item');

            groups.forEach((group, groupIdx) => {
                if (!group.creditorCode) throw new Error(`Group ${groupIdx + 1}: creditor missing`);
                if (!group.lines.length) throw new Error(`Group ${groupIdx + 1}: item lines missing`);
                group.lines.forEach((line, lineIdx) => {
                    if (!line.itemCode) throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: item missing`);
                    if (line.qty <= 0 || line.kg <= 0 || line.total <= 0) {
                        throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: quantity and KG must be >0`);
                    }
                });
            });

            const bulkBatchId = makeBulkBatchId();

            for (let i = 0; i < groups.length; i++) {
                const group = groups[i];
                const payloadLines = group.lines.map(line => buildPayloadLine(line, location));
                const payload = {
                    bulkBatchId,
                    creditorCode: group.creditorCode,
                    creditorName: group.creditorName,
                    CreditorCode: group.creditorCode,
                    CreditorName: group.creditorName,
                    location,
                    Location: location,
                    docDate,
                    remark: '',

                    purchaseInvoiceCompat: buildPiCompatMeta(group, bulkBatchId, i + 1),

                    localDocNo: '',
                    sourceType: 'PURCHASE_INVOICE',
                    sourceSystem: 'WORDPRESS',
                    requestedDocPrefix: REQUESTED_DOC_PREFIX,
                    requestedDocNoMode: 'SERVER_GENERATED',

                    lines: payloadLines
                };
                const body = {
                    type: 'PURCHASE_INVOICE',
                    bulkBatchId,
                    client_request_id: normalizeBatchId(`${bulkBatchId}-${i + 1}`),
                    source: 'wp-ui',
                    payload
                };
                const r = await apiPost(REST_JOB_POST, body);
                const jobId = r.jobId || r.id;
                const returnedDocNo = extractReturnedDocNo(r);
                if (!jobId) throw new Error(`No job ID returned for ${group.creditorName || group.creditorCode}`);
                showToast('info', 'Job queued', `${group.creditorName || group.creditorCode}${returnedDocNo ? ' | ' + returnedDocNo : ' | Job #' + jobId}`);

                savedJobs.push({
                    jobId,
                    groupKey: group.key,
                    docNo: returnedDocNo,
                    creditorCode: group.creditorCode,
                    creditorName: group.creditorName
                });
            }

            showPiSuccessActions({
                batchLabel: `${savedJobs.length} PI${savedJobs.length === 1 ? '' : 's'} | ${bulkBatchId}`
            });
            showBulkSuccessModal({ count: savedJobs.length });
            clearPiFormAfterSave();
            saveSucceeded = true;
            submitBtn.textContent = submitDoneText();
        } catch(err) {
            if (savedJobs.length) {
                removeSavedGroupsFromForm(savedJobs);
            }
I����<���������I���
N����            showBulkPartialFailureModal({
                savedJobs,
                errorMessage: err.message
            });
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        } finally {
            state.isSubmitting = false;
            if (!saveSucceeded) {
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            }
        }
    });
})();
</script>I����M����������P��
N?��ems || [];
                renderPickerItems(pickerState.items);
            } catch(e) { pickerState.items = []; renderPickerNote('Failed to load'); }
        }, 220);
    }
    function openPicker(opts) {
        pickerState.defaultItems = opts.initialItems || [];
        pickerState.items = pickerState.defaultItems;
        pickerState.fetchFn = opts.fetchFn;
        pickerState.onPick = opts.onPick;
        $('acd_resp_pi_picker_title').textContent = opts.title || 'Search';
        $('acd_resp_pi_picker_search').placeholder = opts.placeholder || 'Type to search...';
        $('acd_resp_pi_picker_search').value = '';
        $('acd_resp_pi_picker_modal').classList.add('active');
        if (pickerState.items.length) {
            renderPickerItems(pickerState.items);
        } else {
            renderPickerNote('Type to search');
        }
        setTimeout(() => $('acd_resp_pi_picker_search').focus(), 80);
    }
    function closePicker() {
        $('acd_resp_pi_picker_modal').classList.remove('active');
        $('acd_resp_pi_picker_search').value = '';
        $('acd_resp_pi_picker_results').innerHTML = '';
        pickerState.items = []; pickerState.defaultItems = []; pickerState.fetchFn = null; pickerState.onPick = null;
    }
    function updateClearButtons() {
        const creditorHas = !!($('acdRespPiCreditorInput')?.value.trim());
        const itemHas = !!($('acd_resp_pi_item_name')?.value.trim());
        $('acdRespPiCreditorClear')?.classList.toggle('show', creditorHas);
        $('acdRespPiItemClear')?.classList.toggle('show', itemHas);
    }

    function setCreditor(picked) {
        const name = picked?.name || '';
        const code = picked?.code || '';
        $('acdRespPiCreditorInput').value = name || code || '';
        $('acd_resp_pi_creditor').value = code;
        $('acd_resp_pi_creditor_name').value = name;
        updateClearButtons();
    }

    function clearCreditorSelection() {
        $('acdRespPiCreditorInput').value = '';
        $('acd_resp_pi_creditor').value = '';
        $('acd_resp_pi_creditor_name').value = '';
        updateClearButtons();
    }

    function clearItemSelection() {
        $('acd_resp_pi_item_name').value = '';
        $('acd_resp_pi_item').value = '';
        $('acd_resp_pi_item_display').value = '';
        $('acd_resp_pi_price').value = '';
        updateEntryTotal();
        updateClearButtons();
    }

    function openCreditorPicker() {
        openPicker({
            title: 'Select Creditor',
            placeholder: 'Search creditor...',
            fetchFn: searchCreditorsLive,
            onPick: (picked) => {
                if (!picked) return;
                setCreditor(picked);
                closePicker();
            }
        });
    }

    function openItemPicker() {
        openPicker({
            title: 'Select Item',
            placeholder: 'Search item...',
            fetchFn: async (q) => {
                const items = await searchItemsLive(q);
                return items.map(it => ({
                    label: it.name || it.code,
                    meta: (DROPDOWN_META.showItemCode && it.code) ? it.code : '',
                    raw: { code: it.code, name: it.name || it.code, price: it.price || 0 }
                }));
            },
            onPick: (picked) => {
                if (!picked) return;
                $('acd_resp_pi_item_name').value = picked.name || picked.code || '';
                $('acd_resp_pi_item').value = picked.code || '';
                $('acd_resp_pi_item_display').value = picked.name || picked.code || '';
                const rawPrice = Number(picked.price || 0);
                $('acd_resp_pi_price').value = rawPrice > 0 ? String(rawPrice.toFixed(2)) : '';
                updateEntryTotal();
                updateClearButtons();
                closePicker();
            }
        });
    }

    function initPickerModal() {
        $('acd_resp_pi_picker_close').addEventListener('click', closePicker);
        $('acd_resp_pi_picker_backdrop').addEventListener('click', closePicker);
        $('acd_resp_pi_picker_search').addEventListener('input', function() { runPickerSearch(this.value); });
        $('acd_resp_pi_picker_results').addEventListener('click', (e) => {
            const btn = e.target.closest('[data-picker-idx]');
            if (!btn) return;
            const idx = parseInt(btn.dataset.pickerIdx);
            if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) pickerState.onPick(pickerState.items[idx].raw);
        });
    }
    function initPickerTriggers() {
        $('acdRespPiCreditorInput').setAttribute('readonly', 'readonly');
        $('acd_resp_pi_item_name').setAttribute('readonly', 'readonly');
        $('acdRespPiCreditorInput').addEventListener('click', openCreditorPicker);
        $('acd_resp_pi_item_name').addEventListener('click', openItemPicker);
        $('acdRespPiCreditorClear')?.addEventListener('click', (e) => { e.preventDefault(); clearCreditorSelection(); });
        $('acdRespPiItemClear')?.addEventListener('click', (e) => { e.preventDefault(); clearItemSelection(); });
    }
    function makeClientRequestId(prefix='PI') { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`; }

    function clearLineEntry() {
        $('acd_resp_pi_qty').value = '';
        $('acd_resp_pi_kg').value = '';
        $('acd_resp_pi_price').value = '';
        $('acd_resp_pi_item_name').value = '';
        $('acd_resp_pi_item').value = '';
        $('acd_resp_pi_item_display').value = '';
        updateEntryTotal();
        updateClearButtons();
    }

    // ---- MERGE LOGIC (same creditor + item + type + KG + price) ----
    function findMergeableLineIndex(nextLine) {
        return state.lines.findIndex(line => {
            return String(line.creditorCode || '') === String(nextLine.creditorCode || '')
                && String(line.itemCode || '').toUpperCase() === String(nextLine.itemCode || '').toUpperCase()
                && String(line.packType || '').toUpperCase() === String(nextLine.packType || '').toUpperCase()
                && kgKey(line.kg) === kgKey(nextLine.kg)
                && moneyKey(line.price) === moneyKey(nextLine.price);
        });
    }

    function mergeLine(existingLine, nextLine) {
        const mergedQty = parseQty(existingLine.qty || 0) + parseQty(nextLine.qty || 0);
        const sameKg = parseKg(existingLine.kg || 0);
        existingLine.qty = mergedQty;
        existingLine.kg = sameKg;
        existingLine.total = calcTotalKg(mergedQty, sameKg);
        return existingLine;
    }

    function hidePiSuccessActions() {
        const box = $('acd_resp_pi_success_actions');
        const docNoEl = $('acd_resp_pi_success_docno');
        if (box) box.style.display = 'none';
        if (docNoEl) docNoEl.textContent = '-';
    }

    function showPiSuccessActions(data) {
        const box = $('acd_resp_pi_success_actions');
        const docNoEl = $('acd_resp_pi_success_docno');
        const docNo = data?.docNo || data?.batchLabel || '-';
        if (docNoEl) docNoEl.textContent = docNo;
        if (box) box.style.display = 'block';
    }

    function resetPiForm() {
        clearCreditorSelection();
        clearLineEntry();
        state.lines = [];
        state.jobFinished = false;
        state.savedPendingClear = false;
        const dateField = $('acd_resp_pi_date');
        if (dateField) dateField.value = root.dataset.today || '';
        const submitBtn = $('acd_resp_pi_submit');
        if (submitBtn) {
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        }
        hidePiSuccessActions();
        updateUI();
        updateClearButtons();
    }

    initPickerModal();
    initPickerTriggers();
    $('acd_resp_pi_qty').addEventListener('input', updateEntryTotal);
    $('acd_resp_pi_kg').addEventListener('input', updateEntryTotal);
    $('acd_resp_pi_price').addEventListener('input', updateEntryTotal);
    $('acd_resp_pi_pack_type').addEventListener('change', () => setPackType($('acd_resp_pi_pack_type').value));
    document.querySelectorAll('#acd_resp_pi_pack_type_toggle .acd-resp-type-btn').forEach(btn => btn.addEventListener('click', () => setPackType(btn.dataset.packType)));
    setPackType('BASKET');
    updateUI();

    $('acd_resp_pi_addline').addEventListener('click', () => {
        const itemCode = ($('acd_resp_pi_item').value || '').trim();
        const itemName = ($('acd_resp_pi_item_display').value || '').trim() || itemCode;
        const packType = ($('acd_resp_pi_pack_type').value || '').trim();
        const qtyRaw = ($('acd_resp_pi_qty').value || '').trim();
        const kgRaw = ($('acd_resp_pi_kg').value || '').trim();
        const priceRaw = ($('acd_resp_pi_price').value || '').trim();
        const qty = parseQty(qtyRaw || '0');
        const kg = parseKg(kgRaw || '0');
        const price = parseMoney(priceRaw || '0');
        const creditorCode = ($('acd_resp_pi_creditor').value || '').trim();
        const creditorName = ($('acd_resp_pi_creditor_name').value || '').trim();

        if (!creditorCode) { showToast('error', 'Select creditor'); return; }
        if (!itemCode) { showToast('error', 'Select an item'); return; }
        if (qty <= 0) { showToast('error', 'Qty must be >0'); return; }
        if (kg <= 0) { showToast('error', 'KG must be >0'); return; }

        const nextLine = {
            creditorCode,
            creditorName,
            itemCode,
            itemName,
            packType,
            qty,
            kg,
            total: calcTotalKg(qty, kg),
            price
        };

        const existingIdx = findMergeableLineIndex(nextLine);
        if (existingIdx >= 0) {
            mergeLine(state.lines[existingIdx], nextLine);
            updateUI();
            clearLineEntry();
            showToast(
                'warning',
                'Same item + KG + price merged',
                `${itemName} ${fmtKg(kg)}KG @ ${fmtMoney(price)} already exists for ${creditorName || creditorCode}. Quantity has been added into the same row.`
            );
            return;
        }

        state.lines.push(nextLine);
        updateUI();
        clearLineEntry();
        showToast('success', 'Item added');
    });

    document.getElementById('acd_resp_pi_lines').addEventListener('click', (e) => {
        const btn = e.target.closest('.acd-resp-delete-btn');
        if (!btn) return;
        const idx = parseInt(btn.dataset.idx);
        if (!isNaN(idx)) {
            state.lines.splice(idx, 1);
            updateUI();
            showToast('info', 'Item removed');
        }
    });
    document.getElementById('acd_resp_pi_lines').addEventListener('input', (e) => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, false);
    });
    document.getElementById('acd_resp_pi_lines').addEventListener('change', (e) => {
        const input = e.target.closest('.acd-resp-price-input');
        if (!input) return;
        updateLinePrice(parseInt(input.dataset.priceIdx, 10), input.value, true);
    });

    const clearNewBtn = $('acd_resp_pi_clear_new_btn');
    if (clearNewBtn) {
        clearNewBtn.addEventListener('click', () => {
            resetPiForm();
            showToast('info', 'Ready for new PI');
        });
    }

    $('acd_resp_pi_submit').addEventListener('click', async () => {
        if (state.isSubmitting) { showToast('warning', 'Already submitting'); return; }

        let confirmed = true;
        if (window.Swal) {
            const confirmation = await Swal.fire({
                icon: 'warning',
                title: 'Create direct Purchase Invoice?',
                html: '<p>This directly created Purchase Invoice will increase stock.</p><p><strong>Do not continue if a Goods Receive Note was already created for the same goods.</strong></p>',
                showCancelButton: true,
                confirmButtonText: 'Yes, Queue Purchase Invoice',
                cancelButtonText: 'Cancel',
                focusCancel: true
            });
            confirmed = confirmation.isConfirmed;
        } else {
            confirmed = window.confirm('This direct Purchase Invoice will increase stock. Do not continue if a Goods Receive Note already exists for the same goods. Continue?');
        }
        if (!confirmed) return;

        const submitBtn = $('acd_resp_pi_submit');
        let saveSucceeded = false;
        state.isSubmitting = true;
        state.jobFinished = false;
        submitBtn.disabled = true;
        submitBtn.textContent = submitProgressText();

        const savedJobs = [];

        try {
            const location = ($('acd_resp_pi_location').value || '').trim();
            const docDate = ($('acd_resp_pi_date').value || '').trim();
            const supplierInvoiceNo = ($('acd_resp_pi_supplier_invoice_no').value || '').trim();
            if (!state.lines.length) throw new Error('Add at least one item');

            const groups = groupLinesByCreditor(state.lines);
            if (!groups.length) throw new Error('Add at least one valid item');

            groups.forEach((group, groupIdx) => {
                if (!group.creditorCode) throw new Error(`Group ${groupIdx + 1}: creditor missing`);
                if (!group.lines.length) throw new Error(`Group ${groupIdx + 1}: item lines missing`);
                group.lines.forEach((line, lineIdx) => {
                    if (!line.itemCode) throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: item missing`);
                    if (line.qty <= 0 || line.kg <= 0 || line.total <= 0) {
                        throw new Error(`Group ${groupIdx + 1}, line ${lineIdx + 1}: quantity and KG must be >0`);
                    }
                });
            });

            const bulkBatchId = makeBulkBatchId();

            for (let i = 0; i < groups.length; i++) {
                const group = groups[i];
                const payloadLines = group.lines.map(line => buildPayloadLine(line, location));
                const payload = {
                    bulkBatchId,
                    creditorCode: group.creditorCode,
                    creditorName: group.creditorName,
                    CreditorCode: group.creditorCode,
                    CreditorName: group.creditorName,
                    location,
                    Location: location,
                    docDate,
                    remark: '',

                    purchaseInvoiceCompat: buildPiCompatMeta(group, bulkBatchId, i + 1),

                    localDocNo: '',
                    sourceType: 'PURCHASE_INVOICE',
                    sourceSystem: 'WORDPRESS',
                    requestedDocPrefix: REQUESTED_DOC_PREFIX,
                    requestedDocNoMode: 'SERVER_GENERATED',

                    lines: payloadLines
                };
                const body = {
                    type: 'PURCHASE_INVOICE',
                    bulkBatchId,
                    client_request_id: normalizeBatchId(`${bulkBatchId}-${i + 1}`),
                    source: 'wp-ui',
                    payload
                };
                const r = await apiPost(REST_JOB_POST, body);
                const jobId = r.jobId || r.id;
                const returnedDocNo = extractReturnedDocNo(r);
                if (!jobId) throw new Error(`No job ID returned for ${group.creditorName || group.creditorCode}`);
                showToast('info', 'Job queued', `${group.creditorName || group.creditorCode}${returnedDocNo ? ' | ' + returnedDocNo : ' | Job #' + jobId}`);

                savedJobs.push({
                    jobId,
                    groupKey: group.key,
                    docNo: returnedDocNo,
                    creditorCode: group.creditorCode,
                    creditorNaP��?m����������P��
N�����me: group.creditorName
                });
            }

            showPiSuccessActions({
                batchLabel: `${savedJobs.length} PI${savedJobs.length === 1 ? '' : 's'} | ${bulkBatchId}`
            });
            showBulkSuccessModal({ count: savedJobs.length });
            clearPiFormAfterSave();
            saveSucceeded = true;
            submitBtn.textContent = submitDoneText();
        } catch(err) {
            if (savedJobs.length) {
                removeSavedGroupsFromForm(savedJobs);
            }
            showBulkPartialFailureModal({
                savedJobs,
                errorMessage: err.message
            });
            submitBtn.disabled = false;
            submitBtn.textContent = submitIdleText();
        } finally {
            state.isSubmitting = false;
            if (!saveSucceeded) {
                submitBtn.disabled = false;
                submitBtn.textContent = submitIdleText();
            }
        }
    });
})();
</script>P����

Youez - 2016 - github.com/yon3zu
LinuXploit