import { ApiProperty } from '@nestjs/swagger'; import type { Good as PrismaGood } from '@prisma/client'; export interface GoodRelations { country?: { id: bigint; countryName: string; countryIcon: string | null } | null; category?: { id: bigint; categoryName: string; categoryIcon: string | null } | null; tag?: { id: bigint; tagName: string; tagColor: string | null; tagFontColor: string | null } | null; position?: { id: bigint; indexVal: number } | null; originGood?: { id: bigint; sdsGoodId: string; source: 'SDS' | 'CUSTOM'; goodName: string | null; goodImage: string | null; goodPrice: unknown; family?: { id: bigint; familyCode: string | null; familyName: string; _count?: { originGoods: number } } | null; detail?: { productCode: string | null; syncedAt: Date; sizeChart: unknown; packageSpecs: unknown; [key: string]: unknown; } | null; variants?: Array<{ sdsVariantId: string; sku: string; sizeName: string | null; colorName: string | null; colorHex: string | null; price: unknown; enabled: boolean; [key: string]: unknown; }>; _count?: { variants: number }; } | null; goodTags?: { tag: { id: bigint; tagName: string; tagColor: string | null; tagFontColor: string | null; tagGroupId: bigint | null } }[]; mergedOriginGoods?: Array<{ originGood: { id: bigint; sdsGoodId: string; source: 'SDS' | 'CUSTOM'; goodName: string | null; goodImage: string | null; goodPrice: unknown; detail?: { syncedAt: Date } | Record | null; variants?: Array<{ [key: string]: unknown }>; _count?: { variants: number }; }; }>; } export interface MergedOriginGoodSummary { id: string; sdsGoodId: string; source: 'SDS' | 'CUSTOM'; isCustom: boolean; goodName: string | null; goodImage: string | null; goodPrice: string | null; hasDetail: boolean; variantCount: number; } export class GoodDto { @ApiProperty() id!: string; @ApiProperty() goodName!: string; @ApiProperty({ nullable: true }) goodImage!: string | null; @ApiProperty() goodPriority!: number; @ApiProperty() countryId!: string; @ApiProperty() categoryId!: string; @ApiProperty({ nullable: true }) tagId!: string | null; @ApiProperty({ nullable: true }) positionId!: string | null; @ApiProperty() originGoodId!: string; @ApiProperty() createdAt!: string; @ApiProperty() updatedAt!: string; @ApiProperty({ required: false, nullable: true }) country?: { id: string; countryName: string; countryIcon: string | null } | null; @ApiProperty({ required: false, nullable: true }) category?: { id: string; categoryName: string; categoryIcon: string | null } | null; @ApiProperty({ required: false, nullable: true }) tag?: { id: string; tagName: string; tagColor: string | null; tagFontColor: string | null } | null; @ApiProperty({ required: false, type: Array }) tags!: Array<{ id: string; tagName: string; tagColor: string | null; tagFontColor: string | null }>; @ApiProperty({ required: false, type: Array }) mergedOriginGoods!: MergedOriginGoodSummary[]; @ApiProperty({ required: false, nullable: true }) position?: { id: string; indexVal: number } | null; @ApiProperty({ required: false, nullable: true }) originGood?: { id: string; sdsGoodId: string; source: 'SDS' | 'CUSTOM'; isCustom: boolean; goodName: string | null; goodImage: string | null; goodPrice: string | null; hasDetail: boolean; detailSyncedAt: string | null; variantCount: number; sizeRowCount: number; packageRowCount: number; productCode: string | null; family?: { familyId: string; familyCode: string | null; familyName: string; memberCount?: number } | null; } | null; static from( good: PrismaGood, rel: GoodRelations = {}, ): GoodDto { return { id: good.id.toString(), goodName: good.goodName, goodImage: good.goodImage, goodPriority: good.goodPriority, countryId: good.countryId.toString(), categoryId: good.categoryId.toString(), tagId: good.tagId === null || good.tagId === undefined ? null : good.tagId.toString(), positionId: good.positionId === null || good.positionId === undefined ? null : good.positionId.toString(), originGoodId: good.originGoodId.toString(), createdAt: good.createdAt.toISOString(), updatedAt: good.updatedAt.toISOString(), country: rel.country ? { id: rel.country.id.toString(), countryName: rel.country.countryName, countryIcon: rel.country.countryIcon, } : null, category: rel.category ? { id: rel.category.id.toString(), categoryName: rel.category.categoryName, categoryIcon: rel.category.categoryIcon, } : null, tag: rel.tag ? { id: rel.tag.id.toString(), tagName: rel.tag.tagName, tagColor: rel.tag.tagColor, tagFontColor: rel.tag.tagFontColor, } : null, tags: rel.goodTags ? rel.goodTags.map((gt) => ({ id: gt.tag.id.toString(), tagName: gt.tag.tagName, tagColor: gt.tag.tagColor, tagFontColor: gt.tag.tagFontColor, tagGroupId: gt.tag.tagGroupId?.toString() ?? null, })) : [], mergedOriginGoods: (rel.mergedOriginGoods ?? []).map((m) => ({ id: m.originGood.id.toString(), sdsGoodId: m.originGood.sdsGoodId, source: m.originGood.source, isCustom: m.originGood.source === 'CUSTOM', goodName: m.originGood.goodName, goodImage: m.originGood.goodImage, goodPrice: m.originGood.goodPrice === null || m.originGood.goodPrice === undefined ? null : (m.originGood.goodPrice as { toString(): string }).toString(), hasDetail: Boolean(m.originGood.detail), variantCount: m.originGood._count?.variants ?? m.originGood.variants?.length ?? 0, })), position: rel.position ? { id: rel.position.id.toString(), indexVal: rel.position.indexVal, } : null, originGood: rel.originGood ? { id: rel.originGood.id.toString(), sdsGoodId: rel.originGood.sdsGoodId, source: rel.originGood.source, isCustom: rel.originGood.source === 'CUSTOM', goodName: rel.originGood.goodName, goodImage: rel.originGood.goodImage, goodPrice: rel.originGood.goodPrice === null || rel.originGood.goodPrice === undefined ? null : (rel.originGood.goodPrice as { toString(): string }).toString(), hasDetail: Boolean(rel.originGood.detail), detailSyncedAt: rel.originGood.detail?.syncedAt.toISOString() ?? null, variantCount: rel.originGood._count?.variants ?? rel.originGood.variants?.length ?? 0, sizeRowCount: GoodDto.jsonRows(rel.originGood.detail?.sizeChart), packageRowCount: GoodDto.jsonRows(rel.originGood.detail?.packageSpecs), productCode: rel.originGood.detail?.productCode ?? null, family: rel.originGood.family ? { familyId: rel.originGood.family.id.toString(), familyCode: rel.originGood.family.familyCode, familyName: rel.originGood.family.familyName, memberCount: rel.originGood.family._count?.originGoods, } : null, } : null, }; } private static jsonRows(value: unknown): number { if (!value || typeof value !== 'object' || !('rows' in value)) return 0; const rows = (value as { rows?: unknown }).rows; return Array.isArray(rows) ? rows.length : 0; } } export class GoodDetailDto extends GoodDto { @ApiProperty({ nullable: true, type: Object }) originDetail!: Record | null; @ApiProperty({ type: Array }) variants!: Array>; static fromGood(good: PrismaGood, rel: GoodRelations): GoodDetailDto { const base = GoodDto.from(good, rel); const detail = rel.originGood?.detail; const toAnnotated = ( variant: Record, originGoodId: string, originGoodName: string | null, ) => ({ ...variant, price: variant.price === null || variant.price === undefined ? null : (variant.price as unknown as { toString(): string }).toString(), originGoodId, originGoodName, }); const primaryId = good.originGoodId.toString(); const primaryName = rel.originGood?.goodName ?? null; const mergedVariants = [ ...(rel.originGood?.variants ?? []).map((variant) => toAnnotated(variant as Record, primaryId, primaryName), ), ...(rel.mergedOriginGoods ?? []).flatMap((m) => (m.originGood.variants ?? []).map((variant) => toAnnotated( variant, m.originGood.id.toString(), m.originGood.goodName, ), ), ), ]; return { ...base, originDetail: detail ? { ...detail, syncedAt: detail.syncedAt.toISOString() } : null, variants: mergedVariants, }; } } export interface PaginatedGoods { items: GoodDto[]; total: number; page: number; pageSize: number; }