Initialize the API and add managed app banners.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Min,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class CreateProductDto {
|
||||
@IsString()
|
||||
companyId!: string;
|
||||
|
||||
@IsString()
|
||||
catalogId!: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
name!: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
code!: string;
|
||||
|
||||
/** Alias accepted from admin UI */
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
productCode?: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
color!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
minMeterage?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0.001)
|
||||
orderMultiplier?: number;
|
||||
|
||||
/** Alias from admin UI */
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0.001)
|
||||
orderStep?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
basePrice?: number | null;
|
||||
|
||||
/** Alias from admin UI */
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
price?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
stock?: number | null;
|
||||
|
||||
/** Alias from admin UI */
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
internalStock?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
usageCategory?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
season?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
widthCm?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
composition?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
weightGrams?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
weaveType?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
subtitle?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
rollMeters?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isNew?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
colorsJson?: string[];
|
||||
|
||||
@IsOptional()
|
||||
imageUrlsJson?: string[];
|
||||
|
||||
@IsOptional()
|
||||
tagsJson?: string[];
|
||||
}
|
||||
|
||||
export class UpdateProductDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
catalogId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
code?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
productCode?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
color?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
minMeterage?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0.001)
|
||||
orderMultiplier?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0.001)
|
||||
orderStep?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
basePrice?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
price?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
stock?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
internalStock?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
usageCategory?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
season?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
widthCm?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
composition?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
weightGrams?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
weaveType?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
subtitle?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
rollMeters?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isNew?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
colorsJson?: string[];
|
||||
|
||||
@IsOptional()
|
||||
imageUrlsJson?: string[];
|
||||
|
||||
@IsOptional()
|
||||
tagsJson?: string[];
|
||||
}
|
||||
|
||||
export class SetProductActiveDto {
|
||||
@IsBoolean()
|
||||
isActive!: boolean;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { ProductsService } from './products.service';
|
||||
import { AuthType } from '../common/decorators/auth.decorators';
|
||||
import { CurrentUser } from '../common/decorators/current-user.decorator';
|
||||
import type { AuthPrincipal } from '../common/decorators/current-user.decorator';
|
||||
import {
|
||||
CreateProductDto,
|
||||
SetProductActiveDto,
|
||||
UpdateProductDto,
|
||||
} from './dto/product.dto';
|
||||
|
||||
@Controller('products')
|
||||
export class ProductsController {
|
||||
constructor(private readonly productsService: ProductsService) {}
|
||||
|
||||
@AuthType('any')
|
||||
@Get('meta/categories')
|
||||
categories() {
|
||||
return this.productsService.listCatalogCategories();
|
||||
}
|
||||
|
||||
@AuthType('any')
|
||||
@Get()
|
||||
findAll(
|
||||
@CurrentUser() user: AuthPrincipal | undefined,
|
||||
@Query('catalogId') catalogId?: string,
|
||||
@Query('companyId') companyId?: string,
|
||||
@Query('color') color?: string,
|
||||
@Query('activeOnly') activeOnly?: string,
|
||||
@Query('categoryId') categoryId?: string,
|
||||
@Query('usageCategory') usageCategory?: string,
|
||||
@Query('season') season?: string,
|
||||
) {
|
||||
const isAdmin = user?.type === 'admin';
|
||||
return this.productsService.findAll({
|
||||
catalogId,
|
||||
companyId,
|
||||
color,
|
||||
activeOnly: activeOnly === 'true' || activeOnly === '1',
|
||||
usageCategory: usageCategory || categoryId,
|
||||
season,
|
||||
hidePublicPrice: !isAdmin,
|
||||
});
|
||||
}
|
||||
|
||||
@AuthType('customer')
|
||||
@Get('favorites/ids')
|
||||
favoriteIds(@CurrentUser() user: AuthPrincipal) {
|
||||
return this.productsService.listFavoriteIds(user.id);
|
||||
}
|
||||
|
||||
@AuthType('customer')
|
||||
@Post(':id/favorite')
|
||||
toggleFavorite(
|
||||
@CurrentUser() user: AuthPrincipal,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return this.productsService.toggleFavorite(user.id, id);
|
||||
}
|
||||
|
||||
@AuthType('customer')
|
||||
@Delete(':id/favorite')
|
||||
removeFavorite(
|
||||
@CurrentUser() user: AuthPrincipal,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return this.productsService.removeFavorite(user.id, id);
|
||||
}
|
||||
|
||||
@AuthType('any')
|
||||
@Get(':id')
|
||||
findOne(
|
||||
@CurrentUser() user: AuthPrincipal | undefined,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return this.productsService.findOne(id, user?.type !== 'admin');
|
||||
}
|
||||
|
||||
@AuthType('admin')
|
||||
@Post()
|
||||
create(@Body() dto: CreateProductDto) {
|
||||
return this.productsService.create(dto);
|
||||
}
|
||||
|
||||
@AuthType('admin')
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() dto: UpdateProductDto) {
|
||||
return this.productsService.update(id, dto);
|
||||
}
|
||||
|
||||
@AuthType('admin')
|
||||
@Patch(':id/active')
|
||||
setActive(@Param('id') id: string, @Body() dto: SetProductActiveDto) {
|
||||
return this.productsService.setActive(id, dto.isActive);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ProductsController } from './products.controller';
|
||||
import { ProductsService } from './products.service';
|
||||
|
||||
@Module({
|
||||
controllers: [ProductsController],
|
||||
providers: [ProductsService],
|
||||
exports: [ProductsService],
|
||||
})
|
||||
export class ProductsModule {}
|
||||
@@ -0,0 +1,354 @@
|
||||
import {
|
||||
ConflictException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateProductDto, UpdateProductDto } from './dto/product.dto';
|
||||
import { loadCatalogCategories } from '../catalog/category-defs';
|
||||
|
||||
@Injectable()
|
||||
export class ProductsService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
listCatalogCategories() {
|
||||
return loadCatalogCategories(this.prisma);
|
||||
}
|
||||
|
||||
async findAll(filters: {
|
||||
catalogId?: string;
|
||||
companyId?: string;
|
||||
color?: string;
|
||||
activeOnly?: boolean;
|
||||
usageCategory?: string;
|
||||
season?: string;
|
||||
hidePublicPrice?: boolean;
|
||||
}) {
|
||||
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' } }
|
||||
: {}),
|
||||
...(filters.activeOnly ? { isActive: true } : {}),
|
||||
...(filters.usageCategory
|
||||
? { usageCategory: filters.usageCategory as never }
|
||||
: {}),
|
||||
...(filters.season ? { season: filters.season as never } : {}),
|
||||
},
|
||||
orderBy: [{ isNew: 'desc' }, { name: 'asc' }, { color: 'asc' }],
|
||||
include: {
|
||||
company: { select: { name: true } },
|
||||
catalog: { select: { code: true, name: true } },
|
||||
},
|
||||
});
|
||||
const hidePrice = await this.shouldHidePublicPrice(filters.hidePublicPrice);
|
||||
return products.map((p) => this.map(p, hidePrice));
|
||||
}
|
||||
|
||||
async findOne(id: string, hidePublicPrice?: boolean) {
|
||||
const product = await this.prisma.product.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
company: { select: { name: true } },
|
||||
catalog: { select: { code: true, name: true } },
|
||||
},
|
||||
});
|
||||
if (!product) throw new NotFoundException('محصول یافت نشد');
|
||||
const hidePrice = await this.shouldHidePublicPrice(hidePublicPrice);
|
||||
return this.map(product, hidePrice);
|
||||
}
|
||||
|
||||
private async shouldHidePublicPrice(forceHide?: boolean) {
|
||||
if (forceHide === false) return false;
|
||||
if (forceHide === true) {
|
||||
const settings = await this.prisma.systemSettings.findUnique({
|
||||
where: { id: 'default' },
|
||||
});
|
||||
return !(settings?.showCatalogPublicPrice ?? false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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('کد محصول الزامی است');
|
||||
|
||||
try {
|
||||
const product = await this.prisma.product.create({
|
||||
data: {
|
||||
companyId: dto.companyId,
|
||||
catalogId: dto.catalogId,
|
||||
name: dto.name.trim(),
|
||||
code,
|
||||
color: dto.color.trim(),
|
||||
description: dto.description?.trim() || null,
|
||||
minMeterage: dto.minMeterage ?? 30,
|
||||
orderMultiplier: dto.orderMultiplier ?? dto.orderStep ?? 10,
|
||||
basePrice: this.pickNumber(dto.basePrice, dto.price),
|
||||
stock: this.pickNumber(dto.stock, dto.internalStock),
|
||||
isActive: dto.isActive ?? true,
|
||||
usageCategory: (dto.usageCategory as never) ?? undefined,
|
||||
season: (dto.season as never) ?? undefined,
|
||||
widthCm: dto.widthCm,
|
||||
composition: dto.composition?.trim() || null,
|
||||
weightGrams: dto.weightGrams,
|
||||
weaveType: dto.weaveType?.trim() || null,
|
||||
subtitle: dto.subtitle?.trim() || null,
|
||||
rollMeters: dto.rollMeters,
|
||||
isNew: dto.isNew,
|
||||
colorsJson: dto.colorsJson ?? undefined,
|
||||
imageUrlsJson: dto.imageUrlsJson ?? undefined,
|
||||
tagsJson: dto.tagsJson ?? undefined,
|
||||
},
|
||||
include: {
|
||||
company: { select: { name: true } },
|
||||
catalog: { select: { code: true, name: true } },
|
||||
},
|
||||
});
|
||||
return this.map(product);
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof Prisma.PrismaClientKnownRequestError &&
|
||||
e.code === 'P2002'
|
||||
) {
|
||||
throw new ConflictException('کد محصول در این شرکت تکراری است');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateProductDto) {
|
||||
await this.ensure(id);
|
||||
const code =
|
||||
dto.code !== undefined
|
||||
? dto.code.trim()
|
||||
: dto.productCode !== undefined
|
||||
? dto.productCode.trim()
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
const product = await this.prisma.product.update({
|
||||
where: { id },
|
||||
data: {
|
||||
catalogId: dto.catalogId,
|
||||
name: dto.name?.trim(),
|
||||
code,
|
||||
color: dto.color?.trim(),
|
||||
description:
|
||||
dto.description === undefined
|
||||
? undefined
|
||||
: dto.description?.trim() || null,
|
||||
minMeterage: dto.minMeterage,
|
||||
orderMultiplier:
|
||||
dto.orderMultiplier !== undefined
|
||||
? dto.orderMultiplier
|
||||
: dto.orderStep,
|
||||
basePrice:
|
||||
dto.basePrice !== undefined
|
||||
? dto.basePrice
|
||||
: dto.price !== undefined
|
||||
? dto.price
|
||||
: undefined,
|
||||
stock:
|
||||
dto.stock !== undefined
|
||||
? dto.stock
|
||||
: dto.internalStock !== undefined
|
||||
? dto.internalStock
|
||||
: undefined,
|
||||
isActive: dto.isActive,
|
||||
usageCategory:
|
||||
dto.usageCategory !== undefined
|
||||
? (dto.usageCategory as never)
|
||||
: undefined,
|
||||
season: dto.season !== undefined ? (dto.season as never) : undefined,
|
||||
widthCm: dto.widthCm,
|
||||
composition:
|
||||
dto.composition === undefined
|
||||
? undefined
|
||||
: dto.composition?.trim() || null,
|
||||
weightGrams: dto.weightGrams,
|
||||
weaveType:
|
||||
dto.weaveType === undefined
|
||||
? undefined
|
||||
: dto.weaveType?.trim() || null,
|
||||
subtitle:
|
||||
dto.subtitle === undefined
|
||||
? undefined
|
||||
: dto.subtitle?.trim() || null,
|
||||
rollMeters: dto.rollMeters,
|
||||
isNew: dto.isNew,
|
||||
colorsJson: dto.colorsJson,
|
||||
imageUrlsJson: dto.imageUrlsJson,
|
||||
tagsJson: dto.tagsJson,
|
||||
},
|
||||
include: {
|
||||
company: { select: { name: true } },
|
||||
catalog: { select: { code: true, name: true } },
|
||||
},
|
||||
});
|
||||
return this.map(product);
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof Prisma.PrismaClientKnownRequestError &&
|
||||
e.code === 'P2002'
|
||||
) {
|
||||
throw new ConflictException('کد محصول در این شرکت تکراری است');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async setActive(id: string, isActive: boolean) {
|
||||
await this.ensure(id);
|
||||
const product = await this.prisma.product.update({
|
||||
where: { id },
|
||||
data: { isActive },
|
||||
include: {
|
||||
company: { select: { name: true } },
|
||||
catalog: { select: { code: true, name: true } },
|
||||
},
|
||||
});
|
||||
return this.map(product);
|
||||
}
|
||||
|
||||
async listFavoriteIds(customerId: string) {
|
||||
const rows = await this.prisma.productFavorite.findMany({
|
||||
where: { customerId },
|
||||
select: { productId: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
return rows.map((r) => r.productId);
|
||||
}
|
||||
|
||||
async toggleFavorite(customerId: string, productId: string) {
|
||||
await this.ensure(productId);
|
||||
const existing = await this.prisma.productFavorite.findUnique({
|
||||
where: {
|
||||
customerId_productId: { customerId, productId },
|
||||
},
|
||||
});
|
||||
if (existing) {
|
||||
await this.prisma.productFavorite.delete({ where: { id: existing.id } });
|
||||
return { productId, favorited: false };
|
||||
}
|
||||
await this.prisma.productFavorite.create({
|
||||
data: { customerId, productId },
|
||||
});
|
||||
return { productId, favorited: true };
|
||||
}
|
||||
|
||||
async removeFavorite(customerId: string, productId: string) {
|
||||
await this.prisma.productFavorite.deleteMany({
|
||||
where: { customerId, productId },
|
||||
});
|
||||
return { productId, favorited: false };
|
||||
}
|
||||
|
||||
private pickNumber(
|
||||
primary?: number | null,
|
||||
alias?: number | null,
|
||||
): number | null {
|
||||
if (primary !== undefined) return primary;
|
||||
if (alias !== undefined) return alias;
|
||||
return null;
|
||||
}
|
||||
|
||||
private async ensure(id: string) {
|
||||
const product = await this.prisma.product.findUnique({ where: { id } });
|
||||
if (!product) throw new NotFoundException('محصول یافت نشد');
|
||||
return product;
|
||||
}
|
||||
|
||||
private map(
|
||||
product: {
|
||||
id: string;
|
||||
companyId: string;
|
||||
catalogId: string;
|
||||
name: string;
|
||||
code: string;
|
||||
color: string;
|
||||
description: string | null;
|
||||
minMeterage: number;
|
||||
orderMultiplier: number;
|
||||
basePrice: number | null;
|
||||
stock: number | null;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
company: { name: string };
|
||||
catalog: { code: string; name: string };
|
||||
usageCategory?: string;
|
||||
season?: string;
|
||||
widthCm?: number | null;
|
||||
composition?: string | null;
|
||||
weightGrams?: number | null;
|
||||
weaveType?: string | null;
|
||||
colorsJson?: unknown;
|
||||
tagsJson?: unknown;
|
||||
imageUrlsJson?: unknown;
|
||||
subtitle?: string | null;
|
||||
rollMeters?: number;
|
||||
isNew?: boolean;
|
||||
},
|
||||
hidePrice = false,
|
||||
) {
|
||||
const colors = Array.isArray(product.colorsJson)
|
||||
? (product.colorsJson as string[])
|
||||
: product.color
|
||||
? [product.color]
|
||||
: [];
|
||||
const tags = Array.isArray(product.tagsJson)
|
||||
? (product.tagsJson as string[])
|
||||
: [];
|
||||
const price = hidePrice ? null : product.basePrice;
|
||||
return {
|
||||
id: product.id,
|
||||
companyId: product.companyId,
|
||||
companyName: product.company.name,
|
||||
catalogId: product.catalogId,
|
||||
catalogCode: product.catalog.code,
|
||||
catalogName: product.catalog.name,
|
||||
name: product.name,
|
||||
code: product.code,
|
||||
productCode: product.code,
|
||||
color: product.color,
|
||||
description: product.description ?? undefined,
|
||||
minMeterage: product.minMeterage,
|
||||
orderMultiplier: product.orderMultiplier,
|
||||
orderStep: product.orderMultiplier,
|
||||
basePrice: price,
|
||||
price,
|
||||
stock: product.stock,
|
||||
internalStock: product.stock ?? 0,
|
||||
isActive: product.isActive,
|
||||
categoryId: product.usageCategory ?? 'other',
|
||||
usageCategory: product.usageCategory ?? 'other',
|
||||
season: product.season ?? 'all_season',
|
||||
widthCm: product.widthCm ?? undefined,
|
||||
composition: product.composition ?? undefined,
|
||||
weightGrams: product.weightGrams ?? undefined,
|
||||
weaveType: product.weaveType ?? undefined,
|
||||
colors,
|
||||
tags,
|
||||
imageUrls: Array.isArray(product.imageUrlsJson)
|
||||
? product.imageUrlsJson
|
||||
: [],
|
||||
subtitle: product.subtitle ?? undefined,
|
||||
rollMeters: product.rollMeters ?? 50,
|
||||
isNew: product.isNew ?? false,
|
||||
createdAt: product.createdAt.toISOString(),
|
||||
updatedAt: product.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user