门店账户多账号

This commit is contained in:
2026-07-12 12:24:34 +08:00
parent 06b1cb22e0
commit 54a15d6da7
39 changed files with 1962 additions and 311 deletions
@@ -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) {