initial: bootstrap from BukidBountyApp base
This commit is contained in:
176
resources/js/composables/useOfflineStore.js
Normal file
176
resources/js/composables/useOfflineStore.js
Normal file
@@ -0,0 +1,176 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user