import { z } from 'zod'; import { paymentSplitsSchema, saleTransactionTypeEnum } from './shopkeeper.schemas'; export { saleTransactionTypeEnum }; /** Payload shape for `changes[].data` when `table` is `sales` (mobile Sale.toJson). */ export const syncSaleDataSchema = z.object({ productId: z.string().uuid(), quantity: z.number(), unitPrice: z.number(), totalAmount: z.number(), paymentMethod: z.string(), paymentSplits: paymentSplitsSchema.optional(), inputMethod: z.string(), recordedBy: z.string().optional(), recordedAt: z.union([z.number(), z.string()]).optional(), transcription: z.string().nullable().optional(), confidenceScore: z.number().nullable().optional(), approved: z.boolean().optional(), receiptPrinted: z.boolean().optional(), customerId: z.string().uuid().nullable().optional(), staffProfileId: z.string().uuid().optional(), transactionType: saleTransactionTypeEnum.optional(), originalSaleId: z.string().uuid().optional(), }); export const syncChangeSchema = z.object({ table: z.enum(['products', 'sales', 'customers', 'stock_movements', 'reservations']), id: z.string().uuid(), action: z.enum(['insert', 'update', 'delete']), data: z.record(z.unknown()), timestamp: z.number(), deviceId: z.string(), generationId: z.number().int(), }); export const syncPushRequestSchema = z.object({ shopId: z.string().uuid(), deviceId: z.string().optional(), changes: z.array(syncChangeSchema).max(500), lastSyncTimestamp: z.number().optional(), }); export const syncPushResponseSchema = z.object({ accepted: z.array(z.string().uuid()), conflicts: z.array( z.object({ id: z.string().uuid(), table: z.string(), serverVersion: z.record(z.unknown()), resolution: z.enum(['server_wins', 'device_wins', 'merged']), }), ), serverTimestamp: z.number(), newGenerationId: z.number().int(), }); export const syncPullTablesEnum = z.enum([ 'products', 'sales', 'customers', 'stock_movements', 'insights', 'trade_facts', 'reservations', ]); export type SyncPullTable = z.infer; export const syncPullQuerySchema = z.object({ shopId: z.string().uuid(), deviceId: z.string().optional(), lastSyncTimestamp: z.coerce.number().optional(), /** If set, only pull these tables. If omitted, pull all. */ tables: z.array(syncPullTablesEnum).optional(), }); const syncPullChangeSchema = z.object({ table: z.string(), id: z.string().uuid(), action: z.enum(['insert', 'update', 'delete']), data: z.record(z.unknown()), timestamp: z.number(), generationId: z.number().int(), }); export const syncPullResponseSchema = z.object({ changes: z.array(syncPullChangeSchema), hasMore: z.boolean(), serverTimestamp: z.number(), /** When hasMore is true, client should call pull again with lastSyncTimestamp = this value to get the next page. */ nextLastSyncTimestamp: z.number().optional(), }); export type SyncSaleData = z.infer; export type SyncChange = z.infer; export type SyncPushRequest = z.infer; export type SyncPushResponse = z.infer; export type SyncPullQuery = z.infer; export type SyncPullResponse = z.infer;