feat: initial commit — local services (backend + manager dashboard + waiter PWA)
Includes all work to date: - local_backend: FastAPI backend with products, orders, tables, shifts, cloud sync - manager_dashboard: React manager UI with product/category management, reports, settings - waiter_pwa: React PWA for waiter devices - Category reparent endpoint and UI - Waiter domain: local_ip sent on heartbeat, waiter_domain persisted from cloud response - QR code modal in AppInfoTab for waiter domain - Product form: number input spinners removed, category pre-selected on new product - Category row: count badge moved to far right Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
61
waiter_pwa/src/services/offlinePayments.js
Normal file
61
waiter_pwa/src/services/offlinePayments.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import db from '../db/posdb'
|
||||
import client from '../api/client'
|
||||
|
||||
/**
|
||||
* Queue an emergency payment locally.
|
||||
* Called in Emergency Mode when the server is unreachable.
|
||||
*/
|
||||
export async function queueOfflinePayment({ orderId, itemIds, paymentMethod }) {
|
||||
const uuid = crypto.randomUUID()
|
||||
await db.offline_payments.add({
|
||||
uuid,
|
||||
orderId,
|
||||
itemIds,
|
||||
paymentMethod,
|
||||
offlineAt: new Date().toISOString(),
|
||||
synced: 0,
|
||||
isDuplicate: 0,
|
||||
})
|
||||
return uuid
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all unsynced offline payments to the server.
|
||||
* Called when the server comes back online.
|
||||
* Returns a summary of { synced, duplicates, failed }.
|
||||
*/
|
||||
export async function flushOfflinePayments() {
|
||||
// Boolean is not a valid IndexedDB key — load all and filter in JS
|
||||
const all = await db.offline_payments.toArray()
|
||||
const pending = all.filter(p => !p.synced)
|
||||
const results = { synced: 0, duplicates: 0, failed: 0 }
|
||||
|
||||
for (const payment of pending) {
|
||||
try {
|
||||
const res = await client.post(`/api/orders/${payment.orderId}/pay-offline`, {
|
||||
uuid: payment.uuid,
|
||||
item_ids: payment.itemIds,
|
||||
payment_method: payment.paymentMethod,
|
||||
offline_at: payment.offlineAt,
|
||||
})
|
||||
const isDuplicate = res.data.is_duplicate
|
||||
await db.offline_payments.update(payment.localId, {
|
||||
synced: 1,
|
||||
isDuplicate: isDuplicate ? 1 : 0,
|
||||
})
|
||||
isDuplicate ? results.duplicates++ : results.synced++
|
||||
} catch {
|
||||
results.failed++
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Count unsynced pending payments (to show badge / warning).
|
||||
*/
|
||||
export async function pendingPaymentCount() {
|
||||
const all = await db.offline_payments.toArray()
|
||||
return all.filter(p => !p.synced).length
|
||||
}
|
||||
Reference in New Issue
Block a user