feat(store): 门店可见白名单(对齐商品)
HQ 可配置 visibilityWhitelistEnabled + 手机号;C 端公开门店列表/详情按登录手机号过滤;登录态变化后小程序重新拉门店列表。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -25,6 +25,24 @@ function normalizeStoreOptionalText(value: unknown): string | null {
|
||||
return s;
|
||||
}
|
||||
|
||||
function normalizeVisibilityPhones(phones?: string[]): string[] {
|
||||
if (!phones?.length) return [];
|
||||
const out: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const raw of phones) {
|
||||
const phone = String(raw || '')
|
||||
.replace(/\D/g, '')
|
||||
.trim();
|
||||
if (!phone || seen.has(phone)) continue;
|
||||
if (!/^1\d{10}$/.test(phone)) {
|
||||
throw new BadRequestException(`手机号格式无效:${raw}`);
|
||||
}
|
||||
seen.add(phone);
|
||||
out.push(phone);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AdminStoresService {
|
||||
constructor(
|
||||
@@ -64,19 +82,23 @@ export class AdminStoresService {
|
||||
},
|
||||
},
|
||||
coverResource: { select: { id: true, url: true } },
|
||||
visibilityPhones: { select: { phone: true }, orderBy: { phone: 'asc' } },
|
||||
},
|
||||
}),
|
||||
this.prisma.store.count({ where }),
|
||||
]);
|
||||
return serializeBigInt({
|
||||
items: items.map((s) =>
|
||||
mapStoreCompat({
|
||||
...s,
|
||||
items: items.map((s) => {
|
||||
const { visibilityPhones, ...rest } = s;
|
||||
return mapStoreCompat({
|
||||
...rest,
|
||||
visibilityWhitelistEnabled: s.visibilityWhitelistEnabled,
|
||||
visibilityPhones: visibilityPhones.map((p) => p.phone),
|
||||
partner: s.partnerAccount,
|
||||
account: s.bindings[0]?.storeAccount ?? null,
|
||||
bindings: undefined,
|
||||
}),
|
||||
),
|
||||
});
|
||||
}),
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
@@ -96,6 +118,7 @@ export class AdminStoresService {
|
||||
include: { storeAccount: true },
|
||||
},
|
||||
coverResource: true,
|
||||
visibilityPhones: { select: { phone: true }, orderBy: { phone: 'asc' } },
|
||||
_count: { select: { redeemRecords: true, ratings: true } },
|
||||
},
|
||||
});
|
||||
@@ -111,8 +134,12 @@ export class AdminStoresService {
|
||||
take: 5,
|
||||
}),
|
||||
]);
|
||||
const { visibilityPhones, ...rest } = store;
|
||||
return serializeBigInt(mapStoreCompat({
|
||||
...store,
|
||||
...rest,
|
||||
visibilityWhitelistEnabled: store.visibilityWhitelistEnabled,
|
||||
visibilityPhones: visibilityPhones.map((p) => p.phone),
|
||||
partner: store.partnerAccount,
|
||||
account: store.bindings[0]?.storeAccount ?? null,
|
||||
/** 门店端登录手机号(store_account.phone),与 store.phone 应对齐 */
|
||||
loginPhone: store.bindings[0]?.storeAccount?.phone ?? store.phone,
|
||||
@@ -235,6 +262,26 @@ export class AdminStoresService {
|
||||
}
|
||||
|
||||
const normalizedPhone = dto.phone !== undefined ? dto.phone.trim() : undefined;
|
||||
if (dto.visibilityWhitelistEnabled !== undefined || dto.visibilityPhones !== undefined) {
|
||||
const nextEnabled =
|
||||
dto.visibilityWhitelistEnabled !== undefined
|
||||
? !!dto.visibilityWhitelistEnabled
|
||||
: current.visibilityWhitelistEnabled;
|
||||
if (nextEnabled) {
|
||||
const phones =
|
||||
dto.visibilityPhones !== undefined
|
||||
? normalizeVisibilityPhones(dto.visibilityPhones)
|
||||
: (
|
||||
await this.prisma.storeVisibilityPhone.findMany({
|
||||
where: { storeId: id },
|
||||
select: { phone: true },
|
||||
})
|
||||
).map((p) => p.phone);
|
||||
if (!phones.length) {
|
||||
throw new BadRequestException('开启白名单时请至少添加一个手机号');
|
||||
}
|
||||
}
|
||||
}
|
||||
const bankTouched =
|
||||
dto.bankAccountName !== undefined ||
|
||||
dto.bankAccountNo !== undefined ||
|
||||
@@ -307,10 +354,23 @@ export class AdminStoresService {
|
||||
...(dto.openTime2 !== undefined ? { openTime2: dto.openTime2 || null } : {}),
|
||||
...(dto.closeTime2 !== undefined ? { closeTime2: dto.closeTime2 || null } : {}),
|
||||
...(dto.avgPrice !== undefined ? { avgPrice: dto.avgPrice } : {}),
|
||||
...(dto.visibilityWhitelistEnabled !== undefined
|
||||
? { visibilityWhitelistEnabled: !!dto.visibilityWhitelistEnabled }
|
||||
: {}),
|
||||
...(latitude != null && longitude != null ? { latitude, longitude } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
if (dto.visibilityPhones !== undefined) {
|
||||
const phones = normalizeVisibilityPhones(dto.visibilityPhones);
|
||||
await tx.storeVisibilityPhone.deleteMany({ where: { storeId: id } });
|
||||
if (phones.length) {
|
||||
await tx.storeVisibilityPhone.createMany({
|
||||
data: phones.map((phone) => ({ storeId: id, phone })),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (dto.coverUrl) {
|
||||
if (current.coverResourceId) {
|
||||
await tx.commonResource.update({
|
||||
@@ -430,6 +490,12 @@ export class AdminStoresService {
|
||||
throw new BadRequestException('好客权益券使用规则最多 1000 字');
|
||||
}
|
||||
|
||||
const visibilityPhones = normalizeVisibilityPhones(dto.visibilityPhones);
|
||||
const whitelistEnabled = !!dto.visibilityWhitelistEnabled;
|
||||
if (whitelistEnabled && !visibilityPhones.length) {
|
||||
throw new BadRequestException('开启白名单时请至少添加一个手机号');
|
||||
}
|
||||
|
||||
const store = await this.prisma.store.create({
|
||||
data: {
|
||||
cityId: city.id,
|
||||
@@ -449,11 +515,19 @@ export class AdminStoresService {
|
||||
closeTime,
|
||||
openTime2: openTime2 || null,
|
||||
closeTime2: closeTime2 || null,
|
||||
visibilityWhitelistEnabled: whitelistEnabled,
|
||||
...(latitude != null && longitude != null ? { latitude, longitude } : {}),
|
||||
status: 'OPEN',
|
||||
auditStatus: 'APPROVED',
|
||||
auditedAt: new Date(),
|
||||
rejectReason: null,
|
||||
...(visibilityPhones.length
|
||||
? {
|
||||
visibilityPhones: {
|
||||
create: visibilityPhones.map((phone) => ({ phone })),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user