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 ```javascript // 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 ```bash /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:** ```javascript // 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 ```javascript // In browser console expenseCache.stopAutoSync(); expenseCache.startAutoSync(); ``` #### Solution B: Check Cache Validity ```javascript // 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 ```javascript // Add a refresh button to your UI ``` --- ### Issue 2: Incremental Sync Not Working 📈 **Symptoms:** - Always fetches all records - Slow sync times - High bandwidth usage - `lastRecordId` not updating **Diagnosis:** ```javascript // 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 ```javascript // 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 ```javascript // 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:** ```javascript // 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 ```javascript // 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 ```javascript // 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 ```javascript // 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:** ```javascript // 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 ```javascript // 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 ```javascript // 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:** ```javascript // 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 ```javascript 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 ```javascript // 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 ```javascript // 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 ```javascript // Clear all caches localStorage.clear(); // Clear IndexedDB // DevTools → Application → IndexedDB → Delete database ``` ### Step 2: Test API ```bash /test-connection ``` ### Step 3: Fresh Sync ```javascript // Refresh page location.reload(); // Should fetch all data fresh ``` ### Step 4: Monitor Sync ```javascript // Watch console for sync messages // Should see: // "Syncing x_expense..." // "Fetched X records" // "Cache updated" ``` ### Step 5: Test CRUD ```javascript // 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 ```javascript // 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 ```javascript // 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 ```javascript // 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" ## Related Commands: - `/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