feat(ops): v4.0.4 开发任务多行与版本状态同步、合伙人/门店删除、账单合伙人列

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-31 16:08:55 +08:00
parent e26206b93c
commit ac94f5f5de
15 changed files with 568 additions and 59 deletions
@@ -1,6 +1,12 @@
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { isMobilePhone, isStoreContactPhone, STORE_CONTACT_PHONE_HINT, validateBusinessHours } from '@dukang/domain';
import {
assertCanDeleteStore,
isMobilePhone,
isStoreContactPhone,
STORE_CONTACT_PHONE_HINT,
validateBusinessHours,
} from '@dukang/domain';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import { mapStoreCompat } from '../../common/compat/v31-compat';
@@ -241,6 +247,88 @@ export class AdminStoresService {
return serializeBigInt(store);
}
async deleteStore(id: bigint, actorId: bigint) {
await this.hqPermissions.assertStoreIdInScope(actorId, id);
const store = await this.prisma.store.findUnique({
where: { id },
include: { bindings: { select: { storeAccountId: true } } },
});
if (!store) throw new NotFoundException('门店不存在');
const [redeemCount, pendingCount, payoutCount, billCount, ratingCount, withdrawCount] =
await Promise.all([
this.prisma.redeemRecord.count({ where: { storeId: id } }),
this.prisma.redeemPendingRecord.count({ where: { storeId: id } }),
this.prisma.storePayout.count({ where: { storeId: id } }),
this.prisma.storeBill.count({ where: { storeId: id } }),
this.prisma.storeRating.count({ where: { storeId: id } }),
this.prisma.storeWithdrawRequest.count({ where: { storeId: id } }),
]);
const check = assertCanDeleteStore({
orderCount: 0,
redeemCount:
redeemCount + pendingCount + payoutCount + billCount + ratingCount + withdrawCount,
});
if (!check.ok) throw new BadRequestException(check.message);
const accountIds = [...new Set(store.bindings.map((b) => b.storeAccountId))];
const exclusiveAccountIds: bigint[] = [];
for (const accountId of accountIds) {
const otherBindings = await this.prisma.storeAccountStore.count({
where: { storeAccountId: accountId, NOT: { storeId: id } },
});
if (otherBindings === 0) exclusiveAccountIds.push(accountId);
}
const staffToDelete: bigint[] = [];
if (exclusiveAccountIds.length) {
const staff = await this.prisma.storeAccount.findMany({
where: { parentAccountId: { in: exclusiveAccountIds }, isPrimary: 0 },
select: { id: true },
});
for (const row of staff) {
const other = await this.prisma.storeAccountStore.count({
where: { storeAccountId: row.id, NOT: { storeId: id } },
});
if (other > 0) continue;
const pending = await this.prisma.redeemPendingRecord.count({
where: { storeAccountId: row.id },
});
if (pending > 0) {
throw new BadRequestException('该账号下有关联核销单,禁止删除');
}
staffToDelete.push(row.id);
}
}
await this.prisma.$transaction(async (tx) => {
await tx.store.update({ where: { id }, data: { coverResourceId: null } });
await tx.commonResource.updateMany({
where: { ownerType: 'STORE', ownerId: id },
data: { status: 'DELETED' },
});
await tx.logStoreAnalytics.deleteMany({ where: { storeId: id } });
await tx.store.delete({ where: { id } });
if (staffToDelete.length) {
await tx.logStoreAnalytics.deleteMany({ where: { storeAccountId: { in: staffToDelete } } });
await tx.storeAccount.deleteMany({ where: { id: { in: staffToDelete } } });
}
if (exclusiveAccountIds.length) {
await tx.logStoreAnalytics.deleteMany({
where: { storeAccountId: { in: exclusiveAccountIds } },
});
await tx.storeAccount.updateMany({
where: { parentAccountId: { in: exclusiveAccountIds } },
data: { parentAccountId: null },
});
await tx.storeAccount.deleteMany({ where: { id: { in: exclusiveAccountIds } } });
}
});
return { ok: true, message: '门店及门店账号已删除' };
}
async auditStore(id: bigint, dto: { approved: boolean; remark?: string }, actorId: bigint) {
await this.hqPermissions.assertStoreIdInScope(actorId, id);
const store = await this.prisma.store.findUnique({ where: { id } });