feat(flyer): add flyer session and selection system
- Add FlyerSession, FlyerItem, and FlyerSelection models to Prisma schema - Implement session persistence with weekly key generation in FlyerImportService - Add FlyerSelectionModule to AppModule - Extend FlyerImportResponse with sessionId and flyerItemId fields - Create new flyer-selection module directory structure - Add migration for flyer session and selection tables BREAKING CHANGE: Flyer import now persists data to FlyerSession and FlyerItem tables
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
Logger,
|
||||
ServiceUnavailableException,
|
||||
} from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { normalizeName } from '../common/utils/normalize-name';
|
||||
import {
|
||||
@@ -79,6 +80,7 @@ export class FlyerImportService {
|
||||
const items: FlyerImportItem[] = parsed.items.map((item) => {
|
||||
const match = this.matchItem(item, products, aliasToProduct, productById);
|
||||
return {
|
||||
flyerItemId: null,
|
||||
rawName: item.rawName,
|
||||
normalizedName: item.normalizedName,
|
||||
category: item.category,
|
||||
@@ -97,14 +99,74 @@ export class FlyerImportService {
|
||||
};
|
||||
});
|
||||
|
||||
const persistedItems = await this.persistSessionWithItems(userId, parsed.retailer, items);
|
||||
|
||||
return {
|
||||
sessionId: persistedItems.sessionId,
|
||||
retailer: parsed.retailer,
|
||||
parserVersion: parsed.parserVersion,
|
||||
items,
|
||||
items: persistedItems.items,
|
||||
warnings: parsed.warnings,
|
||||
};
|
||||
}
|
||||
|
||||
private async persistSessionWithItems(
|
||||
userId: number,
|
||||
retailer: 'willys',
|
||||
items: FlyerImportItem[],
|
||||
): Promise<{ sessionId: number; items: FlyerImportItem[] }> {
|
||||
const weekKey = this.toWeekKey(new Date());
|
||||
|
||||
const session = await this.prisma.flyerSession.create({
|
||||
data: {
|
||||
userId,
|
||||
retailer,
|
||||
weekKey,
|
||||
status: 'draft',
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
const savedItems: FlyerImportItem[] = [];
|
||||
for (const item of items) {
|
||||
const created = await this.prisma.flyerItem.create({
|
||||
data: {
|
||||
sessionId: session.id,
|
||||
rawName: item.rawName,
|
||||
normalizedName: item.normalizedName,
|
||||
categoryHint: item.category,
|
||||
price: item.price != null ? new Prisma.Decimal(item.price) : null,
|
||||
priceUnit: item.priceUnit,
|
||||
comparisonPrice:
|
||||
item.comparisonPrice != null ? new Prisma.Decimal(item.comparisonPrice) : null,
|
||||
comparisonUnit: item.comparisonUnit,
|
||||
offerText: item.offerText,
|
||||
parseConfidence: item.parseConfidence,
|
||||
parseReasons: item.parseReasons,
|
||||
matchedProductId: item.matchedProductId,
|
||||
matchedProductName: item.matchedProductName,
|
||||
matchedVia: item.matchedVia,
|
||||
matchConfidence: item.matchConfidence,
|
||||
matchReasons: item.matchReasons,
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
savedItems.push({ ...item, flyerItemId: created.id });
|
||||
}
|
||||
|
||||
return { sessionId: session.id, items: savedItems };
|
||||
}
|
||||
|
||||
private toWeekKey(date: Date): string {
|
||||
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
||||
const dayNum = d.getUTCDay() || 7;
|
||||
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
||||
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
||||
const weekNo = Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
|
||||
return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
private matchItem(
|
||||
item: FlyerParseItem,
|
||||
products: ProductLite[],
|
||||
|
||||
Reference in New Issue
Block a user