403Webshell
Server IP : 121.121.20.254  /  Your IP : 216.73.216.202
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 :  /Program Files/MariaDB 10.6/data/vegebasketdeliveryorder/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /Program Files/MariaDB 10.6/data/vegebasketdeliveryorder/wp_xyz_ips_short_code.ibd
��������e� � ���$��������������������������&&������������������������������������������������e�p7i7��������
��� �
���`_���������e� ��������������������������������i�����������������������������������������������������������������������������������������������������������������������������
�������������������i�	

 !"#e��n<��������
E� ����&�A �� �2infimumsupremum���
pc
,Z���������E�
 �?�<?php
/**
 * DELIVERY ORDER CREATE PAGE
 * Required flow:
 * - Select Debtor Code
 * - Choose Carton / Basket
 * - Fill Qty
 * - Fill Kg
 * - Select Item from Item Code
 * - Display Total = Qty * Kg
 */

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 create Delivery Order.
    </div>';
    return;
}

$rest_nonce = wp_create_nonce('wp_rest');

$list_url  = 'https://website.ipohserver.com/InventorySearch/index.php/delivery-orders/';
$edit_base = 'https://website.ipohserver.com/InventorySearch/index.php/delivery-order-edit/';

$REST_JOB_POST = rest_url('ac/v1/job');
$REST_JOB_BASE = rest_url('ac/v1/job/');

$ajax_url           = admin_url('admin-ajax.php');
$debtor_nonce       = wp_create_nonce('ac_cs_debtor_search');
$item_suggest_nonce = wp_create_nonce('ac_itemcode_suggest');
$default_location   = 'HQ';
?>

<script>
window.AC_SUGGEST = window.AC_SUGGEST || {};
window.AC_SUGGEST.ajaxUrl = "<?php echo esc_js($ajax_url); ?>";
window.AC_SUGGEST.nonce   = "<?php echo esc_js($item_suggest_nonce); ?>";
</script>

<div class="ac-so-wrap do-container"
     data-rest-nonce="<?php echo esc_attr($rest_nonce); ?>"
     data-edit-base="<?php echo esc_attr($edit_base); ?>"
     data-rest-job-post="<?php echo esc_attr($REST_JOB_POST); ?>"
     data-rest-job-base="<?php echo esc_attr($REST_JOB_BASE); ?>">

  <div class="do-pagehead">
    <a class="back-btn" href="<?php echo esc_url($list_url); ?>">← Back to List</a>
    <h1>Create Delivery Order</h1>
  </div>

  <div class="do-card">
    <div class="do-label">Date</div>
    <input id="ac_do_date" type="date" class="do-input" />

    <input type="hidden" id="ac_do_customer_name" value="" />
    <input type="hidden" id="ac_do_sales_agent" value="" />
    <input type="hidden" id="ac_do_location" value="<?php echo esc_attr($default_location); ?>" />

    <div class="do-label">Debtor Code</div>
    <div class="ac-search-wrapper"
         id="acDoDebtorWrapper"
         data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
         data-nonce="<?php echo esc_attr($debtor_nonce); ?>">
      <input type="text"
             id="acDoDebtorInput"
             class="ac-search-input do-input"
             placeholder="Search debtor code..."
             autocomplete="off" />
      <input type="hidden" id="ac_do_customer" value="" />
      <div class="ac-search-dropdown" id="acDoDebtorDropdown"></div>
    </div>

    <div id="ac_do_status" class="ac-so-status"></div>
  </div>

  <div class="do-card">
    <div class="do-label">Item Code</div>
    <div class="ac-item-search-wrap">
      <input id="ac_do_item_name" type="text" class="do-input" placeholder="Scan / type item" autocomplete="off">
      <input id="ac_do_item" type="hidden" value="">
      <input id="ac_do_item_display" type="hidden" value="">
      <div class="ac-dd" id="ac_do_item_dd" style="display:none;"></div>
    </div>

    <div class="do-label">Type</div>
    <div class="type-toggle" id="ac_do_pack_type_toggle">
      <button type="button" class="type-btn active" data-pack-type="CARTON">Carton</button>
      <button type="button" class="type-btn" data-pack-type="BASKET">Basket</button>
    </div>
    <select id="ac_do_pack_type" style="display:none;">
      <option value="CARTON" selected>Carton</option>
      <option value="BASKET">Basket</option>
    </select>

    <div class="do-label">Quantity (Qty)</div>
    <input id="ac_do_qty" type="number" min="0" step="1" value="1" class="do-input" placeholder="Enter quantity">

    <div class="do-label">Weight (KG)</div>
    <input id="ac_do_kg" type="number" min="0" step="1" value="0" class="do-input" placeholder="Enter weight">

    <input id="ac_do_total" type="hidden" value="0">
    <div class="ac-line-preview" id="ac_do_line_preview" style="display:none;"></div>

    <button id="ac_do_addline" class="do-btn" type="button">+ Add Item</button>
  </div>

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

  <div class="save-wrapper">
    <button class="do-btn" id="ac_do_submit" type="button">Save Delivery Order</button>
  </div>
</div>

<style>
  .do-container{
    max-width:560px;
    margin:0 auto;
    padding:16px;
    font-family:"Segoe UI",Arial,sans-serif;
    background:linear-gradient(180deg,#f4faf5 0%,#eef7f1 100%);
    color:#1f2937;
    box-sizing:border-box;
    border-radius:16px;
  }
  .do-pagehead{
    display:flex;
    align-items:center;
    gap:12px;
    flex-wrap:wrap;
    margin-bottom:12px;
  }
  .do-pagehead h1{
    margin:0;
    font-size:clamp(24px,6vw,34px);
    font-weight:800;
    line-height:1.1;
    color:#0f172a;
  }
  .back-btn{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    border:1px solid #cfd9d1;
    background:#fff;
    color:#0f172a;
    padding:10px 12px;
    border-radius:10px;
    text-decoration:none;
    font-weight:700;
    font-size:14px;
    min-height:44px;
  }
  .do-card{
    background:#fff;
    border-radius:14px;
    padding:16px;
    margin-bottom:14px;
    box-shadow:0 4px 16px rgba(15,23,42,.05);
    border:1px solid #e2ebe3;
  }
  .do-label{
    font-size:13px;
    color:#4b5563;
    margin-bottom:6px;
    font-weight:700;
  }
  .do-input{
    width:100%;
    min-height:48px;
    font-size:16px;
    padding:12px;
    border-radius:10px;
    border:1px solid #d1d5db;
    margin-bottom:14px;
    box-sizing:border-box;
    background:#fff;
    color:#111;
  }
  .do-input:focus,
  .type-btn:focus,
  .do-btn:focus,
  .del-btn:focus{
    outline:none;
    box-shadow:0 0 0 3px rgba(40,167,69,.18);
  }
  .do-input:focus{
    border-color:#28a745;
  }
  .type-toggle{
    display:flex;
    gap:10px;
    margin-bottom:14px;
  }
  .type-btn{
    flex:1;
    min-height:48px;
    padding:10px 12px;
    border-radius:10px;
    border:1px solid #28a745;
    background:#fff;
    color:#28a745;
    font-size:18px;
    font-weight:700;
    cursor:pointer;
  }
  .type-btn.active{
    background:linear-gradient(90deg,#28a745,#1e7e34);
    color:#fff;
  }
  .do-btn{
    width:100%;
    min-height:52px;
    background:linear-gradient(90deg,#28a745,#1e7e34);
    color:#fff;
    border:none;
    padding:12px 14px;
    border-radius:12px;
    font-size:20px;
    font-weight:800;
    cursor:pointer;
  }
  .do-btn:disabled{
    opacity:.7;
    cursor:not-allowed;
  }
  .save-wrapper{
    padding:0 2px 10px;
  }

  .ac-search-wrapper,
  .ac-item-search-wrap{ position:relative; margin-bottom:14px; }

  .ac-search-dropdown,
  .ac-dd{
    position:absolute;
    left:0;
    right:0;
    top:calc(100% - 10px);
    background:#fff;
    border:1px solid #dcdcdc;
    border-radius:10px;
    z-index:80;
    max-height:260px;
    overflow:auto;
    box-shadow:0 8px 24px rgba(0,0,0,.10);
  }
  .ac-search-dropdown{ display:none; }
  .ac-search-dropdown.active{ display:block; }

  .ac-dropdown-item,
  .ac-dd .item{
    padding:10px 12px;
    cursor:pointer;
    border-bottom:1px solid #f1f1f1;
    display:flex;
    flex-direction:column;
    gap:2px;
    font-size:15px;
  }
  .ac-dropdown-item:last-child,
  .ac-dd .item:last-child{ border-bottom:none; }
  .ac-dropdown-item:hover,
  .ac-dropdown-item.active,
  .ac-dd .item:hover{ background:#f3f9f4; }

  .ac-dropdown-empty,
  .ac-dropdown-error,
  .ac-dd .item small{
    color:#64748b;
    font-size:12px;
  }

  .ac-line-preview{
    display:none;
    background:#f8fafc;
    border:1px solid #e2e8f0;
    border-radius:12px;
    padding:10px 12px;
    margin-bottom:12px;
    color:#0f172a;
  }
  .preview-head,
  .line-top{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:10px;
    margin-bottom:10px;
  }
  .preview-item,
  .line-item{
    font-size:20px;
    font-weight:800;
    color:#0f5132;
    line-height:1.2;
    word-break:break-word;
  }
  .line-badge{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    border-radius:999px;
    border:1px solid #bfe3c8;
    background:#eefaf1;
    color:#166534;
    font-size:12px;
    font-weight:800;
    letter-spacing:.04em;
    text-transform:uppercase;
    padding:6px 10px;
    white-space:nowrap;
  }
  .preview-grid,
  .metric-grid{
    display:grid;
    grid-template-columns:repeat(3,minmax(0,1fr));
    gap:8px;
  }
  .metric{
    background:#f8fafc;
    border:1px solid #e2e8f0;
    border-radius:10px;
    padding:8px 10px;
  }
  .metric-label{
    color:#64748b;
    font-size:11px;
    font-weight:700;
    letter-spacing:.05em;
    text-transform:uppercase;
    margin-bottom:4px;
  }
  .metric-value{
    color:#0f172a;
    font-size:18px;
    font-weight:800;
    line-height:1;
  }

  .do-list{
    display:grid;
    gap:10px;
  }
  .line-card{
    border:1px solid #e2e8f0;
    border-radius:12px;
    background:#fff;
    padding:12px;
  }
  .no-items{
    border:1px dashed #cbd5e1;
    border-radius:10px;
    background:#f8fafc;
    color:#64748b;
    padding:14px;
    text-align:center;
    font-weight:700;
  }
  .del-btn{
    margin-top:10px;
    min-height:42px;
    background:#ff4d4d;
    color:#fff;
    border:none;
    padding:8px 12px;
    border-radius:8px;
    font-size:14px;
    font-weight:700;
    cursor:pointer;
  }

  .ac-so-status{ display:none; }

  @media (max-width: 768px){
    .do-container{
      padding:12px;
      border-radius:0;
      margin-left:-8px;
      margin-right:-8px;
      max-width:none;
    }
    .do-card{
      padding:14px;
      margin-bottom:12px;
    }
    .preview-item,
    .line-item{ font-size:18px; }
    .metric-value{ font-size:17px; }
    .save-wrapper{
      position:sticky;
      bottom:0;
      z-index:30;
      padding:10px 2px calc(12px + env(safe-area-inset-bottom));
      background:linear-gradient(to top, rgba(239,247,241,.96), rgba(239,247,241,0));
      backdrop-filter:blur(1px);
    }
  }

  @media (max-width: 420px){
    .type-btn{ font-size:16px; }
    .metric-grid,
    .preview-grid{ grid-template-columns:1fr; }
    .line-badge{ font-size:11px; padding:5px 8px; }
  }
</style>


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

<script>
(function(){
  const wrap = document.querySelector('.ac-so-wrap');
  if(!wrap) return;

  const REST_NONCE    = wrap.dataset.restNonce;
  const EDIT_BASE     = wrap.dataset.editBase;
  const REST_JOB_POST = wrap.dataset.restJobPost;
  const REST_JOB_BASE = wrap.dataset.restJobBase;

  const $ = (id) => document.getElementById(id);
  const state = { lines: [], jobFinished: false, isSubmitting: false };

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

  function fmtNum(n){
    const x = parseFloat(n);
    return isNaN(x) ? '0.00' : x.toFixed(2);
  }

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

  function fmtWhole(n){
    return String(parseWhole(n));
  }

  function setStatus(msg, type='info'){
    const toastType =
      type === 'success' ? 'success' :
      type === 'error'   ? 'error' :
      type === 'warning' ? 'warning' :
      'info';
    if (window.Swal) {
      Swal.fire({
        toast: true,
        position: 'top-end',
        icon: toastType,
        title: msg.replace(/<[^>]*>/g, ''),
        showConfirmButton: false,
        timer: 1800,
        timerProgressBar: true
      });
    }
  }

  function showToast(icon, title, text=''){
    if (window.Swal) {
      Swal.fire({
        toast: true,
        position: 'top-end',
        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 calcTotal(qty, kg){
    return (parseFloat(qty) || 0) * (parseFloat(kg) || 0);
  }

  function updateEntryTotal(){
    const qty = parseFloat(($('ac_do_qty').value || '0')) || 0;
    const kg = parseWhole($('ac_do_kg').value || '0');
    $('ac_do_total').value = fmtNum(calcTotal(qty, kg));
    updateLinePreview();
  }

  function setPackType(type){
    const nextType = String(type || '').toUpperCase() === 'BASKET' ? 'BASKET' : 'CARTON';
    $('ac_do_pack_type').value = nextType;
    document.querySelectorAll('#ac_do_pack_type_toggle .type-btn').forEach((btn) => {
      const btnType = (btn.dataset.packType || '').toUpperCase();
      btn.classList.toggle('active', btnType === nextType);
    });
    updateLinePreview();
  }

  function initPackTypeToggle(){
    document.querySelectorAll('#ac_do_pack_type_toggle .type-btn').forEach((btn) => {
      btn.addEventListener('click', () => {
        setPackType(btn.dataset.packType || 'CARTON');
      });
    });
    setPackType($('ac_do_pack_type').value || 'CARTON');
  }

  function updateLinePreview(){
    const pv = $('ac_do_line_preview');
    const itemCode = ($('ac_do_item').value || '').trim();
    const packType = ($('ac_do_pack_type').value || '').trim();
    const qty  = parseFloat(($('ac_do_qty').value || '0')) || 0;
    const kg   = parseWhole($('ac_do_kg').value || '0');
    const total = calcTotal(qty, kg);

    if(!itemCode){
      pv.style.display = 'none';
      pv.innerHTML = '';
      return;
    }

    pv.style.display = 'block';
    pv.innerHTML = `
      <div class="preview-head">
        <span class="preview-item">${escapeHtml(itemCode)}</span>
        <span class="line-badge">${escapeHtml(packType)}</span>
      </div>
      <div class="preview-grid">
        <div class="metric"><div class="metric-label">Qty</div><div class="metric-value">${fmtNum(qty)}</div></div>
        <div class="metric"><div class="metric-label">Kg</div><div class="metric-value">${fmtWhole(kg)}</div></div>
        <div class="metric"><div class="metric-label">Total</div><div class="metric-value">${fmtNum(total)}</div></div>
      </div>
    `;
  }

  function renderLines(){
    const list = $('ac_do_lines');
    list.innerHTML = '';

    if(!state.lines.length){
      list.innerHTML = '<div class="no-items">No items yet</div>';
      return;
    }

    state.lines.forEach((l, idx) => {
      const row = document.createElement('div');
      row.className = 'line-card';
      row.innerHTML = `
        <div class="line-top">
          <div class="line-item">${escapeHtml(l.itemCode)}</div>
          <span class="line-badge">${escapeHtml(l.packType)}</span>
        </div>
        <div class="metric-grid">
          <div class="metric"><div class="metric-label">Qty</div><div class="metric-value">${fmtNum(l.qty)}</div></div>
          <div class="metric"><div class="metric-label">Kg</div><div class="metric-value">${fmtWhole(l.kg)}</div></div>
          <div class="metric"><div class="metric-label">Total</div><div class="metric-value">${fmtNum(l.total)}</div></div>
        </div>
        <button type="button" class="del-btn" data-idx="${idx}">Delete</button>
      `;
      list.appendChild(row);
    });
  }

  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);
E��z���������F�
 �?�
    }

    const text = await res.text();
    let data = null;
    try {
      data = text ? JSON.parse(text) : null;
    } catch (e) {
      throw new Error('Invalid JSON from job status endpoint');
    }
    return data;
  }

  async function apiPost(url, body){
    const res = await fetch(url, {
      method:'POST',
      credentials:'same-origin',
      headers:{
        'Content-Type':'application/json',
        'Accept':'application/json',
        'X-WP-Nonce': REST_NONCE
      },
      body: JSON.stringify(body)
    });

    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();
  }

  async function pollJob(jobId){
    const max = 45;
    for (let i = 0; i < max; i++) {
      await new Promise(r => setTimeout(r, 2000));
      let r;
      try {
        r = await apiGet(REST_JOB_BASE + jobId + '?_t=' + Date.now());
      } catch (err) {
        setStatus('Polling error: ' + (err.message || String(err)), 'error');
        showToast('error', 'Polling failed', err.message || 'Unable to read job status');
        return false;
      }

      const job = r?.job || r?.data?.job || r;
      const st = String(job?.status || '').toUpperCase();
      if (!st) continue;

      setStatus(`Job #${jobId}: ${st}`, (
        st === 'SUCCESS' ? 'success' :
        (st === 'FAILED' || st === 'FAILED_FINAL') ? 'error' :
        st === 'CANCELLED' ? 'warning' :
        'info'
      ));

      if (st === 'SUCCESS') {
        if (state.jobFinished) return true;
        state.jobFinished = true;
        let result = job.result || {};
        if (typeof result === 'string') {
          try { result = JSON.parse(result); } catch(e) {}
        }
        const docNo = result.docNo || result.DocNo || job.docNo || '';
        if (docNo) {
          const editUrl = EDIT_BASE + '?docno=' + encodeURIComponent(docNo);
          showModal(
            'success',
            'Delivery Order Created',
            `Document <b>${escapeHtml(docNo)}</b> was saved successfully.<br><br>
             <a href="${editUrl}" target="_blank" rel="noopener">Open Delivery Order</a>`
          );
        } else {
          showToast('success', 'Delivery Order created');
        }
        return true;
      }

      if (st === 'FAILED' || st === 'FAILED_FINAL') {
        if (state.jobFinished) return false;
        state.jobFinished = true;
        let result = job.result || {};
        if (typeof result === 'string') {
          try { result = JSON.parse(result); } catch(e) {}
        }
        const err = job.error_message || job.error || result.error || 'Job failed';
        showModal('error', 'Delivery Order Failed', escapeHtml(err));
        return false;
      }

      if (st === 'CANCELLED') {
        if (state.jobFinished) return false;
        state.jobFinished = true;
        showToast('warning', 'Job cancelled');
        return false;
      }
    }

    showModal(
      'warning',
      'Queue Timeout',
      'The page did not receive a final status in time.<br><br>Check the Jobs table to confirm whether it already completed.'
    );
    return false;
  }

  function attachSearch(inputEl, ddEl, fetchFn, onPick){
    let t = null;
    function hide(){ ddEl.style.display='none'; ddEl.innerHTML=''; }
    function show(){ ddEl.style.display='block'; }

    inputEl.addEventListener('input', () => {
      clearTimeout(t);
      const q = inputEl.value.trim();
      if(q.length < 1){ hide(); return; }
      t = setTimeout(async () => {
        ddEl.innerHTML = '<div class="item"><small>Searching...</small></div>';
        show();
        try{
          const items = await fetchFn(q);
          if(!items.length){
            ddEl.innerHTML = '<div class="item"><small>No result</small></div>';
            return;
          }
          ddEl.innerHTML = items.map((it, idx) => `
            <div class="item" data-idx="${idx}">
              <strong>${escapeHtml(it.code)}</strong>
              <small>${escapeHtml(it.name || '')}</small>
            </div>
          `).join('');
          ddEl.querySelectorAll('.item[data-idx]').forEach(node => {
            node.addEventListener('click', () => {
              const i = parseInt(node.dataset.idx, 10);
              onPick(items[i]);
              hide();
            });
          });
        }catch(e){
          ddEl.innerHTML = '<div class="item"><small>Failed to load</small></div>';
        }
      }, 200);
    });
    document.addEventListener('click', (e) => {
      if(!ddEl.contains(e.target) && e.target !== inputEl) hide();
    });
  }

  async function searchItemsLive(q){
    const cfg = window.AC_SUGGEST || {};
    const ajaxUrl = cfg.ajaxUrl || "";
    const nonce = cfg.nonce || "";
    if(!ajaxUrl || !nonce) return [];

    const fd = new FormData();
    fd.append("action", "ac_itemcode_suggest");
    fd.append("nonce", nonce);
    fd.append("term", q);

    const res = await fetch(ajaxUrl, {
      method: "POST",
      credentials: "same-origin",
      body: fd
    });

    let data;
    try {
      data = await res.json();
    } catch(e) {
      console.error('Item search parse error', e);
      return [];
    }

    // WordPress standard response: { success: true, data: { items: [...] } }
    if (data && data.success === true && data.data && Array.isArray(data.data.items)) {
      return data.data.items.map(it => ({
        code: it.code || '',
        name: (it.desc || it.name || it.description || '').trim()
      }));
    }
    return [];
  }

  function makeClientRequestId(prefix='DO'){
    const rnd = Math.random().toString(36).slice(2,10);
    const ts  = Date.now().toString(36);
    return `${prefix}-${ts}-${rnd}`;
  }

  (function initCustomerDropdown(){
    const wrapper = document.getElementById('acDoDebtorWrapper');
    if (!wrapper) return;

    const AJAX_URL = wrapper.dataset.ajaxUrl;
    const NONCE = wrapper.dataset.nonce;

    const input = document.getElementById('acDoDebtorInput');
    const dropdown = document.getElementById('acDoDebtorDropdown');
    const hiddenCode = document.getElementById('ac_do_customer');

    let items = [];
    let activeIndex = -1;
    let abortController = null;
    let searchTimeout = null;

    const show = () => dropdown.classList.add('active');
    const hide = () => { dropdown.classList.remove('active'); activeIndex = -1; };

    const renderEmpty = (msg) => {
      dropdown.innerHTML = `<div class="ac-dropdown-empty">${escapeHtml(msg)}</div>`;
      show();
    };
    const renderError = (msg) => {
      dropdown.innerHTML = `<div class="ac-dropdown-error">⚠️ ${escapeHtml(msg)}</div>`;
      show();
    };
    const renderResults = (arr) => {
      if (!arr || arr.length === 0) return renderEmpty('No debtor found');
      dropdown.innerHTML = arr.map((it, idx) => {
        const name = it.name || it.debtorName || it.description || '';
        const code = it.code || it.debtorCode || it.id || '';
        const sa   = (it.salesAgent || it.sales_agent || it.sa || '').trim();
        return `
          <div class="ac-dropdown-item" data-index="${idx}">
            <strong>${escapeHtml(code)}</strong>
            <small>${escapeHtml(name)}${sa ? ' • SA: ' + escapeHtml(sa) : ''}</small>
          </div>
        `;
      }).join('');
      show();
      activeIndex = -1;
    };

    const selectItem = (idx) => {
      if (idx < 0 || idx >= items.length) return;
      const it = items[idx];
      const name = it.name || it.debtorName || it.description || '';
      const code = it.code || it.debtorCode || it.id || '';
      const salesAgent = (it.salesAgent || it.sales_agent || it.sa || '').trim();
      input.value = code;
      hiddenCode.value = code;
      $('ac_do_customer_name').value = name;
      $('ac_do_sales_agent').value = salesAgent;
      hide();
    };

    const performSearch = async (query) => {
      if (abortController) abortController.abort();
      abortController = new AbortController();
      const q = (query || '').trim();
      if (q.length < 1) { hide(); return; }
      renderEmpty('Searching...');
      try {
        const url = `${AJAX_URL}?action=ac_cs_debtor_search&nonce=${encodeURIComponent(NONCE)}&q=${encodeURIComponent(q)}`;
        const response = await fetch(url, { signal: abortController.signal, credentials:'same-origin' });
        const text = await response.text();
        let data;
        try { data = JSON.parse(text); } catch (e) { throw new Error('Invalid JSON response'); }
        if (!data.success) throw new Error((data.data && data.data.error) ? data.data.error : 'Search failed');
        items = (data.data && data.data.items) ? data.data.items : [];
        renderResults(items);
      } catch (err) {
        if (err.name === 'AbortError') return;
        renderError('Failed to load results');
      }
    };

    const debounceSearch = () => {
      clearTimeout(searchTimeout);
      searchTimeout = setTimeout(() => performSearch(input.value), 300);
    };

    input.addEventListener('input', () => {
      hiddenCode.value = '';
      $('ac_do_customer_name').value = '';
      $('ac_do_sales_agent').value = '';
      debounceSearch();
    });
    input.addEventListener('focus', () => {
      if (input.value.trim() !== '') debounceSearch();
    });
    input.addEventListener('keydown', (e) => {
      if (!dropdown.classList.contains('active')) return;
      const nodes = dropdown.querySelectorAll('.ac-dropdown-item');
      if (!nodes.length) return;
      if (e.key === 'ArrowDown') {
        e.preventDefault();
        activeIndex = (activeIndex + 1) % nodes.length;
        nodes.forEach((n, i) => n.classList.toggle('active', i === activeIndex));
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        activeIndex = activeIndex > 0 ? activeIndex - 1 : nodes.length - 1;
        nodes.forEach((n, i) => n.classList.toggle('active', i === activeIndex));
      } else if (e.key === 'Enter' && activeIndex >= 0) {
        e.preventDefault();
        selectItem(activeIndex);
      } else if (e.key === 'Escape') {
        hide();
      }
    });
    dropdown.addEventListener('click', (e) => {
      const node = e.target.closest('.ac-dropdown-item');
      if (!node) return;
      selectItem(parseInt(node.dataset.index, 10));
    });
    document.addEventListener('click', (e) => {
      if (!wrapper.contains(e.target)) hide();
    });
  })();
  (function init(){
    const d = new Date();
    $('ac_do_date').value = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
    attachSearch($('ac_do_item_name'), $('ac_do_item_dd'), searchItemsLive, (it) => {
      $('ac_do_item_name').value = it.code || '';
      $('ac_do_item').value = it.code || '';
      $('ac_do_item_display').value = it.name || '';
      updateLinePreview();
    });
    $('ac_do_qty').addEventListener('input', updateEntryTotal);
    $('ac_do_kg').addEventListener('input', updateEntryTotal);
    $('ac_do_pack_type').addEventListener('change', () => setPackType($('ac_do_pack_type').value));
    $('ac_do_item_name').addEventListener('input', () => {
      $('ac_do_item').value = '';
      $('ac_do_item_display').value = '';
      updateLinePreview();
    });

    $('ac_do_item_name').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
        e.preventDefault();
        $('ac_do_qty').focus();
      }
    });
    $('ac_do_qty').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
        e.preventDefault();
        $('ac_do_kg').focus();
      }
    });
    $('ac_do_kg').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
        e.preventDefault();
        $('ac_do_addline').click();
      }
    });

    initPackTypeToggle();
    updateEntryTotal();
    renderLines();
  })();

  $('ac_do_addline').addEventListener('click', () => {
    const itemCode = ($('ac_do_item').value || '').trim();
    const packType = ($('ac_do_pack_type').value || '').trim();
    const qty = parseFloat(($('ac_do_qty').value || '0')) || 0;
    const kg = parseWhole($('ac_do_kg').value || '0');
    const total = calcTotal(qty, kg);
    if(!itemCode){
      setStatus('Please select Item Code from dropdown.', 'error');
      return;
    }
    if(qty <= 0){
      setStatus('Qty must be greater than 0.', 'error');
      return;
    }
    if(kg <= 0){
      setStatus('Kg must be greater than 0.', 'error');
      return;
    }
    state.lines.push({ itemCode, packType, qty, kg, total });
    setPackType('CARTON');
    $('ac_do_qty').value = '1';
    $('ac_do_kg').value = '0';
    $('ac_do_total').value = '0.00';
    $('ac_do_item_name').value = '';
    $('ac_do_item').value = '';
    $('ac_do_item_display').value = '';
    renderLines();
    updateLinePreview();
    $('ac_do_item_name').focus();
    setStatus('Line added.', 'success');
  });

  $('ac_do_lines').addEventListener('click', (e) => {
    const btn = e.target.closest('button[data-idx]');
    if(!btn) return;
    const idx = parseInt(btn.dataset.idx, 10);
    if(isNaN(idx)) return;
    state.lines.splice(idx, 1);
    renderLines();
    setStatus('Line removed.', 'warning');
  });

  $('ac_do_submit').addEventListener('click', async () => {
    if (state.isSubmitting) {
      setStatus('Already submitting, please wait...', 'warning');
      return;
    }
    const submitBtn = $('ac_do_submit');
    state.jobFinished = false;
    state.isSubmitting = true;
    try {
      submitBtn.disabled = true;
      submitBtn.textContent = 'Saving...';
      const customerCode = ($('ac_do_customer').value || '').trim();
      const customerName = ($('ac_do_customer_name').value || '').trim();
      const salesAgent = ($('ac_do_sales_agent').value || '').trim();
      const location = ($('ac_do_location').value || '').trim();
      const docDate = ($('ac_do_date').value || '').trim();
      if(!customerCode){ setStatus('Please select Debtor Code.', 'error'); return; }
      if(!docDate){ setStatus('Please enter Date.', 'error'); return; }
      if(!location){ setStatus('Location is missing.', 'error'); return; }
      if(!state.lines.length){ setStatus('Please add at least 1 line item.', 'error'); return; }
      const payload = {
        customerCode, customerName, salesAgent,
        debtorCode: customerCode, DebtorCode: customerCode,
        debtorName: customerName, DebtorName: customerName,
        SalesAgent: salesAgent,
        location, Location: location,
        docDate, remark: '',
        lines: state.lines.map(l => ({
          itemCode: l.itemCode,
          qty: l.total,
          uom: 'KG',
          unitPrice: 0,
          amount: 0,
          taxCode: 'SR-0',
          taxRate: 0,
          packType: l.packType,
          cartonQty: l.qty,
          kg: l.kg,
          totalKg: l.total,
          location
        }))
      };
      const body = {
        type: 'DELIVERY_ORDER',
        client_request_id: makeClientRequestId('DO'),
        source: 'wp-ui',
        payload
      };
      const r = await apiPost(REST_JOB_POST, body);
      const jobId = r.jobId || r.id || (r.job && r.job.id);
      if (!jobId) {
        setStatus('Job created but jobId missing in response.', 'warning');
        showToast('warning', 'Job created, but jobId missing');
        return;
      }
      setStatus(`Job queued: #${jobId}. Waiting BridgeWorker...`, 'info');
      showToast('info', 'Job queued', `Job #${jobId}`);
      await pollJob(jobId);
    } catch(err) {
      setStatus('Submit error: ' + (err.message || String(err)), 'error');
      showModal('error', 'Submit error', escapeHtml(err.message || String(err)));
    } finally {
      state.F�-���������F�
 ������isSubmitting = false;
      submitBtn.disabled = false;
      submitBtn.textContent = 'Save Delivery Order';
    }
  });
})();
</script>F�Z�����������j
 �?�<?php
/**
 * DELIVERY ORDER CREATE PAGE
 * Features:
 * - Select Debtor Code
 * - Choose Carton / Basket
 * - Choose UOM (KG / PCS)
 * - Dynamic label changes based on UOM selection
 * - Fill Qty
 * - Fill Kg / Pieces
 * - Select Item from Item Code
 * - Display Total = Qty * Kg
 */

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 create Delivery Order.
    </div>';
    return;
}

$rest_nonce = wp_create_nonce('wp_rest');

$list_url  = 'https://website.ipohserver.com/InventorySearch/index.php/delivery-orders/';
$edit_base = 'https://website.ipohserver.com/InventorySearch/index.php/delivery-order-edit/';

$REST_JOB_POST = rest_url('ac/v1/job');
$REST_JOB_BASE = rest_url('ac/v1/job/');

$ajax_url           = admin_url('admin-ajax.php');
$debtor_nonce       = wp_create_nonce('ac_cs_debtor_search');
$item_suggest_nonce = wp_create_nonce('ac_itemcode_suggest');
$default_location   = 'HQ';
?>

<script>
window.AC_SUGGEST = window.AC_SUGGEST || {};
window.AC_SUGGEST.ajaxUrl = "<?php echo esc_js($ajax_url); ?>";
window.AC_SUGGEST.nonce   = "<?php echo esc_js($item_suggest_nonce); ?>";
</script>

<div class="ac-so-wrap do-container"
     data-rest-nonce="<?php echo esc_attr($rest_nonce); ?>"
     data-edit-base="<?php echo esc_attr($edit_base); ?>"
     data-rest-job-post="<?php echo esc_attr($REST_JOB_POST); ?>"
     data-rest-job-base="<?php echo esc_attr($REST_JOB_BASE); ?>">

  <div class="do-pagehead">
    <a class="back-btn" href="<?php echo esc_url($list_url); ?>">← Back to List</a>
    <h1>Create Delivery Order</h1>
  </div>

  <div class="do-card">
    <div class="do-label">Date</div>
    <input id="ac_do_date" type="date" class="do-input" />

    <input type="hidden" id="ac_do_customer_name" value="" />
    <input type="hidden" id="ac_do_sales_agent" value="" />
    <input type="hidden" id="ac_do_location" value="<?php echo esc_attr($default_location); ?>" />

    <div class="do-label">Debtor Code</div>
    <div class="ac-search-wrapper"
         id="acDoDebtorWrapper"
         data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
         data-nonce="<?php echo esc_attr($debtor_nonce); ?>">
      <input type="text"
             id="acDoDebtorInput"
             class="ac-search-input do-input"
             placeholder="Search debtor code..."
             autocomplete="off" />
      <input type="hidden" id="ac_do_customer" value="" />
      <div class="ac-search-dropdown" id="acDoDebtorDropdown"></div>
    </div>

    <div id="ac_do_status" class="ac-so-status"></div>
  </div>

  <div class="do-card">
    <div class="do-label">Item Code</div>
    <div class="ac-item-search-wrap">
      <input id="ac_do_item_name" type="text" class="do-input" placeholder="Scan / type item" autocomplete="off">
      <input id="ac_do_item" type="hidden" value="">
      <input id="ac_do_item_display" type="hidden" value="">
      <div class="ac-dd" id="ac_do_item_dd" style="display:none;"></div>
    </div>

    <div class="do-label">Type</div>
    <div class="type-toggle" id="ac_do_pack_type_toggle">
      <button type="button" class="type-btn active" data-pack-type="CARTON">Carton</button>
      <button type="button" class="type-btn" data-pack-type="BASKET">Basket</button>
    </div>
    <select id="ac_do_pack_type" style="display:none;">
      <option value="CARTON" selected>Carton</option>
      <option value="BASKET">Basket</option>
    </select>

    <div class="do-label">Unit of Measure (UOM)</div>
    <div class="type-toggle" id="ac_do_uom_toggle">
      <button type="button" class="type-btn active" data-uom="KG">KG</button>
      <button type="button" class="type-btn" data-uom="PCS">PCS</button>
    </div>
    <select id="ac_do_uom" style="display:none;">
      <option value="KG" selected>KG</option>
      <option value="PCS">PCS</option>
    </select>

    <div class="do-label" id="ac_do_weight_label">Weight (KG)</div>
    <input id="ac_do_kg" type="number" min="0" step="1" value="0" class="do-input" placeholder="Enter weight in KG">

    <div class="do-label">Quantity (Qty)</div>
    <input id="ac_do_qty" type="number" min="0" step="1" value="1" class="do-input" placeholder="Enter quantity">

    <input id="ac_do_total" type="hidden" value="0">
    <div class="ac-line-preview" id="ac_do_line_preview" style="display:none;"></div>

    <button id="ac_do_addline" class="do-btn" type="button">+ Add Item</button>
  </div>

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

  <div class="save-wrapper">
    <button class="do-btn" id="ac_do_submit" type="button">Save Delivery Order</button>
  </div>
</div>

<style>
  .do-container{
    max-width:560px;
    margin:0 auto;
    padding:16px;
    font-family:"Segoe UI",Arial,sans-serif;
    background:linear-gradient(180deg,#f4faf5 0%,#eef7f1 100%);
    color:#1f2937;
    box-sizing:border-box;
    border-radius:16px;
  }
  .do-pagehead{
    display:flex;
    align-items:center;
    gap:12px;
    flex-wrap:wrap;
    margin-bottom:12px;
  }
  .do-pagehead h1{
    margin:0;
    font-size:clamp(24px,6vw,34px);
    font-weight:800;
    line-height:1.1;
    color:#0f172a;
  }
  .back-btn{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    border:1px solid #cfd9d1;
    background:#fff;
    color:#0f172a;
    padding:10px 12px;
    border-radius:10px;
    text-decoration:none;
    font-weight:700;
    font-size:14px;
    min-height:44px;
  }
  .do-card{
    background:#fff;
    border-radius:14px;
    padding:16px;
    margin-bottom:14px;
    box-shadow:0 4px 16px rgba(15,23,42,.05);
    border:1px solid #e2ebe3;
  }
  .do-label{
    font-size:13px;
    color:#4b5563;
    margin-bottom:6px;
    font-weight:700;
  }
  .do-input{
    width:100%;
    min-height:48px;
    font-size:16px;
    padding:12px;
    border-radius:10px;
    border:1px solid #d1d5db;
    margin-bottom:14px;
    box-sizing:border-box;
    background:#fff;
    color:#111;
  }
  .do-input:focus,
  .type-btn:focus,
  .do-btn:focus,
  .del-btn:focus{
    outline:none;
    box-shadow:0 0 0 3px rgba(40,167,69,.18);
  }
  .do-input:focus{
    border-color:#28a745;
  }
  .type-toggle{
    display:flex;
    gap:10px;
    margin-bottom:14px;
  }
  .type-btn{
    flex:1;
    min-height:48px;
    padding:10px 12px;
    border-radius:10px;
    border:1px solid #28a745;
    background:#fff;
    color:#28a745;
    font-size:18px;
    font-weight:700;
    cursor:pointer;
  }
  .type-btn.active{
    background:linear-gradient(90deg,#28a745,#1e7e34);
    color:#fff;
  }
  .do-btn{
    width:100%;
    min-height:52px;
    background:linear-gradient(90deg,#28a745,#1e7e34);
    color:#fff;
    border:none;
    padding:12px 14px;
    border-radius:12px;
    font-size:20px;
    font-weight:800;
    cursor:pointer;
  }
  .do-btn:disabled{
    opacity:.7;
    cursor:not-allowed;
  }
  .save-wrapper{
    padding:0 2px 10px;
  }

  .ac-search-wrapper,
  .ac-item-search-wrap{ position:relative; margin-bottom:14px; }

  .ac-search-dropdown,
  .ac-dd{
    position:absolute;
    left:0;
    right:0;
    top:calc(100% - 10px);
    background:#fff;
    border:1px solid #dcdcdc;
    border-radius:10px;
    z-index:80;
    max-height:260px;
    overflow:auto;
    box-shadow:0 8px 24px rgba(0,0,0,.10);
  }
  .ac-search-dropdown{ display:none; }
  .ac-search-dropdown.active{ display:block; }

  .ac-dropdown-item,
  .ac-dd .item{
    padding:10px 12px;
    cursor:pointer;
    border-bottom:1px solid #f1f1f1;
    display:flex;
    flex-direction:column;
    gap:2px;
    font-size:15px;
  }
  .ac-dropdown-item:last-child,
  .ac-dd .item:last-child{ border-bottom:none; }
  .ac-dropdown-item:hover,
  .ac-dropdown-item.active,
  .ac-dd .item:hover{ background:#f3f9f4; }

  .ac-dropdown-empty,
  .ac-dropdown-error,
  .ac-dd .item small{
    color:#64748b;
    font-size:12px;
  }

  .ac-line-preview{
    display:none;
    background:#f8fafc;
    border:1px solid #e2e8f0;
    border-radius:12px;
    padding:10px 12px;
    margin-bottom:12px;
    color:#0f172a;
  }
  .preview-head,
  .line-top{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:10px;
    margin-bottom:10px;
  }
  .preview-item,
  .line-item{
    font-size:20px;
    font-weight:800;
    color:#0f5132;
    line-height:1.2;
    word-break:break-word;
  }
  .line-badge{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    border-radius:999px;
    border:1px solid #bfe3c8;
    background:#eefaf1;
    color:#166534;
    font-size:12px;
    font-weight:800;
    letter-spacing:.04em;
    text-transform:uppercase;
    padding:6px 10px;
    white-space:nowrap;
  }
  .preview-grid,
  .metric-grid{
    display:grid;
    grid-template-columns:repeat(3,minmax(0,1fr));
    gap:8px;
  }
  .metric{
    background:#f8fafc;
    border:1px solid #e2e8f0;
    border-radius:10px;
    padding:8px 10px;
  }
  .metric-label{
    color:#64748b;
    font-size:11px;
    font-weight:700;
    letter-spacing:.05em;
    text-transform:uppercase;
    margin-bottom:4px;
  }
  .metric-value{
    color:#0f172a;
    font-size:18px;
    font-weight:800;
    line-height:1;
  }

  .do-list{
    display:grid;
    gap:10px;
  }
  .line-card{
    border:1px solid #e2e8f0;
    border-radius:12px;
    background:#fff;
    padding:12px;
  }
  .no-items{
    border:1px dashed #cbd5e1;
    border-radius:10px;
    background:#f8fafc;
    color:#64748b;
    padding:14px;
    text-align:center;
    font-weight:700;
  }
  .del-btn{
    margin-top:10px;
    min-height:42px;
    background:#ff4d4d;
    color:#fff;
    border:none;
    padding:8px 12px;
    border-radius:8px;
    font-size:14px;
    font-weight:700;
    cursor:pointer;
  }

  .ac-so-status{ display:none; }

  @media (max-width: 768px){
    .do-container{
      padding:12px;
      border-radius:0;
      margin-left:-8px;
      margin-right:-8px;
      max-width:none;
    }
    .do-card{
      padding:14px;
      margin-bottom:12px;
    }
    .preview-item,
    .line-item{ font-size:18px; }
    .metric-value{ font-size:17px; }
    .save-wrapper{
      position:sticky;
      bottom:0;
      z-index:30;
      padding:10px 2px calc(12px + env(safe-area-inset-bottom));
      background:linear-gradient(to top, rgba(239,247,241,.96), rgba(239,247,241,0));
      backdrop-filter:blur(1px);
    }
  }

  @media (max-width: 420px){
    .type-btn{ font-size:16px; }
    .metric-grid,
    .preview-grid{ grid-template-columns:1fr; }
    .line-badge{ font-size:11px; padding:5px 8px; }
  }
</style>


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

<script>
(function(){
  const wrap = document.querySelector('.ac-so-wrap');
  if(!wrap) return;

  const REST_NONCE    = wrap.dataset.restNonce;
  const EDIT_BASE     = wrap.dataset.editBase;
  const REST_JOB_POST = wrap.dataset.restJobPost;
  const REST_JOB_BASE = wrap.dataset.restJobBase;

  const $ = (id) => document.getElementById(id);
  const state = { lines: [], jobFinished: false, isSubmitting: false };

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

  function fmtNum(n){
    const x = parseFloat(n);
    return isNaN(x) ? '0.00' : x.toFixed(2);
  }

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

  function fmtWhole(n){
    return String(parseWhole(n));
  }

  function setStatus(msg, type='info'){
    const toastType =
      type === 'success' ? 'success' :
      type === 'error'   ? 'error' :
      type === 'warning' ? 'warning' :
      'info';
    if (window.Swal) {
      Swal.fire({
        toast: true,
        position: 'top-end',
        icon: toastType,
        title: msg.replace(/<[^>]*>/g, ''),
        showConfirmButton: false,
        timer: 1800,
        timerProgressBar: true
      });
    }
  }

  function showToast(icon, title, text=''){
    if (window.Swal) {
      Swal.fire({
        toast: true,
        position: 'top-end',
        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 calcTotal(qty, kg){
    return (parseFloat(qty) || 0) * (parseFloat(kg) || 0);
  }

  function updateEntryTotal(){
    const qty = parseFloat(($('ac_do_qty').value || '0')) || 0;
    const kg = parseWhole($('ac_do_kg').value || '0');
    $('ac_do_total').value = fmtNum(calcTotal(qty, kg));
    updateLinePreview();
  }

  function setPackType(type){
    const nextType = String(type || '').toUpperCase() === 'BASKET' ? 'BASKET' : 'CARTON';
    $('ac_do_pack_type').value = nextType;
    document.querySelectorAll('#ac_do_pack_type_toggle .type-btn').forEach((btn) => {
      const btnType = (btn.dataset.packType || '').toUpperCase();
      btn.classList.toggle('active', btnType === nextType);
    });
    updateLinePreview();
  }

  function setUom(uom){
    const selectedUom = String(uom || '').toUpperCase() === 'PCS' ? 'PCS' : 'KG';
    $('ac_do_uom').value = selectedUom;
    document.querySelectorAll('#ac_do_uom_toggle .type-btn').forEach((btn) => {
      btn.classList.toggle('active', btn.dataset.uom === selectedUom);
    });
    
    // Update weight label and placeholder based on UOM
    const weightLabel = $('ac_do_weight_label');
    const weightInput = $('ac_do_kg');
    
    if (selectedUom === 'PCS') {
      weightLabel.textContent = 'Pieces (PCS)';
      weightInput.placeholder = 'Enter number of pieces';
    } else {
      weightLabel.textContent = 'Weight (KG)';
      weightInput.placeholder = 'Enter weight in KG';
    }
    
    updateLinePreview();
  }

  function initPackTypeToggle(){
    document.querySelectorAll('#ac_do_pack_type_toggle .type-btn').forEach((btn) => {
      btn.addEventListener('click', () => {
        setPackType(btn.dataset.packType || 'CARTON');
      });
    });
    setPackType($('ac_do_pack_type').value || 'CARTON');
  }

  function initUomToggle(){
    document.querySelectorAll('#ac_do_uom_toggle .type-btn').forEach((btn) => {
      btn.addEventListener('click', () => {
        setUom(btn.dataset.uom || 'KG');
      });
    });
    setUom($('ac_do_uom').value || 'KG');
  }

  function updateLinePreview(){
    const pv = $('ac_do_line_preview');
    const itemCode = ($('ac_do_item').value || '').trim();
    const packType = ($('ac_do_pack_type').value || '').trim();
    const uom = ($('ac_do_uom').value || 'KG').trim();
    const qty  = parseFloat(($('ac_do_qty').value || '0')) || 0;
    const kg   = parseWhole($('ac_do_kg').value || '0');
    const total = calcTotal(qty, kg);

    if(!itemCode){
      pv.style.display = 'none';
      pv.innerHTML = '';
      return;
    }

    pv.style.display = 'block';
    
    // Use appropriate label based on UOM
    const weightLabel = uom === 'PCS' ? 'Pcs' : 'Kg';
    
    pv.innerHTML = `
      <div class="preview-head">
        <span class="preview-item">${escapeHtml(itemCode)}</span>
        <span class="line-badge">${escapeHtml(packType)}</span>
      </div>
      <div class="preview-grid">
        <div class="metric"><div class="metric-label">Qty</div><div class="metric-value">${fmtNum(qty)} ${escapeHtml(uom)}</div></div>
        <div class="metric"><div class="metric-label">${weightLabel}</div><div class="me�j4s�1���������(
 �?�	tric-value">${fmtWhole(kg)}</div></div>
        <div class="metric"><div class="metric-label">Total</div><div class="metric-value">${fmtNum(total)}</div></div>
      </div>
    `;
  }

  function renderLines(){
    const list = $('ac_do_lines');
    list.innerHTML = '';

    if(!state.lines.length){
      list.innerHTML = '<div class="no-items">No items yet</div>';
      return;
    }

    state.lines.forEach((l, idx) => {
      const row = document.createElement('div');
      row.className = 'line-card';
      
      // Use appropriate label based on UOM
      const weightLabel = l.uom === 'PCS' ? 'Pcs' : 'Kg';
      
      row.innerHTML = `
        <div class="line-top">
          <div class="line-item">${escapeHtml(l.itemCode)}</div>
          <span class="line-badge">${escapeHtml(l.packType)}</span>
        </div>
        <div class="metric-grid">
          <div class="metric"><div class="metric-label">Qty</div><div class="metric-value">${fmtNum(l.qty)} ${escapeHtml(l.uom || 'KG')}</div></div>
          <div class="metric"><div class="metric-label">${weightLabel}</div><div class="metric-value">${fmtWhole(l.kg)}</div></div>
          <div class="metric"><div class="metric-label">Total</div><div class="metric-value">${fmtNum(l.total)}</div></div>
        </div>
        <button type="button" class="del-btn" data-idx="${idx}">Delete</button>
      `;
      list.appendChild(row);
    });
  }

  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);
    }

    const text = await res.text();
    let data = null;
    try {
      data = text ? JSON.parse(text) : null;
    } catch (e) {
      throw new Error('Invalid JSON from job status endpoint');
    }
    return data;
  }

  async function apiPost(url, body){
    const res = await fetch(url, {
      method:'POST',
      credentials:'same-origin',
      headers:{
        'Content-Type':'application/json',
        'Accept':'application/json',
        'X-WP-Nonce': REST_NONCE
      },
      body: JSON.stringify(body)
    });

    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();
  }

  async function pollJob(jobId){
    const max = 45;
    for (let i = 0; i < max; i++) {
      await new Promise(r => setTimeout(r, 2000));
      let r;
      try {
        r = await apiGet(REST_JOB_BASE + jobId + '?_t=' + Date.now());
      } catch (err) {
        setStatus('Polling error: ' + (err.message || String(err)), 'error');
        showToast('error', 'Polling failed', err.message || 'Unable to read job status');
        return false;
      }

      const job = r?.job || r?.data?.job || r;
      const st = String(job?.status || '').toUpperCase();
      if (!st) continue;

      setStatus(`Job #${jobId}: ${st}`, (
        st === 'SUCCESS' ? 'success' :
        (st === 'FAILED' || st === 'FAILED_FINAL') ? 'error' :
        st === 'CANCELLED' ? 'warning' :
        'info'
      ));

      if (st === 'SUCCESS') {
        if (state.jobFinished) return true;
        state.jobFinished = true;
        let result = job.result || {};
        if (typeof result === 'string') {
          try { result = JSON.parse(result); } catch(e) {}
        }
        const docNo = result.docNo || result.DocNo || job.docNo || '';
        if (docNo) {
          const editUrl = EDIT_BASE + '?docno=' + encodeURIComponent(docNo);
          showModal(
            'success',
            'Delivery Order Created',
            `Document <b>${escapeHtml(docNo)}</b> was saved successfully.<br><br>
             <a href="${editUrl}" target="_blank" rel="noopener">Open Delivery Order</a>`
          );
        } else {
          showToast('success', 'Delivery Order created');
        }
        return true;
      }

      if (st === 'FAILED' || st === 'FAILED_FINAL') {
        if (state.jobFinished) return false;
        state.jobFinished = true;
        let result = job.result || {};
        if (typeof result === 'string') {
          try { result = JSON.parse(result); } catch(e) {}
        }
        const err = job.error_message || job.error || result.error || 'Job failed';
        showModal('error', 'Delivery Order Failed', escapeHtml(err));
        return false;
      }

      if (st === 'CANCELLED') {
        if (state.jobFinished) return false;
        state.jobFinished = true;
        showToast('warning', 'Job cancelled');
        return false;
      }
    }

    showModal(
      'warning',
      'Queue Timeout',
      'The page did not receive a final status in time.<br><br>Check the Jobs table to confirm whether it already completed.'
    );
    return false;
  }

  function attachSearch(inputEl, ddEl, fetchFn, onPick){
    let t = null;
    function hide(){ ddEl.style.display='none'; ddEl.innerHTML=''; }
    function show(){ ddEl.style.display='block'; }

    inputEl.addEventListener('input', () => {
      clearTimeout(t);
      const q = inputEl.value.trim();
      if(q.length < 1){ hide(); return; }
      t = setTimeout(async () => {
        ddEl.innerHTML = '<div class="item"><small>Searching...</small></div>';
        show();
        try{
          const items = await fetchFn(q);
          if(!items.length){
            ddEl.innerHTML = '<div class="item"><small>No result</small></div>';
            return;
          }
          ddEl.innerHTML = items.map((it, idx) => `
            <div class="item" data-idx="${idx}">
              <strong>${escapeHtml(it.code)}</strong>
              <small>${escapeHtml(it.name || '')}</small>
            </div>
          `).join('');
          ddEl.querySelectorAll('.item[data-idx]').forEach(node => {
            node.addEventListener('click', () => {
              const i = parseInt(node.dataset.idx, 10);
              onPick(items[i]);
              hide();
            });
          });
        }catch(e){
          ddEl.innerHTML = '<div class="item"><small>Failed to load</small></div>';
        }
      }, 200);
    });
    document.addEventListener('click', (e) => {
      if(!ddEl.contains(e.target) && e.target !== inputEl) hide();
    });
  }

  async function searchItemsLive(q){
    const cfg = window.AC_SUGGEST || {};
    const ajaxUrl = cfg.ajaxUrl || "";
    const nonce = cfg.nonce || "";
    if(!ajaxUrl || !nonce) return [];

    const fd = new FormData();
    fd.append("action", "ac_itemcode_suggest");
    fd.append("nonce", nonce);
    fd.append("term", q);

    const res = await fetch(ajaxUrl, {
      method: "POST",
      credentials: "same-origin",
      body: fd
    });

    let data;
    try {
      data = await res.json();
    } catch(e) {
      console.error('Item search parse error', e);
      return [];
    }

    if (data && data.success === true && data.data && Array.isArray(data.data.items)) {
      return data.data.items.map(it => ({
        code: it.code || '',
        name: (it.desc || it.name || it.description || '').trim()
      }));
    }
    return [];
  }

  function makeClientRequestId(prefix='DO'){
    const rnd = Math.random().toString(36).slice(2,10);
    const ts  = Date.now().toString(36);
    return `${prefix}-${ts}-${rnd}`;
  }

  (function initCustomerDropdown(){
    const wrapper = document.getElementById('acDoDebtorWrapper');
    if (!wrapper) return;

    const AJAX_URL = wrapper.dataset.ajaxUrl;
    const NONCE = wrapper.dataset.nonce;

    const input = document.getElementById('acDoDebtorInput');
    const dropdown = document.getElementById('acDoDebtorDropdown');
    const hiddenCode = document.getElementById('ac_do_customer');

    let items = [];
    let activeIndex = -1;
    let abortController = null;
    let searchTimeout = null;

    const show = () => dropdown.classList.add('active');
    const hide = () => { dropdown.classList.remove('active'); activeIndex = -1; };

    const renderEmpty = (msg) => {
      dropdown.innerHTML = `<div class="ac-dropdown-empty">${escapeHtml(msg)}</div>`;
      show();
    };
    const renderError = (msg) => {
      dropdown.innerHTML = `<div class="ac-dropdown-error">⚠️ ${escapeHtml(msg)}</div>`;
      show();
    };
    const renderResults = (arr) => {
      if (!arr || arr.length === 0) return renderEmpty('No debtor found');
      dropdown.innerHTML = arr.map((it, idx) => {
        const name = it.name || it.debtorName || it.description || '';
        const code = it.code || it.debtorCode || it.id || '';
        const sa   = (it.salesAgent || it.sales_agent || it.sa || '').trim();
        return `
          <div class="ac-dropdown-item" data-index="${idx}">
            <strong>${escapeHtml(code)}</strong>
            <small>${escapeHtml(name)}${sa ? ' • SA: ' + escapeHtml(sa) : ''}</small>
          </div>
        `;
      }).join('');
      show();
      activeIndex = -1;
    };

    const selectItem = (idx) => {
      if (idx < 0 || idx >= items.length) return;
      const it = items[idx];
      const name = it.name || it.debtorName || it.description || '';
      const code = it.code || it.debtorCode || it.id || '';
      const salesAgent = (it.salesAgent || it.sales_agent || it.sa || '').trim();
      input.value = code;
      hiddenCode.value = code;
      $('ac_do_customer_name').value = name;
      $('ac_do_sales_agent').value = salesAgent;
      hide();
    };

    const performSearch = async (query) => {
      if (abortController) abortController.abort();
      abortController = new AbortController();
      const q = (query || '').trim();
      if (q.length < 1) { hide(); return; }
      renderEmpty('Searching...');
      try {
        const url = `${AJAX_URL}?action=ac_cs_debtor_search&nonce=${encodeURIComponent(NONCE)}&q=${encodeURIComponent(q)}`;
        const response = await fetch(url, { signal: abortController.signal, credentials:'same-origin' });
        const text = await response.text();
        let data;
        try { data = JSON.parse(text); } catch (e) { throw new Error('Invalid JSON response'); }
        if (!data.success) throw new Error((data.data && data.data.error) ? data.data.error : 'Search failed');
        items = (data.data && data.data.items) ? data.data.items : [];
        renderResults(items);
      } catch (err) {
        if (err.name === 'AbortError') return;
        renderError('Failed to load results');
      }
    };

    const debounceSearch = () => {
      clearTimeout(searchTimeout);
      searchTimeout = setTimeout(() => performSearch(input.value), 300);
    };

    input.addEventListener('input', () => {
      hiddenCode.value = '';
      $('ac_do_customer_name').value = '';
      $('ac_do_sales_agent').value = '';
      debounceSearch();
    });
    input.addEventListener('focus', () => {
      if (input.value.trim() !== '') debounceSearch();
    });
    input.addEventListener('keydown', (e) => {
      if (!dropdown.classList.contains('active')) return;
      const nodes = dropdown.querySelectorAll('.ac-dropdown-item');
      if (!nodes.length) return;
      if (e.key === 'ArrowDown') {
        e.preventDefault();
        activeIndex = (activeIndex + 1) % nodes.length;
        nodes.forEach((n, i) => n.classList.toggle('active', i === activeIndex));
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        activeIndex = activeIndex > 0 ? activeIndex - 1 : nodes.length - 1;
        nodes.forEach((n, i) => n.classList.toggle('active', i === activeIndex));
      } else if (e.key === 'Enter' && activeIndex >= 0) {
        e.preventDefault();
        selectItem(activeIndex);
      } else if (e.key === 'Escape') {
        hide();
      }
    });
    dropdown.addEventListener('click', (e) => {
      const node = e.target.closest('.ac-dropdown-item');
      if (!node) return;
      selectItem(parseInt(node.dataset.index, 10));
    });
    document.addEventListener('click', (e) => {
      if (!wrapper.contains(e.target)) hide();
    });
  })();

  (function init(){
    const d = new Date();
    $('ac_do_date').value = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
    
    attachSearch($('ac_do_item_name'), $('ac_do_item_dd'), searchItemsLive, (it) => {
      $('ac_do_item_name').value = it.code || '';
      $('ac_do_item').value = it.code || '';
      $('ac_do_item_display').value = it.name || '';
      updateLinePreview();
    });
    
    $('ac_do_qty').addEventListener('input', updateEntryTotal);
    $('ac_do_kg').addEventListener('input', updateEntryTotal);
    $('ac_do_pack_type').addEventListener('change', () => setPackType($('ac_do_pack_type').value));
    $('ac_do_uom').addEventListener('change', () => {
      setUom($('ac_do_uom').value);
      updateLinePreview();
    });
    
    $('ac_do_item_name').addEventListener('input', () => {
      $('ac_do_item').value = '';
      $('ac_do_item_display').value = '';
      updateLinePreview();
    });

    $('ac_do_item_name').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
        e.preventDefault();
        $('ac_do_kg').focus();
      }
    });
    $('ac_do_kg').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
        e.preventDefault();
        $('ac_do_qty').focus();
      }
    });
    $('ac_do_qty').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') {
        e.preventDefault();
        $('ac_do_addline').click();
      }
    });

    initPackTypeToggle();
    initUomToggle();
    updateEntryTotal();
    renderLines();
  })();

  $('ac_do_addline').addEventListener('click', () => {
    const itemCode = ($('ac_do_item').value || '').trim();
    const packType = ($('ac_do_pack_type').value || '').trim();
    const uom = ($('ac_do_uom').value || 'KG').trim();
    const qty = parseFloat(($('ac_do_qty').value || '0')) || 0;
    const kg = parseWhole($('ac_do_kg').value || '0');
    const total = calcTotal(qty, kg);
    
    if(!itemCode){
      setStatus('Please select Item Code from dropdown.', 'error');
      return;
    }
    if(qty <= 0){
      setStatus('Qty must be greater than 0.', 'error');
      return;
    }
    if(kg <= 0){
      setStatus(uom === 'PCS' ? 'Pieces must be greater than 0.' : 'Kg must be greater than 0.', 'error');
      return;
    }
    
    state.lines.push({ itemCode, packType, qty, kg, total, uom });
    
    setPackType('CARTON');
    setUom('KG');
    $('ac_do_qty').value = '1';
    $('ac_do_kg').value = '0';
    $('ac_do_total').value = '0.00';
    $('ac_do_item_name').value = '';
    $('ac_do_item').value = '';
    $('ac_do_item_display').value = '';
    
    renderLines();
    updateLinePreview();
    $('ac_do_item_name').focus();
    setStatus('Line added.', 'success');
  });

  $('ac_do_lines').addEventListener('click', (e) => {
    const btn = e.target.closest('button[data-idx]');
    if(!btn) return;
    const idx = parseInt(btn.dataset.idx, 10);
    if(isNaN(idx)) return;
    state.lines.splice(idx, 1);
    renderLines();
    setStatus('Line removed.', 'warning');
  });

  $('ac_do_submit').addEventListener('click', async () => {
    if (state.isSubmitting) {
      setStatus('Already submitting, please wait...', 'warning');
      return;
    }
    const submitBtn = $('ac_do_submit');
    state.jobFinished = false;
    state.isSubmitting = true;
    try {
      submitBtn.disabled = true;
      submitBtn.textContent = 'Saving...';
      const cus�(�Q>�	���������(
 �	'����tomerCode = ($('ac_do_customer').value || '').trim();
      const customerName = ($('ac_do_customer_name').value || '').trim();
      const salesAgent = ($('ac_do_sales_agent').value || '').trim();
      const location = ($('ac_do_location').value || '').trim();
      const docDate = ($('ac_do_date').value || '').trim();
      if(!customerCode){ setStatus('Please select Debtor Code.', 'error'); return; }
      if(!docDate){ setStatus('Please enter Date.', 'error'); return; }
      if(!location){ setStatus('Location is missing.', 'error'); return; }
      if(!state.lines.length){ setStatus('Please add at least 1 line item.', 'error'); return; }
      
      const payload = {
        customerCode, customerName, salesAgent,
        debtorCode: customerCode, DebtorCode: customerCode,
        debtorName: customerName, DebtorName: customerName,
        SalesAgent: salesAgent,
        location, Location: location,
        docDate, remark: '',
        lines: state.lines.map(l => ({
          itemCode: l.itemCode,
          qty: l.total,
          uom: l.uom || 'KG',
          unitPrice: 0,
          amount: 0,
          taxCode: 'SR-0',
          taxRate: 0,
          packType: l.packType,
          cartonQty: l.qty,
          kg: l.kg,
          totalKg: l.total,
          location
        }))
      };
      
      const body = {
        type: 'DELIVERY_ORDER',
        client_request_id: makeClientRequestId('DO'),
        source: 'wp-ui',
        payload
      };
      
      const r = await apiPost(REST_JOB_POST, body);
      const jobId = r.jobId || r.id || (r.job && r.job.id);
      if (!jobId) {
        setStatus('Job created but jobId missing in response.', 'warning');
        showToast('warning', 'Job created, but jobId missing');
        return;
      }
      setStatus(`Job queued: #${jobId}. Waiting BridgeWorker...`, 'info');
      showToast('info', 'Job queued', `Job #${jobId}`);
      await pollJob(jobId);
    } catch(err) {
      setStatus('Submit error: ' + (err.message || String(err)), 'error');
      showModal('error', 'Submit error', escapeHtml(err.message || String(err)));
    } finally {
      state.isSubmitting = false;
      submitBtn.disabled = false;
      submitBtn.textContent = 'Save Delivery Order';
    }
  });
})();
</script>�(���
��������	!M
 �=K����<?php
/**
 * BASKET RETURN PAGE
 * Fields:
 * - Date
 * - Debtor Code
 * - Basket Return Qty
 * - Remark (optional)
 * - Back to List button removed
 */

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

if (!function_exists('ac_used_is_mobile_or_tablet')) {
    function ac_used_is_mobile_or_tablet() {
        if (wp_is_mobile()) {
            return true;
        }

        $ua = strtolower($_SERVER['HTTP_USER_AGENT'] ?? '');
        foreach (array('ipad', 'tablet', 'kindle', 'silk', 'playbook') as $needle) {
            if ($needle !== '' && strpos($ua, $needle) !== false) {
                return true;
            }
        }

        return false;
    }
}

if (ac_used_is_mobile_or_tablet()) {
    return;
}

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 record Basket Return.</div>';
    return;
}

$rest_nonce = wp_create_nonce('wp_rest');
$list_url   = home_url('/delivery-order-records/');
$ajax_url   = admin_url('admin-ajax.php');
$debtor_nonce = wp_create_nonce('ac_cs_debtor_search');
$rest_return_url = rest_url('ac/v1/basket/return');
?>

<div id="ac-basket-return-desktop-root" class="ac-br-wrap br-container"
     data-rest-nonce="<?php echo esc_attr($rest_nonce); ?>"
     data-rest-return-url="<?php echo esc_attr($rest_return_url); ?>">
  <div class="br-head">
    <h1>Basket Return</h1>
  </div>

  <div class="br-card">
    <label class="br-label" for="ac_br_date">Date</label>
    <input id="ac_br_date" type="date" class="br-input" />

    <label class="br-label" for="acBrDebtorInput">Debtor Code</label>
    <div class="br-search-wrap" id="acBrDebtorWrapper"
         data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
         data-nonce="<?php echo esc_attr($debtor_nonce); ?>">
      <input type="text" id="acBrDebtorInput" class="br-input" placeholder="Search debtor code..." autocomplete="off" />
      <input type="hidden" id="ac_br_debtor_code" value="" />
      <input type="hidden" id="ac_br_debtor_name" value="" />
      <div class="br-dropdown" id="acBrDebtorDropdown"></div>
    </div>

    <label class="br-label" for="ac_br_qty">Basket Return Qty</label>
    <input id="ac_br_qty" type="number" min="1" step="1" class="br-input" value="1" placeholder="Enter basket return quantity" />

    <label class="br-label" for="ac_br_remark">Remark (optional)</label>
    <textarea id="ac_br_remark" class="br-input br-textarea" placeholder="Optional remark"></textarea>

    <button id="ac_br_submit" class="br-btn" type="button">Save Basket Return</button>
    <div id="ac_br_status" class="br-status"></div>
  </div>
</div>

<style>
  .br-container{
    max-width:560px;
    margin:0 auto;
    padding:16px;
    font-family:"Segoe UI",Arial,sans-serif;
    background:linear-gradient(180deg,#f4faf5 0%,#eef7f1 100%);
    border-radius:16px;
    color:#0f172a;
  }
  .br-head{
    display:flex;
    align-items:center;
    gap:12px;
    flex-wrap:wrap;
    margin-bottom:12px;
  }
  .br-head h1{
    margin:0;
    font-size:clamp(24px,6vw,34px);
    font-weight:800;
    line-height:1.1;
  }
  .br-card{
    background:#fff;
    border:1px solid #e2ebe3;
    border-radius:14px;
    box-shadow:0 4px 16px rgba(15,23,42,.05);
    padding:16px;
  }
  .br-label{
    display:block;
    font-size:13px;
    color:#4b5563;
    margin-bottom:6px;
    font-weight:700;
  }
  .br-input{
    width:100%;
    min-height:48px;
    font-size:16px;
    padding:12px;
    border-radius:10px;
    border:1px solid #d1d5db;
    margin-bottom:14px;
    box-sizing:border-box;
    background:#fff;
    color:#111;
  }
  .br-textarea{ min-height:100px; resize:vertical; }
  .br-input:focus,
  .br-btn:focus{
    outline:none;
    border-color:#28a745;
    box-shadow:0 0 0 3px rgba(40,167,69,.18);
  }
  .br-search-wrap{ position:relative; }
  .br-dropdown{
    position:absolute;
    left:0;
    right:0;
    top:calc(100% - 10px);
    background:#fff;
    border:1px solid #dcdcdc;
    border-radius:10px;
    z-index:80;
    max-height:260px;
    overflow:auto;
    box-shadow:0 8px 24px rgba(0,0,0,.10);
    display:none;
  }
  .br-dropdown.active{ display:block; }
  .br-item{
    padding:10px 12px;
    cursor:pointer;
    border-bottom:1px solid #f1f1f1;
    display:flex;
    flex-direction:column;
    gap:2px;
    font-size:15px;
  }
  .br-item:last-child{ border-bottom:none; }
  .br-item:hover,
  .br-item.active{ background:#f3f9f4; }
  .br-btn{
    width:100%;
    min-height:52px;
    border:none;
    border-radius:12px;
    background:linear-gradient(90deg,#28a745,#1e7e34);
    color:#fff;
    font-size:20px;
    font-weight:800;
    padding:12px 14px;
    cursor:pointer;
  }
  .br-btn[disabled]{
    opacity:.7;
    cursor:not-allowed;
  }
  .br-status{
    margin-top:10px;
    font-size:14px;
    color:#334155;
    min-height:20px;
  }

  @media (max-width: 768px){
    .br-container{
      max-width:none;
      margin-left:-8px;
      margin-right:-8px;
      border-radius:0;
      padding:12px;
    }
    .br-card{ padding:14px; }
  }
</style>

<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
(function(){
  const wrap = document.getElementById('ac-basket-return-desktop-root');
  if (!wrap || wrap.dataset.init === '1') return;
  wrap.dataset.init = '1';

  const REST_NONCE = wrap.dataset.restNonce || '';
  const REST_RETURN_URL = wrap.dataset.restReturnUrl || '';

  const $ = (id) => wrap.querySelector('#' + id);
  let isSubmitting = false;

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

  function toast(icon, title, text=''){
    if (!window.Swal) return;
    Swal.fire({
      toast:true,
      position:'top-end',
      icon, title, text,
      showConfirmButton:false,
      timer:2200,
      timerProgressBar:true
    });
  }

  function setStatus(text){
    const el = $('ac_br_status');
    if (el) el.textContent = text || '';
  }

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

  async function apiPost(url, body){
    const res = await fetch(url, {
      method:'POST',
      credentials:'same-origin',
      headers:{
        'Content-Type':'application/json',
        'Accept':'application/json',
        'X-WP-Nonce': REST_NONCE
      },
      body: JSON.stringify(body)
    });

    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 initDate(){
    const d = new Date();
    $('ac_br_date').value = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
  })();

  (function initDebtorDropdown(){
    const wrapper = $('acBrDebtorWrapper');
    if (!wrapper) return;

    const AJAX_URL = wrapper.dataset.ajaxUrl;
    const NONCE = wrapper.dataset.nonce;
    const input = $('acBrDebtorInput');
    const hiddenCode = $('ac_br_debtor_code');
    const hiddenName = $('ac_br_debtor_name');
    const dropdown = $('acBrDebtorDropdown');

    let items = [];
    let activeIndex = -1;
    let abortController = null;
    let timer = null;

    const show = () => dropdown.classList.add('active');
    const hide = () => { dropdown.classList.remove('active'); activeIndex = -1; };

    const render = () => {
      if (!items.length) {
        dropdown.innerHTML = '<div class="br-item"><small>No debtor found</small></div>';
        show();
        return;
      }
      dropdown.innerHTML = items.map((it, idx) => {
        const code = it.code || it.debtorCode || it.id || '';
        const name = it.name || it.debtorName || it.description || '';
        return `<div class="br-item" data-index="${idx}"><strong>${esc(code)}</strong><small>${esc(name)}</small></div>`;
      }).join('');
      show();
    };

    const pick = (idx) => {
      if (idx < 0 || idx >= items.length) return;
      const it = items[idx];
      const code = it.code || it.debtorCode || it.id || '';
      const name = it.name || it.debtorName || it.description || '';
      input.value = code;
      hiddenCode.value = code;
      hiddenName.value = name;
      hide();
    };

    const search = async (q) => {
      if (abortController) abortController.abort();
      abortController = new AbortController();
      const query = (q || '').trim();
      if (query.length < 1) { hide(); return; }

      dropdown.innerHTML = '<div class="br-item"><small>Searching...</small></div>';
      show();

      try {
        const url = `${AJAX_URL}?action=ac_cs_debtor_search&nonce=${encodeURIComponent(NONCE)}&q=${encodeURIComponent(query)}`;
        const res = await fetch(url, { signal: abortController.signal, credentials:'same-origin' });
        const data = await res.json();
        if (!data.success) throw new Error((data.data && data.data.error) ? data.data.error : 'Search failed');
        items = (data.data && data.data.items) ? data.data.items : [];
        render();
      } catch (err) {
        if (err.name === 'AbortError') return;
        dropdown.innerHTML = '<div class="br-item"><small>Failed to load</small></div>';
        show();
      }
    };

    input.addEventListener('input', () => {
      hiddenCode.value = '';
      hiddenName.value = '';
      clearTimeout(timer);
      timer = setTimeout(() => search(input.value), 300);
    });

    input.addEventListener('keydown', (e) => {
      if (!dropdown.classList.contains('active')) return;
      const nodes = Array.from(dropdown.querySelectorAll('.br-item[data-index]'));
      if (!nodes.length) return;

      if (e.key === 'ArrowDown') {
        e.preventDefault();
        activeIndex = Math.min(activeIndex + 1, nodes.length - 1);
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        activeIndex = Math.max(activeIndex - 1, 0);
      } else if (e.key === 'Enter') {
        if (activeIndex >= 0) {
          e.preventDefault();
          pick(activeIndex);
        }
      } else if (e.key === 'Escape') {
        hide();
      }

      nodes.forEach((n, i) => n.classList.toggle('active', i === activeIndex));
    });

    dropdown.addEventListener('click', (e) => {
      const node = e.target.closest('.br-item[data-index]');
      if (!node) return;
      pick(parseInt(node.dataset.index, 10));
    });

    document.addEventListener('click', (e) => {
      if (!wrapper.contains(e.target)) hide();
    });
  })();

  $('ac_br_submit').addEventListener('click', async () => {
    if (isSubmitting) return;

    const debtorCode = ($('ac_br_debtor_code').value || '').trim();
    const debtorName = ($('ac_br_debtor_name').value || '').trim();
    const docDate = ($('ac_br_date').value || '').trim();
    const basketQty = whole(($('ac_br_qty').value || '0'));
    const remark = ($('ac_br_remark').value || '').trim();

    if (!debtorCode) { toast('error', 'Debtor required'); setStatus('Please select Debtor Code.'); return; }
    if (!docDate) { toast('error', 'Date required'); setStatus('Please enter Date.'); return; }
    if (basketQty <= 0) { toast('error', 'Invalid quantity'); setStatus('Basket Return Qty must be greater than 0.'); return; }
    if (!REST_RETURN_URL) { toast('error', 'Config error'); setStatus('Basket return endpoint is missing.'); return; }

    const submitBtn = $('ac_br_submit');

    try {
      isSubmitting = true;
      submitBtn.disabled = true;
      submitBtn.textContent = 'Saving...';
      setStatus('Saving basket return...');

      const payload = {
        debtorCode,
        debtorName,
        docDate,
        basketQty,
        remark
      };

      const res = await apiPost(REST_RETURN_URL, payload);
      if (!res || !res.ok) {
        throw new Error((res && (res.message || res.error)) ? (res.message || res.error) : 'Failed to save basket return');
      }

      const suffix = res.duplicate ? ' (duplicate ignored)' : '';
      setStatus(`Saved basket return for ${debtorCode}: ${basketQty}${suffix}`);
      toast('success', 'Basket return saved', `${debtorCode} | Qty ${basketQty}${suffix}`);

      $('ac_br_qty').value = '1';
      $('ac_br_remark').value = '';
      $('ac_br_qty').focus();
    } catch (err) {
      const msg = err && err.message ? err.message : 'Failed to save basket return';
      setStatus(msg);
      toast('error', 'Save failed', msg);
    } finally {
      isSubmitting = false;
      submitBtn.disabled = false;
      submitBtn.textContent = 'Save Basket Return';
    }
  });
})();
</script>

<!-- BasketReturn.desktop full copy variant -->
<style id="BasketReturn-desktop-full-copy-variant-style">
  .br-container{max-width:1100px !important;padding:24px !important;border-radius:18px !important;background:linear-gradient(180deg,#f0f7f2 0%,#eaf4ee 100%) !important;}
  .br-head h1{font-size:36px !important;}
  .br-card{padding:20px !important;margin-bottom:0 !important;}
  .br-desktop-grid{display:grid;grid-template-columns:1fr 320px;gap:14px;margin-top:12px;}
  .br-info{background:#fff;border:1px solid #dbe8de;border-radius:14px;padding:14px;box-shadow:0 4px 12px rgba(15,23,42,.04);}
</style>
<script>
(function(){
  const container = document.getElementById('ac-basket-return-desktop-root');
  const card = container ? container.querySelector('.br-card') : null;
  if (!container || !card || container.querySelector('.br-desktop-grid')) return;

  const grid = document.createElement('div');
  grid.className = 'br-desktop-grid';

  const info = document.createElement('aside');
  info.className = 'br-info';
  info.innerHTML = `
    <h3 style="margin:0 0 8px;font-size:18px;color:#0f172a;">Return Summary</h3>
    <p style="margin:0;font-size:14px;line-height:1.45;color:#475569;">Desktop view keeps more context visible while recording returns.</p>
    <div style="font-size:12px;color:#64748b;font-weight:700;margin-top:10px;">Current Qty</div>
    <div style="font-size:30px;font-weight:800;color:#0f172a;line-height:1.1;" id="acBrDesktopQty">0</div>
    <div style="font-size:12px;color:#64748b;font-weight:700;margin-top:10px;">Selected Debtor</div>
    <div style="font-size:20px;font-weight:800;color:#0f172a;line-height:1.1;" id="acBrDesktopDebtor">-</div>
  `;

  card.parentNode.insertBefore(grid, card);
  grid.appendChild(card);
  grid.appendChild(info);

  const qtyInput = container.querySelector('#ac_br_qty');
  const debtorInput = container.querySelector('#acBrDebtorInput');

  function update(){
    const qty = Math.max(0, Math.round(Number(qtyInput ? qtyInput.value : 0) || 0));
    const debtor = ((debtorInput ? debtorInput.value : '') || '').trim();
    const q = container.querySelector('#acBrDesktopQty');
    const d = container.querySelector('#acBrDesktopDebtor');
    if (q) q.textContent = String(qty);
    if (d) d.textContent = debtor || '-';
  }

  if (qtyInput) qtyInput.addEventListener('input', update);
  if (debtorInput) debtorInput.addEventListener('input', update);
  update();
})();
</script>	!Mb����������	k�
 �;A����<?php
/**
 * BASKET RETURN PAGE
 * Fields:
 * - Date (hidden, default today)
 * - Debtor Code (saved hidden)
 * - Customer Name (shown)
 * - Basket Return Qty
 * - Remark (optional)
 */

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

if (!function_exists('ac_used_is_mobile_or_tablet')) {
    function ac_used_is_mobile_or_tablet() {
        if (wp_is_mobile()) {
            return true;
        }

        $ua = strtolower($_SERVER['HTTP_USER_AGENT'] ?? '');
        foreach (array('ipad', 'tablet', 'kindle', 'silk', 'playbook') as $needle) {
            if ($needle !== '' && strpos($ua, $needle) !== false) {
                return true;
            }
        }

        return false;
    }
}

if (!ac_used_is_mobile_or_tablet()) {
    return;
}

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 record Basket Return.</div>';
    return;
}

$rest_nonce       = wp_create_nonce('wp_rest');
$ajax_url         = admin_url('admin-ajax.php');
$debtor_nonce     = wp_create_nonce('ac_cs_debtor_search');
$rest_return_url  = rest_url('ac/v1/basket/return');
$today_date       = current_time('Y-m-d');
$show_debtor_code = false; // true or false
?>

<div id="ac-basket-return-mobile-root" class="ac-br-wrap br-container"
     data-rest-nonce="<?php echo esc_attr($rest_nonce); ?>"
     data-rest-return-url="<?php echo esc_attr($rest_return_url); ?>"
     data-show-debtor-code="<?php echo $show_debtor_code ? '1' : '0'; ?>">
  <div class="br-head">
    <h1>Basket Return</h1>
  </div>

  <div class="br-card">
    <input id="ac_br_date" type="hidden" value="<?php echo esc_attr($today_date); ?>" />

    <label class="br-label" for="acBrDebtorInput">Customer Name</label>
    <div class="br-search-wrap" id="acBrDebtorWrapper"
         data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
         data-nonce="<?php echo esc_attr($debtor_nonce); ?>">
      <input type="text" id="acBrDebtorInput" class="br-input" placeholder="Search customer name..." autocomplete="off" />
      <input type="hidden" id="ac_br_debtor_code" value="" />
      <input type="hidden" id="ac_br_debtor_name" value="" />
      <div class="br-dropdown" id="acBrDebtorDropdown"></div>
    </div>

    <label class="br-label" for="ac_br_qty">Basket Return Qty</label>
    <input id="ac_br_qty" type="number" min="1" step="1" class="br-input" value="1" placeholder="Enter basket return quantity" />

    <label class="br-label" for="ac_br_remark">Remark (optional)</label>
    <textarea id="ac_br_remark" class="br-input br-textarea" placeholder="Optional remark"></textarea>

    <button id="ac_br_submit" class="br-btn" type="button">Save Basket Return</button>
    <div id="ac_br_status" class="br-status"></div>
  </div>
</div>

<style>
  .br-container{
    max-width:560px;
    margin:0 auto;
    padding:16px;
    font-family:"Segoe UI",Arial,sans-serif;
    background:linear-gradient(180deg,#f4faf5 0%,#eef7f1 100%);
    border-radius:16px;
    color:#0f172a;
  }
  .br-head{
    display:flex;
    align-items:center;
    gap:12px;
    flex-wrap:wrap;
    margin-bottom:12px;
  }
  .br-head h1{
    margin:0;
    font-size:clamp(24px,6vw,34px);
    font-weight:800;
    line-height:1.1;
  }
  .br-card{
    background:#fff;
    border:1px solid #e2ebe3;
    border-radius:14px;
    box-shadow:0 4px 16px rgba(15,23,42,.05);
    padding:16px;
  }
  .br-label{
    display:block;
    font-size:13px;
    color:#4b5563;
    margin-bottom:6px;
    font-weight:700;
  }
  .br-input{
    width:100%;
    min-height:48px;
    font-size:16px;
    padding:12px;
    border-radius:10px;
    border:1px solid #d1d5db;
    margin-bottom:14px;
    box-sizing:border-box;
    background:#fff;
    color:#111;
  }
  .br-textarea{
    min-height:100px;
    resize:vertical;
  }
  .br-input:focus,
  .br-btn:focus{
    outline:none;
    border-color:#28a745;
    box-shadow:0 0 0 3px rgba(40,167,69,.18);
  }
  .br-search-wrap{
    position:relative;
  }
  .br-dropdown{
    position:absolute;
    left:0;
    right:0;
    top:calc(100% - 10px);
    background:#fff;
    border:1px solid #dcdcdc;
    border-radius:10px;
    z-index:80;
    max-height:260px;
    overflow:auto;
    box-shadow:0 8px 24px rgba(0,0,0,.10);
    display:none;
  }
  .br-dropdown.active{
    display:block;
  }
  .br-item{
    padding:10px 12px;
    cursor:pointer;
    border-bottom:1px solid #f1f1f1;
    display:flex;
    flex-direction:column;
    gap:2px;
    font-size:15px;
  }
  .br-item:last-child{
    border-bottom:none;
  }
  .br-item:hover,
  .br-item.active{
    background:#f3f9f4;
  }
  .br-item strong{
    font-weight:700;
    color:#111827;
  }
  .br-item small{
    font-size:12px;
    color:#6b7280;
  }
  .br-btn{
    width:100%;
    min-height:52px;
    border:none;
    border-radius:12px;
    background:linear-gradient(90deg,#28a745,#1e7e34);
    color:#fff;
    font-size:20px;
    font-weight:800;
    padding:12px 14px;
    cursor:pointer;
  }
  .br-btn[disabled]{
    opacity:.7;
    cursor:not-allowed;
  }
  .br-status{
    margin-top:10px;
    font-size:14px;
    color:#334155;
    min-height:20px;
  }

  @media (max-width: 768px){
    .br-container{
      max-width:none;
      margin-left:-8px;
      margin-right:-8px;
      border-radius:0;
      padding:12px;
    }
    .br-card{
      padding:14px;
    }
  }
</style>

<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
(function(){
  const wrap = document.getElementById('ac-basket-return-mobile-root');
  if (!wrap || wrap.dataset.init === '1') return;
  wrap.dataset.init = '1';

  const REST_NONCE = wrap.dataset.restNonce || '';
  const REST_RETURN_URL = wrap.dataset.restReturnUrl || '';
  const SHOW_DEBTOR_CODE = wrap.dataset.showDebtorCode === '1';

  const $ = (id) => wrap.querySelector('#' + id);
  let isSubmitting = false;

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

  function toast(icon, title, text=''){
    if (!window.Swal) return;
    Swal.fire({
      toast:true,
      position:'top-end',
      icon, title, text,
      showConfirmButton:false,
      timer:2200,
      timerProgressBar:true
    });
  }

  function setStatus(text){
    const el = $('ac_br_status');
    if (el) el.textContent = text || '';
  }

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

  function formatDisplayValue(name, code){
    const safeName = (name || '').trim();
    const safeCode = (code || '').trim();

    if (SHOW_DEBTOR_CODE && safeName && safeCode) {
      return `${safeName} (${safeCode})`;
    }

    return safeName || safeCode;
  }

  async function apiPost(url, body){
    const res = await fetch(url, {
      method:'POST',
      credentials:'same-origin',
      headers:{
        'Content-Type':'application/json',
        'Accept':'application/json',
        'X-WP-Nonce': REST_NONCE
      },
      body: JSON.stringify(body)
    });

    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 initDate(){
    const dateInput = $('ac_br_date');
    if (!dateInput) return;

    if (!dateInput.value) {
      const d = new Date();
      dateInput.value = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
    }
  })();

  (function initDebtorDropdown(){
    const wrapper = $('acBrDebtorWrapper');
    if (!wrapper) return;

    const AJAX_URL = wrapper.dataset.ajaxUrl;
    const NONCE = wrapper.dataset.nonce;
    const input = $('acBrDebtorInput');
    const hiddenCode = $('ac_br_debtor_code');
    const hiddenName = $('ac_br_debtor_name');
    const dropdown = $('acBrDebtorDropdown');

    let items = [];
    let activeIndex = -1;
    let abortController = null;
    let timer = null;

    const show = () => dropdown.classList.add('active');
    const hide = () => {
      dropdown.classList.remove('active');
      activeIndex = -1;
    };

    const render = () => {
      if (!items.length) {
        dropdown.innerHTML = '<div class="br-item"><small>No customer found</small></div>';
        show();
        return;
      }

      dropdown.innerHTML = items.map((it, idx) => {
        const code = it.code || it.debtorCode || it.id || '';
        const name = it.name || it.debtorName || it.description || '';

        return `
          <div class="br-item" data-index="${idx}">
            <strong>${esc(name || code)}</strong>
            ${SHOW_DEBTOR_CODE && code ? `<small>${esc(code)}</small>` : ''}
          </div>
        `;
      }).join('');

      show();
    };

    const pick = (idx) => {
      if (idx < 0 || idx >= items.length) return;

      const it = items[idx];
      const code = it.code || it.debtorCode || it.id || '';
      const name = it.name || it.debtorName || it.description || '';

      input.value = formatDisplayValue(name, code);
      hiddenCode.value = code;
      hiddenName.value = name;
      hide();
    };

    const search = async (q) => {
      if (abortController) abortController.abort();
      abortController = new AbortController();

      const query = (q || '').trim();
      if (query.length < 1) {
        hide();
        return;
      }

      dropdown.innerHTML = '<div class="br-item"><small>Searching...</small></div>';
      show();

      try {
        const url = `${AJAX_URL}?action=ac_cs_debtor_search&nonce=${encodeURIComponent(NONCE)}&q=${encodeURIComponent(query)}`;
        const res = await fetch(url, { signal: abortController.signal, credentials:'same-origin' });
        const data = await res.json();

        if (!data.success) {
          throw new Error((data.data && data.data.error) ? data.data.error : 'Search failed');
        }

        items = (data.data && data.data.items) ? data.data.items : [];
        render();
      } catch (err) {
        if (err.name === 'AbortError') return;
        dropdown.innerHTML = '<div class="br-item"><small>Failed to load</small></div>';
        show();
      }
    };

    input.addEventListener('input', () => {
      hiddenCode.value = '';
      hiddenName.value = '';
      clearTimeout(timer);
      timer = setTimeout(() => search(input.value), 300);
    });

    input.addEventListener('keydown', (e) => {
      if (!dropdown.classList.contains('active')) return;

      const nodes = Array.from(dropdown.querySelectorAll('.br-item[data-index]'));
      if (!nodes.length) return;

      if (e.key === 'ArrowDown') {
        e.preventDefault();
        activeIndex = Math.min(activeIndex + 1, nodes.length - 1);
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        activeIndex = Math.max(activeIndex - 1, 0);
      } else if (e.key === 'Enter') {
        if (activeIndex >= 0) {
          e.preventDefault();
          pick(activeIndex);
        }
      } else if (e.key === 'Escape') {
        hide();
      }

      nodes.forEach((n, i) => n.classList.toggle('active', i === activeIndex));
    });

    dropdown.addEventListener('click', (e) => {
      const node = e.target.closest('.br-item[data-index]');
      if (!node) return;
      pick(parseInt(node.dataset.index, 10));
    });

    document.addEventListener('click', (e) => {
      if (!wrapper.contains(e.target)) {
        hide();
      }
    });
  })();

  $('ac_br_submit').addEventListener('click', async () => {
    if (isSubmitting) return;

    const debtorCode = ($('ac_br_debtor_code').value || '').trim();
    const debtorName = ($('ac_br_debtor_name').value || '').trim();
    const docDate = ($('ac_br_date').value || '').trim();
    const basketQty = whole(($('ac_br_qty').value || '0'));
    const remark = ($('ac_br_remark').value || '').trim();
    const savedLabel = debtorName || debtorCode;

    if (!debtorCode) {
      toast('error', 'Customer required');
      setStatus('Please select customer.');
      return;
    }

    if (!docDate) {
      toast('error', 'Date required');
      setStatus('Today date is missing.');
      return;
    }

    if (basketQty <= 0) {
      toast('error', 'Invalid quantity');
      setStatus('Basket Return Qty must be greater than 0.');
      return;
    }

    if (!REST_RETURN_URL) {
      toast('error', 'Config error');
      setStatus('Basket return endpoint is missing.');
      return;
    }

    const submitBtn = $('ac_br_submit');

    try {
      isSubmitting = true;
      submitBtn.disabled = true;
      submitBtn.textContent = 'Saving...';
      setStatus('Saving basket return...');

      const payload = {
        debtorCode,
        debtorName,
        docDate,
        basketQty,
        remark
      };

      const res = await apiPost(REST_RETURN_URL, payload);
      if (!res || !res.ok) {
        throw new Error((res && (res.message || res.error)) ? (res.message || res.error) : 'Failed to save basket return');
      }

      const suffix = res.duplicate ? ' (duplicate ignored)' : '';
      setStatus(`Saved basket return for ${savedLabel}: ${basketQty}${suffix}`);
      toast('success', 'Basket return saved', `${savedLabel} | Qty ${basketQty}${suffix}`);

      $('ac_br_qty').value = '1';
      $('ac_br_remark').value = '';
      $('ac_br_qty').focus();
    } catch (err) {
      const msg = err && err.message ? err.message : 'Failed to save basket return';
      setStatus(msg);
      toast('error', 'Save failed', msg);
    } finally {
      isSubmitting = false;
      submitBtn.disabled = false;
      submitBtn.textContent = 'Save Basket Return';
    }
  });
})();
</script>

<style id="BasketReturn-mobile-full-copy-variant-style">
  .br-container{max-width:none !important;margin:0 !important;border-radius:0 !important;padding:10px !important;background:#f5f8f6 !important;}
  .br-head{gap:8px !important;margin-bottom:10px !important;}
  .br-head h1{font-size:24px !important;}
  .br-card{padding:12px !important;border-radius:12px !important;}
  .br-label{font-size:12px !important;margin-bottom:5px !important;}
  .br-input{min-height:46px !important;margin-bottom:10px !important;font-size:16px !important;}
  .br-textarea{min-height:84px !important;}
  .br-btn{min-height:50px !important;font-size:19px !important;border-radius:11px !important;position:sticky;bottom:8px;z-index:20;}
  .br-status{font-size:12px !important;margin-top:8px !important;}
</style>	kܳ�����
��E� �;��
	W2h�A infimumsupremum�	f��DO-Create �&�"[xyz-ips snippet="DO-Create"]�����j��DO-CREATE-2 �&��[xyz-ips snippet="DO-CREATE-2"]����(� |��BasketReturn-desktop �
&=K[xyz-ips snippet="BasketReturn-desktop"]����'�(z��BasketReturn-mobile �&;A[xyz-ips snippet="BasketReturn-mobile"]����0��0�'��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"]����(��8��^��KQMenuInfo-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"]����&��@���^��K^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"]����,	�Hy��^��KkDashboardInfo-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�Pр�^��KxDashboardInfo-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�Xl��^��K�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"]����.'�`��^��K�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"]����p�c��]˹-
�����E� �:��:�A infimumsupremum(����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�(р�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�0l��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"]����.'�8���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"]����/͂@C��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"]����+�H���DOcreate-staff-workflow �&`#[xyz-ips snippet="DOcreate-staff-workflow"]����,�P���page-view-delivery-order �&̚[xyz-ips snippet="page-view-delivery-order"]����%�Xv� �BasketSummary-All �&)[xyz-ips snippet="BasketSummary-All"]����6�"`��"�basketdo-assigned-driver-dashboard �&��[xyz-ips snippet="basketdo-assigned-driver-dashboard"]����"��hQ�#�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"]����-�p��$�Delivery-Order-Staff-List �@&�J[xyz-ips snippet="Delivery-Order-Staff-List"]����'�x�W�&�Delivery-Order-Edit �D&F�[xyz-ips snippet="Delivery-Order-Edit"]����p6.c����������
��
 �?�<?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.
 */

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');
$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/VegeBasketDO/do-receipt/';
$records_page_base   = 'https://website.ipohserver.com/VegeBasketDO/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_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-item-nonce="<?php echo esc_attr($item_suggest_nonce); ?>"
     data-show-debtor-code="<?php echo $show_debtor_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)); ?>">

    <!-- Tab Bar (mobile: always two-column grid; desktop: flex with underline) -->
    <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="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">
                    <input type="hidden" id="acd_resp_do_date" value="<?php echo esc_attr($today_date); ?>">

                    <!-- Customer dropdown -->
                    <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>
                            <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 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>
                            <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>
                            <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 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;">
                            <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_do_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_do_kg" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Weight (KG)">
                        </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>

    <!-- ==================== 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">✕</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">×</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">×</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 - mobile always grid, desktop flex with underline */
.acd-resp-tab-bar {
    display: grid;
    grid-template-columns: 1fr 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;
        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 {
        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;
    }
}

/* 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);
}
.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;

�'�߼���������&I
 �?�
    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'),
        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 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) {
            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 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 qty = parseQty(qtyRaw || '0');
            const kg = parseKg(kgRaw || '0');
            const total = calcTotalKg(qty, kg);
            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)}</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&I�����������hl
 �?�-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 : 0,
                cartonQty: !isBasket ? count : 0,
                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) 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 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 || '';
                    $('acd_resp_do_item_price').value = fmtMoney(picked.price || 0);
                    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('clihl�66����������
 �?�ck', 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_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_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 price = parseMoney($('acd_resp_do_item_price').value || '0');
            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,
                        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;
                    if (!jobId) throw new Error(`No job ID returned for ${group.customerName || group.customerCode}`);
                    showToast('info', 'Job queued', `${group.customerName || group.customerCode} | Job #${jobId}`);

                    savedJobs.push({
                        jobId,
                        groupKey: group.key,
                        docNo: '',
                        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 ���P�����������
 �!1����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 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>��x#?s���������
 �?�<?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_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_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);
                $pack_type = $carton > 0 ? 'CTN' : ($basket > 0 ? 'BSK' : '');
                $display_qty = $carton > 0 ? $carton : ($basket > 0 ? $basket : $qty);

                $lines[] = array(
                    'qty' => $display_qty,
                    'kg' => $line['WeightKG'] ?? 0,
                    'totalKg' => $line['WeightKG'] ?? 0,
                    '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';
            $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);

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 (!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/VegeBasketDO/wp-content/uploads/2026/05/Untitled-design-15.png';

$total_ctn = 0;
$total_bsk = 0;
$total_kg = 0;

foreach ($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);
    }

    $total_kg += (float)ac_do_pick($line, array('totalKg', 'TotalKg', 'UDF_WEIGHTKG', 'kg', 'Kg'), 0);
}

$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);
$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 = strtoupper((string)ac_do_pick($line, array('packType', 'PackType'), ''));
    $is_ctn = ($pack_type === 'CARTON' || $pack_type === 'CTN');
    $is_bsk = ($pack_type === 'BASKET' || $pack_type === 'BSK');

    if ($is_ctn) {
        $qty = ac_do_pick($line, array('cartonQty', 'ctnQty', 'qty', 'Qty'), '');
    } elseif ($is_bsk) {
        $qty = ac_do_pick($line, array('basketQty', 'bskQty', 'qty', 'Qty'), '');
    } else {
        $qty = ac_do_pick($line, array('cartonQty', 'basketQty', 'qty', 'Qty'), '');
    }

    $pdf_lines[] = array(
        'qty' => ac_do_num($qty),
        'kg' => ac_do_num(ac_do_pick($line, array('kg', 'Kg', 'UDF_WEIGHTKG'), '')),
        '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(ac_do_pick($line, array('totalKg', 'TotalKg', 'UDF_WEIGHTKG', 'kg', '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,
);
?>

<style>
    * { box-sizing: border-box; }

    .ac-do-page-wrap {
        --ac-do-preview-scale: 1;
        --ac-do-preview-width: 794px;
        --ac-do-preview-height: 1123px;
        --ac-do-preview-gap: 24px;
        width: 100%;����a��������R�
 �?�
        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-share-btn {
        background: #128C7E;
    }

    .ac-do-actions button:disabled {
        cursor: not-allowed;
        opacity: 0.65;
    }

    .ac-do-share-source {
        width: 794px;
        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;
        min-height: 1123px;
        margin: 0 auto;
        background: #fff;
        padding: 34px 42px;
        border: 1px solid #d1d5db;
        position: relative;
    }

    .ac-do-proof-paper { margin-top: 24px; }
    .ac-do-top-label {
        position: absolute;
        top: 36px;
        right: 42px;
        text-align: right;
        margin-bottom: 0;
    }

    .ac-do-top-label span {
        background: #444;
        color: #fff;
        font-weight: 700;
        font-size: 13px;
        padding: 4px 12px;
        border-radius: 10px;
        letter-spacing: 0.5px;
    }

    .ac-do-header {
        display: grid;
        grid-template-columns: 128px 1fr 145px;
        gap: 14px;
        align-items: start;
        margin: 24px 0 12px;
    }

    .ac-do-logo {
        height: 94px;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    .ac-do-logo img {
        display: block;
        max-width: 124px;
        max-height: 86px;
        width: auto;
        height: auto;
        object-fit: contain;
    }

    .ac-do-company h1 {
        margin: 0;
        font-size: 28px;
        letter-spacing: 0.2px;
        line-height: 1.02;
        font-weight: 900;
    }

    .ac-do-company p {
        margin: 3px 0;
        font-size: 10.5px;
        line-height: 1.25;
    }

    .ac-do-doc-no {
        text-align: right;
        padding-top: 74px;
        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: 1fr 190px;
        gap: 20px;
        margin: 8px 0 12px;
        font-size: 13px;
    }

    .ac-do-line-field {
        display: grid;
        grid-template-columns: 70px 1fr;
        align-items: end;
    }

    .ac-do-line-field.ac-do-date { grid-template-columns: 45px 1fr; }
    .ac-do-line-field span { font-weight: 700; }

    .ac-do-line-field div {
        border-bottom: 1px dotted #666;
        min-height: 20px;
        padding-left: 6px;
    }

    .ac-do-table {
        width: 100%;
        border-collapse: collapse;
        background: rgba(255, 255, 255, 0.15);
        font-size: 13px;
    }

    .ac-do-table th,
    .ac-do-table td {
        border: 1px solid #444;
        padding: 5px 6px;
        vertical-align: middle;
    }

    .ac-do-table th {
        text-align: center;
        font-size: 11px;
        line-height: 1.1;
        font-weight: 700;
    }

    .ac-do-qty,
    .ac-do-kg,
    .ac-do-total {
        text-align: center;
        width: 70px;
    }

    .ac-do-desc { width: 360px; }

    .ac-do-pack {
        width: 100px;
        text-align: center;
        font-size: 11px;
        white-space: nowrap;
    }

    .ac-do-checkbox {
        display: inline-block;
        width: 11px;
        height: 11px;
        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: 16px;
        font-weight: 700;
    }

    .ac-do-bottom-area {
        display: grid;
        grid-template-columns: 1fr 270px;
        gap: 20px;
        margin-top: 12px;
        align-items: start;
    }

    .ac-do-slogan {
        font-weight: 700;
        font-style: italic;
        font-size: 15px;
        margin-top: 8px;
    }

    .ac-do-totals {
        display: grid;
        grid-template-columns: 1fr 80px;
        gap: 8px 10px;
        align-items: center;
        font-size: 14px;
    }

    .ac-do-total-label {
        text-align: right;
        line-height: 1.1;
    }

    .ac-do-total-box {
        border: 1px solid #444;
        height: 28px;
        background: rgba(255, 255, 255, 0.25);
        display: flex;
        align-items: center;
        justify-content: center;
        font-weight: 700;
    }

    .ac-do-remark {
        margin-top: 12px;
        font-size: 12px;
    }

    .ac-do-signature-row {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 120px;
        margin-top: 90px;
        font-size: 13px;
    }

    .ac-do-signature {
        border-top: 1px dotted #555;
        padding-top: 6px;
    }

    .ac-proof-title-row {
        display: grid;
        grid-template-columns: 1fr auto;
        gap: 20px;
        align-items: start;
        margin-bottom: 20px;
        border-bottom: 2px solid #333;
        padding-bottom: 12px;
    }

    .ac-proof-title h2 {
        margin: 0;
        font-size: 24px;
        line-height: 1.2;
    }

    .ac-proof-title p {
        margin: 4px 0 0;
        font-size: 13px;
    }

    .ac-proof-meta {
        text-align: right;
        font-size: 13px;
        line-height: 1.5;
    }

    .ac-proof-image-box {
        width: 100%;
        height: 820px;
        border: 1px solid #444;
        background: rgba(255,255,255,0.28);
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 14px;
        overflow: hidden;
    }

    .ac-proof-image-box img {
        max-width: 100%;
        max-height: 790px;
        width: auto;
        height: auto;
        object-fit: contain;
        display: block;
    }

    .ac-proof-empty {
        font-size: 16px;
        font-weight: 700;
        color: #555;
        text-align: center;
    }

    .ac-proof-footer {
        margin-top: 18px;
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 70px;
        font-size: 13px;
    }

    .ac-proof-footer-line {
        border-top: 1px dotted #555;
        padding-top: 6px;
    }

    @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: 1011px;
            --ac-do-preview-gap: 22px;
        }
    }

    @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: 921px;
            --ac-do-preview-gap: 20px;
            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: 809px;
            --ac-do-preview-gap: 17px;
        }
    }

    @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: 696px;
            --ac-do-preview-gap: 15px;
        }
    }

    @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: 584px;
            --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.45;
            --ac-do-preview-width: 357px;
            --ac-do-preview-height: 505px;
            --ac-do-preview-gap: 11px;
        }
    }

    @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: visible !important;
        }

        .ac-do-preview-page {
            width: auto !important;
            height: auto !important;
            margin: 0 auto !important;
            position: static !important;
        }

        .ac-do-paper,
        .ac-do-proof-paper {
            width: 132mm !important;
            min-height: 194mm !important;
            margin: 0 auto !important;
            padding: 6mm !important;
            border: 0 !important;
            box-shadow: none !important;
            overflow: visible !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-proof-image-box {
            height: 130mm !important;
            min-height: 130mm !important;
            max-height: 130mm !important;
        }

        .ac-proof-image-box img {
            max-width: 100% !important;
            max-height: 126mm !important;
            object-fit: contain !important;
        }

        @page {
            size: A5 portrait;
            margin: 6mm;
        }
    }
</style>

<div class="ac-do-page-wrap">
    <div class="ac-do-actions">
        <button type="button" onclick="acDoPrintWithImages()">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-top-label">
            <span>DELIVERY ORDER</span>
        </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 ($lines as $line): ?>
                    <?php
                    $pack_type = strtoupper((string)ac_do_pick($line, array('packType', 'PackType'), ''));
                    $is_ctn = ($pack_type === 'CARTON' || $pack_type === 'CTN');
                    $is_bsk = ($pack_type === 'BASKET' || $pack_type === 'BSK');

                    if ($is_ctn) {
                        $qty = ac_do_pick($line, array('cartonQty', 'ctnQty', 'qty', 'Qty'), '');
                    } elseif ($is_bsk) {
                        $qty = ac_do_pick($line, array('basketQty', 'bskQty', 'qty', 'Qty'), '');
                    } else {
                        $qty = ac_do_pick($line, array('cartonQty', 'basketQty', 'qty', 'Qty'), '');
                    }

                    $kg = ac_do_pick($line, array('kg', 'Kg', 'UDF_WEIGHTKG'), '');
                    $total_line_kg = ac_do_pick($line, array('totalKg', 'TotalKg', 'UDF_WEIGHTKG', 'kg', 'Kg'), '');
                    $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; ?>

             R�����������`�
 �?�   <?php
                $minimum_rows = 14;
                $remaining_rows = max(0, $minimum_rows - count($lines));

                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>

        <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>
    </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 acDoJsPdfPromise = null;

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 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 acDoDrawReceiptCanvas(logoImage) {
    var out = acDoCanvas(1240, 1754);
    var canvas = out.canvas;
    var ctx = out.ctx;
    var data = acDoPdfData || {};
    var lines = Array.isArray(data.lines) ? data.lines : [];
    var pageX = 106;
    var pageY = 58;
    var pageW = 1028;
    var pageH = 1638;
    var tableX = 160;
    var tableY = 360;
    var rowH = 47;
    var col = [tableX, tableX + 105, tableX + 210, tableX + 680, tableX + 830, tableX + 930];
    var minRows = Math.max(14, lines.length);
    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 + 44, pageY + 74, 180, 118)) {
        acDoText(ctx, 'Vege', pageX + 86, pageY + 154, 44, '#164f38', '700');
    }
    acDoText(ctx, data.companyName || '', pageX + 240, pageY + 125, 38, '#0f172a', '900');
    acDoText(ctx, data.companyAddr || '', pageX + 242, pageY + 157, 13, '#111111', '400');
    acDoText(ctx, 'H/P: ' + (data.companyTel || ''), pageX + 242, pageY + 181, 13, '#111111', '700');

    acDoFillRect(ctx, pageX + 785, pageY + 36, 230, 36, '#444444');
    acDoText(ctx, 'DELIVERY ORDER', pageX + 900, pageY + 62, 18, '#ffffff', '700', 'center');
    acDoText(ctx, 'No', pageX + 805, pageY + 190, 20, '#111111', '400');
    acDoText(ctx, data.docNo || '', pageX + 835, pageY + 190, 27, '#ef4444', '700');

    acDoText(ctx, 'Customer', pageX + 63, pageY + 250, 18, '#111111', '700');
    acDoText(ctx, (data.customerName || '') + (data.customerCode ? ' (' + data.customerCode + ')' : ''), pageX + 170, pageY + 250, 18, '#111111', '400');
    acDoLine(ctx, pageX + 150, pageY + 260, pageX + 650, pageY + 260, '#666666', 1);
    acDoText(ctx, 'Date', pageX + 770, pageY + 250, 18, '#111111', '700');
    acDoText(ctx, data.displayDate || '', pageX + 835, pageY + 250, 18, '#111111', '400');
    acDoLine(ctx, pageX + 830, pageY + 260, pageX + 980, pageY + 260, '#666666', 1);

    acDoRect(ctx, tableX, tableY, col[5] - col[0], rowH * (minRows + 1), '#333333', 1.2);
    for (i = 1; i < col.length - 1; i++) {
        acDoLine(ctx, col[i], tableY, col[i], tableY + rowH * (minRows + 1), '#333333', 1);
    }
    for (i = 1; i <= minRows + 1; i++) {
        acDoLine(ctx, tableX, tableY + rowH * i, col[5], tableY + rowH * i, '#333333', 1);
    }

    acDoText(ctx, '\u6570\u91cf', tableX + 55, tableY + 20, 15, '#111111', '700', 'center');
    acDoText(ctx, 'Quantity', tableX + 55, tableY + 39, 13, '#111111', '700', 'center');
    acDoText(ctx, '\u516c\u65a4', col[1] + 55, tableY + 20, 15, '#111111', '700', 'center');
    acDoText(ctx, 'Kg', col[1] + 55, tableY + 39, 13, '#111111', '700', 'center');
    acDoText(ctx, '\u8d27\u7269\u540d\u79f0', col[2] + 235, tableY + 20, 15, '#111111', '700', 'center');
    acDoText(ctx, 'Description', col[2] + 235, tableY + 39, 13, '#111111', '700', 'center');
    acDoText(ctx, '\u7bb1 / \u7bee', col[3] + 75, tableY + 20, 15, '#111111', '700', 'center');
    acDoText(ctx, 'Box / Basket', col[3] + 75, tableY + 39, 13, '#111111', '700', 'center');
    acDoText(ctx, '\u603b\u516c\u65a4', col[4] + 55, tableY + 20, 15, '#111111', '700', 'center');
    acDoText(ctx, 'Total Kg', col[4] + 55, tableY + 39, 13, '#111111', '700', 'center');

    for (i = 0; i < minRows; i++) {
        y = tableY + rowH * (i + 1);
        item = lines[i] || {};

        acDoText(ctx, item.qty || '', tableX + 55, y + 30, 18, '#111111', '400', 'center');
        acDoText(ctx, item.kg || '', col[1] + 55, y + 30, 18, '#111111', '400', 'center');
        acDoWrap(ctx, item.description || '', col[2] + 10, y + 22, 450, 18, 18, '#111111', '400', 2);
        acDoDrawCheckbox(ctx, col[3] + 25, y + 16, !!item.isCtn);
        acDoText(ctx, 'Ctn', col[3] + 43, y + 29, 14, '#111111', '400');
        acDoDrawCheckbox(ctx, col[3] + 86, y + 16, !!item.isBsk);
        acDoText(ctx, 'Bsk', col[3] + 104, y + 29, 14, '#111111', '400');
        acDoText(ctx, item.totalKg || '', col[4] + 55, y + 30, 18, '#111111', '400', 'center');
    }

    y = tableY + rowH * (minRows + 1) + 35;
    acDoText(ctx, 'We Do The EXCELLENT Way', tableX, y + 25, 23, '#111111', '700');

    if (data.remark) {
        acDoText(ctx, 'Remark: ' + data.remark, tableX, y + 65, 16, '#111111', '700');
    }

    var totalX = pageX + 705;
    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 * 48, 18, '#111111', '700', 'right');
        acDoText(ctx, totalRows[i][1], totalX, totalY + 38 + i * 48, 16, '#111111', '400', 'right');
        acDoFillRect(ctx, totalX + 20, totalY + 4 + i * 48, 100, 37, 'rgba(255,255,255,0.25)');
        acDoRect(ctx, totalX + 20, totalY + 4 + i * 48, 100, 37, '#333333', 1);
        acDoText(ctx, totalRows[i][2], totalX + 70, totalY + 30 + i * 48, 18, '#111111', '700', 'center');
    }

    acDoLine(ctx, tableX, pageY + pageH - 140, tableX + 260, pageY + pageH - 140, '#555555', 1);
    acDoLine(ctx, pageX + pageW - 420, pageY + pageH - 140, pageX + pageW - 160, pageY + pageH - 140, '#555555', 1);
    acDoText(ctx, '\u7ecf\u624b\u4eba Issued by', tableX, pageY + pageH - 110, 17, '#111111', '400');
    acDoText(ctx, '\u6536\u8d27\u4eba Received by', pageX + pageW - 420, pageY + pageH - 110, 17, '#111111', '400');

    return canvas;
}

function acDoDrawProofCanvas() {
    var out = acDoCanvas(1240, 1754);
    var canvas = out.canvas;
    var ctx = out.ctx;
    var data = acDoPdfData || {};
    var pageX = 106;
    var pageY = 58;
    var pageW = 1028;
    var pageH = 1638;

    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 + 105, 36, '#111111', '700');
    acDoText(ctx, data.companyName || '', pageX + 58, pageY + 142, 18, '#111111', '400');
    acDoText(ctx, 'DO No: ' + (data.docNo || ''), pageX + pageW - 60, pageY + 100, 18, '#111111', '700', 'right');
    acDoText(ctx, 'Customer: ' + (data.customerName || ''), pageX + pageW - 60, pageY + 132, 18, '#111111', '400', 'right');
    acDoText(ctx, 'Date: ' + (data.displayDate || ''), pageX + pageW - 60, pageY + 164, 18, '#111111', '400', 'right');
    acDoLine(ctx, pageX + 58, pageY + 190, pageX + pageW - 58, pageY + 190, '#333333', 3);

    acDoRect(ctx, pageX + 58, pageY + 240, pageW - 116, 1210, '#333333', 1.5);
    acDoText(ctx, 'No proof of delivery image uploaded yet.', pageX + pageW / 2, pageY + 850, 24, '#555555', '700', 'center');

    acDoLine(ctx, pageX + 58, pageY + pageH - 120, pageX + 410, pageY + pageH - 120, '#555555', 1);
    acDoLine(ctx, pageX + pageW - 410, pageY + pageH - 120, pageX + pageW - 58, pageY + pageH - 120, '#555555', 1);
    acDoText(ctx, 'Driver / Issued by', pageX + 58, pageY + pageH - 90, 17, '#111111', '400');
    acDoText(ctx, 'Customer / Received by', pageX + pageW - 410, pag`�M��O��������`�
 �
<����eY + pageH - 90, 17, '#111111', '400');

    return canvas;
}

function acDoBuildPdfBlob() {
    return acDoLoadJsPdf()
        .then(function(jsPDF) {
            return Promise.all([
                Promise.resolve(jsPDF),
                acDoLoadCanvasImage(acDoPdfData.companyLogoUrl)
            ]);
        })
        .then(function(result) {
            var jsPDF = result[0];
            var logoImage = result[1];
            var pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a5' });
            var pageOne = acDoDrawReceiptCanvas(logoImage);

            pdf.addImage(pageOne.toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, 148, 210);
            pdf.addPage();
            pdf.addImage(acDoDrawProofCanvas().toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, 148, 210);

            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 acDoSharePdf(button) {
    var originalText = button ? button.textContent : '';
    var images = document.querySelectorAll('.ac-do-proof-paper img');
    var waits = [];

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

    images.forEach(function(img) {
        waits.push(acDoWaitImage(img));
    });

    if (button) {
        button.disabled = true;
        button.textContent = 'Preparing PDF...';
    }

    Promise.all(waits)
        .then(acDoBuildPdfBlob)
        .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() {
            if (button) {
                button.disabled = false;
                button.textContent = originalText || 'Share PDF';
            }
        });
}

function acDoPrintWithImages() {
    var images = document.querySelectorAll('.ac-do-proof-paper img');
    var waits = [];

    images.forEach(function(img) {
        waits.push(acDoWaitImage(img));
    });

    Promise.all(waits).then(function() {
        setTimeout(function() {
            window.print();
        }, 300);
    });
}
</script>`�5�0����������
 �?�<?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/VegeBasketDO/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-table-wrap">
          <table class="bs-table bs-ledger-table">
            <thead>
              <tr>
                <th style="width:60px;">No</th>
                <th style="width:120px;">Date</th>
                <th style="width:120px;">Movement</th>
                <th style="width:90px;">Qty</th>
                <th style="width:150px;">From</th>
                <th style="width:140px;">Document No.</th>
                <th>Note</th>
                <th style="width:150px;">Receipt</th>
              </tr>
            </thead>
            <tbody id="ac_bs_ledger_table">
              <tr><td colspan="8" 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-soft:#f0fdf4;
  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-head{display:none;}
.bs-card{background:#fff;border:1px solid var(--bs-border);border-radius:14px;box-shadow:0 4px 16px rgba(15,23,42,.05);padding:16px;margin-bottom:14px;box-sizing:border-box;}
.bs-grid{display:grid;grid-template-columns:1fr 1fr 1fr auto;gap:10px;align-items:end;}
.bs-label{display:block;font-size:13px;color:var(--bs-muted);margin-bottom:6px;font-weight:700;}
.bs-input{width:100%;min-height:46px;font-size:15px;padding:10px 12px;border-radius:10px;border:1px solid var(--bs-border-strong);box-sizing:border-box;background:#fff;color:#111;}
.bs-input:focus,.bs-btn:focus,.bs-view-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;border:0!important;border-radius:12px!important;background:linear-gradient(90deg,#16a34a,#166534)!important;color:#fff!important;font-size:15px!important;font-weight:800!important;padding:10px 18px!important;cursor:pointer;box-shadow:0 8px 18px rgba(22,101,52,.18)!important;}
#ac-basket-summary-root #ac_bs_refresh.bs-btn:hover{background:linear-gradient(90deg,#15803d,#14532d)!important;transform:translateY(-1px);}
.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:#f8fafc;border-radius:10px;padding:10px;}
.bs-total-box.issue{border-color:#fecaca;background:#fff7f7;}
.bs-total-label{font-size:12px;color:#64748b;font-weight:700;}
.bs-total-value{font-size:22px;font-weight:800;color:#0f172a;margin-top:4px;}
.bs-total-sub{font-size:12px;color:#64748b;font-weight:700;margin-top:3px;line-height:1.25;}
.bs-table-wrap{width:100%;overflow:auto;border:1px solid #e5e7eb;border-radius:12px;}
.bs-table{width:100%;min-width:980px;border-collapse:collapse;background:#fff;}
.bs-table thead th{background:#f8fafc;color:#334155;font-size:13px;font-weight:900;text-align:left;padding:10px 12px;border-bottom:1px solid #e5e7eb;white-space:nowrap;}
.bs-table tbody td{padding:10px 12px;font-size:14px;line-height:1.25;color:#0f172a;border-bottom:0;vertical-align:top;}
.bs-table tbody tr{box-shadow:inset 0 -1px 0 #edf2f7;}
.bs-table tbody tr:nth-child(odd){background:#ffffff;}
.bs-table tbody tr:nth-child(even){background:#f1f8f3;}
.bs-table tbody tr:hover{background:#e8f5ec;}
.bs-empty-cell{color:#64748b;text-align:center;padding:18px!important;}
.bs-view-btn{-webkit-appearance:none!important;appearance:none!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;min-height:34px!important;border:1px solid #16a34a!important;border-radius:8px!important;background:#f0fdf4!important;color:#166534!important;font-size:12px!important;font-weight:800!important;line-height:1.15!important;padding:6px 10px!important;text-shadow:none!important;box-shadow:none!important;cursor:pointer!important;}
.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;}
.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 6px 14px rgba(22,101,52,.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:4px 10px;font-size:12px;font-weight:800;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:800;}
.bs-type-return{color:#9a3412;font-weight:800;}
.bs-search-wrap{position:relative;}
.bs-search-wrap .bs-input{padding-right:2.35rem;}
.bs-field-clear{position:absolute;top:50%;right:.35rem;transform:translateY(-50%);width:1.65rem;height:1.65rem;border:1px solid var(--bs-border);background:#fff;color:#64748b;border-radius:.42rem;display:none;align-items:center;justify-content:center;font-size:.9rem;cursor:pointer;padding:0;}
.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:150px;min-width:0;border:1px solid #bbf7d0;background:#f0fdf4;color:#166534;border-radius:999px;padding:5px 8px;font-size:12px;font-weight:800;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:900!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(440px, calc(100vw - 48px));display:none;background:#fff;border:1px solid #cbd5e1;border-radius:12px;box-shadow:0 18px 40px rgba(15,23,42,.18);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:8px!important;padding:7px 10px!important;font-size:12px!important;font-weight:900!important;cursor:pointer!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:8px;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:8px;}
.bs-ledger-modal,.bs-picker-modal{position:fixed;inset:0;z-index:99990;display:none;align-items:center;justify-content:center;padding:1rem;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,.55);}
.bs-ledger-dialog{position:relative;width:min(1040px, calc(100vw - 2rem));max-height:calc(100vh - 2rem);background:#fff;border-radius:14px;box-shadow:0 24px 60px rgba(15,23,42,.28);display:flex;flex-direction:column;overflow:hidden;}
.bs-ledger-head,.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-ledger-head .bs-subtitle{margin:0;font-size:18px;line-height:1.2;}
.bs-ledger-close,.bs-picker-close{width:34px;heig�[i�_��������
'�
 �?�ht:34px;border:1px solid #dbe4ee!important;border-radius:8px!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-ledger-body{padding:10px;overflow:auto;}
.bs-ledger-table{min-width:760px!important;table-layout:fixed;}
.bs-ledger-table th,.bs-ledger-table td{padding:8px 10px!important;font-size:13px!important;line-height:1.25!important;vertical-align:middle!important;}
.bs-ledger-loading-cell{padding:36px 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:800;}
.bs-ledger-spinner{width:34px;height:34px;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:.75rem;box-shadow:0 1rem 2rem rgba(0,0,0,.18);overflow:hidden;}
.bs-picker-title{font-size:.95rem;font-weight:800;}
.bs-picker-body{padding:.7rem .8rem .8rem;display:flex;flex-direction:column;gap:.45rem;}
.bs-picker-results{max-height:16rem;overflow-y:auto;display:flex;flex-direction:column;gap:.35rem;}
.bs-picker-note{text-align:center;padding:.55rem;color:var(--bs-muted);font-size:.8rem;}
.bs-picker-item{display:block;width:100%;text-align:left;padding:.62rem .7rem;border:1px solid var(--bs-border);border-radius:.55rem;background:#fff;color:var(--bs-text);cursor:pointer;}
.bs-picker-item-main{display:block;font-weight:800;font-size:.88rem;color:var(--bs-text);}
.bs-picker-item-sub{display:block;font-size:.72rem;color:var(--bs-muted);margin-top:.08rem;}

/* Driver-style Basket Return Receipt */
.bs-br-overlay{position:fixed;inset:0;z-index:100001;background:rgba(15,23,42,.55);overflow:auto;padding:24px;display:flex;align-items:flex-start;justify-content:center;font-family:Arial,Helvetica,sans-serif;color:#111;}
.bs-br-modal{width:min(560px, calc(100vw - 40px));background:#f3f4f6;border-radius:16px;padding:14px;box-shadow:0 24px 70px 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:15px!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-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:.6rem;}
  .bs-card{padding:.6rem;margin-bottom:.5rem;border-radius:.75rem;box-shadow:0 1px 3px rgba(15,23,42,.04);}
  .bs-head{display:flex;align-items:center;margin-bottom:12px;}
  .bs-head h1{font-size:1.35rem;}
  .bs-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:.45rem;}
  .bs-actions{grid-column:1 / -1;}
  .bs-btn{width:100%;min-height:2.35rem;border-radius:.55rem;}
  .bs-table{min-width:880px;}
  .bs-table thead th,.bs-table tbody td{font-size:.78rem;padding:.5rem .6rem;}
  .bs-totals{gap:.45rem;margin-bottom:.6rem;}
  .bs-total-value{font-size:1rem;}
}
@media (max-width:680px){
  .bs-container{padding:.45rem;}
  .bs-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:.4rem;}
  .bs-input{min-height:2.2rem;padding:.45rem .58rem;font-size:.9rem;border-radius:.5rem;}
  .bs-totals{gap:.3rem;margin-bottom:.45rem;}
  .bs-total-label{font-size:.56rem;}
  .bs-total-value{font-size:.84rem;}
  .bs-br-overlay{padding:12px;}
  .bs-br-modal{width:100%;border-radius:12px;padding:12px;}
  .bs-br-info,.bs-br-actions{grid-template-columns:1fr;}
}
@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(/[&<>"']/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 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 filterRowsB
'�\,���������
i�
 �?�ySelectedCustomers(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.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>${i+1}</td>
        <td>${esc(code)}</td>
        <td>${esc(name)}</td>
        <td>${fmtQty(send)}</td>
        <td>${fmtQty(ret)}</td>
        <td><span class="bs-chip ${cls}" title="${esc(outTitle)}" aria-label="${esc(outTitle)}">${fmtQty(out)}</span></td>
        <td>${esc(lastDate)}</td>
        <td><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 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 = () => rejec
i�)4�q��������
��
 �?�t(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;
    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="8" 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>`;
  }

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

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

      return `<tr>
        <td>${i+1}</td>
        <td>${esc(r.txnDate || r.txn_date || '-')}</td>
        <td><span class="${typeClass}">${esc(label)}</span></td>
        <td>${fmtQty(r.qty || 0)}</td>
        <td>${esc(r.sourceType || r.source_type || '-')}</td>
        <td>${esc(r.sourceRef || r.source_ref || '-')}</td>
        <td>${esc(r.remark || r.note || '-')}</td>
        <td>${receiptBtn}</td>
      </tr>`;
    }).join('');
  }

  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="8" 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_sear
��68���������
��
 �����ch').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').addEventListener('click', loadSummary);
  $('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 btn = e.target.closest('[data-basket-receipt-idx]');
    if (!btn) return;
    e.preventDefault();
    e.stopPropagation();
    openBasketReceiptByIndex(btn.dataset.basketReceiptIdx);
  });
  $('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();
  updateCustomerClearButton();
  loadSummary();
})();
</script>
��ڹ!����������
 �?�<?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.
 * - Driver confirms received before marking delivered.
 * - 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);
$is_admin        = current_user_can('manage_options');

if (!$is_driver && !$is_admin) {
    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_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,
            '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);
        $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("SELECT * 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_������������I

 �?�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_job_summary')) {
    function bdo_drv_job_summary($job) {
        $payload = bdo_drv_json($job->payload ?? '');
        $result  = bdo_drv_json($job->result ?? '');
        $lines   = bdo_drv_sum_lines($payload);

        $doc_no = bdo_drv_pick($result, ['docNo', 'DocNo', 'doc_no'], '');
        if ($doc_no === '') {
            $doc_no = bdo_drv_pick($payload, ['docNo', 'DocNo', 'sourceDocNo', 'SourceDocNo'], 'JOB-' . (int) ($job->id ?? 0));
        }

        $doc_key       = (int) bdo_drv_pick($result, ['docKey', 'DocKey', 'doc_key'], 0);
        $customer_name = bdo_drv_pick($payload, ['customerName', 'CustomerName', 'debtorName', 'DebtorName', 'customer_name'], 'Customer');
        $customer_code = bdo_drv_pick($payload, ['customerCode', 'CustomerCode', 'debtorCode', 'DebtorCode', 'customer_code'], '');
        $location      = bdo_drv_pick($payload, ['location', 'Location', 'headerLocation', 'HeaderLocation'], 'HQ');

        $address_parts = [];
        foreach (['deliveryAddress', 'DeliveryAddress', 'address', 'Address', 'shipToAddress', 'ShipToAddress', 'address1', 'Address1', 'address2', 'Address2', 'address3', 'Address3', 'address4', 'Address4'] as $key) {
            $value = bdo_drv_pick($payload, [$key], '');
            if ($value !== '') {
                $address_parts[] = $value;
            }
        }

        $created_at = (string) ($job->created_at ?? '');
        $doc_date   = (string) bdo_drv_pick($payload, ['docDate', 'DocDate'], $created_at);
        $date_ts    = strtotime($doc_date);
        $job_id     = (int) ($job->id ?? 0);

        return [
            'id'             => $job_id,
            'docNo'          => (string) $doc_no,
            'docKey'         => $doc_key,
            'customerName'   => (string) $customer_name,
            'customerCode'   => (string) $customer_code,
            'address'        => implode(', ', array_unique($address_parts)),
            'location'       => (string) $location,
            'status'         => (string) ($job->status ?? ''),
            'deliveryStatus' => (string) ($job->delivery_status ?? ''),
            'createdAt'      => $created_at,
            'displayDate'    => $date_ts ? date('d/m/Y', $date_ts) : date('d/m/Y'),
            'proofUrl'       => bdo_drv_get_do_proof_url($job_id, $doc_key, $doc_no),
            'summary'        => $lines,
        ];
    }
}

if (!function_exists('bdo_drv_driver_where')) {
    function bdo_drv_driver_where($jobs_table, $jobs_table_safe, $current_user_id) {
        $cols = bdo_drv_table_columns($jobs_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_job')) {
    function bdo_drv_get_assigned_job($job_id, $jobs_table, $jobs_table_safe, $current_user_id) {
        if (!$job_id || !bdo_drv_table_exists($jobs_table)) {
            return null;
        }

        $cols = bdo_drv_table_columns($jobs_table);
        if (!isset($cols['assigned_driver_id'])) {
            return null;
        }

        $where = 'id = %d AND assigned_driver_id = %d';
        $args  = [(int) $job_id, (int) $current_user_id];

        return $GLOBALS['wpdb']->get_row($GLOBALS['wpdb']->prepare("SELECT * FROM `{$jobs_table_safe}` WHERE {$where} LIMIT 1", $args));
    }
}

$jobs_table         = $wpdb->prefix . 'ac_jobs';
$ledger_table       = $wpdb->prefix . 'ac_basket_ledger';
$basket_proof_table = $wpdb->prefix . 'ac_basket_return_proof_images';
$jobs_table_safe    = preg_replace('/[^A-Za-z0-9_]/', '', $jobs_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);
$jobs_cols          = bdo_drv_table_exists($jobs_table) ? bdo_drv_table_columns($jobs_table) : [];

$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 ($action === 'confirm_received') {
        $job_id = isset($_POST['job_id']) ? absint($_POST['job_id']) : 0;
        $job    = bdo_drv_get_assigned_job($job_id, $jobs_table, $jobs_table_safe, $current_user_id);

        if (!$job) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Assigned delivery not found.')], $current_url));
            exit;
        }

        $delivery_status = strtoupper((string) ($job->delivery_status ?? ''));
        if (in_array($delivery_status, ['DELIVERED', 'COMPLETED', 'CANCELLED'], true)) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('This delivery is already closed.')], $current_url));
            exit;
        }

        if (strtoupper((string) ($job->status ?? '')) !== 'SUCCESS') {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Official AutoCount DO is not ready yet. Please ask staff to reprint after sync completes.')], $current_url));
            exit;
        }

        $job_summary = bdo_drv_job_summary($job);
        if (empty($job_summary['docNo']) || strpos((string) $job_summary['docNo'], 'JOB-') === 0) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Official AutoCount DO number is missing. Please ask staff to check this order.')], $current_url));
            exit;
        }

        $update = [];
        if (isset($jobs_cols['delivery_status'])) {
            $update['delivery_status'] = 'OUT_FOR_DELIVERY';
        }
        if (isset($jobs_cols['driver_received_by'])) {
            $update['driver_received_by'] = $current_user_id;
        }
        if (isset($jobs_cols['driver_received_at'])) {
            $update['driver_received_at'] = current_time('mysql');
        }
        if (isset($jobs_cols['driver_receive_note'])) {
            $update['driver_receive_note'] = 'Driver confirmed received printed DO and goods.';
        }
        if (isset($jobs_cols['updated_at'])) {
            $update['updated_at'] = current_time('mysql');
        }

        if (!empty($update)) {
            $wpdb->update($jobs_table, $update, ['id' => $job_id], null, ['%d']);
        }

        wp_safe_redirect(add_query_arg(['bdo_msg' => 'received', 'bdo_job_id' => $job_id, 'bdo_tab' => 'deliveries', 'bdo_filter' => 'assigned'], $current_url));
        exit;
    }

    if ($action === 'complete_delivery') {
        $job_id = isset($_POST['job_id']) ? absint($_POST['job_id']) : 0;
        $job    = bdo_drv_get_assigned_job($job_id, $jobs_table, $jobs_table_safe, $current_user_id);

        if (!$job) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Assigned delivery not found.')], $current_url));
            exit;
        }

        $delivery_status = strtoupper((string) ($job->delivery_status ?? ''));
        if (!in_array($delivery_status, ['OUT_FOR_DELIVERY', 'DRIVER_ACKNOWLEDGED'], true)) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Please confirm received before marking delivered.')], $current_url));
            exit;
        }

        if (strtoupper((string) ($job->status ?? '')) !== 'SUCCESS') {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Official AutoCount DO is not ready yet.')], $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_job_summary($job);
            bdo_drv_insert_proof(array_merge($upload, [
                'job_id'  => $job_id,
                'doc_no'  => $summary['docNo'],
                'doc_key' => $summary['docKey'],
            ]));
        }

        $update = [];
        if (isset($jobs_cols['delivery_status'])) {
            $update['delivery_status'] = 'DELIVERED';
        }
        if (isset($jobs_cols['delivery_completed_by'])) {
            $update['delivery_completed_by'] = $current_user_id;
        }
        if (isset($jobs_cols['delivery_completed_at'])) {
            $update['delivery_completed_at'] = current_time('mysql');
        }
        if (isset($jobs_cols['delivery_note'])) {
            $update['delivery_note'] = !empty($upload['uploaded']) ? 'Completed with proof of delivery' : 'Completed without proof of delivery';
        }
        if (isset($jobs_cols['updated_at'])) {
            $update['updated_at'] = current_time('mysql');
        }

        if (!empty($update)) {
            $wpdb->update($jobs_table, $update, ['id' => $job_id], null, ['%d']);
        }

        wp_safe_redirect(add_query_arg(['bdo_msg' => 'delivered', 'bdo_job_id' => $job_id, 'bdo_tab' => 'deliveries', 'bdo_filter' => 'delivered'], $current_url));
        exit;
    }

    if ($action === 'report_shortage') {
        $job_id = isset($_POST['job_id']) ? absint($_POST['job_id']) : 0;
        $job    = bdo_drv_get_assigned_job($job_id, $jobs_table, $jobs_table_safe, $current_user_id);

        if (!$job) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Assigned delivery not found.')], $current_url));
            exit;
        }

        $delivery_status = strtoupper((string) ($job->delivery_status ?? ''));
        if (in_array($delivery_status, ['DELIVERED', 'COMPLETED', 'CANCELLED', 'NEEDS_STAFF_EDIT'], true)) {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('This delivery cannot be sent back for edit now.')], $current_url));
            exit;
        }

        if (strtoupper((string) ($job->status ?? '')) !== 'SUCCESS') {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Official AutoCount DO is not ready yet.')], $current_url));
            exit;
        }

        $update = [];
        if (isset($jobs_cols['delivery_status'])) {
            $update['delivery_status'] = 'NEEDS_STAFF_EDIT';
        }
        if (isset($jobs_cols['delivery_note'])) {
            $update['delivery_note'] = 'Driver reported not enough item. Printed DO should be returned to staff for edit and reprint.';
        }
        if (isset($jobs_cols['driver_receive_note'])) {
            $update['driver_receive_note'] = 'Not enough item. Driver sent DO back to staff for correction.';
        }
        if (isset($jobs_cols['updated_at'])) {
            $update['updated_at'] = current_time('mysql');
        }

        if (!empty($update)) {
            $wpdb->update($jobs_table, $update, ['id' => $job_id], null, ['%d']);
        }

        wp_safe_redirect(add_query_arg(['bdo_msg' => 'shortage', 'bdo_job_id' => $job_id, 'bdo_tab' => 'deliveries', 'bdo_filter' => 'assigned'], $current_url));
        exit;
    }

    if ($action === 'basket_return') {
        $debtor_code = isset($_POST['br_debtor_code']) ? sanitize_text_field(wp_unslash($_POST['br_debtor_code'])) : '';
        $debtor_name = isset($_POST['br_debtor_name']) ? sanitize_text_field(wp_unslash($_POST['br_debtor_name'])) : '';
        $basket_qty  = isset($_POST['br_basket_qty']) ? absint($_POST['br_basket_qty']) : 0;

        if ($debtor_code === '' || $debtor_name === '') {
            wp_safe_redirect(add_query_arg(['bdo_err' => rawurlencode('Please select customer for basket return.')], $current_url));
            exit;
        }

        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));
            exit;
        }

        $source_ref = 'BR-' . current_time('YmdHis') . '-' . $current_user_id;
        $cols       = bdo_drv_table_columns($ledger_table);
        $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,
             I
� �����������/
 �?�    '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($jobs_table) ? bdo_drv_driver_where($jobs_table, $jobs_table_safe, $current_user_id) : ['sql' => ' AND 1 = 0 ', 'args' => [], 'usable' => false];

$stats = [
    'assigned'  => 0,
    'received'  => 0,
    'delivered' => 0,
    'returns'   => 0,
];

$active_jobs    = [];
$delivered_jobs = [];
$return_rows    = [];
$basket_receipts = [];

if (bdo_drv_table_exists($jobs_table) && $driver_where['usable']) {
    $base_args = $driver_where['args'];

    $stats['assigned'] = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM `{$jobs_table_safe}` WHERE job_type = 'DELIVERY_ORDER' AND status = 'SUCCESS' AND delivery_status = 'ASSIGNED' {$driver_where['sql']}",
        $base_args
    ));

    $stats['received'] = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM `{$jobs_table_safe}` WHERE job_type = 'DELIVERY_ORDER' AND status = 'SUCCESS' AND delivery_status IN ('OUT_FOR_DELIVERY','DRIVER_ACKNOWLEDGED') {$driver_where['sql']}",
        $base_args
    ));

    $stats['delivered'] = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM `{$jobs_table_safe}` WHERE job_type = 'DELIVERY_ORDER' AND status = 'SUCCESS' AND delivery_status = 'DELIVERED' AND DATE(COALESCE(delivery_completed_at, updated_at, created_at)) = %s {$driver_where['sql']}",
        array_merge([$today], $base_args)
    ));

    $active_sql = "SELECT * FROM `{$jobs_table_safe}`
        WHERE job_type = 'DELIVERY_ORDER'
        AND status = 'SUCCESS'
        AND delivery_status IN ('ASSIGNED','OUT_FOR_DELIVERY','DRIVER_ACKNOWLEDGED')
        {$driver_where['sql']}
        ORDER BY
            CASE delivery_status
                WHEN 'OUT_FOR_DELIVERY' THEN 1
                WHEN 'DRIVER_ACKNOWLEDGED' THEN 2
                WHEN 'ASSIGNED' THEN 3
                ELSE 4
            END,
            id DESC
        LIMIT 100";

    foreach ((array) $wpdb->get_results($wpdb->prepare($active_sql, $base_args)) as $row) {
        $active_jobs[] = bdo_drv_job_summary($row);
    }

    $delivered_sql = "SELECT * FROM `{$jobs_table_safe}`
        WHERE job_type = 'DELIVERY_ORDER'
        AND status = 'SUCCESS'
        AND delivery_status = 'DELIVERED'
        AND DATE(COALESCE(delivery_completed_at, updated_at, created_at)) = %s
        {$driver_where['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_job_summary($row);
    }
}

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 !== '' && isset($ledger_cols['txn_type'])) {
        $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 === 'received') {
    $alert_html = '<div class="bdo-alert ok">Delivery received. You can now mark it delivered after drop-off.</div>';
} elseif ($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>';
} elseif ($message === 'shortage') {
    $alert_html = '<div class="bdo-alert ok">Sent back to staff for DO edit and reprint.</div>';
}

if ($error !== '') {
    $alert_html = '<div class="bdo-alert err">' . esc_html($error) . '</div>';
}

if (!$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(4,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-grid .bdo-quick:nth-child(3){grid-column:1/-1;min-height:82px}
.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:#b9ddc8!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-weigh�/rv�H ���������R
 �?�!t: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;border:0;border-radius:14px;background:var(--main);color:#fff;font-size:26px;font-weight:800;line-height:1;cursor:pointer}
.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-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="assigned"><strong id="bdoStatAssigned">0</strong><span>Need<br>Receive</span></button>
                <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>Return<br>Basket</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="assigned"><div class="bdo-quick-icon">DO</div><strong>Confirm Receive</strong><span>Confirm printed DO and goods received from staff.</span></button>
                <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">Assigned Deliveries</div><div class="bdo-card-sub">Only jobs 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">Confirm received first, then mark delivered</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">×</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">×</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 stats = JSON.parse(root.dataset.stats || '{}');
    let activeJob = activeJobs.length ? activeJobs[0] : null;
    let currentPanel = 'home';
    let deliveryFilter = ['assigned','received','delivered','returns'].includes(root.dataset.initialFilter || '') ? root.dataset.initialFilter : 'assigned';
    let submittingForm = false;
    let brJsPdfPromise = null;
    const receiptLogoUrl = 'https://website.ipohserver.com/VegeBasketDO/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(opts.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) {
        $('bdoStatAssigned').textContent = s.assigned || 0;
        $('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();
        window.scrollTo({top:0, behavior:'smooth'});
    }

    function statusLabel(job) {
        const st = String(job?.deliveryStatus || '').toUpperCase();
        if (st === 'ASSIGNED') return 'NEED RECEIVE';
        if (st === 'OUT_FOR_DELIVERY' || st === 'DRIVER_ACKNOWLEDGED') return 'OUT FOR DELIVERY';
        if (st === 'DELIVERED') return 'DELIVERED';
        return st.replaceAll('_', ' ') || 'READY';
    }

    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 || w�R�s��!��������t
 �?�"hole(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 shortageForm(jobId) {
        return `
            <form method="post" class="bdo-action-form bdo-shortage-form">
                <input type="hidden" name="bdo_driver_action" value="report_shortage">
                <input type="hidden" name="bdo_driver_nonce" value="__FORM_NONCE__">
                <input type="hidden" name="job_id" value="${jobId}">
                <button type="submit" class="bdo-btn bdo-btn-warn" data-bdo-loading-text="Sending...">Not Enough Item - Send Back To Staff</button>
            </form>`;
    }

    function activeCardHtml(job, compact=false) {
        if (!job || !job.id) {
            return '<div class="bdo-empty">No ready delivery yet. It will appear after staff assigns the DO and AutoCount sync completes.</div>';
        }

        const st = String(job.deliveryStatus || '').toUpperCase();
        const needsReceive = st === 'ASSIGNED';
        const canDeliver = st === 'OUT_FOR_DELIVERY' || st === 'DRIVER_ACKNOWLEDGED';
        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${needsReceive ? ' wait' : ''}">${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}">${needsReceive ? 'Confirm Receive' : 'Open Delivery'}</button>`;
        } else if (needsReceive) {
            html += `
                <div style="height:12px"></div>
                <form method="post" class="bdo-action-form">
                    <input type="hidden" name="bdo_driver_action" value="confirm_received">
                    <input type="hidden" name="bdo_driver_nonce" value="__FORM_NONCE__">
                    <input type="hidden" name="job_id" value="${job.id}">
                    <button type="submit" class="bdo-btn" data-bdo-loading-text="Confirming...">Confirm Received From Staff</button>
                </form>
                ${shortageForm(job.id)}`;
        } 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 needsReceive = st === 'ASSIGNED';
        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${needsReceive ? ' wait' : ''}">${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}">${needsReceive ? 'Confirm Receive' : '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 = 'Deliveries waiting for receive confirmation';
        let rows = activeJobs.filter(j => String(j.deliveryStatus || '').toUpperCase() === 'ASSIGNED');
        let renderer = deliveryListCard;

        if (deliveryFilter === 'received') {
            title = 'Deliveries ready for POD';
            rows = activeJobs.filter(j => ['OUT_FOR_DELIVERY','DRIVER_ACKNOWLEDGED'].includes(String(j.deliveryStatus || '').toUpperCase()));
        } else 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;
        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(ts뜅"��������Ef
 �?�#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, 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 (!navigatoEf�]QP#��������Ef
 �3�����r.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.classList.contains('bdo-shortage-form') && e.target.dataset.bdoConfirmed !== '1') {
            e.preventDefault();

            const ok = await confirmModal({
                icon: 'warning',
                title: 'Send back to staff?',
                text: 'Use this when items are not enough and staff needs to edit and reprint this DO.',
                confirmButtonText: 'Send to staff',
                confirmButtonColor: '#b45309'
            });

            if (!ok) {
                return;
            }

            e.target.dataset.bdoConfirmed = '1';
            e.target.requestSubmit();
            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 clearBrCustomer() {
        $('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();
        }
    });
    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'] : ''),
    '__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);Ef�n5�@��������� 
 �?�A<?php
if (!defined('ABSPATH')) exit;

/*
 * VegeBasketDO staff Delivery Order list.
 *
 * 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;
}

if (!function_exists('get_mssql')) {
    echo '<div class="wst-dod-alert wst-dod-alert-error">Delivery Order connection is not available.</div>';
    return;
}

$conn = get_mssql();
if (!$conn) {
    echo '<div class="wst-dod-alert wst-dod-alert-error">Failed to connect to Delivery Order records.</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_errors')) {
    function wst_dod_errors() {
        if (!function_exists('sqlsrv_errors')) return 'Unknown SQLSRV error.';

        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
        if (empty($errors)) return 'Unknown SQLSRV error.';

        $out = array();
        foreach ($errors as $e) {
            $out[] = '[' . ($e['code'] ?? '') . '] ' . ($e['message'] ?? '');
        }

        return implode(' | ', $out);
    }
}

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 !== '') {
            $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_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',
            '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',
        );

        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'), true)) {
            return 'wst-dod-badge-good';
        }

        if (in_array($value, array('FAILED', 'CANCELLED'), true)) {
            return 'wst-dod-badge-danger';
        }

        if (in_array($value, array('PENDING', 'EDIT_PENDING_AUTOCOUNT', 'NEEDS_STAFF_EDIT'), 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.',
            '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.',
        );

        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();

        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('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' : 'captured_at';

        $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_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_doc_key_from_data')) {
    function wst_dod_doc_key_from_data($data) {
        if (!is_array($data)) return 0;

        foreach (array('docKey', 'DocKey', 'dockey') 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', 'sourceDocNo', 'oldDocNo', 'originalDocNo') as $key) {
            if (!empty($data[$key])) {
                return strtoupper(trim((string)$data[$key]));
            }
        }

        return '';
    }
}

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', 'assignedDriverLogin', 'driverLogin') as $key) {
            if (!empty($payload[$key])) {
                return trim((string)$payload[$key]);
            }
        }

        return '';
    }
}

if (!function_exists('wst_dod_display_status_from_job')) {
    function wst_dod_display_status_from_job($syncStatus, $deliveryStatus, $jobSubtype, $assignedDriverId) {
        $syncStatus = strtoupper(trim((string)$syncStatus));
        $deliveryStatus = strtoupper(trim((string)$deliveryStatus));
        $jobSubtype = strtoupper(trim((string)$jobSubtype));
        $assignedDriverId = (int)$assignedDriverId;

        if (in_array($syncStatus, array('PENDING', 'PROCESSING', 'FAILED', 'FAILED_FINAL'), true)) {
            return $syncStatus;
        }

        if (
            in_array($jobSubtype, array('EDIT', 'UPDATE'), true)
            && $syncStatus === 'SUCCESS'
            && $deliveryStatus === 'EDIT_PENDING_AUTOCOUNT'
        ) {
            return $assignedDriverId > 0 ? 'ASSIGNED' : 'EDITED_IN_AUTOCOUNT';
        }

        return $deliveryStatus !== '' ? $deliveryStatus : $syncStatus;
    }
}

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_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', 'code'), ''),
            'name' => wst_dod_pick_payload_value($line, array('description', 'Description', 'itemName', 'name'), ''),
            'qty' => (float)($line['qty'] ?? $line['Qty'] ?? 0),
            'basket' => (float)($line['basketQty'] ?? $line['basket'] ?? $line['Basket'] ?? $line['UDF_BASKET'] ?? 0),
            'carton' => (float)($line['cartonQty'] ?? $line['carton'] ?? $line['Carton'] ?? $line['UDF_CARTON'] ?? 0),
            'weightKg' => (float)($line['kg'] ?? $line['weightKg'] ?? $line['WeightKG'] ?? $line['UDF_WEIGHTKG'] ?? 0),
        );
    }
}

if (!function_exists('wst_dod_pending_row_from_job')) {
    function wst_dod_pending_row_from_job($job, $payload, $item) {
        $lines = isset($payload['lines']) && is_array($payload['lines']) ? $payload['lines'] : array();
        $items = array_map('wst_dod_item_from_payload_line', $lines);
        $totalBasket = 0.0;
        $totalCarton = 0.0;

        foreach ($items as $line) {
            $totalBasket += (float)($line['basket'] ?? 0);
            $totalCarton += (float)($line['carton'] ?? 0);
        }

        $docDate = wst_dod_pick_payload_value($payload, array('docDate', 'DocDate'), '');
        if ($docDate === '') {
            $docDate = substr((string)($job['created_at'] ?? ''), 0, 10);
        }

        return array(
            'docKey' => 0,
            'docNo' => '',
            'docDate' => $docDate,
            'debtorCode' => wst_dod_pick_payload_value($payload, array('customerCode', 'debtorCode', 'DebtorCode'), ''),
            'debtorName' => wst_dod_pick_payload_value($payload, array('customerName', 'debtorName', 'DebtorName'), ''),
            'autoCountStatus' => (string)($item['syncStatus'] ?? ''),
            'displayStatus' => (string)($item['displayStatus'] ?? ''),
            'driver' => (string)($item['driver'] ?? ''),
            'jobId' => (int)($item['jobId'] ?? 0),
            'jobSubtype' => (string)($item['jobSubtype'] ?? ''),
            'syncStatus' => (string)($item['syncStatus'] ?? ''),
            'deliveryStatus' => (string)($item['deliveryStatus'] ?? ''),
            'jobError' => (string)($item['errorMessage'] ?? ''),
            'createdAt' => wst_dod_datetime($job['created_at'] ?? ''),
            'lastModified' => wst_dod_datetime($job['updated_at'] ?? ''),
            'totalBasket' => $totalBasket,
            'totalCarton' => $totalCarton,
            'items' => $items,
            'proofImage' => '',
            'hasAuthoritativeJob' => true,
            'isPendingJob' => true,
        );
    }
}

if (!function_exists('wst_dod_staff_edit_locked_statuses')) {
    function wst_dod_staff_edit_locked_statuses() {
        return array(
            'DRIVER_ACKNOWLEDGED',
            'RECEIVED',
            'OUT_FOR_DELIVERY',
            'DELIVERED',
        );
    }
}

if (!function_exists('wst_dod_can_staff_edit_row')) {
    function wst_dod_can_staff_edit_row($row) {
        if (!empty($row['isPendingJob'])) return false;
        if (empty($row['hasAuthoritativeJob'])) return false;
        if ((int)($row['docKey'] ?? 0) <= 0) return false;
        if (trim((string)($row['docNo'] ?? '')) === '') return false;

        $locked_statuses = wst_dod_staff_edit_locked_statuses();
        $display_status = strtoupper(trim((string)($row['displayStatus'] ?? '')));
        $delivery_status = strtoupper(trim((string)($row['deliveryStatus'] ?? '')));

        return !in_array($display_status, $locked_statuses, true)
            && !in_array($delivery_status, $locked_statuses, true);
    }
}

if (!function_exists('wst_dod_staff_edit_disabled_reason')) {
    function wst_dod_staff_edit_disabled_reason($row) {
        if (!empty($row['isPendi� ���A��������3
 �?�BngJob'])) {
            return 'AutoCount is still processing this order.';
        }

        if (empty($row['hasAuthoritativeJob'])) {
            return 'This order has no authoritative WordPress delivery status record.';
        }

        if ((int)($row['docKey'] ?? 0) <= 0 || trim((string)($row['docNo'] ?? '')) === '') {
            return 'This order is missing the AutoCount document reference.';
        }

        return 'Driver already confirmed receiving this order, so staff editing is locked.';
    }
}

if (!function_exists('wst_dod_load_job_map')) {
    function wst_dod_load_job_map($customer = '', $status = 'ALL', $dateFrom = '', $dateTo = '') {
        global $wpdb;

        if (!$wpdb) return array('byDocNo' => array(), 'byDocKey' => array(), 'pendingRows' => array());

        $table = $wpdb->prefix . 'ac_jobs';
        if (!wst_dod_wp_table_exists($table)) {
            return array('byDocNo' => array(), 'byDocKey' => array(), 'pendingRows' => array());
        }

        $safe_table = preg_replace('/[^A-Za-z0-9_]/', '', $table);
        $cols = wst_dod_wp_table_columns($table);

        $select = array('id', 'payload', 'result', 'status', 'created_at', 'updated_at');
        foreach (array('job_subtype', 'delivery_status', 'assigned_driver_id', 'completed_at', 'error_message', 'source_doc_no', 'source_doc_key') as $col) {
            if (isset($cols[$col])) {
                $select[] = $col;
            }
        }

        $rows = $wpdb->get_results(
            "SELECT " . implode(',', array_map(function($c) { return "`{$c}`"; }, $select)) . "
             FROM `{$safe_table}`
             WHERE job_type = 'DELIVERY_ORDER'
             ORDER BY id DESC
             LIMIT 800",
            ARRAY_A
        );

        $map = array('byDocNo' => array(), 'byDocKey' => array(), 'pendingRows' => array());

        foreach ((array)$rows as $job) {
            $payload = wst_dod_read_json_array($job['payload'] ?? '');
            $result = wst_dod_read_json_array($job['result'] ?? '');

            $docNo = isset($job['source_doc_no']) ? strtoupper(trim((string)$job['source_doc_no'])) : '';
            if ($docNo === '') {
                $docNo = wst_dod_doc_no_from_data($result);
            }
            if ($docNo === '') {
                $docNo = wst_dod_doc_no_from_data($payload);
            }

            $docKey = isset($job['source_doc_key']) ? (int)$job['source_doc_key'] : 0;
            if ($docKey <= 0) {
                $docKey = wst_dod_doc_key_from_data($result);
            }
            if ($docKey <= 0) {
                $docKey = wst_dod_doc_key_from_data($payload);
            }

            $deliveryStatus = strtoupper(trim((string)($job['delivery_status'] ?? '')));
            $syncStatus = strtoupper(trim((string)($job['status'] ?? '')));
            $jobSubtype = strtoupper(trim((string)($job['job_subtype'] ?? '')));
            $assignedDriverId = (int)($job['assigned_driver_id'] ?? 0);
            $displayStatus = wst_dod_display_status_from_job($syncStatus, $deliveryStatus, $jobSubtype, $assignedDriverId);

            if (
                in_array($jobSubtype, array('EDIT', 'UPDATE'), true)
                && $syncStatus === 'SUCCESS'
                && $deliveryStatus === 'EDIT_PENDING_AUTOCOUNT'
            ) {
                $displayStatus = $assignedDriverId > 0 ? 'ASSIGNED' : 'EDITED_IN_AUTOCOUNT';

                if (isset($cols['delivery_status'])) {
                    $repair_update = array('delivery_status' => $displayStatus);
                    $repair_formats = array('%s');

                    if (isset($cols['updated_at'])) {
                        $repair_update['updated_at'] = current_time('mysql');
                        $repair_formats[] = '%s';
                    }

                    $wpdb->update(
                        $table,
                        $repair_update,
                        array('id' => (int)($job['id'] ?? 0)),
                        $repair_formats,
                        array('%d')
                    );

                    $deliveryStatus = $displayStatus;
                }
            }

            $item = array(
                'jobId' => (int)($job['id'] ?? 0),
                'jobSubtype' => $jobSubtype,
                'deliveryStatus' => $deliveryStatus,
                'syncStatus' => $syncStatus,
                'displayStatus' => $displayStatus,
                'driver' => wst_dod_driver_label_from_job($job, $payload),
                'createdAt' => (string)($job['created_at'] ?? ''),
                'updatedAt' => (string)($job['updated_at'] ?? ''),
                'completedAt' => (string)($job['completed_at'] ?? ''),
                'errorMessage' => (string)($job['error_message'] ?? ''),
            );

            if ($docNo === '' && $docKey <= 0) {
                $pendingRow = wst_dod_pending_row_from_job($job, $payload, $item);
                $haystack = strtoupper(implode(' ', array(
                    $pendingRow['debtorCode'],
                    $pendingRow['debtorName'],
                    'JOB-' . $pendingRow['jobId'],
                )));
                $pendingDate = substr((string)$pendingRow['docDate'], 0, 10);
                $wantedStatus = strtoupper(trim((string)$status));

                if (
                    ($customer === '' || strpos($haystack, strtoupper($customer)) !== false)
                    && ($dateFrom === '' || $pendingDate >= $dateFrom)
                    && ($dateTo === '' || $pendingDate <= $dateTo)
                    && (
                        $wantedStatus === 'ALL'
                        || strtoupper($pendingRow['displayStatus']) === $wantedStatus
                        || strtoupper($pendingRow['autoCountStatus']) === $wantedStatus
                        || strtoupper($pendingRow['syncStatus']) === $wantedStatus
                        || strtoupper($pendingRow['deliveryStatus']) === $wantedStatus
                    )
                ) {
                    $map['pendingRows'][] = $pendingRow;
                }

                continue;
            }

            if ($docNo !== '' && !isset($map['byDocNo'][$docNo])) {
                $map['byDocNo'][$docNo] = $item;
            }

            if ($docKey > 0 && !isset($map['byDocKey'][$docKey])) {
                $map['byDocKey'][$docKey] = $item;
            }
        }

        return $map;
    }
}

$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',
    'PENDING' => 'Pending AutoCount',
    'SUCCESS' => 'Created',
    'FAILED' => 'AutoCount Failed',
    'PENDING_DELIVERY' => 'Pending Delivery',
    'ASSIGNED' => 'Assigned',
    'DRIVER_ACKNOWLEDGED' => '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',
    'ACTIVE' => 'Active',
    'CANCELLED' => 'Cancelled',
);

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

$where = array();
$params = array();

if ($customer !== '') {
    $where[] = "(DOH.DebtorCode LIKE ? OR DOH.DebtorName LIKE ? OR DOH.DocNo LIKE ?)";
    $like = '%' . str_replace(array('%', '_', '['), array('[%]', '[_]', '[[]'), $customer) . '%';
    $params[] = $like;
    $params[] = $like;
    $params[] = $like;
}

$where[] = "CAST(DOH.DocDate AS date) >= ?";
$params[] = $dateFrom;

$where[] = "CAST(DOH.DocDate AS date) <= ?";
$params[] = $dateTo;

if ($status === 'ACTIVE') {
    $where[] = "ISNULL(DOH.Cancelled,'F') <> 'T'";
} elseif ($status === 'CANCELLED') {
    $where[] = "ISNULL(DOH.Cancelled,'F') = 'T'";
}

$whereSql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';

$sql = "
    SELECT TOP ($limit)
        DOH.DocKey,
        DOH.DocNo,
        DOH.DocDate,
        DOH.DebtorCode,
        DOH.DebtorName,
        ISNULL(DOH.DocStatus,'') AS DocStatus,
        ISNULL(DOH.Cancelled,'F') AS Cancelled,
        DOH.CreatedTimeStamp,
        DOH.LastModified,
        ISNULL(DOH.UDF_SUMBASKET, 0) AS TotalBasket,
        ISNULL(DOH.UDF_SUMCARTON, 0) AS TotalCarton
    FROM dbo.DO AS DOH
    $whereSql
    ORDER BY DOH.DocDate DESC, DOH.DocKey DESC
";

$stmt = sqlsrv_query($conn, $sql, $params);

if ($stmt === false) {
    $err = wst_dod_errors();
    wst_dod_log_error($err);
    echo '<div class="wst-dod-alert wst-dod-alert-error">Failed to load Delivery Order records.'
        . ($show_technical_errors ? ' ' . esc_html($err) : '')
        . '</div>';
    return;
}

$jobMap = wst_dod_load_job_map($customer, $status, $dateFrom, $dateTo);
$rows = array();

while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
    $docKey = (int)($row['DocKey'] ?? 0);
    $docNo = (string)($row['DocNo'] ?? '');
    $docStatus = strtoupper(trim((string)($row['DocStatus'] ?? '')));
    $cancelled = strtoupper(trim((string)($row['Cancelled'] ?? 'F')));

    $autoCountStatus = ($cancelled === 'T')
        ? 'CANCELLED'
        : (($docStatus === '' || $docStatus === 'A') ? 'ACTIVE' : $docStatus);

    $job = array();
    $docNoKey = strtoupper(trim($docNo));
    if ($docNoKey !== '' && isset($jobMap['byDocNo'][$docNoKey])) {
        $job = $jobMap['byDocNo'][$docNoKey];
    } elseif ($docKey > 0 && isset($jobMap['byDocKey'][$docKey])) {
        $job = $jobMap['byDocKey'][$docKey];
    }

    $displayStatus = !empty($job['displayStatus']) ? $job['displayStatus'] : $autoCountStatus;

    $syncStatus = strtoupper(trim((string)($job['syncStatus'] ?? '')));
    $deliveryStatus = strtoupper(trim((string)($job['deliveryStatus'] ?? '')));

    if (
        $status !== 'ALL'
        && strtoupper($displayStatus) !== $status
        && strtoupper($autoCountStatus) !== $status
        && $syncStatus !== $status
        && $deliveryStatus !== $status
    ) {
        continue;
    }

    $rows[] = array(
        'docKey' => $docKey,
        'docNo' => $docNo,
        'docDate' => wst_dod_date($row['DocDate'] ?? null),
        'debtorCode' => (string)($row['DebtorCode'] ?? ''),
        'debtorName' => (string)($row['DebtorName'] ?? ''),
        'autoCountStatus' => $autoCountStatus,
        'displayStatus' => $displayStatus,
        'driver' => (string)($job['driver'] ?? ''),
        'jobId' => (int)($job['jobId'] ?? 0),
        'jobSubtype' => (string)($job['jobSubtype'] ?? ''),
        'syncStatus' => (string)($job['syncStatus'] ?? ''),
        'deliveryStatus' => (string)($job['deliveryStatus'] ?? ''),
        'jobError' => (string)($job['errorMessage'] ?? ''),
        'createdAt' => wst_dod_datetime($row['CreatedTimeStamp'] ?? null),
        'lastModified' => wst_dod_datetime($row['LastModified'] ?? null),
        'totalBasket' => (float)($row['TotalBasket'] ?? 0),
        'totalCarton' => (float)($row['TotalCarton'] ?? 0),
        'items' => array(),
        'proofImage' => wst_dod_get_proof_image_by_doc($docNo, $docKey),
        'hasAuthoritativeJob' => !empty($job),
    );
}

sqlsrv_free_stmt($stmt);

$rows = array_merge($jobMap['pendingRows'] ?? array(), $rows);
usort($rows, function($a, $b) {
    $aTime = strtotime((string)($a['lastModified'] ?? '')) ?: strtotime((string)($a['createdAt'] ?? '')) ?: strtotime((string)($a['docDate'] ?? '')) ?: 0;
    $bTime = strtotime((string)($b['lastModified'] ?? '')) ?: strtotime((string)($b['createdAt'] ?? '')) ?: strtotime((string)($b['docDate'] ?? '')) ?: 0;

    if ($aTime === $bTime) {
        return (int)($b['jobId'] ?? 0) <=> (int)($a['jobId'] ?? 0);
    }

    return $bTime <=> $aTime;
});

$detailWarning = '';

if (!empty($rows)) {
    $docKeys = array_values(array_filter(array_map(function($r) {
        return (int)$r['docKey'];
    }, $rows)));

    if (!empty($docKeys)) {
        $placeholders = implode(',', array_fill(0, count($docKeys), '?'));

        $detailSql = "
            SELECT
                DTL.DocKey,
                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 IN ($placeholders)
            ORDER BY DTL.DocKey ASC, ISNULL(DTL.Seq, 0) ASC, ISNULL(DTL.ItemCode, '') ASC
        ";

        $detailStmt = sqlsrv_query($conn, $detailSql, $docKeys);

        if ($detailStmt === false) {
            $detailWarning = wst_dod_errors();
            wst_dod_log_error($detailWarning);
        } else {
            $itemsByDocKey = array();

            while ($d = sqlsrv_fetch_array($detailStmt, SQLSRV_FETCH_ASSOC)) {
                $docKey = (int)($d['DocKey'] ?? 0);

                if (!isset($itemsByDocKey[$docKey])) {
                    $itemsByDocKey[$docKey] = array();
                }

                $itemsByDocKey[$docKey][] = array(
                    'itemCode' => (string)($d['ItemCode'] ?? ''),
                    'name' => (string)($d['Description'] ?? ''),
                    'qty' => (float)($d['Qty'] ?? 0),
                    'basket' => (float)($d['Basket'] ?? 0),
                    'carton' => (float)($d['Carton'] ?? 0),
                    'weightKg' => (float)($d['WeightKG'] ?? 0),
                );
            }

            sqlsrv_free_stmt($detailStmt);

            foreach ($rows as $idx => $r) {
                if ((int)($r['docKey'] ?? 0) > 0) {
                    $rows[$idx]['items'] = $itemsByDocKey[$r['docKey']] ?? array();
                }
            }
        }
    }
}
?>

<div class="wst-dod-wrap">
    <div class="wst-dod-filter-card">
        <form method="get" class="wst-dod-form">
            <div class="wst-dod-field wst-dod-search-field">
                <label class="wst-dod-label" for="wstDodCustomer">Customer / Doc No</label>
                <input id="wstDodCustomer" name="customer" class="wst-dod-input" type="search" value="<?php echo esc_attr($customer); ?>" placeholder="Search customer or DO 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" t3}l5-B��������x
 �?�Cype="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>
    </div>

    <?php if ($detailWarning !== ''): ?>
        <div class="wst-dod-alert wst-dod-alert-warning">
            Item detail could not be loaded.
            <?php if ($show_technical_errors): ?>
                <?php echo esc_html($detailWarning); ?>
            <?php endif; ?>
        </div>
    <?php endif; ?>

    <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</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
                            $edit_url = add_query_arg(
                                array(
                                    'docNo' => $r['docNo'],
                                    'docKey' => (int)$r['docKey'],
                                ),
                                $edit_page_url
                            );
                            $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);
                            $badgeClass = wst_dod_status_class($r['displayStatus']);
                            $canEdit = wst_dod_can_staff_edit_row($r);
                            $editDisabledReason = $canEdit ? '' : wst_dod_staff_edit_disabled_reason($r);
                            ?>

                            <tr class="wst-dod-main-row">
                                <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'] ?: '-'); ?>
                                    <?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['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>
                                </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">
                                    <a class="wst-dod-action-btn wst-dod-action-view" href="<?php echo esc_url($view_url); ?>">
                                        View
                                    </a>
                                    <?php if ($canEdit): ?>
                                        <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($editDisabledReason); ?>"
                                            aria-label="<?php echo esc_attr($editDisabledReason); ?>"
                                        >Edit</span>
                                    <?php endif; ?>
                                </td>
                            </tr>
                        <?php endforeach; ?>
                    <?php endif; ?>
                </tbody>
            </table>
        </div>
    </div>
</div>

<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-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-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:1120px;
    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-col-date{width:13%;}
.wst-dod-col-doc{width:10%;}
.wst-dod-col-customer{width:20%;}
.wst-dod-col-driver{width:11%;}
.wst-dod-col-status{width:11%;}
.wst-dod-col-summary{width:7%;}
.wst-dod-col-items{width:20%;}
.wst-dod-col-action{width:8%;}

.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-action{
    white-space:nowrap;
    vertical-align:top;
}

.wst-dod-action .wst-dod-action-btn + .wst-dod-action-btn{
    margin-left:1rem;
}

.wst-dod-action-btn{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    min-height:30px;
    padding:6px 7px;
    border-radius:6px;
    border:1px solid;
    font-size:11px;
    font-weight:900;
    line-height:1;
    text-decoration:none;
    flex:0 0 auto;
}

.wst-dod-action-view{
    background:#f8fafc;
    border-color:#cbd5e1;
    color:#334155;
}

.wst-dod-action-edit{
    background:#f0fdf4;
    border-color:#86efac;
    color:#166534;
}

.wst-dod-action-disabled{
    background:#f8fafc;
    border-color:#e2e8f0;
    color:#94a3b8;
    cursor:not-allowed;
}

.wst-dod-action-btn:hover{
    filter:brightness(.97);
}

.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:2x�.0:C��������x
 ����px;
    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:1120px;
    }
}

@media (max-width:480px){
    .wst-dod-filter-grid{
        grid-template-columns:1fr;
    }

    .wst-dod-table{
        min-width:1080px;
    }
}
</style>x^
�D���������,
 �?�E<?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;
}

if (!function_exists('get_mssql')) {
    echo '<div class="wst-doe-alert wst-doe-alert-error">Delivery Order connection is not available.</div>';
    return;
}

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() {
        return array(
            'ASSIGNED',
            'PENDING_DELIVERY',
            'NEEDS_STAFF_EDIT',
        );
    }
}

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

        $table = $wpdb->prefix . 'ac_jobs';
        if (!wst_doe_table_exists($table)) {
            return new WP_Error('jobs_table_missing', 'AutoCount jobs table was not found. Editing is blocked.');
        }

        $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';

        $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)) {
            return new WP_Error('authoritative_job_missing', 'No authoritative AutoCount job record was found for this Delivery Order. Editing is blocked.');
        }

        $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;
        }

        $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_doe_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() {
        return array(
            'DRIVER_ACKNOWLEDGED',
            'RECEIVED',
            'OUT_FOR_DELIVERY',
            'DELIVERED',
        );
    }
}

if (!function_exists('wst_doe_is_edit_locked_status')) {
    function wst_doe_is_edit_locked_status($delivery_status) {
        $delivery_status = strtoupper(trim((string) $delivery_status));
        return in_array($delivery_status, wst_doe_edit_locked_statuses(), true);
    }
}

if (!function_exists('wst_doe_load_order')) {
    function wst_doe_load_order($conn, $doc_no, $doc_key) {
        $header_sql = "
            SELECT TOP 1
                DOH.DocKey,
                DOH.DocNo,
                DOH.DocDate,
                ISNULL(DOH.DebtorCode, '') AS DebtorCode,
                ISNULL(DOH.DebtorName, '') AS DebtorName,
                ISNULL(DOH.SalesAgent, '') AS SalesAgent
            FROM dbo.DO AS DOH
            WHERE DOH.DocNo = ? AND DOH.DocKey = ?
        ";
        $stmt = sqlsrv_query($conn, $header_sql, array($doc_no, $doc_key), array('QueryTimeout' => 10));
        if ($stmt === false) {
            return new WP_Error('do_header_failed', wst_doe_sql_errors());
        }

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

        if (!$header) {
            return new WP_Error('do_not_found', 'Delivery Order was not found for this DocNo/DocKey.');
        }

        $detail_sql = "
            SELECT
                ISNULL(DTL.ItemCode, '') AS ItemCode,
                ISNULL(DTL.Description, '') AS Description,
                ISNULL(DTL.UOM, '') AS UOM,
                ISNULL(DTL.Qty, 0) AS Qty,
                ISNULL(DTL.UnitPrice, 0) AS UnitPrice,
                ISNULL(DTL.UDF_BASKET, 0) AS Basket,
                ISNULL(DTL.UDF_CARTON, 0) AS Carton,
                ISNULL(DTL.UDF_WEIGHTKG, 0) AS WeightKG,
                ISNULL(DTL.Location, '') AS Location
            FROM dbo.DODTL AS DTL
            WHERE DTL.DocKey = ?
            ORDER BY ISNULL(DTL.Seq, 0) ASC, ISNULL(DTL.ItemCode, '') ASC
        ";
        $detail_stmt = sqlsrv_query($conn, $detail_sql, array($doc_key), array('QueryTimeout' => 10));
        if ($detail_stmt === false) {
            return new WP_Error('do_detail_failed', wst_doe_sql_errors());
        }

        $lines = array();
        while ($line = sqlsrv_fetch_array($detail_stmt, SQLSRV_FETCH_ASSOC)) {
            $basket = wst_doe_float($line['Basket'] ?? 0);
            $carton = wst_doe_float($line['Carton'] ?? 0);
            $total_kg = wst_doe_float($line['Qty'] ?? 0);
            $pack_type = $carton > 0 ? 'CARTON' : 'BASKET';
            $unit_qty = $pack_type === 'CARTON' ? $carton : $basket;
            if ($unit_qty <= 0) {
                $unit_qty = 1;
            }
            $kg_per_unit = wst_doe_float($line['WeightKG'] ?? 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['ItemCode'] ?? '')),
                'itemName' => trim((string) ($line['Description'] ?? '')),
                'uom' => trim((string) ($line['UOM'] ?? 'KG')),
                'unitPrice' => wst_doe_float($line['UnitPrice'] ?? 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'] ?? '')),
            );
        }
        sqlsrv_free_stmt($detail_stmt);

        return array(
            'docKey' => (int) ($header['DocKey'] ?? 0),
            'docNo' => trim((string) ($header['DocNo'] ?? '')),
            'docDate' => ($header['DocDate'] ?? null) instanceof DateTime ? $header['DocDate']->format('Y-m-d') : wst_doe_valid_date((string) ($header['DocDate'] ?? ''), current_time('Y-m-d')),
            'debtorCode' => trim((string) ($header['DebtorCode'] ?? '')),
          �,�A�yE���������?
 �?�F  'debtorName' => trim((string) ($header['DebtorName'] ?? '')),
            'salesAgent' => trim((string) ($header['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_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];
        }

        $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) {
            return new WP_Error('invalid_item', 'Invalid item, inactive item, or missing BaseUOM row: ' . $item_code);
        }

        $cache[$cache_key] = 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),
        );

        return $cache[$cache_key];
    }
}

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;

        $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');
        $doc_no = wst_doe_clean_doc_no($payload['docNo'] ?? '');
        $doc_key = absint($payload['docKey'] ?? 0);
        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 NOT (
                       job_subtype IN ('UPDATE', 'EDIT')
                       AND delivery_status 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.');
        }

        $current_delivery_status = strtoupper(trim((string) ($authoritative_job['delivery_status'] ?? '')));
        if (!in_array($current_delivery_status, wst_doe_editable_statuses(), true)) {
            $wpdb->query('ROLLBACK');
            return new WP_Error('delivery_status_locked', 'This Delivery Order can no longer be edited in its current delivery status: ' . $current_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($wpdb->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_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/');

if ($wst_doe_doc_no === '' || $wst_doe_doc_key <= 0) {
    echo '<div class="wst-doe-alert wst-doe-alert-error">Invalid Delivery Order link. DocNo and DocKey are required.</div>';
    return;
}

$wst_doe_conn = get_mssql();
if (!$wst_doe_conn) {
    echo '<div class="wst-doe-alert wst-doe-alert-error">Failed to connect to Delivery Order records.</div>';
    return;
}

$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_job = wst_doe_get_related_job($wst_doe_doc_no, $wst_doe_doc_key);
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_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'] ?? '')));

if (wst_doe_is_edit_locked_status($wst_doe_delivery_status)) {
    echo '<div class="wst-doe-alert wst-doe-alert-error">This Delivery Order can no longer be edited because the current delivery status is ' . esc_html(str_replace('_', ' ', $wst_doe_delivery_status)) . '.</div>';
    return;
}

if (!in_array($wst_doe_delivery_status, wst_doe_editable_statuses(), true)) {
    echo '<div class="wst-doe-alert wst-doe-alert-error">This Delivery Order is not in an editable delivery status.</div>';
    return;
}

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_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 = wst_doe_valid_date((string) ($wst_doe_order['docDate'] ?? ''), current_time('Y-m-d'));
        $posted_remark = 'Staff edited existing Delivery Order.';

        if ($posted_type !== 'DELIVERY_ORDER' || $posted_subtype !== 'UPDATE') {
            $wst_doe_error = 'Invalid update request type.';
        } elseif ($posted_doc_no !== $wst_doe_doc_no || $posted_doc_key !== $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)) {
            $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($ws�?���YF��������R
 �?�Gt_doe_conn, $item_code);
                if (is_wp_error($validated_item)) {
                    $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($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);
                $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,
                    'assignedDriverId' => $posted_driver_id,
                    'driverId' => $posted_driver_id,
                    'lines' => $payload_lines,
                    'client_request_id' => $client_request_id,
                );

                $queued = wst_doe_queue_update_job($payload, $client_request_id, $posted_driver_id, $wst_doe_original_job_id);
                if (is_wp_error($queued)) {
                    $wst_doe_error = $queued->get_error_message();
                } else {
                    $redirect = add_query_arg(
                        array(
                            'wst_doe_saved' => (int) $queued,
                            'docNo' => rawurlencode($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); ?>">
        <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']); ?>">
        <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 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>
                <small>DocKey <?php echo esc_html((string) $wst_doe_doc_key); ?></small>
            </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-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 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>
                                <button type="button" id="acdRespDriverClear" class="acd-resp-field-clear" aria-label="Clear driver">&times;</button>
                            </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>
                                <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="">
                            </div>
                        </div>
                        <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;"><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_do_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_do_kg" class="acd-resp-input" value="" step="0.01" min="0" placeholder="Weight (KG)"></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" 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 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)); }, $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{maRxT
�G��������]f
 �?�Hrgin-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-green);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(14rem,2fr) .85fr .65fr .65fr .85fr 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:54rem}.acd-resp-line{display:grid;grid-template-columns:minmax(14rem,2fr) .85fr .65fr .65fr .85fr 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-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('keyd]f�ܽcH��������e�
 �?�Iown', 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:'#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)}</p>
                ${driverChanged ? `<p style="padding:.7rem .8rem;border:1px solid #fbbf24;background:#fef3c7;color:#92400e;border-radius:.55rem"><strong>Driver reassignment:</strong><br>Driver will change from ${esc(originalDriver)} to ${esc(selectedDriver)}.<br>This means ${esc(selectedDriver)} will take this Delivery Order after AutoCount update and reprint.</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>This will queue an AutoCount update for the existing DO. Staff should reprint the corrected DO after the update is ready.</p>
            </div>`;

        if (window.Swal) {
            const result = await Swal.fire({
                icon: driverChanged ? 'warning' : 'question',
                title: 'Confirm AutoCount update',
                html,
                confirmButtonText: 'Confirm Queue Update',
                confirmButtonColor: '#166534',
                showCancelButton: true,
                cancelButtonText: 'Cancel / Review Again',
                reverseButtons: true,
                focusCancel: driverChanged
            });
            return !!result.isConfirmed;
        }

        return localDialog({
            icon: driverChanged ? 'warning' : 'question',
            title: 'Confirm AutoCount update',
            html,
            confirmText: 'Confirm Queue Update',
            cancelText: 'Cancel / Review Again',
            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 qty = parseQty(qtyRaw || '0');
        const kg = parseKg(kgRaw || '0');
        const total = calcTotalKg(qty, kg);
        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)}</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 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)}">`).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>
            </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><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()}));
        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=''; 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=''; 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_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}})),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 || ''; updateEntryTotal(); updateClearButtons(); closePicker(); }}); }

    $('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,10); if (!isNaN(idx) && pickerState.items[idx] && pickerState.onPick) pickerState.onPick(pickerState.items[idx].raw); });
    $('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);
    document.querySelectorAll('#acd_resp_do_pack_type_toggle .acd-resp-type-btn').forEach(btn => btn.addEventListener('click', () => setPackType(btn.dataset.packType)));
    $('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 qty = parseQty($('acd_resp_do_qty').value || '0'); const kg = parseKg($('acd_resp_do_kg').value || '0'); 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 (qty <= 0) { toast('error','Qty must be >0'); return; } if (kg <= 0) { toast('error','KG must be >0'); return; }
        const nextLine = {itemCode,itemName,packType,qty,kg,total:calcTotalKg(qty,kg)};
        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 => {
     e�A,�I��������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>e��D

Youez - 2016 - github.com/yon3zu
LinuXploit