From 9d2e01c6d227e8920de0c55ef8848d36cfe177d0 Mon Sep 17 00:00:00 2001 From: shafi54 <108669266+shafi-aviz@users.noreply.github.com> Date: Sat, 15 Aug 2026 00:30:04 +0530 Subject: [PATCH] enh --- .../dashboard/customize-app/popular-items.tsx | 2 +- apps/admin-ui/components/BannerForm.tsx | 2 +- apps/admin-ui/components/ProductGroupForm.tsx | 2 +- apps/admin-ui/components/ProductsSelector.tsx | 2 + apps/admin-ui/components/StoreForm.tsx | 8 ++-- .../admin-ui/components/VendorSnippetForm.tsx | 2 +- .../src/trpc/apis/admin-apis/apis/product.ts | 25 ++--------- apps/backend/wrangler.dev.toml | 2 +- .../drizzle/0002_sku_split.sql | 39 +++++++++++++++++ .../src/admin-apis/product.ts | 42 ++++++++++++------- .../db_helper_sqlite/src/admin-apis/store.ts | 37 ++++++++++++---- packages/db_helper_sqlite/src/db/schema.ts | 8 ++-- .../db_helper_sqlite/src/user-apis/product.ts | 9 +++- packages/shared/types/admin.ts | 4 +- 14 files changed, 120 insertions(+), 64 deletions(-) diff --git a/apps/admin-ui/app/(drawer)/dashboard/customize-app/popular-items.tsx b/apps/admin-ui/app/(drawer)/dashboard/customize-app/popular-items.tsx index 6101832..15137d1 100644 --- a/apps/admin-ui/app/(drawer)/dashboard/customize-app/popular-items.tsx +++ b/apps/admin-ui/app/(drawer)/dashboard/customize-app/popular-items.tsx @@ -416,7 +416,7 @@ export default function CustomizePopularItems() { multiple={true} label="Select Products" placeholder="Choose products..." - labelFormat={(product) => `${product.name} - ₹${product.price}`} + labelFormat={(product) => `${product.productName} - ₹${product.price}`} /> diff --git a/apps/admin-ui/components/BannerForm.tsx b/apps/admin-ui/components/BannerForm.tsx index 033b299..a40b053 100644 --- a/apps/admin-ui/components/BannerForm.tsx +++ b/apps/admin-ui/components/BannerForm.tsx @@ -185,7 +185,7 @@ export default function BannerForm({ multiple={true} label="Select Products" placeholder="Select products for banner (optional)" - labelFormat={(product) => `${product.name} (${product.price})`} + labelFormat={(product) => `${product.productName} (₹${product.price})`} /> diff --git a/apps/admin-ui/components/ProductGroupForm.tsx b/apps/admin-ui/components/ProductGroupForm.tsx index 7e59918..216a93d 100644 --- a/apps/admin-ui/components/ProductGroupForm.tsx +++ b/apps/admin-ui/components/ProductGroupForm.tsx @@ -119,7 +119,7 @@ const ProductGroupForm: React.FC = ({ multiple={true} label="Products" placeholder="Select products" - labelFormat={(product) => `${product.name}${product.shortDescription ? ` - ${product.shortDescription}` : ''}`} + labelFormat={(product) => product.label} /> {/* Actions */} diff --git a/apps/admin-ui/components/ProductsSelector.tsx b/apps/admin-ui/components/ProductsSelector.tsx index 30eba0b..b688a9f 100644 --- a/apps/admin-ui/components/ProductsSelector.tsx +++ b/apps/admin-ui/components/ProductsSelector.tsx @@ -9,6 +9,8 @@ interface SkuSummary { productId: number; productName: string; label: string; + storeId: number | null; + price: string; } interface Group { diff --git a/apps/admin-ui/components/StoreForm.tsx b/apps/admin-ui/components/StoreForm.tsx index 4a856a7..fd5399e 100644 --- a/apps/admin-ui/components/StoreForm.tsx +++ b/apps/admin-ui/components/StoreForm.tsx @@ -45,12 +45,12 @@ const StoreForm = forwardRef((props, ref) => { const [selectedImages, setSelectedImages] = useState<{ blob: Blob; mimeType: string }[]>([]); const [displayImages, setDisplayImages] = useState<{ uri?: string }[]>([]); - // For edit mode, pre-select products belonging to this store + // For edit mode, pre-select SKUs belonging to this store's products const initialSelectedProducts = useMemo(() => { if (mode !== 'edit' || !productsData?.products) return []; return productsData.products .filter(p => p.storeId === storeId) - .map(p => p.id); + .flatMap(p => (p.skus || []).map(sku => sku.id)); }, [mode, productsData?.products, storeId]); useEffect(() => { @@ -166,8 +166,8 @@ const StoreForm = forwardRef((props, ref) => { multiple={true} label="Products" placeholder="Select products" - isDisabled={(product) => product.storeId !== null && product.storeId !== storeId} - labelFormat={(product) => `${product.name} - ₹${product.price}`} + isDisabled={(sku) => sku.storeId !== null && sku.storeId !== storeId} + labelFormat={(sku) => `${sku.productName} - ₹${sku.price}`} /> Store Image diff --git a/apps/admin-ui/components/VendorSnippetForm.tsx b/apps/admin-ui/components/VendorSnippetForm.tsx index 0d0b2fa..87bd2c2 100644 --- a/apps/admin-ui/components/VendorSnippetForm.tsx +++ b/apps/admin-ui/components/VendorSnippetForm.tsx @@ -185,7 +185,7 @@ const VendorSnippetForm: React.FC = ({ multiple={true} label="Select Products" placeholder="Select products" - labelFormat={(product) => `${product.name} (${product.unit})`} + labelFormat={(product) => product.label} /> {formik.errors.skuIds && formik.touched.skuIds && ( {formik.errors.skuIds} diff --git a/apps/backend/src/trpc/apis/admin-apis/apis/product.ts b/apps/backend/src/trpc/apis/admin-apis/apis/product.ts index 396dc75..f4475ff 100644 --- a/apps/backend/src/trpc/apis/admin-apis/apis/product.ts +++ b/apps/backend/src/trpc/apis/admin-apis/apis/product.ts @@ -606,31 +606,12 @@ export const productRouter = router({ getGroups: protectedProcedure .query(async (): Promise => { + // getAllProductGroupsInDb already returns groups with products mapped + // as AdminSku[] (SKU ids per the ProductsSelector). const groups = await getAllProductGroupsInDb() - /* - // Old implementation - direct DB queries: - const groups = await db.query.productGroupInfo.findMany({ - with: { - memberships: { - with: { - product: true, - }, - }, - }, - orderBy: desc(productGroupInfo.createdAt), - }); - */ - return { - groups: groups.map(group => ({ - ...group, - products: group.memberships.map((m: any) => ({ - ...(m.product as AdminProduct), - images: (m.product.images as string[]) || null, - })), - productCount: group.memberships.length, - })), + groups, } }), diff --git a/apps/backend/wrangler.dev.toml b/apps/backend/wrangler.dev.toml index a86b89b..bdcbe8b 100644 --- a/apps/backend/wrangler.dev.toml +++ b/apps/backend/wrangler.dev.toml @@ -9,7 +9,7 @@ routes = [ [[d1_databases]] binding = "DB" database_name = "freshyo-backend-dev" -database_id = "b0c7a47d-3807-4e13-8b3e-fb43a45b02b2" +database_id = "b2528bdb-0ec0-478e-8765-b118918153c0" #database_name = "freshyo-dev" #database_id = "3cad440f-dc14-4baa-ab05-04eb08e206de" migrations_dir="../../packages/db_helper_sqlite/drizzle" diff --git a/packages/db_helper_sqlite/drizzle/0002_sku_split.sql b/packages/db_helper_sqlite/drizzle/0002_sku_split.sql index 5f681dc..2490303 100644 --- a/packages/db_helper_sqlite/drizzle/0002_sku_split.sql +++ b/packages/db_helper_sqlite/drizzle/0002_sku_split.sql @@ -187,6 +187,45 @@ DROP TABLE `coupon_applicable_products`; ALTER TABLE `__new_coupon_applicable_products` RENAME TO `coupon_applicable_products`; CREATE UNIQUE INDEX `unique_coupon_sku` ON `coupon_applicable_products` (`coupon_id`,`sku_id`); +-- 5b. Recreate tag + group memberships to reference product_skus.id (SKU ids) +-- instead of product_info.id. ProductsSelector now saves SKU ids into these. +CREATE TABLE `__new_product_tags` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `product_id` integer NOT NULL, + `tag_id` integer NOT NULL, + `assigned_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (`product_id`) REFERENCES `product_skus`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`tag_id`) REFERENCES `product_tag_info`(`id`) ON UPDATE no action ON DELETE no action +); + +INSERT INTO `__new_product_tags` (`id`, `product_id`, `tag_id`, `assigned_at`) +SELECT + `pt`.`id`, `m`.`sku_id`, `pt`.`tag_id`, `pt`.`assigned_at` +FROM `product_tags` `pt` +JOIN `__product_to_sku` `m` ON `m`.`product_id` = `pt`.`product_id`; + +DROP TABLE `product_tags`; +ALTER TABLE `__new_product_tags` RENAME TO `product_tags`; +CREATE UNIQUE INDEX `unique_product_tag` ON `product_tags` (`product_id`,`tag_id`); + +CREATE TABLE `__new_product_group_membership` ( + `product_id` integer NOT NULL, + `group_id` integer NOT NULL, + `added_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL, + PRIMARY KEY (`product_id`,`group_id`), + FOREIGN KEY (`product_id`) REFERENCES `product_skus`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`group_id`) REFERENCES `product_group_info`(`id`) ON UPDATE no action ON DELETE no action +); + +INSERT INTO `__new_product_group_membership` (`product_id`, `group_id`, `added_at`) +SELECT + `m`.`sku_id`, `pgm`.`group_id`, `pgm`.`added_at` +FROM `product_group_membership` `pgm` +JOIN `__product_to_sku` `m` ON `m`.`product_id` = `pgm`.`product_id`; + +DROP TABLE `product_group_membership`; +ALTER TABLE `__new_product_group_membership` RENAME TO `product_group_membership`; + -- 6. Remap JSON product id arrays to sku id arrays (before renaming the columns). UPDATE `delivery_slot_info` AS `target` SET `product_ids` = COALESCE( diff --git a/packages/db_helper_sqlite/src/admin-apis/product.ts b/packages/db_helper_sqlite/src/admin-apis/product.ts index e7e64f8..0b7ff88 100644 --- a/packages/db_helper_sqlite/src/admin-apis/product.ts +++ b/packages/db_helper_sqlite/src/admin-apis/product.ts @@ -660,19 +660,21 @@ export async function getAllProductTags(): Promise }> + }) as Array }> - return tags.map((tag: ProductTagInfoRow & { products: Array }) => ({ + return tags.map((tag: any) => ({ ...mapTagInfo(tag), - products: tag.products.map((assignment: ProductTagRow & { product: ProductRow }) => ({ + products: tag.products.map((assignment: any) => ({ productId: assignment.productId, tagId: assignment.tagId, assignedAt: assignment.assignedAt, - product: mapProduct(assignment.product), + product: mapSku(assignment.sku, assignment.sku.features, [], assignment.sku.marketStats), })), productIds: tag.products.map((assignment: ProductTagRow) => assignment.productId), })) @@ -730,7 +732,9 @@ export async function getProductTagById(tagId: number): Promise ({ + products: (tag.products || []).map((assignment: any) => ({ productId: assignment.productId, tagId: assignment.tagId, assignedAt: assignment.assignedAt, - product: mapProduct(assignment.product), + product: mapSku(assignment.sku, assignment.sku.features, [], assignment.sku.marketStats), })), - productIds: tag.products.map((assignment: ProductTagRow) => assignment.productId), + productIds: (tag.products || []).map((assignment: ProductTagRow) => assignment.productId), } } @@ -776,7 +780,9 @@ export async function updateProductTag(tagId: number, input: UpdateProductTagInp with: { products: { with: { - product: true, + sku: { + with: { features: true, marketStats: true }, + }, }, }, }, @@ -784,13 +790,13 @@ export async function updateProductTag(tagId: number, input: UpdateProductTagInp return { ...mapTagInfo(tag), - products: fullTag?.products.map((assignment: ProductTagRow & { product: ProductRow }) => ({ + products: (fullTag?.products || []).map((assignment: any) => ({ productId: assignment.productId, tagId: assignment.tagId, assignedAt: assignment.assignedAt, - product: mapProduct(assignment.product), - })) || [], - productIds: fullTag?.products.map((assignment: ProductTagRow) => assignment.productId) || [], + product: mapSku(assignment.sku, assignment.sku.features, [], assignment.sku.marketStats), + })), + productIds: (fullTag?.products || []).map((assignment: ProductTagRow) => assignment.productId) || [], } } @@ -906,7 +912,9 @@ export async function getAllProductGroups() { with: { memberships: { with: { - product: true, + sku: { + with: { features: true, marketStats: true }, + }, }, }, }, @@ -918,7 +926,9 @@ export async function getAllProductGroups() { groupName: group.groupName, description: group.description ?? null, createdAt: group.createdAt, - products: group.memberships.map((membership: any) => mapProduct(membership.product)), + products: (group.memberships || []).map((membership: any) => + mapSku(membership.sku, membership.sku.features, [], membership.sku.marketStats) + ), productCount: group.memberships.length, memberships: group.memberships })) diff --git a/packages/db_helper_sqlite/src/admin-apis/store.ts b/packages/db_helper_sqlite/src/admin-apis/store.ts index 650c288..178056e 100644 --- a/packages/db_helper_sqlite/src/admin-apis/store.ts +++ b/packages/db_helper_sqlite/src/admin-apis/store.ts @@ -1,5 +1,5 @@ import { db } from '../db/db_index' -import { storeInfo, productInfo } from '../db/schema' +import { storeInfo, productInfo, productSkus } from '../db/schema' import { eq, inArray } from 'drizzle-orm' import { runBatched } from '../lib/run-batched' @@ -57,12 +57,23 @@ export async function createStore( .returning() if (products && products.length > 0) { - await runBatched(tx, products, 10, async (t, chunk) => { - await t - .update(productInfo) - .set({ storeId: newStore.id }) - .where(inArray(productInfo.id, chunk)) - }) + // ProductsSelector sends SKU ids — resolve to the owning product ids + // (product_info.storeId is a product-level field). + const productIds = products.length > 0 + ? await tx.query.productSkus.findMany({ + where: inArray(productSkus.id, products), + columns: { productId: true }, + }).then((rows) => [...new Set(rows.map((r) => r.productId))]) + : [] + + if (productIds.length > 0) { + await runBatched(tx, productIds, 10, async (t, chunk) => { + await t + .update(productInfo) + .set({ storeId: newStore.id }) + .where(inArray(productInfo.id, chunk)) + }) + } } return { @@ -109,8 +120,16 @@ export async function updateStore( .set({ storeId: null }) .where(eq(productInfo.storeId, id)) - if (products.length > 0) { - await runBatched(tx, products, 10, async (t, chunk) => { + // ProductsSelector sends SKU ids — resolve to the owning product ids. + const productIds = products.length > 0 + ? await tx.query.productSkus.findMany({ + where: inArray(productSkus.id, products), + columns: { productId: true }, + }).then((rows) => [...new Set(rows.map((r) => r.productId))]) + : [] + + if (productIds.length > 0) { + await runBatched(tx, productIds, 10, async (t, chunk) => { await t .update(productInfo) .set({ storeId: id }) diff --git a/packages/db_helper_sqlite/src/db/schema.ts b/packages/db_helper_sqlite/src/db/schema.ts index a6a5c63..c2247b3 100644 --- a/packages/db_helper_sqlite/src/db/schema.ts +++ b/packages/db_helper_sqlite/src/db/schema.ts @@ -244,7 +244,7 @@ export const productGroupInfo = sqliteTable('product_group_info', { }) export const productGroupMembership = sqliteTable('product_group_membership', { - productId: integer('product_id').notNull().references(() => productInfo.id), + productId: integer('product_id').notNull().references(() => productSkus.id), groupId: integer('group_id').notNull().references(() => productGroupInfo.id), addedAt: timestampText('added_at').notNull().default(sql`CURRENT_TIMESTAMP`), }, (t) => ({ @@ -298,7 +298,7 @@ export const productTagInfo = sqliteTable('product_tag_info', { export const productTags = sqliteTable('product_tags', { id: integer().primaryKey({ autoIncrement: true }), - productId: integer('product_id').notNull().references(() => productInfo.id), + productId: integer('product_id').notNull().references(() => productSkus.id), tagId: integer('tag_id').notNull().references(() => productTagInfo.id), assignedAt: timestampText('assigned_at').notNull().default(sql`CURRENT_TIMESTAMP`), }, (t) => ({ @@ -630,7 +630,7 @@ export const productTagInfoRelations = relations(productTagInfo, ({ many }) => ( })) export const productTagsRelations = relations(productTags, ({ one }) => ({ - product: one(productInfo, { fields: [productTags.productId], references: [productInfo.id] }), + sku: one(productSkus, { fields: [productTags.productId], references: [productSkus.id] }), tag: one(productTagInfo, { fields: [productTags.tagId], references: [productTagInfo.id] }), })) @@ -760,7 +760,7 @@ export const productGroupInfoRelations = relations(productGroupInfo, ({ many }) })) export const productGroupMembershipRelations = relations(productGroupMembership, ({ one }) => ({ - product: one(productInfo, { fields: [productGroupMembership.productId], references: [productInfo.id] }), + sku: one(productSkus, { fields: [productGroupMembership.productId], references: [productSkus.id] }), group: one(productGroupInfo, { fields: [productGroupMembership.groupId], references: [productGroupInfo.id] }), })) diff --git a/packages/db_helper_sqlite/src/user-apis/product.ts b/packages/db_helper_sqlite/src/user-apis/product.ts index 8cd373b..b8ea694 100644 --- a/packages/db_helper_sqlite/src/user-apis/product.ts +++ b/packages/db_helper_sqlite/src/user-apis/product.ts @@ -242,7 +242,8 @@ export async function getAllProductsWithUnits(tagId?: number): Promise { const features = sku.features || [] @@ -306,6 +307,8 @@ export interface SkuSummary { productName: string label: string images: unknown + storeId: number | null + price: string } export async function getAllSkusSummary(): Promise { @@ -314,7 +317,7 @@ export async function getAllSkusSummary(): Promise { features: true, marketStats: true, product: { - columns: { name: true }, + columns: { name: true, storeId: true }, }, }, }) @@ -330,6 +333,8 @@ export async function getAllSkusSummary(): Promise { productName: sku.product?.name ?? 'Unknown', label, images: sku.images, + storeId: sku.product?.storeId ?? null, + price: sku.marketStats?.ourPrice ? String(sku.marketStats.ourPrice) : '0', } }) } diff --git a/packages/shared/types/admin.ts b/packages/shared/types/admin.ts index a1d122b..d58531a 100644 --- a/packages/shared/types/admin.ts +++ b/packages/shared/types/admin.ts @@ -464,7 +464,7 @@ export interface AdminProductTagAssignment { productId: number; tagId: number; assignedAt: Date; - product: AdminProduct; + product: AdminSku; } export interface AdminProductTagWithProducts extends AdminProductTagInfo { @@ -539,7 +539,7 @@ export interface AdminProductGroup { groupName: string; description: string | null; createdAt: Date; - products: AdminProduct[]; + products: AdminSku[]; productCount: number; }