x
This commit is contained in:
1 parent
ebc8f2ffeb
commit
7fd3c40ae2
2 files changed
+240
-27
No files matched your search
@@ -42,6 +42,9 @@ export default function BackupCatalogPage() {
|
|||||||
categoriesCount: number
|
categoriesCount: number
|
||||||
attributesCount: number
|
attributesCount: number
|
||||||
brandsCount: number
|
brandsCount: number
|
||||||
|
detailSectionsCount: number
|
||||||
|
productAttributesCount: number
|
||||||
|
categorySectionsCount: number
|
||||||
rawPayload: any
|
rawPayload: any
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
const [restoreLoading, setRestoreLoading] = useState(false)
|
const [restoreLoading, setRestoreLoading] = useState(false)
|
||||||
@@ -108,11 +111,26 @@ export default function BackupCatalogPage() {
|
|||||||
? payload
|
? payload
|
||||||
: []
|
: []
|
||||||
|
|
||||||
|
let detailSectionsCount = 0
|
||||||
|
let productAttributesCount = 0
|
||||||
|
for (const p of products) {
|
||||||
|
if (Array.isArray(p.detailSections)) detailSectionsCount += p.detailSections.length
|
||||||
|
if (Array.isArray(p.attributes)) productAttributesCount += p.attributes.length
|
||||||
|
}
|
||||||
|
|
||||||
|
let categorySectionsCount = 0
|
||||||
|
for (const c of categories) {
|
||||||
|
if (Array.isArray(c.sections)) categorySectionsCount += c.sections.length
|
||||||
|
}
|
||||||
|
|
||||||
setRestorePreview({
|
setRestorePreview({
|
||||||
categoriesCount: categories.length,
|
categoriesCount: categories.length,
|
||||||
attributesCount: attributes.length,
|
attributesCount: attributes.length,
|
||||||
brandsCount: brands.length,
|
brandsCount: brands.length,
|
||||||
productsCount: products.length,
|
productsCount: products.length,
|
||||||
|
detailSectionsCount,
|
||||||
|
productAttributesCount,
|
||||||
|
categorySectionsCount,
|
||||||
rawPayload: payload,
|
rawPayload: payload,
|
||||||
})
|
})
|
||||||
} catch {
|
} catch {
|
||||||
@@ -390,6 +408,32 @@ export default function BackupCatalogPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Extended Preview Metadata */}
|
||||||
|
{(restorePreview.detailSectionsCount > 0 ||
|
||||||
|
restorePreview.productAttributesCount > 0 ||
|
||||||
|
restorePreview.categorySectionsCount > 0) && (
|
||||||
|
<div className="flex flex-wrap gap-2 pt-1 text-[11px] text-slate-400">
|
||||||
|
{restorePreview.productAttributesCount > 0 && (
|
||||||
|
<span className="bg-white/5 border border-white/10 px-2 py-0.5 rounded-md flex items-center gap-1">
|
||||||
|
<Sliders className="w-3 h-3 text-violet-400" />
|
||||||
|
Product Attribute Values: <b className="text-white">{restorePreview.productAttributesCount}</b>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{restorePreview.detailSectionsCount > 0 && (
|
||||||
|
<span className="bg-white/5 border border-white/10 px-2 py-0.5 rounded-md flex items-center gap-1">
|
||||||
|
<Package className="w-3 h-3 text-cyan-400" />
|
||||||
|
Product Detail Sections: <b className="text-white">{restorePreview.detailSectionsCount}</b>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{restorePreview.categorySectionsCount > 0 && (
|
||||||
|
<span className="bg-white/5 border border-white/10 px-2 py-0.5 rounded-md flex items-center gap-1">
|
||||||
|
<FolderTree className="w-3 h-3 text-amber-400" />
|
||||||
|
Category CMS Sections: <b className="text-white">{restorePreview.categorySectionsCount}</b>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Confirmation Checkbox */}
|
{/* Confirmation Checkbox */}
|
||||||
<div className="p-3 bg-amber-500/10 rounded-lg border border-amber-500/20 flex items-start gap-2.5">
|
<div className="p-3 bg-amber-500/10 rounded-lg border border-amber-500/20 flex items-start gap-2.5">
|
||||||
<AlertTriangle className="w-4 h-4 text-amber-400 shrink-0 mt-0.5" />
|
<AlertTriangle className="w-4 h-4 text-amber-400 shrink-0 mt-0.5" />
|
||||||
@@ -441,8 +485,9 @@ export default function BackupCatalogPage() {
|
|||||||
{restoreResult.summary && (
|
{restoreResult.summary && (
|
||||||
<div className="text-xs text-slate-400 pl-6 space-y-0.5">
|
<div className="text-xs text-slate-400 pl-6 space-y-0.5">
|
||||||
<p>• Products: {restoreResult.summary.products} (Variants: {restoreResult.summary.variants})</p>
|
<p>• Products: {restoreResult.summary.products} (Variants: {restoreResult.summary.variants})</p>
|
||||||
<p>• Categories: {restoreResult.summary.categories}</p>
|
<p>• Categories: {restoreResult.summary.categories} {restoreResult.summary.categorySections > 0 ? `(Sections: ${restoreResult.summary.categorySections})` : ''}</p>
|
||||||
<p>• Attributes: {restoreResult.summary.attributes}</p>
|
<p>• Attribute Definitions: {restoreResult.summary.attributes} {restoreResult.summary.productAttributes > 0 ? `(Product Values: ${restoreResult.summary.productAttributes})` : ''}</p>
|
||||||
|
<p>• Product Detail Sections: {restoreResult.summary.detailSections || 0}</p>
|
||||||
<p>• Brands: {restoreResult.summary.brands}</p>
|
<p>• Brands: {restoreResult.summary.brands}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
productImageErrorResponse,
|
productImageErrorResponse,
|
||||||
ProductImagePayloadError,
|
ProductImagePayloadError,
|
||||||
} from "../lib/productImages";
|
} from "../lib/productImages";
|
||||||
|
import { defaultProductDetailSections } from "../lib/productDetailSections";
|
||||||
|
|
||||||
// ─── Helper: stream JSON as file download ────────────────────────────────────
|
// ─── Helper: stream JSON as file download ────────────────────────────────────
|
||||||
|
|
||||||
@@ -90,10 +91,19 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
if (section === "products" || section === "all") {
|
if (section === "products" || section === "all") {
|
||||||
const products = await prisma.product.findMany({
|
const products = await prisma.product.findMany({
|
||||||
include: {
|
include: {
|
||||||
variants: true,
|
variants: { orderBy: { id: "asc" } },
|
||||||
images: true,
|
images: { orderBy: { order: "asc" } },
|
||||||
translations: true,
|
translations: true,
|
||||||
attributes: { include: { attributeDefinition: true, translations: true } },
|
attributes: {
|
||||||
|
include: {
|
||||||
|
translations: true,
|
||||||
|
attributeDefinition: {
|
||||||
|
select: { id: true, code: true, label: true, type: true, unit: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: { id: "asc" },
|
||||||
|
},
|
||||||
|
detailSections: { orderBy: { order: "asc" } },
|
||||||
brand: { include: { translations: true } },
|
brand: { include: { translations: true } },
|
||||||
category: { select: { id: true, name: true } },
|
category: { select: { id: true, name: true } },
|
||||||
},
|
},
|
||||||
@@ -107,9 +117,14 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
name: p.name,
|
name: p.name,
|
||||||
description: p.description,
|
description: p.description,
|
||||||
categoryId: p.categoryId,
|
categoryId: p.categoryId,
|
||||||
|
categoryName: p.category?.name,
|
||||||
brandId: p.brandId,
|
brandId: p.brandId,
|
||||||
|
brandName: p.brand?.name,
|
||||||
variantCount: p.variants.length,
|
variantCount: p.variants.length,
|
||||||
imageCount: p.images.length,
|
imageCount: p.images.length,
|
||||||
|
attributeCount: p.attributes.length,
|
||||||
|
detailSectionCount: p.detailSections.length,
|
||||||
|
isDeleted: p.isDeleted,
|
||||||
createdAt: p.createdAt,
|
createdAt: p.createdAt,
|
||||||
}));
|
}));
|
||||||
return csvResponse(rows, `homaroma_products_${dt}.csv`);
|
return csvResponse(rows, `homaroma_products_${dt}.csv`);
|
||||||
@@ -120,11 +135,15 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
// full catalog
|
// full catalog
|
||||||
const categories = await prisma.category.findMany({
|
const categories = await prisma.category.findMany({
|
||||||
where: { deletedAt: null },
|
where: { deletedAt: null },
|
||||||
include: { translations: true, media: { orderBy: [{ sortOrder: "asc" }, { id: "asc" }] } },
|
include: {
|
||||||
|
translations: true,
|
||||||
|
media: { orderBy: [{ sortOrder: "asc" }, { id: "asc" }] },
|
||||||
|
sections: { orderBy: { order: "asc" } },
|
||||||
|
},
|
||||||
orderBy: { id: "asc" },
|
orderBy: { id: "asc" },
|
||||||
});
|
});
|
||||||
const attributes = await prisma.attributeDefinition.findMany({
|
const attributes = await prisma.attributeDefinition.findMany({
|
||||||
include: { translations: true, media: { orderBy: [{ sortOrder: "asc" }, { id: "asc" }] } },
|
include: { translations: true },
|
||||||
orderBy: { sortOrder: "asc" },
|
orderBy: { sortOrder: "asc" },
|
||||||
});
|
});
|
||||||
const brands = await prisma.productBrand.findMany({
|
const brands = await prisma.productBrand.findMany({
|
||||||
@@ -144,6 +163,9 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
brandName: p.brand?.name,
|
brandName: p.brand?.name,
|
||||||
variantCount: p.variants.length,
|
variantCount: p.variants.length,
|
||||||
imageCount: p.images.length,
|
imageCount: p.images.length,
|
||||||
|
attributeCount: p.attributes.length,
|
||||||
|
detailSectionCount: p.detailSections.length,
|
||||||
|
isDeleted: p.isDeleted,
|
||||||
createdAt: p.createdAt,
|
createdAt: p.createdAt,
|
||||||
}));
|
}));
|
||||||
return csvResponse(rows, `homaroma_catalog_${dt}.csv`);
|
return csvResponse(rows, `homaroma_catalog_${dt}.csv`);
|
||||||
@@ -167,7 +189,11 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
if (section === "categories") {
|
if (section === "categories") {
|
||||||
const categories = await prisma.category.findMany({
|
const categories = await prisma.category.findMany({
|
||||||
where: { deletedAt: null },
|
where: { deletedAt: null },
|
||||||
include: { translations: true },
|
include: {
|
||||||
|
translations: true,
|
||||||
|
media: { orderBy: [{ sortOrder: "asc" }, { id: "asc" }] },
|
||||||
|
sections: { orderBy: { order: "asc" } },
|
||||||
|
},
|
||||||
orderBy: { id: "asc" },
|
orderBy: { id: "asc" },
|
||||||
});
|
});
|
||||||
if (format === "csv") {
|
if (format === "csv") {
|
||||||
@@ -175,6 +201,9 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
id: c.id,
|
id: c.id,
|
||||||
name: c.name,
|
name: c.name,
|
||||||
imageUrl: c.imageUrl,
|
imageUrl: c.imageUrl,
|
||||||
|
layoutType: c.layoutType,
|
||||||
|
mediaCount: c.media.length,
|
||||||
|
sectionCount: c.sections.length,
|
||||||
createdAt: c.createdAt,
|
createdAt: c.createdAt,
|
||||||
}));
|
}));
|
||||||
return csvResponse(rows, `homaroma_categories_${dt}.csv`);
|
return csvResponse(rows, `homaroma_categories_${dt}.csv`);
|
||||||
@@ -541,6 +570,7 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
products, categories, customers, orders,
|
products, categories, customers, orders,
|
||||||
articles, pages, menus, coupons, promotions,
|
articles, pages, menus, coupons, promotions,
|
||||||
media, subscribers, rawMaterials, tiktokOrders, shopeeOrders, lazadaOrders,
|
media, subscribers, rawMaterials, tiktokOrders, shopeeOrders, lazadaOrders,
|
||||||
|
productDetailSections, categorySections, productAttributes,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
prisma.product.count(),
|
prisma.product.count(),
|
||||||
prisma.category.count({ where: { deletedAt: null } }),
|
prisma.category.count({ where: { deletedAt: null } }),
|
||||||
@@ -557,6 +587,9 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
prisma.tiktokOrder.count(),
|
prisma.tiktokOrder.count(),
|
||||||
prisma.shopeeOrder.count(),
|
prisma.shopeeOrder.count(),
|
||||||
prisma.lazadaOrder.count(),
|
prisma.lazadaOrder.count(),
|
||||||
|
prisma.productDetailSection.count(),
|
||||||
|
prisma.categorySection.count(),
|
||||||
|
prisma.productAttribute.count(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -577,6 +610,9 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
tiktokOrders,
|
tiktokOrders,
|
||||||
shopeeOrders,
|
shopeeOrders,
|
||||||
lazadaOrders,
|
lazadaOrders,
|
||||||
|
productDetailSections,
|
||||||
|
categorySections,
|
||||||
|
productAttributes,
|
||||||
},
|
},
|
||||||
pgDumpNote: "Full pg_dump requires shell access. Use SSH: pg_dump -U postgres <dbname> > backup.sql",
|
pgDumpNote: "Full pg_dump requires shell access. Use SSH: pg_dump -U postgres <dbname> > backup.sql",
|
||||||
};
|
};
|
||||||
@@ -847,10 +883,13 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
|
|
||||||
const summary = {
|
const summary = {
|
||||||
categories: 0,
|
categories: 0,
|
||||||
|
categorySections: 0,
|
||||||
attributes: 0,
|
attributes: 0,
|
||||||
brands: 0,
|
brands: 0,
|
||||||
products: 0,
|
products: 0,
|
||||||
variants: 0,
|
variants: 0,
|
||||||
|
productAttributes: 0,
|
||||||
|
detailSections: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. Categories
|
// 1. Categories
|
||||||
@@ -862,14 +901,21 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
const savedCat = await prisma.category.upsert({
|
const savedCat = await prisma.category.upsert({
|
||||||
where: { name: cat.name },
|
where: { name: cat.name },
|
||||||
update: {
|
update: {
|
||||||
imageUrl: cat.imageUrl,
|
imageUrl: cat.imageUrl ?? null,
|
||||||
layoutType: cat.layoutType || 'split_showcase',
|
layoutType: cat.layoutType || 'split_showcase',
|
||||||
deletedAt: null,
|
featuredSeries: cat.featuredSeries ?? undefined,
|
||||||
|
uspBenefits: cat.uspBenefits ?? undefined,
|
||||||
|
filterConfig: cat.filterConfig ?? undefined,
|
||||||
|
deletedAt: cat.deletedAt ? new Date(cat.deletedAt) : null,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
name: cat.name,
|
name: cat.name,
|
||||||
imageUrl: cat.imageUrl,
|
imageUrl: cat.imageUrl ?? null,
|
||||||
layoutType: cat.layoutType || 'split_showcase',
|
layoutType: cat.layoutType || 'split_showcase',
|
||||||
|
featuredSeries: cat.featuredSeries ?? undefined,
|
||||||
|
uspBenefits: cat.uspBenefits ?? undefined,
|
||||||
|
filterConfig: cat.filterConfig ?? undefined,
|
||||||
|
deletedAt: cat.deletedAt ? new Date(cat.deletedAt) : null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (cat.id) categoryMap.set(cat.id, savedCat.id);
|
if (cat.id) categoryMap.set(cat.id, savedCat.id);
|
||||||
@@ -909,6 +955,26 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore category dynamic sections if present
|
||||||
|
if (Array.isArray(cat.sections) && cat.sections.length > 0) {
|
||||||
|
await prisma.categorySection.deleteMany({ where: { categoryId: savedCat.id } });
|
||||||
|
for (const sec of cat.sections) {
|
||||||
|
if (!sec.type) continue;
|
||||||
|
await prisma.categorySection.create({
|
||||||
|
data: {
|
||||||
|
categoryId: savedCat.id,
|
||||||
|
type: sec.type,
|
||||||
|
order: sec.order ?? 0,
|
||||||
|
payload: sec.payload ?? {},
|
||||||
|
isActive: sec.isActive ?? true,
|
||||||
|
isLocked: sec.isLocked ?? false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
summary.categorySections++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
summary.categories++;
|
summary.categories++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -969,6 +1035,8 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
|
|
||||||
// 3. Attribute Definitions
|
// 3. Attribute Definitions
|
||||||
const attributes = Array.isArray(payload.attributes) ? payload.attributes : [];
|
const attributes = Array.isArray(payload.attributes) ? payload.attributes : [];
|
||||||
|
const attrDefMap = new Map<number | string, number>();
|
||||||
|
|
||||||
for (const attr of attributes) {
|
for (const attr of attributes) {
|
||||||
if (!attr.code || !attr.label) continue;
|
if (!attr.code || !attr.label) continue;
|
||||||
const mappedCatId = attr.categoryId ? (categoryMap.get(attr.categoryId) || defaultCatId) : null;
|
const mappedCatId = attr.categoryId ? (categoryMap.get(attr.categoryId) || defaultCatId) : null;
|
||||||
@@ -999,6 +1067,9 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (attr.id) attrDefMap.set(attr.id, savedAttr.id);
|
||||||
|
attrDefMap.set(attr.code, savedAttr.id);
|
||||||
|
|
||||||
if (Array.isArray(attr.translations)) {
|
if (Array.isArray(attr.translations)) {
|
||||||
for (const t of attr.translations) {
|
for (const t of attr.translations) {
|
||||||
if (t.langCode && t.label) {
|
if (t.langCode && t.label) {
|
||||||
@@ -1037,29 +1108,27 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
existing = await prisma.product.findFirst({ where: { name: prod.name } });
|
existing = await prisma.product.findFirst({ where: { name: prod.name } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const prodData = {
|
||||||
|
name: prod.name,
|
||||||
|
titleGraphicUrl: prod.titleGraphicUrl ?? null,
|
||||||
|
description: prod.description,
|
||||||
|
details: prod.details,
|
||||||
|
categoryId: catId,
|
||||||
|
brandId: bId,
|
||||||
|
options: prod.options ?? [],
|
||||||
|
isDeleted: Boolean(prod.isDeleted),
|
||||||
|
deletedAt: prod.deletedAt ? new Date(prod.deletedAt) : null,
|
||||||
|
};
|
||||||
|
|
||||||
let savedProduct;
|
let savedProduct;
|
||||||
if (existing) {
|
if (existing) {
|
||||||
savedProduct = await prisma.product.update({
|
savedProduct = await prisma.product.update({
|
||||||
where: { id: existing.id },
|
where: { id: existing.id },
|
||||||
data: {
|
data: prodData,
|
||||||
name: prod.name,
|
|
||||||
description: prod.description,
|
|
||||||
details: prod.details,
|
|
||||||
categoryId: catId,
|
|
||||||
brandId: bId,
|
|
||||||
options: prod.options ?? [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
savedProduct = await prisma.product.create({
|
savedProduct = await prisma.product.create({
|
||||||
data: {
|
data: prodData,
|
||||||
name: prod.name,
|
|
||||||
description: prod.description,
|
|
||||||
details: prod.details,
|
|
||||||
categoryId: catId,
|
|
||||||
brandId: bId,
|
|
||||||
options: prod.options ?? [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1084,6 +1153,7 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
barcode: v.barcode,
|
barcode: v.barcode,
|
||||||
price: Number(v.price) || 0,
|
price: Number(v.price) || 0,
|
||||||
stock: Number(v.stock) || 0,
|
stock: Number(v.stock) || 0,
|
||||||
|
imageUrl: v.imageUrl || null,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
productId: savedProduct.id,
|
productId: savedProduct.id,
|
||||||
@@ -1094,6 +1164,7 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
barcode: v.barcode,
|
barcode: v.barcode,
|
||||||
price: Number(v.price) || 0,
|
price: Number(v.price) || 0,
|
||||||
stock: Number(v.stock) || 0,
|
stock: Number(v.stock) || 0,
|
||||||
|
imageUrl: v.imageUrl || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
summary.variants++;
|
summary.variants++;
|
||||||
@@ -1145,6 +1216,103 @@ export const backupRoute = new Elysia({ prefix: "/backup" })
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Product Attributes
|
||||||
|
if (Array.isArray(prod.attributes) && prod.attributes.length > 0) {
|
||||||
|
for (const attr of prod.attributes) {
|
||||||
|
let targetAttrDefId: number | undefined = undefined;
|
||||||
|
if (attr.attributeDefinitionId && attrDefMap.has(attr.attributeDefinitionId)) {
|
||||||
|
targetAttrDefId = attrDefMap.get(attr.attributeDefinitionId);
|
||||||
|
} else if (attr.attributeDefinition?.code && attrDefMap.has(attr.attributeDefinition.code)) {
|
||||||
|
targetAttrDefId = attrDefMap.get(attr.attributeDefinition.code);
|
||||||
|
} else if (attr.attributeDefinition?.code) {
|
||||||
|
const found = await prisma.attributeDefinition.findUnique({
|
||||||
|
where: { code: attr.attributeDefinition.code },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (found) targetAttrDefId = found.id;
|
||||||
|
} else if (attr.attributeDefinitionId) {
|
||||||
|
targetAttrDefId = attr.attributeDefinitionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetAttrDefId) {
|
||||||
|
const savedProdAttr = await prisma.productAttribute.upsert({
|
||||||
|
where: {
|
||||||
|
productId_attributeDefinitionId: {
|
||||||
|
productId: savedProduct.id,
|
||||||
|
attributeDefinitionId: targetAttrDefId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
valueText: attr.valueText ?? null,
|
||||||
|
valueNumber: attr.valueNumber != null ? Number(attr.valueNumber) : null,
|
||||||
|
valueBoolean: attr.valueBoolean != null ? Boolean(attr.valueBoolean) : null,
|
||||||
|
valueJson: attr.valueJson ?? null,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
productId: savedProduct.id,
|
||||||
|
attributeDefinitionId: targetAttrDefId,
|
||||||
|
valueText: attr.valueText ?? null,
|
||||||
|
valueNumber: attr.valueNumber != null ? Number(attr.valueNumber) : null,
|
||||||
|
valueBoolean: attr.valueBoolean != null ? Boolean(attr.valueBoolean) : null,
|
||||||
|
valueJson: attr.valueJson ?? null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Array.isArray(attr.translations)) {
|
||||||
|
for (const t of attr.translations) {
|
||||||
|
if (t.langCode && t.valueText) {
|
||||||
|
await prisma.productAttributeTranslation.upsert({
|
||||||
|
where: {
|
||||||
|
productAttributeId_langCode: {
|
||||||
|
productAttributeId: savedProdAttr.id,
|
||||||
|
langCode: t.langCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: { valueText: t.valueText },
|
||||||
|
create: {
|
||||||
|
productAttributeId: savedProdAttr.id,
|
||||||
|
langCode: t.langCode,
|
||||||
|
valueText: t.valueText,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
summary.productAttributes++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Product Detail Sections
|
||||||
|
if (Array.isArray(prod.detailSections) && prod.detailSections.length > 0) {
|
||||||
|
await prisma.productDetailSection.deleteMany({ where: { productId: savedProduct.id } });
|
||||||
|
for (const sec of prod.detailSections) {
|
||||||
|
if (!sec.type) continue;
|
||||||
|
await prisma.productDetailSection.create({
|
||||||
|
data: {
|
||||||
|
productId: savedProduct.id,
|
||||||
|
type: sec.type,
|
||||||
|
order: sec.order ?? 0,
|
||||||
|
payload: sec.payload ?? {},
|
||||||
|
isActive: sec.isActive ?? true,
|
||||||
|
isLocked: sec.isLocked ?? false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
summary.detailSections++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no detail sections provided, check if product has existing ones. If not, auto-seed default sections.
|
||||||
|
const existingSectionCount = await prisma.productDetailSection.count({
|
||||||
|
where: { productId: savedProduct.id },
|
||||||
|
});
|
||||||
|
if (existingSectionCount === 0) {
|
||||||
|
await prisma.productDetailSection.createMany({
|
||||||
|
data: defaultProductDetailSections(savedProduct.id),
|
||||||
|
});
|
||||||
|
summary.detailSections += 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
summary.products++;
|
summary.products++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user