v3.5.4版本提交
CI / verify (pull_request) Has been cancelled

This commit is contained in:
2026-08-21 15:48:15 +08:00
parent 26334ed072
commit a01217539c
32 changed files with 2278 additions and 147 deletions
@@ -6,6 +6,14 @@ import {
normalizeTestPhone,
} from '../../common/test-whitelist/test-whitelist.service';
import { groupResourcesByProductId, mapProductMedia } from './catalog.mapper';
import {
flattenSkuOntoProduct,
listMinOnSalePrice,
mapSkuDto,
mapSpecAttrsDto,
pickDisplaySku,
resolveOrderSku,
} from './product-sku.util';
export type CatalogViewer = {
/** C 端用户手机号;无则无法看到白名单商品 */
@@ -81,6 +89,8 @@ export class CatalogService {
orderBy: { sortOrder: 'asc' },
include: {
coverResource: true,
skus: { orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }] },
specAttrs: { select: { id: true } },
},
});
@@ -106,12 +116,25 @@ export class CatalogService {
return serializeBigInt(
visible.map((p) => {
const media = mapProductMedia(p, resourceMap.get(p.id.toString()) ?? []);
const { visibilityWhitelistEnabled: _wl, ...rest } = p;
const { visibilityWhitelistEnabled: _wl, skus, specAttrs, ...rest } = p;
const display = pickDisplaySku(skus);
const flat = flattenSkuOntoProduct(p, display);
const minPrice = listMinOnSalePrice(skus);
const price = minPrice ?? flat.price;
const benefitAmount = flat.benefitAmount;
return {
...rest,
benefitAmount: p.benefitAmount ?? p.price,
price: Number(p.price),
benefitDisplay: Number(p.benefitAmount ?? p.price),
skuCode: flat.skuCode,
barcode69: undefined,
spec: flat.spec,
price,
benefitAmount,
benefitDisplay: benefitAmount,
allowOnSitePickup: flat.allowOnSitePickup,
allowOnlinePurchase: flat.allowOnlinePurchase,
allowCrossCityDelivery: flat.allowCrossCityDelivery,
specEnabled: specAttrs.length > 0 || skus.length > 1,
saleUnit: flat.saleUnit,
...media,
};
}),
@@ -123,6 +146,14 @@ export class CatalogService {
where: { id },
include: {
coverResource: true,
skus: {
orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }],
include: { skuSpecs: { select: { valueId: true } } },
},
specAttrs: {
orderBy: { sortOrder: 'asc' },
include: { values: { orderBy: { sortOrder: 'asc' } } },
},
},
});
if (!product) return null;
@@ -144,30 +175,57 @@ export class CatalogService {
});
const media = mapProductMedia(product, resources);
const { visibilityWhitelistEnabled: _wl, ...rest } = product;
const { visibilityWhitelistEnabled: _wl, skus, specAttrs, ...rest } = product;
const display = pickDisplaySku(skus);
const flat = flattenSkuOntoProduct(product, display);
const defaultSku = skus.find((s) => s.isDefault) ?? display;
const cSkus = skus.map((s) => {
const dto = mapSkuDto(s);
const { skuCode: _c, barcode69: _b, sortOrder: _o, ...publicSku } = dto;
return publicSku;
});
return serializeBigInt({
...rest,
benefitAmount: product.benefitAmount ?? product.price,
price: Number(product.price),
skuCode: flat.skuCode,
spec: flat.spec,
price: flat.price,
benefitAmount: flat.benefitAmount,
benefitDisplay: flat.benefitAmount,
allowOnSitePickup: flat.allowOnSitePickup,
allowOnlinePurchase: flat.allowOnlinePurchase,
allowCrossCityDelivery: flat.allowCrossCityDelivery,
saleUnit: flat.saleUnit,
specEnabled: specAttrs.length > 0 || skus.length > 1,
specAttrs: mapSpecAttrsDto(specAttrs),
skus: cSkus,
defaultSkuId: defaultSku?.id.toString(),
...media,
});
}
/** 下单前校验:白名单商品仅全局测试白名单手机号可买 */
async assertPurchasable(productId: bigint, viewerPhone?: string | null) {
/** 下单前校验:白名单商品仅全局测试白名单手机号可买;返回 SPU + 解析后的 SKU */
async assertPurchasable(
productId: bigint,
viewerPhone?: string | null,
skuId?: string | null,
options?: { bypassWhitelist?: boolean },
) {
const product = await this.prisma.commonProductItem.findUnique({
where: { id: productId },
include: { skus: true },
});
if (!product || product.status !== 'ON_SALE') {
throw new BadRequestException('商品不可购买');
}
if (product.visibilityWhitelistEnabled) {
if (!options?.bypassWhitelist && product.visibilityWhitelistEnabled) {
const ok = await this.testWhitelist.isPhoneInWhitelist(viewerPhone);
if (!ok) {
throw new BadRequestException('该商品暂不对当前账号开放');
}
}
return product;
const sku = resolveOrderSku(product.skus, skuId);
return { product, sku };
}
async resolveUserPhone(userId: bigint): Promise<string | null> {
@@ -177,4 +235,12 @@ export class CatalogService {
});
return user?.phone ?? null;
}
async listSkusForProduct(productId: bigint) {
return this.prisma.commonProductSku.findMany({
where: { productId },
orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }],
include: { skuSpecs: { select: { valueId: true } } },
});
}
}