177 lines
5.5 KiB
JavaScript
177 lines
5.5 KiB
JavaScript
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
|
|
};
|
|
}
|