UEP
USA Event Pro
RY

Import from ERS

Pull your whole catalog, customer list and order history out of Event Rental Systems. Runs inside your own logged-in session — no password, no API key, nothing leaves your browser except the data itself.

Safe to run more than once. Records are matched on item name, customer email/phone and ERS order id, so a second run updates what changed instead of creating duplicates.
How to run it
About two minutes
  1. Open your ERS control panel and sign in as an administrator.
  2. Press F12 to open developer tools, then click the Console tab.
  3. Copy the script on the right and paste it into the console.
  4. Press Enter and leave the tab open — 1,800 customers takes a minute or two.
What it brings across
Itemsname, price, stock, category, packages, add-ons, surfaces
Categoriesincluding parent nesting
Customersdeduplicated on email then phone
Ordersline items, adjustments, payments, driver, balance due
The script
Paste this into the ERS console
/* ===========================================================================
 * Marquee — ERS extractor
 * ---------------------------------------------------------------------------
 * Pulls your catalog, customers and order history out of Event Rental Systems.
 *
 * HOW TO RUN
 *   1. Log in to your ERS control panel (…ourers.com/cp/) as an administrator.
 *   2. Open DevTools -> Console.
 *   3. Paste this whole file and press Enter.
 *
 * It runs entirely inside your own logged-in session. No password, no API key
 * and no cookie ever leaves your browser. When it finishes it will either POST
 * the data straight to your Marquee instance, or — if that is not reachable —
 * save a marquee-ers-export.json file to your Downloads folder for you to
 * upload manually.
 * =========================================================================== */
(async () => {
  const TARGET = window.MARQUEE_URL || 'http://localhost:3000';
  const log = (...a) => console.log('%c[marquee]', 'color:#2a78d6;font-weight:bold', ...a);

  /* ---- helpers ---------------------------------------------------------- */
  const parseDoc = (html) => new DOMParser().parseFromString(html, 'text/html');
  const biggestTable = (doc) =>
    [...doc.querySelectorAll('table')].sort((a, b) => b.rows.length - a.rows.length)[0];

  // Keep newlines: ERS packs multi-line values (order contents, payments)
  // into a single cell as separate child nodes.
  const cellText = (td) =>
    td.innerText.replace(/ /g, ' ').split('\n').map((s) => s.trim()).filter(Boolean).join('\n');

  async function grid(path) {
    const res = await fetch(path, { credentials: 'include' });
    if (!res.ok) throw new Error(`${path} -> ${res.status}`);
    const doc = parseDoc(await res.text());
    const table = biggestTable(doc);
    if (!table) return { headers: [], rows: [], totalRecords: 0 };
    const headers = [...table.rows[0].cells].map((c) => c.innerText.trim().replace(/\s+/g, ' '));
    const rows = [...table.rows].slice(1).map((tr) => {
      const cells = [...tr.cells].map(cellText);
      // ERS serves item photos from a public CDN. Capture the URL so the
      // catalog has real pictures instead of placeholder glyphs.
      const img = tr.querySelector('img[src*="files.sysers.com"]');
      cells.__image = img ? img.src : '';
      return cells;
    });
    const m = doc.body.innerText.match(/of ([\d,]+) records/);
    return { headers, rows, totalRecords: m ? Number(m[1].replace(/,/g, '')) : rows.length };
  }

  /** ERS paginates with ?limit_page=N and 100 rows per page. */
  async function allPages(path, hardCap = 60) {
    const first = await grid(path);
    const per = first.rows.length || 100;
    const pages = Math.min(hardCap, Math.max(1, Math.ceil(first.totalRecords / per)));
    let rows = first.rows;
    for (let p = 2; p <= pages; p++) {
      const sep = path.includes('?') ? '&' : '?';
      const next = await grid(`${path}${sep}limit_page=${p}`);
      rows = rows.concat(next.rows);
      log(`  ${path} page ${p}/${pages} (${rows.length} rows)`);
      await new Promise((r) => setTimeout(r, 120)); // be polite to ERS
    }
    return { headers: first.headers, rows, totalRecords: first.totalRecords };
  }

  /**
   * ERS body rows carry an extra leading cell (the expander icon) plus a
   * trailing action cell, so body index = header index + 1. Verified against
   * the Items, Customer List and Order List grids.
   */
  const objectify = (headers, rows, offset = 1) =>
    rows.map((cells) => {
      const o = {};
      headers.forEach((h, i) => { if (h) o[h] = cells[i + offset] ?? ''; });
      if (cells.__image) o.__image = cells.__image;
      return o;
    }).filter((o) => Object.values(o).some(Boolean));

  /* ---- extract ---------------------------------------------------------- */
  log('Reading catalog…');
  const items = await allPages('/cp/items/');
  log('Reading categories…');
  const cats = await allPages('/cp/categories/');
  log('Reading customers… (this is the slow one)');
  const custs = await allPages('/cp/customer_list/');
  log('Reading orders…');
  const ords = await allPages('/cp/order_list/');

  const payload = {
    source: 'ers',
    extractedAt: new Date().toISOString(),
    company: document.title.replace(/^ERS CP - /, ''),
    items: objectify(items.headers, items.rows),
    categories: objectify(cats.headers, cats.rows),
    customers: objectify(custs.headers, custs.rows),
    orders: objectify(ords.headers, ords.rows),
  };

  log('Extracted:', {
    items: payload.items.length,
    categories: payload.categories.length,
    customers: payload.customers.length,
    orders: payload.orders.length,
  });

  /* ---- deliver ---------------------------------------------------------- */
  try {
    const res = await fetch(`${TARGET}/api/import/ers`, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(payload),
    });
    const out = await res.json();
    log('%cImported into Marquee ✓', 'color:#0ca30c;font-weight:bold', out);
    return out;
  } catch (err) {
    log('Marquee not reachable — saving a file instead.', err.message);
    const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'marquee-ers-export.json';
    a.click();
    log('Saved marquee-ers-export.json to your Downloads folder.');
  }
})();