门店账户多账号
This commit is contained in:
@@ -54,16 +54,20 @@ export class AdminRedeemDebugService {
|
||||
throw new NotFoundException('用户不存在,请检查 ID、用户编号或手机号');
|
||||
}
|
||||
|
||||
private async resolveStoreAccountId(storeId: string): Promise<bigint> {
|
||||
const account = await this.prisma.storeAccount.findFirst({
|
||||
where: { storeId: this.parseStoreId(storeId), status: 'ACTIVE' },
|
||||
private async resolveStoreAccountId(storeId: string): Promise<{ accountId: bigint; storeId: bigint }> {
|
||||
const sid = this.parseStoreId(storeId);
|
||||
const binding = await this.prisma.storeAccountStore.findFirst({
|
||||
where: {
|
||||
storeId: sid,
|
||||
storeAccount: { status: 'ACTIVE', isPrimary: 1 },
|
||||
},
|
||||
orderBy: { id: 'asc' },
|
||||
select: { id: true, store: { select: { name: true } } },
|
||||
select: { storeAccountId: true, storeId: true },
|
||||
});
|
||||
if (!account) {
|
||||
if (!binding) {
|
||||
throw new NotFoundException('该门店无可用账户,请先创建门店账户');
|
||||
}
|
||||
return account.id;
|
||||
return { accountId: binding.storeAccountId, storeId: binding.storeId };
|
||||
}
|
||||
|
||||
async createToken(dto: AdminRedeemDebugCreateTokenDto) {
|
||||
@@ -76,32 +80,32 @@ export class AdminRedeemDebugService {
|
||||
}
|
||||
|
||||
async preview(dto: AdminRedeemDebugStoreTokenDto) {
|
||||
const storeAccountId = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.previewRedeem(storeAccountId, dto.token);
|
||||
const { accountId, storeId } = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.previewRedeem(accountId, storeId, dto.token);
|
||||
}
|
||||
|
||||
async confirm(dto: AdminRedeemDebugStoreTokenDto) {
|
||||
const storeAccountId = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.confirmRedeem(storeAccountId, { token: dto.token });
|
||||
const { accountId, storeId } = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.confirmRedeem(accountId, storeId, { token: dto.token });
|
||||
}
|
||||
|
||||
async sendPhoneLookupSms(dto: AdminRedeemDebugPhoneStoreDto) {
|
||||
const storeAccountId = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.sendPhoneLookupSms(storeAccountId, dto.phone);
|
||||
const { accountId, storeId } = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.sendPhoneLookupSms(accountId, storeId, dto.phone);
|
||||
}
|
||||
|
||||
async phoneBalance(dto: AdminRedeemDebugPhoneBalanceDto) {
|
||||
const storeAccountId = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.verifyPhoneAndGetBalance(storeAccountId, dto.phone, dto.code);
|
||||
const { accountId, storeId } = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.verifyPhoneAndGetBalance(accountId, storeId, dto.phone, dto.code);
|
||||
}
|
||||
|
||||
async phonePrepare(dto: AdminRedeemDebugPhonePrepareDto) {
|
||||
const storeAccountId = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.preparePhoneRedeem(storeAccountId, dto.sessionId, dto.amount);
|
||||
const { accountId, storeId } = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.preparePhoneRedeem(accountId, storeId, dto.sessionId, dto.amount);
|
||||
}
|
||||
|
||||
async phoneConfirm(dto: AdminRedeemDebugPhoneConfirmDto) {
|
||||
const storeAccountId = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.confirmPhoneRedeem(storeAccountId, dto.sessionId, dto.code);
|
||||
const { accountId, storeId } = await this.resolveStoreAccountId(dto.storeId);
|
||||
return this.redeemService.confirmPhoneRedeem(accountId, storeId, dto.sessionId, dto.code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,11 +131,13 @@ export class AdminStoreLogsService {
|
||||
if (query.phone) accountWhere.phone = { contains: query.phone };
|
||||
const accounts = await this.prisma.storeAccount.findMany({
|
||||
where: accountWhere,
|
||||
select: { storeId: true },
|
||||
select: {
|
||||
bindings: { select: { storeId: true } },
|
||||
},
|
||||
take: 100,
|
||||
});
|
||||
if (accounts.length === 0) return [];
|
||||
const ids = [...new Set(accounts.map((a) => a.storeId))];
|
||||
const ids = [...new Set(accounts.flatMap((a) => a.bindings.map((b) => b.storeId)))];
|
||||
if (storeWhere.name) {
|
||||
const stores = await this.prisma.store.findMany({
|
||||
where: { id: { in: ids }, ...storeWhere },
|
||||
|
||||
@@ -41,14 +41,26 @@ export class AdminStoresService {
|
||||
include: {
|
||||
cityRef: { select: { id: true, name: true, code: true } },
|
||||
partnerAccount: { select: { id: true, companyName: true } },
|
||||
account: { select: { id: true, phone: true, name: true, status: true } },
|
||||
bindings: {
|
||||
where: { storeAccount: { isPrimary: 1 } },
|
||||
take: 1,
|
||||
include: {
|
||||
storeAccount: { select: { id: true, phone: true, name: true, status: true } },
|
||||
},
|
||||
},
|
||||
coverResource: { select: { id: true, url: true } },
|
||||
},
|
||||
}),
|
||||
this.prisma.store.count({ where }),
|
||||
]);
|
||||
return serializeBigInt({
|
||||
items: items.map((s) => mapStoreCompat(s)),
|
||||
items: items.map((s) =>
|
||||
mapStoreCompat({
|
||||
...s,
|
||||
account: s.bindings[0]?.storeAccount ?? null,
|
||||
bindings: undefined,
|
||||
}),
|
||||
),
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
@@ -62,7 +74,11 @@ export class AdminStoresService {
|
||||
cityRef: true,
|
||||
partnerAccount: true,
|
||||
category: true,
|
||||
account: true,
|
||||
bindings: {
|
||||
where: { storeAccount: { isPrimary: 1 } },
|
||||
take: 1,
|
||||
include: { storeAccount: true },
|
||||
},
|
||||
coverResource: true,
|
||||
_count: { select: { redeemRecords: true, ratings: true } },
|
||||
},
|
||||
@@ -81,6 +97,8 @@ export class AdminStoresService {
|
||||
]);
|
||||
return serializeBigInt(mapStoreCompat({
|
||||
...store,
|
||||
account: store.bindings[0]?.storeAccount ?? null,
|
||||
bindings: undefined,
|
||||
media,
|
||||
audits,
|
||||
redeemCount: store._count.redeemRecords,
|
||||
@@ -165,7 +183,12 @@ export class AdminStoresService {
|
||||
const existingAccount = await this.prisma.storeAccount.findUnique({
|
||||
where: { phone: normalizedPhone },
|
||||
});
|
||||
if (existingAccount) throw new BadRequestException('该手机号已绑定门店');
|
||||
if (existingAccount && existingAccount.isPrimary !== 1) {
|
||||
throw new BadRequestException('该手机号已是门店子账号');
|
||||
}
|
||||
if (existingAccount && existingAccount.status !== 'ACTIVE') {
|
||||
throw new BadRequestException('该手机号对应门店账号已停用');
|
||||
}
|
||||
|
||||
const partnerAccountId = BigInt(dto.partnerAccountId);
|
||||
const partnerAccount = await this.prisma.partnerAccount.findUnique({
|
||||
@@ -191,9 +214,6 @@ export class AdminStoresService {
|
||||
district: dto.district ?? '',
|
||||
address: dto.address,
|
||||
intro: dto.intro ?? null,
|
||||
bankAccountName: dto.bankAccountName ?? null,
|
||||
bankAccountNo: dto.bankAccountNo ?? null,
|
||||
bankBranch: dto.bankBranch ?? null,
|
||||
openTime: '10:00',
|
||||
closeTime: '22:00',
|
||||
status: 'OPEN',
|
||||
@@ -258,13 +278,37 @@ export class AdminStoresService {
|
||||
},
|
||||
});
|
||||
|
||||
await this.prisma.storeAccount.create({
|
||||
data: {
|
||||
storeId: store.id,
|
||||
phone: normalizedPhone,
|
||||
name: dto.accountName ?? dto.name,
|
||||
},
|
||||
});
|
||||
const bankAccountName = dto.bankAccountName ?? null;
|
||||
const bankAccountNo = dto.bankAccountNo ?? null;
|
||||
const bankBranch = dto.bankBranch ?? null;
|
||||
|
||||
if (existingAccount) {
|
||||
await this.prisma.storeAccountStore.create({
|
||||
data: { storeAccountId: existingAccount.id, storeId: store.id },
|
||||
});
|
||||
if (bankAccountName || bankAccountNo || bankBranch) {
|
||||
await this.prisma.storeAccount.update({
|
||||
where: { id: existingAccount.id },
|
||||
data: {
|
||||
...(bankAccountName != null ? { bankAccountName } : {}),
|
||||
...(bankAccountNo != null ? { bankAccountNo } : {}),
|
||||
...(bankBranch != null ? { bankBranch } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await this.prisma.storeAccount.create({
|
||||
data: {
|
||||
phone: normalizedPhone,
|
||||
name: dto.accountName ?? dto.name,
|
||||
isPrimary: 1,
|
||||
bankAccountName,
|
||||
bankAccountNo,
|
||||
bankBranch,
|
||||
bindings: { create: [{ storeId: store.id }] },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return this.detailStore(store.id);
|
||||
}
|
||||
@@ -272,12 +316,37 @@ export class AdminStoresService {
|
||||
async createStoreAccount(dto: CreateStoreAccountDto) {
|
||||
const store = await this.prisma.store.findUnique({
|
||||
where: { id: BigInt(dto.storeId) },
|
||||
include: { account: true },
|
||||
include: { bindings: true },
|
||||
});
|
||||
if (!store) throw new BadRequestException('门店不存在');
|
||||
if (store.account) throw new BadRequestException('门店已有账户');
|
||||
const primaryBound = store.bindings.length > 0
|
||||
? await this.prisma.storeAccount.findFirst({
|
||||
where: {
|
||||
isPrimary: 1,
|
||||
bindings: { some: { storeId: store.id } },
|
||||
},
|
||||
})
|
||||
: null;
|
||||
if (primaryBound) throw new BadRequestException('门店已有主账号绑定');
|
||||
|
||||
const existing = await this.prisma.storeAccount.findUnique({ where: { phone: dto.phone } });
|
||||
if (existing) {
|
||||
if (existing.isPrimary !== 1) {
|
||||
throw new BadRequestException('该手机号已是门店子账号');
|
||||
}
|
||||
await this.prisma.storeAccountStore.create({
|
||||
data: { storeAccountId: existing.id, storeId: store.id },
|
||||
});
|
||||
return serializeBigInt(existing);
|
||||
}
|
||||
|
||||
const account = await this.prisma.storeAccount.create({
|
||||
data: { storeId: store.id, phone: dto.phone, name: dto.name },
|
||||
data: {
|
||||
phone: dto.phone,
|
||||
name: dto.name,
|
||||
isPrimary: 1,
|
||||
bindings: { create: [{ storeId: store.id }] },
|
||||
},
|
||||
});
|
||||
return serializeBigInt(account);
|
||||
}
|
||||
@@ -345,9 +414,11 @@ export class AdminStoresService {
|
||||
async listStoreAccounts(query: AdminStoreAccountsQueryDto) {
|
||||
const page = query.page ?? 1;
|
||||
const pageSize = query.pageSize ?? 20;
|
||||
const where: Prisma.StoreAccountWhereInput = {};
|
||||
const where: Prisma.StoreAccountWhereInput = { isPrimary: 1 };
|
||||
if (query.phone) where.phone = { contains: query.phone };
|
||||
if (query.storeId) where.storeId = BigInt(query.storeId);
|
||||
if (query.storeId) {
|
||||
where.bindings = { some: { storeId: BigInt(query.storeId) } };
|
||||
}
|
||||
if (query.status) where.status = query.status as Prisma.EnumAccountStatusFilter['equals'];
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
@@ -357,21 +428,53 @@ export class AdminStoresService {
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
include: {
|
||||
store: { select: { id: true, name: true, status: true, cityName: true } },
|
||||
bindings: {
|
||||
include: {
|
||||
store: { select: { id: true, name: true, status: true, cityName: true } },
|
||||
},
|
||||
},
|
||||
_count: { select: { childAccounts: true, bindings: true } },
|
||||
},
|
||||
}),
|
||||
this.prisma.storeAccount.count({ where }),
|
||||
]);
|
||||
return serializeBigInt({ items, total, page, pageSize });
|
||||
const mapped = items.map((row) => ({
|
||||
...row,
|
||||
storeCount: row._count.bindings,
|
||||
staffCount: row._count.childAccounts,
|
||||
stores: row.bindings.map((b) => b.store),
|
||||
store: row.bindings[0]?.store ?? null,
|
||||
}));
|
||||
return serializeBigInt({ items: mapped, total, page, pageSize });
|
||||
}
|
||||
|
||||
async detailStoreAccount(id: bigint) {
|
||||
const account = await this.prisma.storeAccount.findUnique({
|
||||
where: { id },
|
||||
include: { store: { include: { cityRef: true, partnerAccount: true } } },
|
||||
include: {
|
||||
bindings: {
|
||||
include: {
|
||||
store: { include: { cityRef: true, partnerAccount: true } },
|
||||
},
|
||||
},
|
||||
childAccounts: {
|
||||
include: {
|
||||
bindings: {
|
||||
include: {
|
||||
store: { select: { id: true, name: true, status: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!account) throw new NotFoundException('门店账号不存在');
|
||||
return serializeBigInt(account);
|
||||
return serializeBigInt({
|
||||
...account,
|
||||
stores: account.bindings.map((b) => b.store),
|
||||
store: account.bindings[0]?.store ?? null,
|
||||
staff: account.childAccounts,
|
||||
});
|
||||
}
|
||||
|
||||
async updateStoreAccount(id: bigint, dto: UpdateStoreAccountDto) {
|
||||
|
||||
@@ -185,11 +185,17 @@ export class AdminWechatBindingsService {
|
||||
|
||||
const accounts = await this.prisma.storeAccount.findMany({
|
||||
where,
|
||||
include: { store: { select: { id: true, name: true } } },
|
||||
include: {
|
||||
bindings: {
|
||||
take: 1,
|
||||
include: { store: { select: { id: true, name: true } } },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const a of accounts) {
|
||||
if (!a.wxOpenId) continue;
|
||||
const firstStore = a.bindings[0]?.store;
|
||||
rows.push({
|
||||
actorType: 'STORE',
|
||||
actorId: a.id,
|
||||
@@ -197,8 +203,8 @@ export class AdminWechatBindingsService {
|
||||
name: a.name,
|
||||
wxOpenId: a.wxOpenId,
|
||||
wxUnionId: a.wxUnionId,
|
||||
refId: a.storeId,
|
||||
refLabel: a.store.name,
|
||||
refId: firstStore?.id,
|
||||
refLabel: firstStore?.name ?? a.name,
|
||||
lastLoginAt: a.lastLoginAt,
|
||||
status: a.status,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user