Solabran — Data Sync Tool

Fix duplicates → Export from editor → Import to portal.solabran.ca

Follow the steps in order: First fix duplicates on the live site (Step A), then export fresh data from here (Step 1), load the file (Step 2), generate the import script (Step 3), and run it on portal.solabran.ca. Finally fix any remaining customer name issues (Step 4).
Step A — Fix duplicates on portal.solabran.ca RIGHT NOW
You have duplicate records from previous import runs.
Copy one of the two scripts below. Open portal.solabran.ca in a new tab, press F12Console, paste and hit Enter.

 Option 1 — NUKE ALL DATA (fastest, recommended when many duplicates)

Deletes every record in every table. Downloads a full backup first automatically — your data is saved before anything is deleted. Do a fresh import with Step 1→2→3 below after running this.

// ═══════════════════════════════════════════════════════════
// SOLABRAN NUKE SCRIPT — Wipes ALL live data
// ⚠ Run ONLY on portal.solabran.ca (F12 → Console)
// ✅ DOWNLOADS A FULL BACKUP before deleting anything
// After this, use data-import.html Step 1→2→3 to reimport.
// ═══════════════════════════════════════════════════════════
(async () => {
  const TABLES = [
    'payments','invoices','estimates','jobs',
    'customers','product_catalog','settings',
    'catalog_versions','activities','receipts'
  ];

  async function fetchAll(t) {
    let all=[], page=1;
    while(true){
      const r=await fetch(`tables/${t}?limit=500&page=${page}`);
      if(!r.ok) break;
      const j=await r.json(); const rows=j.data||[];
      all=all.concat(rows); if(rows.length<500) break; page++;
    } return all;
  }

  // ── STEP 1: Type-to-confirm guard ────────────────────────────
  const answer = prompt('⚠️ DANGER: This will DELETE ALL DATA on portal.solabran.ca.\n\nA backup will be downloaded first.\n\nType  NUKE  (all caps) to continue, or Cancel to abort:');
  if (answer !== 'NUKE') { alert('Aborted. Nothing was changed.'); return; }

  // ── STEP 2: Download a full backup BEFORE deleting anything ──
  console.log('%c[Nuke] Downloading backup first…','color:#f97316;font-weight:bold;font-size:14px');
  const backup = { app:'Solabran CRM', backed_up: new Date().toISOString(), tables:{} };
  let backupTotal = 0;
  for(const t of TABLES){
    try {
      backup.tables[t] = await fetchAll(t);
      backupTotal += backup.tables[t].length;
      console.log(`[Nuke] Backed up ${t}: ${backup.tables[t].length} records`);
    } catch(e) { backup.tables[t]=[]; }
  }
  // Trigger download
  const blob = new Blob([JSON.stringify(backup,null,2)],{type:'application/json'});
  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = 'solabran-backup-before-nuke-' + new Date().toISOString().slice(0,10) + '.json';
  document.body.appendChild(a); a.click(); document.body.removeChild(a);
  console.log(`%c[Nuke] ✅ Backup downloaded — ${backupTotal} records saved`,'color:#10b981;font-weight:bold');

  // ── STEP 3: Second confirmation after seeing backup count ─────
  const confirm2 = confirm(`✅ Backup downloaded (${backupTotal} records saved to your Downloads folder).\n\nNow deleting all live data. Continue?`);
  if(!confirm2) { alert('Aborted after backup. Your data is unchanged. Use the downloaded backup file with the MERGE tool if needed.'); return; }

  // ── STEP 4: Delete everything ─────────────────────────────────
  let totalDel=0;
  console.log('%c[Nuke] Deleting…','color:#ef4444;font-weight:bold;font-size:14px');
  for(const t of TABLES){
    const rows=backup.tables[t]||[];
    let n=0;
    for(const row of rows){
      try{ const r=await fetch(`tables/${t}/${row.id}`,{method:'DELETE'}); if(r.ok||r.status===204) n++; }catch(e){}
    }
    totalDel+=n;
    console.log(`[Nuke] ${t}: ${n}/${rows.length} deleted`);
  }
  console.log(`%c[Nuke] Done — ${totalDel} records deleted`,'color:#10b981;font-weight:bold;font-size:14px');
  alert(`✅ Nuke complete!\n\n${totalDel} records deleted.\nBackup file saved to your Downloads folder.\n\nNow go back to data-import.html and follow Steps 1 → 2 → 3.\n\nIf you need to recover data, use the MERGE tool in data-import.html with your backup file.`);
})();

 Option 2 — DEDUP only (keeps newest copy of each record, removes extras)

Safer option if you want to preserve existing live data but remove exact duplicates. Uses business keys (estimate number, invoice number, etc.) to identify duplicates.

// ═══════════════════════════════════════════════════════════
// SOLABRAN DEDUP SCRIPT — Keeps newest, removes duplicates
// ⚠ Run ONLY on portal.solabran.ca (F12 → Console)
// ═══════════════════════════════════════════════════════════
(async () => {
  const BKEY = {
    customers:       ['customer_number','company_name'],
    estimates:       ['estimate_number'],
    invoices:        ['invoice_number'],
    jobs:            ['title'],
    payments:        ['invoice_id','amount','payment_date'],
    product_catalog: ['sku'],
    settings:        ['key'],
  };

  async function fetchAll(t) {
    let all=[], page=1;
    while(true){
      const r=await fetch(`tables/${t}?limit=500&page=${page}`);
      if(!r.ok) break;
      const j=await r.json(); const rows=j.data||[];
      all=all.concat(rows); if(rows.length<500) break; page++;
    } return all;
  }

  let totalDel=0;
  console.log('%c[Dedup] Starting…','color:#6366f1;font-weight:bold;font-size:14px');
  for(const [table,keyFields] of Object.entries(BKEY)){
    const rows=await fetchAll(table);
    if(!rows.length){ console.log(`[Dedup] ${table}: empty`); continue; }
    const groups={};
    for(const row of rows){
      const kp=keyFields.map(f=>String(row[f]??'').toLowerCase().trim());
      if(kp.every(k=>!k||k==='undefined')) continue;
      const key=kp.join('||');
      if(!groups[key]) groups[key]=[];
      groups[key].push(row);
    }
    let del=0;
    for(const [,group] of Object.entries(groups)){
      if(group.length<=1) continue;
      group.sort((a,b)=>(b.created_at||0)-(a.created_at||0));
      for(const row of group.slice(1)){
        try{ const r=await fetch(`tables/${table}/${row.id}`,{method:'DELETE'}); if(r.ok||r.status===204) del++; }catch(e){}
      }
    }
    totalDel+=del;
    console.log(`[Dedup] ${table}: ${rows.length} rows → ${del} duplicates removed`);
  }
  console.log(`%c[Dedup] Done — ${totalDel} duplicates removed`,'color:#10b981;font-weight:bold;font-size:14px');
  alert(`✅ Dedup complete!\n\n${totalDel} duplicate records removed.\n\nRefresh portal.solabran.ca to verify.\n\nIf customer names still show as raw IDs, run Steps 1→2→3 below to do a fresh import, OR use Step 4 to run the re-link script.`);
})();
1 Export data from this editor

Fetches all records from the editor's database and downloads solabran-export.json. The file also auto-loads into Step 2 below.

2 Load the exported JSON & choose tables

If you exported in Step 1, the file is already loaded. Otherwise, pick the downloaded solabran-export.json.

No file chosen
⚡ MERGE / RECOVER — Restore missing records from an older JSON file Safe — never overwrites existing records
Use this when: You have an older solabran-export.json that contains estimates (or other records) that are now missing from the live site. This tool compares your JSON against the live database and only inserts what's missing — it never deletes or overwrites anything already there.

Step M1 — Load your older backup JSON file

No file chosen — pick your older backup
4 Fix customer names on estimates & invoices
Why names show as "Unknown Customer":
When customers are wiped and re-inserted, they get brand new UUIDs. Estimates still reference the old UUIDs — those no longer exist in the customers table. This must be fixed by scanning the live site and patching the broken customer_id values.
 Recommended: Interactive Re-link Tool

Open relink-tool.html directly on the live portal. It scans your live data, auto-matches broken records, shows a visual table of results, and lets you manually assign any that couldn't be matched automatically — no console needed.

Open Re-link Tool Opens on this site — use the live portal URL
 Fallback: Console Script (requires export file)

If the Re-link Tool is unavailable, load your solabran-export.json in Step 2 above, then generate a console script here. The export file maps old customer UUIDs to names so the script can find their new live UUIDs.

No export file loaded — load your solabran-export.json in Step 2 first for best results