feat(store): separate login phone from public contactPhone

Store.phone stays login/boss; contactPhone is C-end dial. Public store APIs keep phone compat via publicDial mapping.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-09 07:50:40 +08:00
parent a598d1cd30
commit 07630c9046
11 changed files with 265 additions and 67 deletions
@@ -179,11 +179,14 @@ export class StoreService {
for (const store of visible) {
const coords = await this.ensureStoreCoordinates(store);
const { visibilityWhitelistEnabled: _wl, ...rest } = store;
const mapped = mapStoreCompat({
...rest,
latitude: coords?.latitude ?? store.latitude,
longitude: coords?.longitude ?? store.longitude,
});
const mapped = mapStoreCompat(
{
...rest,
latitude: coords?.latitude ?? store.latitude,
longitude: coords?.longitude ?? store.longitude,
},
{ publicDial: true },
);
const distanceMeters =
hasUser && coords
? Math.round(haversineMeters(userLat!, userLng!, coords.latitude, coords.longitude))
@@ -230,29 +233,32 @@ export class StoreService {
});
const { visibilityWhitelistEnabled: _wl, ...rest } = store;
return serializeBigInt(
mapStoreCompat({
...rest,
latitude: coords?.latitude ?? store.latitude,
longitude: coords?.longitude ?? store.longitude,
media,
packages: packageRows.map((p) => {
const imageUrls = normalizeStorePackageImageUrls({
imageUrl: p.imageUrl,
imageUrls: p.imageUrls,
});
return {
name: p.name,
price: p.price.toFixed(2),
dishes: p.dishes,
usableTime: p.usableTime,
otherNotes: p.otherNotes,
imageUrl: imageUrls[0] ?? null,
/** C 端始终返回数组,便于多图轮播 */
imageUrls,
sortOrder: p.sortOrder,
};
}),
}),
mapStoreCompat(
{
...rest,
latitude: coords?.latitude ?? store.latitude,
longitude: coords?.longitude ?? store.longitude,
media,
packages: packageRows.map((p) => {
const imageUrls = normalizeStorePackageImageUrls({
imageUrl: p.imageUrl,
imageUrls: p.imageUrls,
});
return {
name: p.name,
price: p.price.toFixed(2),
dishes: p.dishes,
usableTime: p.usableTime,
otherNotes: p.otherNotes,
imageUrl: imageUrls[0] ?? null,
/** C 端始终返回数组,便于多图轮播 */
imageUrls,
sortOrder: p.sortOrder,
};
}),
},
{ publicDial: true },
),
);
}
@@ -275,7 +281,7 @@ export class StoreService {
include: { category: true, coverResource: true },
orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }],
});
return serializeBigInt(stores.map(mapStoreCompat));
return serializeBigInt(stores.map((s) => mapStoreCompat(s)));
}
async partnerGetStore(partnerAccountId: bigint, storeId: bigint) {
@@ -307,10 +313,10 @@ export class StoreService {
async partnerCheckStorePhone(phone: string) {
const normalizedPhone = phone.trim();
if (!normalizedPhone) {
return { available: false, message: '请填写联系电话' };
return { available: false, message: '请填写门店登录手机号' };
}
if (!/^1[3-9]\d{9}$/.test(normalizedPhone)) {
return { available: false, message: '联系电话须为11位手机号' };
return { available: false, message: '门店登录手机号须为11位手机号' };
}
const existingAccount = await this.prisma.storeAccount.findUnique({
where: { phone: normalizedPhone },
@@ -360,6 +366,14 @@ export class StoreService {
const confirmBindExisting = body.confirmBindExisting === true || body.confirmBindExisting === 'true';
await this.assertStorePhoneAvailable(normalizedPhone, confirmBindExisting);
const contactPhoneRaw =
body.contactPhone != null && String(body.contactPhone).trim()
? String(body.contactPhone).trim()
: normalizedPhone;
if (!/^1[3-9]\d{9}$/.test(contactPhoneRaw)) {
throw new BadRequestException('联系电话须为11位手机号');
}
const city = await this.resolvePartnerCity(partnerAccountId, body.cityId);
const coverUrl = body.coverUrl ? String(body.coverUrl).trim() : '';
const envPhotoUrls = this.normalizeEnvPhotoUrls(body.envPhotoUrls);
@@ -416,6 +430,7 @@ export class StoreService {
categoryId,
name: String(body.name),
phone: normalizedPhone,
contactPhone: contactPhoneRaw,
province: String(body.province ?? city.province ?? '河南省'),
cityName: String(body.city ?? city.name ?? '郑州市'),
district: String(body.district ?? ''),
@@ -614,7 +629,13 @@ export class StoreService {
}
const name = body.name !== undefined ? String(body.name).trim() : undefined;
const phone = body.phone !== undefined ? String(body.phone).trim() : undefined;
// 合伙人资料修改:联系电话 → contactPhone(兼容旧客户端把 phone 当联系电话提交)
const contactPhone =
body.contactPhone !== undefined
? String(body.contactPhone).trim()
: body.phone !== undefined
? String(body.phone).trim()
: undefined;
const address = body.address !== undefined ? String(body.address).trim() : undefined;
const introRaw = body.intro !== undefined ? String(body.intro).trim() : undefined;
const benefitUsageRuleRaw =
@@ -627,7 +648,7 @@ export class StoreService {
body.longitude !== undefined ? parseOptionalCoord(body.longitude, 'lng') : undefined;
if (name !== undefined && !name) throw new BadRequestException('请填写门店名称');
if (phone !== undefined && !/^1\d{10}$/.test(phone)) {
if (contactPhone !== undefined && !/^1[3-9]\d{9}$/.test(contactPhone)) {
throw new BadRequestException('联系电话须为11位手机号');
}
if (address !== undefined && !address) throw new BadRequestException('请填写详细地址');
@@ -650,7 +671,7 @@ export class StoreService {
where: { id: storeId },
data: {
...(name !== undefined ? { name } : {}),
...(phone !== undefined ? { phone } : {}),
...(contactPhone !== undefined ? { contactPhone } : {}),
...(address !== undefined
? hasCoordsUpdate
? { address }