2026-05-14 18:57:57 +09:00
|
|
|
import { z } from 'zod/v4';
|
|
|
|
|
import { ServingUnit } from '../enums/product.enums.js';
|
|
|
|
|
|
|
|
|
|
export const CreatePriceRecordSchema = z.object({
|
|
|
|
|
productId: z.string().min(1),
|
|
|
|
|
storeId: z.string().min(1),
|
|
|
|
|
price: z.number().positive(),
|
|
|
|
|
currency: z.string().min(1).max(10).default('USD'),
|
|
|
|
|
quantity: z.number().positive(),
|
|
|
|
|
unit: z.nativeEnum(ServingUnit),
|
|
|
|
|
date: z.iso.datetime().optional(),
|
|
|
|
|
notes: z.string().max(1000).trim().optional(),
|
|
|
|
|
receiptImageUrl: z.string().url().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const BulkPriceRecordInputSchema = z.object({
|
|
|
|
|
storeId: z.string().min(1),
|
|
|
|
|
date: z.iso.datetime().optional(),
|
2026-05-14 19:59:42 +09:00
|
|
|
items: z
|
|
|
|
|
.array(
|
|
|
|
|
z.object({
|
|
|
|
|
productId: z.string().min(1),
|
|
|
|
|
price: z.number().positive(),
|
|
|
|
|
quantity: z.number().positive(),
|
|
|
|
|
unit: z.nativeEnum(ServingUnit),
|
|
|
|
|
notes: z.string().max(1000).trim().optional(),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.min(1),
|
2026-05-14 18:57:57 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const PriceHistoryQuerySchema = z.object({
|
|
|
|
|
storeId: z.string().optional(),
|
|
|
|
|
startDate: z.iso.datetime().optional(),
|
|
|
|
|
endDate: z.iso.datetime().optional(),
|
|
|
|
|
cursor: z.string().optional(),
|
|
|
|
|
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const PriceRecordResponseSchema = z.object({
|
|
|
|
|
_id: z.string(),
|
|
|
|
|
householdId: z.string(),
|
|
|
|
|
productId: z.string(),
|
|
|
|
|
productName: z.string(),
|
|
|
|
|
storeId: z.string(),
|
|
|
|
|
storeName: z.string(),
|
|
|
|
|
price: z.number(),
|
|
|
|
|
currency: z.string(),
|
|
|
|
|
quantity: z.number(),
|
|
|
|
|
unit: z.nativeEnum(ServingUnit),
|
|
|
|
|
pricePerUnit: z.number(),
|
|
|
|
|
date: z.string(),
|
|
|
|
|
receiptImageUrl: z.string().optional(),
|
|
|
|
|
notes: z.string().optional(),
|
|
|
|
|
createdBy: z.string(),
|
|
|
|
|
createdAt: z.string(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const StorePriceComparisonSchema = z.object({
|
|
|
|
|
storeId: z.string(),
|
|
|
|
|
storeName: z.string(),
|
|
|
|
|
latestPrice: z.number(),
|
|
|
|
|
latestPricePerUnit: z.number(),
|
|
|
|
|
currency: z.string(),
|
|
|
|
|
date: z.string(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const PriceAnalyticsResponseSchema = z.object({
|
|
|
|
|
averageBasketByStore: z.array(
|
|
|
|
|
z.object({
|
|
|
|
|
storeId: z.string(),
|
|
|
|
|
storeName: z.string(),
|
|
|
|
|
avgTotal: z.number(),
|
|
|
|
|
tripCount: z.number(),
|
2026-05-14 19:59:42 +09:00
|
|
|
}),
|
2026-05-14 18:57:57 +09:00
|
|
|
),
|
|
|
|
|
priceAlerts: z.array(
|
|
|
|
|
z.object({
|
|
|
|
|
productId: z.string(),
|
|
|
|
|
productName: z.string(),
|
|
|
|
|
storeId: z.string(),
|
|
|
|
|
storeName: z.string(),
|
|
|
|
|
previousPrice: z.number(),
|
|
|
|
|
currentPrice: z.number(),
|
|
|
|
|
changePercent: z.number(),
|
|
|
|
|
date: z.string(),
|
2026-05-14 19:59:42 +09:00
|
|
|
}),
|
2026-05-14 18:57:57 +09:00
|
|
|
),
|
|
|
|
|
spendingOverTime: z.array(
|
|
|
|
|
z.object({
|
|
|
|
|
period: z.string(),
|
|
|
|
|
total: z.number(),
|
2026-05-14 19:59:42 +09:00
|
|
|
}),
|
2026-05-14 18:57:57 +09:00
|
|
|
),
|
|
|
|
|
spendingByCategory: z.array(
|
|
|
|
|
z.object({
|
|
|
|
|
category: z.string(),
|
|
|
|
|
total: z.number(),
|
|
|
|
|
avgPerItem: z.number(),
|
2026-05-14 19:59:42 +09:00
|
|
|
}),
|
2026-05-14 18:57:57 +09:00
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const PriceHistoryResponseSchema = z.object({
|
|
|
|
|
data: z.array(PriceRecordResponseSchema),
|
|
|
|
|
pagination: z.object({
|
|
|
|
|
cursor: z.string().nullable(),
|
|
|
|
|
hasMore: z.boolean(),
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const FoodStoreComparisonResponseSchema = z.object({
|
|
|
|
|
data: z.array(StorePriceComparisonSchema),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const FoodSpendingAnalyticsResponseSchema = PriceAnalyticsResponseSchema;
|
|
|
|
|
|
|
|
|
|
export type CreatePriceRecordInput = z.infer<typeof CreatePriceRecordSchema>;
|
|
|
|
|
export type BulkPriceRecordInput = z.infer<typeof BulkPriceRecordInputSchema>;
|
|
|
|
|
export type PriceHistoryQueryInput = z.infer<typeof PriceHistoryQuerySchema>;
|