feat(flyer): add flyer session and selection system
Test Suite / backend-pr-quick (push) Has been skipped
Test Suite / quick-import-pr-quick (push) Has been skipped
Test Suite / backend-full (push) Successful in 2m49s
Test Suite / flutter-quality (push) Successful in 2m0s

- 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:
Nils-Johan Gynther
2026-05-18 19:02:32 +02:00
parent a31aff7c35
commit 24a96c3da1
11 changed files with 619 additions and 9 deletions
@@ -0,0 +1,85 @@
CREATE TABLE `FlyerSession` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`userId` INTEGER NOT NULL,
`retailer` VARCHAR(191) NOT NULL,
`weekKey` VARCHAR(191) NOT NULL,
`status` VARCHAR(191) NOT NULL DEFAULT 'draft',
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
`expiresAt` DATETIME(3) NULL,
INDEX `FlyerSession_userId_idx`(`userId`),
INDEX `FlyerSession_weekKey_idx`(`weekKey`),
INDEX `FlyerSession_status_idx`(`status`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE `FlyerItem` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`sessionId` INTEGER NOT NULL,
`rawName` VARCHAR(191) NOT NULL,
`normalizedName` VARCHAR(191) NOT NULL,
`categoryHint` VARCHAR(191) NULL,
`price` DECIMAL(10, 2) NULL,
`priceUnit` VARCHAR(191) NULL,
`comparisonPrice` DECIMAL(10, 2) NULL,
`comparisonUnit` VARCHAR(191) NULL,
`offerText` VARCHAR(191) NULL,
`parseConfidence` DOUBLE NOT NULL,
`parseReasons` JSON NULL,
`matchedProductId` INTEGER NULL,
`matchedProductName` VARCHAR(191) NULL,
`matchedVia` VARCHAR(191) NULL,
`matchConfidence` DOUBLE NULL,
`matchReasons` JSON NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
INDEX `FlyerItem_sessionId_idx`(`sessionId`),
INDEX `FlyerItem_normalizedName_idx`(`normalizedName`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE `FlyerSelection` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`sessionId` INTEGER NOT NULL,
`itemId` INTEGER NOT NULL,
`userId` INTEGER NOT NULL,
`plannedQuantity` DECIMAL(10, 2) NULL,
`plannedUnit` VARCHAR(191) NULL,
`priority` VARCHAR(191) NOT NULL DEFAULT 'normal',
`note` VARCHAR(191) NULL,
`status` VARCHAR(191) NOT NULL DEFAULT 'planned',
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
UNIQUE INDEX `FlyerSelection_sessionId_itemId_key`(`sessionId`, `itemId`),
INDEX `FlyerSelection_sessionId_idx`(`sessionId`),
INDEX `FlyerSelection_userId_status_idx`(`userId`, `status`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `FlyerSession`
ADD CONSTRAINT `FlyerSession_userId_fkey`
FOREIGN KEY (`userId`) REFERENCES `User`(`id`)
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE `FlyerItem`
ADD CONSTRAINT `FlyerItem_sessionId_fkey`
FOREIGN KEY (`sessionId`) REFERENCES `FlyerSession`(`id`)
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE `FlyerSelection`
ADD CONSTRAINT `FlyerSelection_sessionId_fkey`
FOREIGN KEY (`sessionId`) REFERENCES `FlyerSession`(`id`)
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE `FlyerSelection`
ADD CONSTRAINT `FlyerSelection_itemId_fkey`
FOREIGN KEY (`itemId`) REFERENCES `FlyerItem`(`id`)
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE `FlyerSelection`
ADD CONSTRAINT `FlyerSelection_userId_fkey`
FOREIGN KEY (`userId`) REFERENCES `User`(`id`)
ON DELETE CASCADE ON UPDATE CASCADE;
+79 -8
View File
@@ -27,10 +27,12 @@ model User {
ownedProducts Product[]
inventoryItems InventoryItem[]
pantryItems PantryItem[]
mealPlanEntries MealPlanEntry[]
receiptAliases ReceiptAlias[]
unitMappings UnitMapping[]
}
mealPlanEntries MealPlanEntry[]
receiptAliases ReceiptAlias[]
unitMappings UnitMapping[]
flyerSessions FlyerSession[]
flyerSelections FlyerSelection[]
}
model Product {
id Int @id @default(autoincrement())
@@ -264,7 +266,7 @@ model UnitMapping {
@@index([userId])
}
model HelpText {
model HelpText {
id Int @id @default(autoincrement())
key String
scope String @default("default")
@@ -274,6 +276,75 @@ model HelpText {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([key, scope])
@@index([key, isActive])
}
@@unique([key, scope])
@@index([key, isActive])
}
model FlyerSession {
id Int @id @default(autoincrement())
userId Int
retailer String
weekKey String
status String @default("draft")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
expiresAt DateTime?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
items FlyerItem[]
selections FlyerSelection[]
@@index([userId])
@@index([weekKey])
@@index([status])
}
model FlyerItem {
id Int @id @default(autoincrement())
sessionId Int
rawName String
normalizedName String
categoryHint String?
price Decimal? @db.Decimal(10, 2)
priceUnit String?
comparisonPrice Decimal? @db.Decimal(10, 2)
comparisonUnit String?
offerText String?
parseConfidence Float
parseReasons Json?
matchedProductId Int?
matchedProductName String?
matchedVia String?
matchConfidence Float?
matchReasons Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
session FlyerSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
selections FlyerSelection[]
@@index([sessionId])
@@index([normalizedName])
}
model FlyerSelection {
id Int @id @default(autoincrement())
sessionId Int
itemId Int
userId Int
plannedQuantity Decimal? @db.Decimal(10, 2)
plannedUnit String?
priority String @default("normal")
note String?
status String @default("planned")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
session FlyerSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
item FlyerItem @relation(fields: [itemId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([sessionId, itemId])
@@index([sessionId])
@@index([userId, status])
}