Clear Bins

Quita en un clic todos los Bin IDs (⊗) asociados a una locación en Providence, para que deje de dar "Location … is not empty" en putaway.

Instalar

Arrastra este botón a la barra de marcadores:

🧹 Clear Bins v2

Usar

  1. Abre el Location Detail de la locación (ej. NYC1-R06-159-G04-07).
  2. Haz clic en el marcador Clear Bins.
  3. Confirma el aviso (muestra la locación y cuántos bins hay).
  4. Mira el panel abajo a la derecha. Puedes darle Detener en cualquier momento.
  5. Al terminar, refresca la página y repite el putaway.

Seguridad: si el primer ⊗ no quita nada, o Providence muestra un error, se detiene solo. Solo actúa sobre filas cuyo Bin ID empieza con BN-.

Código fuente
(async () => {
  if (window.__clearBins) { alert('Clear Bins ya está corriendo.'); return; }
  window.__clearBins = true;
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  const visible = el => el && el.offsetParent !== null;
  const BIN_RE = /^BN-[A-Z0-9]+-[A-Z0-9]+/;
  let stop = false;

  // Panel de progreso
  const box = document.createElement('div');
  box.style.cssText = 'position:fixed;bottom:16px;right:16px;z-index:999999;background:#111;color:#fff;font:13px/1.4 system-ui,sans-serif;padding:12px 14px;border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,.3);min-width:240px';
  box.innerHTML = '<b>Clear Bins</b><div id="cb-msg">Preparando…</div><button id="cb-stop" style="margin-top:8px;padding:4px 10px;border:0;border-radius:4px;background:#e5484d;color:#fff;cursor:pointer">Detener</button>';
  document.body.appendChild(box);
  const msg = t => { box.querySelector('#cb-msg').textContent = t; };
  box.querySelector('#cb-stop').onclick = () => { stop = true; msg('Deteniendo…'); };
  const done = t => { msg(t); window.__clearBins = false; const b = box.querySelector('#cb-stop'); b.textContent = 'Cerrar'; b.onclick = () => box.remove(); };

  // Botones ⊗ junto a cada Bin ID de la tabla
  const icons = () => {
    const out = [];
    document.querySelectorAll('td').forEach(td => {
      if (!BIN_RE.test(td.textContent.trim()) || !visible(td)) return;
      const el = td.querySelector('button,[role="button"],.anticon,svg,img,i');
      if (el) { const t = el.closest('button,[role="button"],.anticon') || el; t.__bin = td.textContent.trim().match(BIN_RE)[0]; out.push(t); }
    });
    return out;
  };
  const click = el => ['mousedown', 'mouseup', 'click'].forEach(type =>
    el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, view: window })));
  const totalText = () => { const m = document.body.innerText.match(/(\d+)\s+results?\s+in\s+total/i); return m ? +m[1] : null; };
  const errEls = () => [...document.querySelectorAll('.ant-message-error,.ant-notification-notice-error,.ant-alert-error')]
    .filter(e => visible(e) && !/success/i.test(e.textContent));
  let seenErr = new Set();
  const errorShown = () => errEls().find(e => !seenErr.has(e));
  const detachedToast = bin => [...document.querySelectorAll('.ant-notification-notice,.ant-message-notice,[role="alert"]')]
    .some(e => visible(e) && e.textContent.includes(bin) && /detached|success/i.test(e.textContent));

  // Busca el botón de confirmación del popup (si aparece)
  const CONFIRM_RE = /^(yes|confirm|ok|remove|delete|unlink|s[ií]|aceptar|eliminar|quitar|borrar)$/i;
  const findConfirm = () => {
    const scopes = document.querySelectorAll('.ant-modal,.ant-popover,.ant-popconfirm,[role="dialog"],[role="alertdialog"]');
    for (const s of scopes) {
      if (!visible(s)) continue;
      for (const b of s.querySelectorAll('button')) {
        if (visible(b) && !b.disabled && CONFIRM_RE.test(b.textContent.trim())) return b;
      }
    }
    return null;
  };

  const loc = (document.querySelector('h1,h2') || {}).textContent || 'esta locación';
  let list = icons();
  if (!list.length) { done('No encontré Bin IDs con ⊗ en esta página. Abre el Location Detail.'); return; }
  const total = totalText() ?? list.length;
  if (!confirm(`Clear Bins\n\nLocación: ${loc.trim()}\nBins asociados: ${total}\n\n¿Quitar TODOS los Bin IDs de esta locación?`)) { box.remove(); window.__clearBins = false; return; }

  let removed = 0, guard = 0, firstChecked = false;
  while (!stop && guard++ < 1000) {
    list = icons();
    if (!list.length) {
      const next = document.querySelector('.ant-pagination-next:not(.ant-pagination-disabled)');
      if (next && visible(next)) { click(next.querySelector('button') || next); await sleep(1500); if (icons().length) continue; }
      break;
    }
    const before = list.length, beforeTotal = totalText(), bin = list[0].__bin;
    seenErr = new Set(errEls());
    msg(`Quitando ${removed + 1}${total ? ' de ' + total : ''}: ${bin}…`);
    click(list[0]);

    // Esperar popup de confirmación (hasta 2.5 s)
    for (let i = 0; i < 10; i++) { await sleep(250); const c = findConfirm(); if (c) { click(c); break; } }

    // Esperar a que la tabla se actualice (hasta 8 s)
    let ok = false;
    for (let i = 0; i < 32; i++) {
      await sleep(250);
      if (errorShown()) break;
      if (detachedToast(bin)) { ok = true; break; }
      const t = totalText();
      if (icons().length < before || (t != null && beforeTotal != null && t < beforeTotal)) { ok = true; break; }
    }
    const err = errorShown();
    if (err) { done(`Providence mostró un error: "${err.textContent.trim().slice(0, 120)}". Me detuve tras ${removed}.`); return; }
    if (!ok) {
      done(firstChecked
        ? `La tabla dejó de cambiar. Me detuve tras ${removed}. Refresca la página y vuelve a correrlo.`
        : 'El primer intento no quitó nada. No sigo. (Puede que el popup de confirmación tenga otro texto; avísame.)');
      return;
    }
    firstChecked = true;
    removed++;
    for (let i = 0; i < 20; i++) { if (!icons().some(e => e.__bin === bin) || icons().length < before) break; await sleep(250); }
    await sleep(500);
  }
  done(stop ? `Detenido. Quitados: ${removed}.` : `Listo. Quitados: ${removed}. Refresca y prueba el putaway.`);
})();