Files
gh-jamshu-jamshi-marketplac…/commands/fix-sync.md
2025-11-29 18:50:06 +08:00

13 KiB

Diagnose and fix synchronization issues between your PWA and Odoo.

What this command does:

  • Identifies sync problems
  • Tests each component of the sync system
  • Provides step-by-step fixes
  • Clears problematic cached data
  • Verifies sync works correctly

Quick Diagnosis 🔍

Run through these quick checks first:

1. Visual Inspection

□ Check browser console for errors
□ Look at "Last synced" timestamp
□ Try manual refresh
□ Check network tab for failed requests

2. Quick Tests

// In browser console:

// 1. Check if cache exists
localStorage.getItem('expenseCache');

// 2. Test API endpoint
fetch('/api/odoo', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    action: 'search',
    model: 'res.partner',
    domain: [],
    fields: ['name'],
    limit: 1
  })
}).then(r => r.json()).then(console.log);

// 3. Force refresh
expenseCache.refresh();

3. Automatic Diagnosis

/test-connection

Common Sync Issues

Issue 1: Initial Load Works, But No Updates 🔄

Symptoms:

  • Data loads on first visit
  • But never updates with new Odoo data
  • "Last synced" timestamp is old
  • Background sync not happening

Diagnosis:

// Check if sync timer is running
// In cache store, look for:
setInterval(() => syncInBackground(), 180000);

// Check if stale detection works
const cacheData = JSON.parse(localStorage.getItem('expenseCache'));
console.log('Last sync:', new Date(cacheData?.lastSyncTime));
console.log('Is stale?', Date.now() - cacheData?.lastSyncTime > 5 * 60 * 1000);

Solutions:

Solution A: Restart Background Sync

// In browser console
expenseCache.stopAutoSync();
expenseCache.startAutoSync();

Solution B: Check Cache Validity

// In cache store file (e.g., expenseCache.js)
const CACHE_VALIDITY = 5 * 60 * 1000; // 5 minutes

function isCacheStale() {
  const cacheData = JSON.parse(localStorage.getItem('expenseCache'));
  if (!cacheData) return true;

  const now = Date.now();
  const age = now - cacheData.lastSyncTime;

  console.log(`Cache age: ${Math.floor(age / 1000)}s`);

  return age > CACHE_VALIDITY;
}

Solution C: Force Manual Sync

// Add a refresh button to your UI
<button onclick={() => expenseCache.refresh()}>
  Sync Now
</button>

Issue 2: Incremental Sync Not Working 📈

Symptoms:

  • Always fetches all records
  • Slow sync times
  • High bandwidth usage
  • lastRecordId not updating

Diagnosis:

// Check lastRecordId
const cacheData = JSON.parse(localStorage.getItem('expenseCache'));
console.log('Last record ID:', cacheData?.lastRecordId);

// Check if it's being updated after sync

Solutions:

Solution A: Verify Domain Filter

// In syncInBackground() function
async function syncInBackground() {
  const { lastRecordId } = getCacheMetadata();

  console.log('Fetching records with id >', lastRecordId);

  const domain = lastRecordId > 0
    ? [['id', '>', lastRecordId]]
    : [];

  const newRecords = await odoo.searchRecords(
    MODEL_NAME,
    domain,
    fields
  );

  console.log('Fetched:', newRecords.length, 'new records');

  if (newRecords.length > 0) {
    // Update lastRecordId
    const maxId = Math.max(...newRecords.map(r => r.id));
    updateCacheMetadata({ lastRecordId: maxId });
  }
}

Solution B: Reset lastRecordId

// If stuck, reset to fetch all
const cacheData = JSON.parse(localStorage.getItem('expenseCache'));
cacheData.lastRecordId = 0;
localStorage.setItem('expenseCache', JSON.stringify(cacheData));

// Then refresh
expenseCache.refresh();

Issue 3: Optimistic Updates Not Syncing

Symptoms:

  • Create/update works in UI
  • But changes don't save to Odoo
  • Records disappear on refresh
  • Temp IDs persist

Diagnosis:

// Check for temp IDs
console.log($expenseCache.filter(e => e.id.toString().startsWith('temp-')));

// Check browser console for API errors

Solutions:

Solution A: Check API Route

// Test create
fetch('/api/odoo', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    action: 'create',
    model: 'x_expense',
    fields: {
      x_studio_description: 'Test',
      x_studio_amount: 10.00
    }
  })
})
.then(r => r.json())
.then(console.log)
.catch(console.error);

Solution B: Fix Error Handling

// In cache store create() function
export async function create(data) {
  const tempId = `temp-${Date.now()}`;
  const tempRecord = { id: tempId, ...data };

  // Add to cache
  records.update(r => [...r, tempRecord]);

  try {
    // Create in Odoo
    const realId = await odoo.createRecord(MODEL_NAME, data);

    // Replace temp ID
    records.update(r =>
      r.map(rec => rec.id === tempId ? { ...rec, id: realId } : rec)
    );

    // Update cache metadata
    await refresh(); // Sync to get the complete record

    return realId;
  } catch (error) {
    console.error('Failed to create record:', error);

    // Rollback
    records.update(r => r.filter(rec => rec.id !== tempId));

    // Re-throw
    throw error;
  }
}

Solution C: Clean Up Temp Records

// Remove stuck temp records
expenseCache.records.update(records =>
  records.filter(r => !r.id.toString().startsWith('temp-'))
);

// Then refresh
expenseCache.refresh();

Issue 4: Partner Names Not Resolving 👥

Symptoms:

  • Partner fields show IDs instead of names
  • Many2one fields display as arrays
  • "undefined" or "[object Object]" displayed

Diagnosis:

// Check partner field format
const record = $expenseCache[0];
console.log('Employee field:', record.x_studio_employee);
// Should be: [12, "John Doe"] or 12

Solutions:

Solution A: Add Partner Resolution

// In syncInBackground()
async function syncInBackground() {
  // ... fetch records ...

  // Resolve partner names
  const partnerIds = new Set();

  records.forEach(record => {
    if (record.x_studio_employee) {
      const id = Array.isArray(record.x_studio_employee)
        ? record.x_studio_employee[0]
        : record.x_studio_employee;
      partnerIds.add(id);
    }
  });

  if (partnerIds.size > 0) {
    const partners = await odoo.fetchPartners(Array.from(partnerIds));
    const partnerMap = new Map(partners.map(p => [p.id, p.name]));

    // Cache partners
    localStorage.setItem('partnerCache', JSON.stringify(
      Array.from(partnerMap.entries())
    ));

    // Update records with names
    records = records.map(record => {
      if (record.x_studio_employee) {
        const id = Array.isArray(record.x_studio_employee)
          ? record.x_studio_employee[0]
          : record.x_studio_employee;

        return {
          ...record,
          x_studio_employee: [id, partnerMap.get(id) || 'Unknown']
        };
      }
      return record;
    });
  }
}

Solution B: Display Helper Function

// Helper to display partner name
function getPartnerName(field) {
  if (!field) return 'None';
  if (Array.isArray(field)) return field[1] || `ID: ${field[0]}`;
  return `ID: ${field}`;
}

// In component
{getPartnerName(expense.x_studio_employee)}

Issue 5: Duplicate Records 📋📋

Symptoms:

  • Same record appears multiple times
  • ID conflicts
  • Sync creates duplicates

Diagnosis:

// Check for duplicates
const ids = $expenseCache.map(r => r.id);
const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
console.log('Duplicate IDs:', duplicates);

Solutions:

Solution A: Deduplicate Cache

function deduplicateCache() {
  records.update(currentRecords => {
    const seen = new Set();
    return currentRecords.filter(record => {
      if (seen.has(record.id)) {
        return false;
      }
      seen.add(record.id);
      return true;
    });
  });
}

// Run deduplication
deduplicateCache();

Solution B: Fix Sync Logic

// When appending new records, check for duplicates
async function appendToCache(newRecords) {
  records.update(currentRecords => {
    const existingIds = new Set(currentRecords.map(r => r.id));

    // Only add records that don't exist
    const toAdd = newRecords.filter(r => !existingIds.has(r.id));

    return [...currentRecords, ...toAdd];
  });
}

Issue 6: Offline Queue Not Syncing 📴

Symptoms:

  • Changes made offline
  • Online now, but changes not syncing
  • Stuck in "pending" state

Solutions:

Solution A: Implement Offline Queue

// Store failed operations
const offlineQueue = writable([]);

async function queueOperation(operation) {
  offlineQueue.update(q => [...q, operation]);
  saveOfflineQueue();
}

async function processOfflineQueue() {
  const queue = get(offlineQueue);

  for (const operation of queue) {
    try {
      if (operation.type === 'create') {
        await odoo.createRecord(operation.model, operation.data);
      } else if (operation.type === 'update') {
        await odoo.updateRecord(operation.model, operation.id, operation.data);
      } else if (operation.type === 'delete') {
        await odoo.deleteRecord(operation.model, operation.id);
      }

      // Remove from queue
      offlineQueue.update(q => q.filter(op => op !== operation));
    } catch (error) {
      console.error('Failed to process queued operation:', error);
      // Keep in queue, will retry
    }
  }

  saveOfflineQueue();
}

// Listen for online event
window.addEventListener('online', () => {
  console.log('Back online, processing queue...');
  processOfflineQueue();
});

Step-by-Step Fix Procedure 🔧

Step 1: Clear Everything

// Clear all caches
localStorage.clear();

// Clear IndexedDB
// DevTools → Application → IndexedDB → Delete database

Step 2: Test API

/test-connection

Step 3: Fresh Sync

// Refresh page
location.reload();

// Should fetch all data fresh

Step 4: Monitor Sync

// Watch console for sync messages
// Should see:
// "Syncing x_expense..."
// "Fetched X records"
// "Cache updated"

Step 5: Test CRUD

// Test create
await expenseCache.create({ /* data */ });

// Test update
await expenseCache.update(id, { /* data */ });

// Test delete
await expenseCache.remove(id);

// Verify in Odoo

Sync Debugging Checklist

□ Browser console shows no errors
□ /api/odoo endpoint responds
□ ODOO_API_KEY is valid
□ lastSyncTime is updating
□ lastRecordId is updating
□ Background sync interval is running
□ Stale detection works correctly
□ Incremental fetch has correct domain
□ Optimistic updates resolve temp IDs
□ Partner names resolve correctly
□ No duplicate records
□ Offline queue processes when online
□ IndexedDB is storing data
□ localStorage has metadata

Advanced Debugging 🐛

Enable Verbose Logging

// Add to cache store
const DEBUG = true;

function log(...args) {
  if (DEBUG) console.log('[ExpenseCache]', ...args);
}

async function syncInBackground() {
  log('Starting background sync...');
  log('lastRecordId:', getLastRecordId());

  const records = await fetch();
  log('Fetched records:', records.length);

  await saveToCache(records);
  log('Cache updated');
}

Monitor Network

// Log all API calls
const originalFetch = window.fetch;
window.fetch = async (...args) => {
  console.log('Fetch:', args[0]);
  const response = await originalFetch(...args);
  console.log('Response:', response.status);
  return response;
};

Profile Performance

// Measure sync time
console.time('sync');
await expenseCache.refresh();
console.timeEnd('sync');

Example prompts to use this command:

  • /fix-sync - Diagnose sync issues
  • User: "Data is not syncing"
  • User: "My changes aren't saving"
  • User: "Sync is broken"
  • /test-connection - Test Odoo connectivity
  • /clear-cache - Clear all cached data
  • /troubleshoot - General troubleshooting
  • /help - Full documentation

Prevention Tips 🛡️

  1. Monitor sync health

    • Display "Last synced" timestamp in UI
    • Show sync status indicator
    • Alert on sync failures
  2. Handle errors gracefully

    • Catch and log all errors
    • Show user-friendly messages
    • Provide retry mechanisms
  3. Test offline scenarios

    • Test creating records offline
    • Test going offline mid-sync
    • Test coming back online
  4. Keep sync simple

    • Stick to generated patterns
    • Don't overcomplicate logic
    • Follow proven examples
  5. Regular maintenance

    • Clear old data periodically
    • Update dependencies
    • Monitor performance