fix bug of edit product
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
-- Remove Catalog as independent entity; Product is the sole merchandise unit.
|
||||
ALTER TABLE "Product" DROP CONSTRAINT IF EXISTS "Product_catalogId_fkey";
|
||||
DROP INDEX IF EXISTS "Product_catalogId_idx";
|
||||
ALTER TABLE "Product" DROP COLUMN IF EXISTS "catalogId";
|
||||
DROP TABLE IF EXISTS "Catalog";
|
||||
+1
-23
@@ -143,7 +143,6 @@ model Company {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
bankAccounts BankAccount[]
|
||||
catalogs Catalog[]
|
||||
products Product[]
|
||||
orders Order[]
|
||||
customers Customer[]
|
||||
@@ -169,35 +168,16 @@ model BankAccount {
|
||||
@@index([companyId])
|
||||
}
|
||||
|
||||
model Catalog {
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
name String
|
||||
code String
|
||||
description String?
|
||||
imageUrl String?
|
||||
displayOrder Int @default(0)
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
products Product[]
|
||||
|
||||
@@unique([companyId, code])
|
||||
@@index([companyId])
|
||||
}
|
||||
|
||||
model Product {
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
catalogId String
|
||||
name String
|
||||
code String
|
||||
color String
|
||||
description String?
|
||||
minMeterage Float @default(30)
|
||||
orderMultiplier Float @default(10)
|
||||
/// قیمت هر متر (تومان) برای نمایش کاتالوگ / پیشفاکتور
|
||||
basePrice Float?
|
||||
stock Float?
|
||||
isActive Boolean @default(true)
|
||||
@@ -219,7 +199,6 @@ model Product {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
catalog Catalog @relation(fields: [catalogId], references: [id], onDelete: Cascade)
|
||||
brand Brand? @relation(fields: [brandId], references: [id], onDelete: SetNull)
|
||||
cartItems CartItem[]
|
||||
orderItems OrderItem[]
|
||||
@@ -229,7 +208,6 @@ model Product {
|
||||
productSuggestions ProductSuggestion[]
|
||||
|
||||
@@unique([companyId, code])
|
||||
@@index([catalogId])
|
||||
@@index([companyId])
|
||||
@@index([brandId])
|
||||
@@index([usageCategory])
|
||||
|
||||
@@ -40,7 +40,6 @@ async function main() {
|
||||
await prisma.otpChallenge.deleteMany();
|
||||
await prisma.customerNotification.deleteMany();
|
||||
await prisma.product.deleteMany();
|
||||
await prisma.catalog.deleteMany();
|
||||
await prisma.bankAccount.deleteMany();
|
||||
await prisma.levelBenefit.deleteMany();
|
||||
await prisma.customerLevelDef.deleteMany();
|
||||
@@ -130,24 +129,11 @@ async function main() {
|
||||
],
|
||||
});
|
||||
|
||||
await prisma.catalog.createMany({
|
||||
data: [
|
||||
{ id: 'cat-377', companyId: 'company-par', name: 'کالیته ۳۷۷', code: '377', displayOrder: 1, isActive: true },
|
||||
{ id: 'cat-378', companyId: 'company-par', name: 'کالیته ۳۷۸', code: '378', displayOrder: 2, isActive: true },
|
||||
{ id: 'cat-379', companyId: 'company-par', name: 'کالیته ۳۷۹', code: '379', displayOrder: 3, isActive: true },
|
||||
{ id: 'cat-380', companyId: 'company-par', name: 'کالیته ۳۸۰', code: '380', displayOrder: 4, isActive: true },
|
||||
{ id: 'cat-381', companyId: 'company-par', name: 'کالیته ۳۸۱', code: '381', displayOrder: 5, isActive: true },
|
||||
{ id: 'cat-fam-401', companyId: 'company-fam-baft', name: 'کالیته ۴۰۱', code: '401', displayOrder: 1, isActive: true },
|
||||
{ id: 'cat-fam-402', companyId: 'company-fam-baft', name: 'کالیته ۴۰۲', code: '402', displayOrder: 2, isActive: true },
|
||||
],
|
||||
});
|
||||
|
||||
await prisma.product.createMany({
|
||||
data: [
|
||||
{
|
||||
id: 'prod-377-blue',
|
||||
companyId: 'company-par',
|
||||
catalogId: 'cat-377',
|
||||
name: 'فاستونی ایتالیایی',
|
||||
code: '377-BL',
|
||||
color: 'آبی',
|
||||
@@ -171,7 +157,6 @@ async function main() {
|
||||
{
|
||||
id: 'prod-377-black',
|
||||
companyId: 'company-par',
|
||||
catalogId: 'cat-377',
|
||||
name: 'پارچه مشکی رسمی',
|
||||
code: '377-BK',
|
||||
color: 'مشکی',
|
||||
@@ -195,7 +180,6 @@ async function main() {
|
||||
{
|
||||
id: 'prod-378-blue',
|
||||
companyId: 'company-par',
|
||||
catalogId: 'cat-378',
|
||||
name: 'پیراهنی نخی آبی',
|
||||
code: '378-BL',
|
||||
color: 'آبی',
|
||||
@@ -219,7 +203,6 @@ async function main() {
|
||||
{
|
||||
id: 'prod-378-black',
|
||||
companyId: 'company-par',
|
||||
catalogId: 'cat-378',
|
||||
name: 'پیراهنی مشکی',
|
||||
code: '378-BK',
|
||||
color: 'مشکی',
|
||||
@@ -242,7 +225,6 @@ async function main() {
|
||||
{
|
||||
id: 'prod-378-blue-white',
|
||||
companyId: 'company-par',
|
||||
catalogId: 'cat-378',
|
||||
name: 'آبی پشت سفید',
|
||||
code: '378-BLW',
|
||||
color: 'آبی پشت سفید',
|
||||
@@ -265,7 +247,6 @@ async function main() {
|
||||
{
|
||||
id: 'prod-378-black-white',
|
||||
companyId: 'company-par',
|
||||
catalogId: 'cat-378',
|
||||
name: 'مشکی پشت سفید',
|
||||
code: '378-BKW',
|
||||
color: 'مشکی پشت سفید',
|
||||
@@ -288,7 +269,6 @@ async function main() {
|
||||
{
|
||||
id: 'prod-fam-401-1',
|
||||
companyId: 'company-fam-baft',
|
||||
catalogId: 'cat-fam-401',
|
||||
name: 'پارچه مخمل',
|
||||
code: '401-MV',
|
||||
color: 'سرمهای',
|
||||
@@ -311,7 +291,6 @@ async function main() {
|
||||
{
|
||||
id: 'prod-fam-401-2',
|
||||
companyId: 'company-fam-baft',
|
||||
catalogId: 'cat-fam-401',
|
||||
name: 'پارچه کجراه',
|
||||
code: '401-KJ',
|
||||
color: 'کرم',
|
||||
|
||||
@@ -6,7 +6,6 @@ import { PrismaModule } from './prisma/prisma.module';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { JwtAuthGuard } from './common/guards/jwt-auth.guard';
|
||||
import { CompaniesModule } from './companies/companies.module';
|
||||
import { CatalogsModule } from './catalogs/catalogs.module';
|
||||
import { ProductsModule } from './products/products.module';
|
||||
import { CustomersModule } from './customers/customers.module';
|
||||
import { AdminUsersModule } from './admin-users/admin-users.module';
|
||||
@@ -35,7 +34,6 @@ import { InternalMessagesModule } from './internal-messages/internal-messages.mo
|
||||
PrismaModule,
|
||||
AuthModule,
|
||||
CompaniesModule,
|
||||
CatalogsModule,
|
||||
ProductsModule,
|
||||
CustomersModule,
|
||||
AdminUsersModule,
|
||||
|
||||
@@ -23,7 +23,6 @@ export class CartsService {
|
||||
const product = await this.prisma.product.findUnique({
|
||||
where: { id: dto.productId },
|
||||
include: {
|
||||
catalog: true,
|
||||
company: true,
|
||||
},
|
||||
});
|
||||
@@ -54,7 +53,7 @@ export class CartsService {
|
||||
data: {
|
||||
cartId: cart.id,
|
||||
productId: product.id,
|
||||
catalogCode: product.catalog.code,
|
||||
catalogCode: product.code,
|
||||
productName: product.name,
|
||||
productCode: product.code,
|
||||
color: product.color,
|
||||
|
||||
@@ -24,7 +24,7 @@ export class CompaniesService {
|
||||
orderBy: [{ displayOrder: 'asc' }, { name: 'asc' }],
|
||||
include: {
|
||||
_count: {
|
||||
select: { catalogs: true, products: true, bankAccounts: true },
|
||||
select: { products: true, bankAccounts: true },
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -40,7 +40,7 @@ export class CompaniesService {
|
||||
include: {
|
||||
bankAccounts: { orderBy: { displayOrder: 'asc' } },
|
||||
_count: {
|
||||
select: { catalogs: true, products: true, bankAccounts: true },
|
||||
select: { products: true, bankAccounts: true },
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -78,7 +78,7 @@ export class CompaniesService {
|
||||
},
|
||||
include: {
|
||||
_count: {
|
||||
select: { catalogs: true, products: true, bankAccounts: true },
|
||||
select: { products: true, bankAccounts: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -116,7 +116,7 @@ export class CompaniesService {
|
||||
},
|
||||
include: {
|
||||
_count: {
|
||||
select: { catalogs: true, products: true, bankAccounts: true },
|
||||
select: { products: true, bankAccounts: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -139,7 +139,7 @@ export class CompaniesService {
|
||||
data: { isActive },
|
||||
include: {
|
||||
_count: {
|
||||
select: { catalogs: true, products: true, bankAccounts: true },
|
||||
select: { products: true, bankAccounts: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -226,7 +226,7 @@ export class CompaniesService {
|
||||
cashInvoiceValidUntilHour: number | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
_count?: { catalogs: number; products: number; bankAccounts: number };
|
||||
_count?: { products: number; bankAccounts: number };
|
||||
},
|
||||
fallbackMaxMeterage: number | null = null,
|
||||
) {
|
||||
@@ -247,7 +247,6 @@ export class CompaniesService {
|
||||
ownMaxMeteragePerInvoice: company.maxMeteragePerInvoice ?? undefined,
|
||||
cashInvoiceValidityHours: company.cashInvoiceValidityHours,
|
||||
cashInvoiceValidUntilHour: company.cashInvoiceValidUntilHour ?? undefined,
|
||||
catalogCount: company._count?.catalogs ?? 0,
|
||||
productCount: company._count?.products ?? 0,
|
||||
bankAccountCount: company._count?.bankAccounts ?? 0,
|
||||
createdAt: company.createdAt.toISOString(),
|
||||
|
||||
@@ -736,7 +736,7 @@ export class OrdersService {
|
||||
|
||||
const products = await this.prisma.product.findMany({
|
||||
where: { id: { in: uniqueProductIds } },
|
||||
include: { catalog: true, company: true },
|
||||
include: { company: true },
|
||||
});
|
||||
const productMap = new Map(products.map((p) => [p.id, p]));
|
||||
|
||||
@@ -818,7 +818,7 @@ export class OrdersService {
|
||||
totalMeterage += item.meterage;
|
||||
return {
|
||||
productId: product.id,
|
||||
catalogCode: product.catalog.code,
|
||||
catalogCode: product.code,
|
||||
productName: product.name,
|
||||
productCode: product.code,
|
||||
color: product.color,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
colorVariantImageLists,
|
||||
parseColorEntries,
|
||||
parseGeneralImageUrls,
|
||||
} from './color-entries';
|
||||
|
||||
describe('product image separation', () => {
|
||||
it('keeps general images separate from color variant urls', () => {
|
||||
const entries = parseColorEntries(
|
||||
[
|
||||
{ name: 'کرم', imageUrls: ['https://cdn/a.jpg', 'https://cdn/b.jpg'] },
|
||||
{ name: 'طوسی', imageUrl: 'https://cdn/grey.jpg' },
|
||||
],
|
||||
'کرم',
|
||||
);
|
||||
const general = parseGeneralImageUrls(
|
||||
['https://cdn/g1.jpg', 'https://cdn/a.jpg'],
|
||||
entries,
|
||||
);
|
||||
expect(general).toEqual(['https://cdn/g1.jpg']);
|
||||
expect(colorVariantImageLists(entries)).toEqual([
|
||||
['https://cdn/a.jpg', 'https://cdn/b.jpg'],
|
||||
['https://cdn/grey.jpg'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not merge color images into general list when only imageUrlsJson is set', () => {
|
||||
const entries = parseColorEntries([{ name: 'Red', imageUrl: 'https://cdn/r.jpg' }]);
|
||||
expect(parseGeneralImageUrls(['https://cdn/g.jpg'], entries)).toEqual([
|
||||
'https://cdn/g.jpg',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,48 +1,85 @@
|
||||
export type ProductColorEntry = {
|
||||
name: string;
|
||||
/** @deprecated use imageUrls — first image for swatches */
|
||||
imageUrl?: string;
|
||||
imageUrls: string[];
|
||||
};
|
||||
|
||||
function normalizeUrlList(raw: unknown): string[] {
|
||||
if (!Array.isArray(raw)) return [];
|
||||
const out: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const item of raw) {
|
||||
if (typeof item !== 'string') continue;
|
||||
const url = item.trim();
|
||||
if (!url || seen.has(url)) continue;
|
||||
seen.add(url);
|
||||
out.push(url);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function parseColorEntries(
|
||||
colorsJson: unknown,
|
||||
fallbackColor?: string,
|
||||
): ProductColorEntry[] {
|
||||
if (!Array.isArray(colorsJson) || colorsJson.length === 0) {
|
||||
const name = fallbackColor?.trim();
|
||||
return name ? [{ name }] : [];
|
||||
return name ? [{ name, imageUrls: [] }] : [];
|
||||
}
|
||||
|
||||
return colorsJson
|
||||
.map((item): ProductColorEntry | null => {
|
||||
if (typeof item === 'string') {
|
||||
const name = item.trim();
|
||||
return name ? { name } : null;
|
||||
return name ? { name, imageUrls: [] } : null;
|
||||
}
|
||||
if (item && typeof item === 'object') {
|
||||
const raw = item as { name?: unknown; imageUrl?: unknown };
|
||||
const raw = item as {
|
||||
name?: unknown;
|
||||
imageUrl?: unknown;
|
||||
imageUrls?: unknown;
|
||||
};
|
||||
const name = String(raw.name ?? '').trim();
|
||||
if (!name) return null;
|
||||
const imageUrl =
|
||||
const fromArray = normalizeUrlList(raw.imageUrls);
|
||||
const legacy =
|
||||
typeof raw.imageUrl === 'string' && raw.imageUrl.trim()
|
||||
? raw.imageUrl
|
||||
? raw.imageUrl.trim()
|
||||
: undefined;
|
||||
return { name, imageUrl };
|
||||
const imageUrls =
|
||||
fromArray.length > 0
|
||||
? legacy && !fromArray.includes(legacy)
|
||||
? [legacy, ...fromArray]
|
||||
: fromArray
|
||||
: legacy
|
||||
? [legacy]
|
||||
: [];
|
||||
const imageUrl = imageUrls[0];
|
||||
return { name, imageUrl, imageUrls };
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((entry): entry is ProductColorEntry => Boolean(entry));
|
||||
}
|
||||
|
||||
export function mergeProductImages(
|
||||
entries: ProductColorEntry[],
|
||||
/** تصاویر عمومی محصول — فقط از imageUrlsJson، بدون merge با رنگها */
|
||||
export function parseGeneralImageUrls(
|
||||
imageUrlsJson: unknown,
|
||||
colorEntries?: ProductColorEntry[],
|
||||
): string[] {
|
||||
const extras = Array.isArray(imageUrlsJson)
|
||||
? imageUrlsJson.filter((url): url is string => typeof url === 'string' && url.trim().length > 0)
|
||||
: [];
|
||||
const fromColors = entries
|
||||
.map((entry) => entry.imageUrl)
|
||||
.filter((url): url is string => Boolean(url));
|
||||
const seen = new Set(fromColors);
|
||||
return [...fromColors, ...extras.filter((url) => !seen.has(url))];
|
||||
const general = normalizeUrlList(imageUrlsJson);
|
||||
if (!colorEntries?.length) return general;
|
||||
|
||||
const colorOnly = new Set<string>();
|
||||
for (const entry of colorEntries) {
|
||||
for (const url of entry.imageUrls) colorOnly.add(url);
|
||||
}
|
||||
return general.filter((url) => !colorOnly.has(url));
|
||||
}
|
||||
|
||||
export function colorVariantImageLists(
|
||||
entries: ProductColorEntry[],
|
||||
): string[][] {
|
||||
return entries.map((entry) => [...entry.imageUrls]);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IsOptional, IsString, MinLength } from 'class-validator';
|
||||
import { IsArray, IsOptional, IsString, MinLength } from 'class-validator';
|
||||
|
||||
export class ProductColorJsonItemDto {
|
||||
@IsString()
|
||||
@@ -8,6 +8,11 @@ export class ProductColorJsonItemDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
imageUrl?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
imageUrls?: string[];
|
||||
}
|
||||
|
||||
/** نرمالسازی colorsJson (رشتهٔ legacy یا آبجکت ادمین) قبل از اعتبارسنجی */
|
||||
@@ -26,11 +31,20 @@ export function transformColorsJson(value: unknown): ProductColorJsonItemDto[] |
|
||||
const raw = item as Record<string, unknown>;
|
||||
const name = String(raw.name ?? '').trim();
|
||||
if (!name) continue;
|
||||
const imageUrls = Array.isArray(raw.imageUrls)
|
||||
? raw.imageUrls
|
||||
.filter((u): u is string => typeof u === 'string' && u.trim().length > 0)
|
||||
.map((u) => u.trim())
|
||||
: undefined;
|
||||
const imageUrl =
|
||||
typeof raw.imageUrl === 'string' && raw.imageUrl.trim()
|
||||
? raw.imageUrl.trim()
|
||||
: undefined;
|
||||
out.push({ name, imageUrl });
|
||||
out.push({
|
||||
name,
|
||||
imageUrl,
|
||||
imageUrls: imageUrls?.length ? imageUrls : imageUrl ? [imageUrl] : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
return out;
|
||||
|
||||
@@ -18,9 +18,6 @@ export class CreateProductDto {
|
||||
@IsString()
|
||||
companyId!: string;
|
||||
|
||||
@IsString()
|
||||
catalogId!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
brandId?: string | null;
|
||||
@@ -69,9 +66,10 @@ export class CreateProductDto {
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
/** قیمت هر متر (تومان) */
|
||||
basePrice?: number | null;
|
||||
|
||||
/** Alias from admin UI */
|
||||
/** Alias from admin UI — قیمت هر متر (تومان) */
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@@ -154,10 +152,6 @@ export class CreateProductDto {
|
||||
}
|
||||
|
||||
export class UpdateProductDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
catalogId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
brandId?: string | null;
|
||||
|
||||
@@ -32,7 +32,6 @@ export class ProductsController {
|
||||
@Get()
|
||||
findAll(
|
||||
@CurrentUser() user: AuthPrincipal | undefined,
|
||||
@Query('catalogId') catalogId?: string,
|
||||
@Query('companyId') companyId?: string,
|
||||
@Query('color') color?: string,
|
||||
@Query('activeOnly') activeOnly?: string,
|
||||
@@ -42,7 +41,6 @@ export class ProductsController {
|
||||
) {
|
||||
const isAdmin = user?.type === 'admin';
|
||||
return this.productsService.findAll({
|
||||
catalogId,
|
||||
companyId,
|
||||
color,
|
||||
activeOnly: activeOnly === 'true' || activeOnly === '1',
|
||||
|
||||
@@ -8,11 +8,14 @@ import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateProductDto, UpdateProductDto } from './dto/product.dto';
|
||||
import { loadCatalogCategories } from '../catalog/category-defs';
|
||||
import { mergeProductImages, parseColorEntries } from './color-entries';
|
||||
import {
|
||||
colorVariantImageLists,
|
||||
parseColorEntries,
|
||||
parseGeneralImageUrls,
|
||||
} from './color-entries';
|
||||
|
||||
const productInclude = {
|
||||
company: { select: { name: true } },
|
||||
catalog: { select: { code: true, name: true } },
|
||||
brand: { select: { id: true, name: true } },
|
||||
};
|
||||
|
||||
@@ -25,7 +28,6 @@ export class ProductsService {
|
||||
}
|
||||
|
||||
async findAll(filters: {
|
||||
catalogId?: string;
|
||||
companyId?: string;
|
||||
color?: string;
|
||||
activeOnly?: boolean;
|
||||
@@ -35,7 +37,6 @@ export class ProductsService {
|
||||
}) {
|
||||
const products = await this.prisma.product.findMany({
|
||||
where: {
|
||||
...(filters.catalogId ? { catalogId: filters.catalogId } : {}),
|
||||
...(filters.companyId ? { companyId: filters.companyId } : {}),
|
||||
...(filters.color
|
||||
? { color: { contains: filters.color, mode: 'insensitive' } }
|
||||
@@ -75,14 +76,6 @@ export class ProductsService {
|
||||
}
|
||||
|
||||
async create(dto: CreateProductDto) {
|
||||
const catalog = await this.prisma.catalog.findUnique({
|
||||
where: { id: dto.catalogId },
|
||||
});
|
||||
if (!catalog) throw new NotFoundException('کاتالوگ یافت نشد');
|
||||
if (catalog.companyId !== dto.companyId) {
|
||||
throw new ConflictException('کاتالوگ متعلق به این شرکت نیست');
|
||||
}
|
||||
|
||||
const code = (dto.code || dto.productCode || '').trim();
|
||||
if (!code) throw new ConflictException('کد محصول الزامی است');
|
||||
const brandId = await this.resolveBrandId(dto.brandId ?? null);
|
||||
@@ -91,7 +84,6 @@ export class ProductsService {
|
||||
const product = await this.prisma.product.create({
|
||||
data: {
|
||||
companyId: dto.companyId,
|
||||
catalogId: dto.catalogId,
|
||||
brandId,
|
||||
name: dto.name.trim(),
|
||||
code,
|
||||
@@ -145,7 +137,6 @@ export class ProductsService {
|
||||
const product = await this.prisma.product.update({
|
||||
where: { id },
|
||||
data: {
|
||||
catalogId: dto.catalogId,
|
||||
...(brandId !== undefined ? { brandId } : {}),
|
||||
name: dto.name?.trim(),
|
||||
code,
|
||||
@@ -290,7 +281,6 @@ export class ProductsService {
|
||||
product: {
|
||||
id: string;
|
||||
companyId: string;
|
||||
catalogId: string;
|
||||
name: string;
|
||||
code: string;
|
||||
color: string;
|
||||
@@ -303,7 +293,6 @@ export class ProductsService {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
company: { name: string };
|
||||
catalog: { code: string; name: string };
|
||||
brand?: { id: string; name: string } | null;
|
||||
usageCategory?: string;
|
||||
season?: string;
|
||||
@@ -322,8 +311,14 @@ export class ProductsService {
|
||||
) {
|
||||
const colorEntries = parseColorEntries(product.colorsJson, product.color);
|
||||
const colors = colorEntries.map((entry) => entry.name);
|
||||
const colorImages = colorEntries.map((entry) => entry.imageUrl ?? null);
|
||||
const imageUrls = mergeProductImages(colorEntries, product.imageUrlsJson);
|
||||
const colorImages = colorEntries.map(
|
||||
(entry) => entry.imageUrls[0] ?? entry.imageUrl ?? null,
|
||||
);
|
||||
const colorVariantImages = colorVariantImageLists(colorEntries);
|
||||
const imageUrls = parseGeneralImageUrls(
|
||||
product.imageUrlsJson,
|
||||
colorEntries,
|
||||
);
|
||||
const tags = Array.isArray(product.tagsJson)
|
||||
? (product.tagsJson as string[])
|
||||
: [];
|
||||
@@ -332,9 +327,10 @@ export class ProductsService {
|
||||
id: product.id,
|
||||
companyId: product.companyId,
|
||||
companyName: product.company.name,
|
||||
catalogId: product.catalogId,
|
||||
catalogCode: product.catalog.code,
|
||||
catalogName: product.catalog.name,
|
||||
/** @deprecated use `code` — kept for existing clients */
|
||||
catalogCode: product.code,
|
||||
/** @deprecated use `name` */
|
||||
catalogName: product.name,
|
||||
brandId: product.brand?.id ?? null,
|
||||
brandName: product.brand?.name ?? null,
|
||||
name: product.name,
|
||||
@@ -359,6 +355,7 @@ export class ProductsService {
|
||||
weaveType: product.weaveType ?? undefined,
|
||||
colors,
|
||||
colorImages,
|
||||
colorVariantImages,
|
||||
tags,
|
||||
imageUrls,
|
||||
subtitle: product.subtitle ?? undefined,
|
||||
|
||||
@@ -18,7 +18,6 @@ export class StockNotifyService {
|
||||
color: true,
|
||||
stock: true,
|
||||
imageUrlsJson: true,
|
||||
catalog: { select: { code: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -110,7 +109,6 @@ export class StockNotifyService {
|
||||
color: string;
|
||||
stock: number | null;
|
||||
imageUrlsJson: unknown;
|
||||
catalog: { code: string };
|
||||
};
|
||||
}) {
|
||||
return {
|
||||
@@ -118,7 +116,7 @@ export class StockNotifyService {
|
||||
productId: r.productId,
|
||||
productName: r.product.name,
|
||||
productCode: r.product.code,
|
||||
catalogCode: r.product.catalog.code,
|
||||
catalogCode: r.product.code,
|
||||
color: r.product.color,
|
||||
stock: r.product.stock,
|
||||
imageUrl: this.productThumb(r.product.imageUrlsJson),
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { NotificationTone } from '@prisma/client';
|
||||
import { NotificationsService } from '../notifications/notifications.service';
|
||||
import {
|
||||
mergeProductImages,
|
||||
parseGeneralImageUrls,
|
||||
parseColorEntries,
|
||||
} from '../products/color-entries';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
@@ -19,7 +19,6 @@ const PRODUCT_INCLUDE = {
|
||||
imageUrlsJson: true,
|
||||
widthCm: true,
|
||||
composition: true,
|
||||
catalog: { select: { name: true, code: true } },
|
||||
},
|
||||
},
|
||||
customer: { select: { firstName: true, lastName: true, phone: true } },
|
||||
@@ -147,17 +146,20 @@ export class SuggestionsService {
|
||||
imageUrlsJson: unknown;
|
||||
widthCm: number | null;
|
||||
composition: string | null;
|
||||
catalog: { name: string; code: string } | null;
|
||||
};
|
||||
}) {
|
||||
const colorEntries = parseColorEntries(r.product.colorsJson, r.product.color);
|
||||
const images = mergeProductImages(colorEntries, r.product.imageUrlsJson);
|
||||
const imageUrls = parseGeneralImageUrls(
|
||||
r.product.imageUrlsJson,
|
||||
colorEntries,
|
||||
);
|
||||
const colors = colorEntries.map((entry) => entry.name);
|
||||
const colorImages = colorEntries.map((entry) => entry.imageUrl ?? null);
|
||||
const catalogName = r.product.catalog?.name;
|
||||
const colorImages = colorEntries.map(
|
||||
(entry) => entry.imageUrls[0] ?? entry.imageUrl ?? null,
|
||||
);
|
||||
const subtitle =
|
||||
r.product.subtitle?.trim() ||
|
||||
[catalogName, r.product.color].filter(Boolean).join(' · ') ||
|
||||
[r.product.name, r.product.color].filter(Boolean).join(' · ') ||
|
||||
undefined;
|
||||
|
||||
return {
|
||||
@@ -168,11 +170,11 @@ export class SuggestionsService {
|
||||
productId: r.productId,
|
||||
productName: r.product.name,
|
||||
productCode: r.product.code,
|
||||
catalogName,
|
||||
catalogCode: r.product.catalog?.code,
|
||||
catalogName: r.product.name,
|
||||
catalogCode: r.product.code,
|
||||
color: r.product.color,
|
||||
subtitle,
|
||||
imageUrl: images[0] ?? undefined,
|
||||
imageUrl: imageUrls[0] ?? colorImages.find(Boolean) ?? undefined,
|
||||
colors,
|
||||
colorImages,
|
||||
widthCm: r.product.widthCm ?? undefined,
|
||||
|
||||
Reference in New Issue
Block a user