Files
inkreach-official-website/apps/api/src/goods/dto/good.dto.ts
T

283 lines
9.0 KiB
TypeScript

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;
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 } }[];
mergedOriginGoods?: Array<{
originGood: {
id: bigint;
sdsGoodId: string;
source: 'SDS' | 'CUSTOM';
goodName: string | null;
goodImage: string | null;
goodPrice: unknown;
detail?: { syncedAt: Date } | Record<string, unknown> | 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;
} | 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,
}))
: [],
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,
}
: 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<string, unknown> | null;
@ApiProperty({ type: Array })
variants!: Array<Record<string, unknown>>;
static fromGood(good: PrismaGood, rel: GoodRelations): GoodDetailDto {
const base = GoodDto.from(good, rel);
const detail = rel.originGood?.detail;
const toAnnotated = (
variant: Record<string, unknown>,
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<string, unknown>, 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;
}