import { ref, computed } from 'vue'; import { db } from '../db'; import axios from 'axios'; import { useNetworkStore } from '../stores/network'; const isSyncing = ref(false); const syncError = ref(null); const syncErrors = ref([]); const lastSyncTime = ref(localStorage.getItem('last_pos_sync') || null); const pendingTransactionsCount = ref(0); export function useOfflineStore() { const networkStore = useNetworkStore(); const isOnline = computed(() => networkStore.isOnline); const updatePendingCount = async () => { const count = await db.pending_transactions .where('status') .equals('PENDING') .count(); pendingTransactionsCount.value = count; return count; }; /** * Pull products from server and update local DB */ const syncProducts = async (storeHash) => { if (!isOnline.value) return; try { isSyncing.value = true; const response = await axios.get('/POS/GetProducts', { params: { target: storeHash } }); if (response.data && response.data.products) { // Bulk put (overwrite existing) await db.products.bulkPut(response.data.products); lastSyncTime.value = new Date().toISOString(); localStorage.setItem('last_pos_sync', lastSyncTime.value); } } catch (err) { console.error('[OfflineStore] Failed to sync products:', err); syncError.value = err.message; } finally { isSyncing.value = false; } }; /** * Store a completed transaction locally */ const storeTransactionOffline = async (txnData) => { const result = await db.pending_transactions.add({ ...txnData, timestamp: new Date().toISOString(), status: 'PENDING' }); await updatePendingCount(); return result; }; /** * Push all pending local transactions to the server */ const pushPendingTransactions = async () => { if (!networkStore.isOnline || isSyncing.value) return 0; const pendings = await db.pending_transactions .where('status') .equals('PENDING') .toArray(); if (pendings.length === 0) { await updatePendingCount(); return 0; } isSyncing.value = true; let syncedCount = 0; syncErrors.value = []; try { const validPendings = pendings.filter(txn => txn.store_hash); const skippedCount = pendings.length - validPendings.length; if (skippedCount > 0) { console.warn(`[OfflineStore] Skipping ${skippedCount} transactions with missing store_hash`); } if (validPendings.length === 0) { syncErrors.value = ['No valid transactions to sync (missing store reference)']; return 0; } const response = await axios.post('/api/pos/sync-offline', { transactions: validPendings.map(txn => ({ local_id: txn.id, store_hash: txn.store_hash, customer_name: txn.customer_name, items: txn.items, total: txn.total, received: txn.received, method: txn.method, timestamp: txn.timestamp })) }); if (response.data && response.data.success) { const { synced_count, synced_ids, errors } = response.data.data; syncedCount = synced_count; if (errors && errors.length > 0) { console.error('[OfflineStore] Server sync errors:', errors); syncErrors.value = errors; } if (synced_ids && synced_ids.length > 0) { await db.pending_transactions.bulkUpdate(synced_ids.map(id => ({ key: id, changes: { status: 'SYNCED' } }))); await db.pending_transactions.where('status').equals('SYNCED').delete(); } lastSyncTime.value = new Date().toLocaleTimeString(); } } catch (error) { console.error('Offline Sync Failed:', error); syncErrors.value = [error?.response?.data?.message || error.message || 'Sync request failed']; } finally { isSyncing.value = false; await updatePendingCount(); } return syncedCount; }; /** * Search products locally */ const searchProductsLocally = async (query, category = 'All') => { let collection = db.products; if (category !== 'All') { collection = collection.where('category').equals(category); } else { collection = collection.toCollection(); } const products = await collection.toArray(); if (!query) return products; const lowerQuery = query.toLowerCase(); return products.filter(p => p.name.toLowerCase().includes(lowerQuery) || (p.barcode && p.barcode.includes(lowerQuery)) ); }; return { isSyncing, syncError, syncErrors, lastSyncTime, pendingTransactionsCount, updatePendingCount, syncProducts, storeTransactionOffline, pushPendingTransactions, searchProductsLocally }; }