v4.0.17企业微信API插件优化
This commit is contained in:
@@ -1,19 +1,29 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import {
|
||||
clampWecomPluginPage,
|
||||
clampWecomPluginPageSize,
|
||||
maskContactPhone,
|
||||
MOBILE_PHONE_RE,
|
||||
parseWecomPluginMetricsKind,
|
||||
parseWecomPluginDateRange,
|
||||
toWecomPluginMetricsView,
|
||||
toWecomPluginUserView,
|
||||
wecomPluginMetricsPeriod,
|
||||
type WecomReportStats,
|
||||
} from '@dukang/domain';
|
||||
import {
|
||||
WECOM_PLUGIN_TOOL_PATHS,
|
||||
wecomPluginHasPermission,
|
||||
STORE_INFO_CHANGEABLE_FIELD_LABELS,
|
||||
type StoreInfoChangeableField,
|
||||
type WecomPluginPermission,
|
||||
} from '@dukang/shared-types';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { PromoCodeService } from '../../modules/promo/promo-code.service';
|
||||
import { WecomBotAuditService } from './wecom-bot-audit.service';
|
||||
import { WECOM_PLUGIN_AUDIT_BOT } from './wecom-plugin.constants';
|
||||
import { wecomPluginAuditBot, type WecomPluginRuntime } from './wecom-plugin.types';
|
||||
import { filterWecomPluginOpenApi, WECOM_PLUGIN_OPENAPI } from './wecom-plugin.openapi';
|
||||
|
||||
function asNumber(v: Prisma.Decimal | number | null | undefined): number {
|
||||
if (v == null) return 0;
|
||||
@@ -35,22 +45,40 @@ export class WecomPluginQueryService {
|
||||
private readonly audit: WecomBotAuditService,
|
||||
) {}
|
||||
|
||||
info() {
|
||||
info(plugin: WecomPluginRuntime) {
|
||||
return {
|
||||
name: '杜康好客运营查询',
|
||||
description: '企微智能机器人只读 API 插件。查询订单、用户、门店、核销、推广码与经营指标。',
|
||||
name: plugin.name,
|
||||
description: '企微智能机器人只读 API 插件。查询订单、用户、门店经营、核销、推广码、经营指标、审核对照与合伙人关联数据。',
|
||||
auth: { header: 'X-Api-Key' },
|
||||
tools: ['orders', 'users', 'stores', 'redeems', 'promo-codes', 'metrics'],
|
||||
permissions: plugin.permissions,
|
||||
tools: plugin.permissions.flatMap((p) => WECOM_PLUGIN_TOOL_PATHS[p]),
|
||||
};
|
||||
}
|
||||
|
||||
queryOrders(wecomUserId: string, q?: string, page?: string, pageSize?: string) {
|
||||
openapi(plugin: WecomPluginRuntime) {
|
||||
return filterWecomPluginOpenApi(WECOM_PLUGIN_OPENAPI, plugin.permissions, plugin.name);
|
||||
}
|
||||
|
||||
private requirePerm(plugin: WecomPluginRuntime, permission: WecomPluginPermission) {
|
||||
if (!wecomPluginHasPermission(plugin.permissions, permission)) {
|
||||
throw new ForbiddenException(`当前插件无权限:${permission}`);
|
||||
}
|
||||
}
|
||||
|
||||
queryOrders(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
q?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'order.read');
|
||||
const keyword = requireQuery(q);
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const skip = (clampWecomPluginPage(page) - 1) * take;
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: WECOM_PLUGIN_AUDIT_BOT,
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.order.read',
|
||||
permission: 'order.read',
|
||||
@@ -96,13 +124,20 @@ export class WecomPluginQueryService {
|
||||
);
|
||||
}
|
||||
|
||||
queryUsers(wecomUserId: string, q?: string, page?: string, pageSize?: string) {
|
||||
queryUsers(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
q?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'user.read');
|
||||
const keyword = requireQuery(q);
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const skip = (clampWecomPluginPage(page) - 1) * take;
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: WECOM_PLUGIN_AUDIT_BOT,
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.user.read',
|
||||
permission: 'user.read',
|
||||
@@ -152,13 +187,20 @@ export class WecomPluginQueryService {
|
||||
);
|
||||
}
|
||||
|
||||
queryStores(wecomUserId: string, q?: string, page?: string, pageSize?: string) {
|
||||
queryStores(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
q?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'store.read');
|
||||
const keyword = requireQuery(q);
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const skip = (clampWecomPluginPage(page) - 1) * take;
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: WECOM_PLUGIN_AUDIT_BOT,
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.store.read',
|
||||
permission: 'store.read',
|
||||
@@ -173,39 +215,71 @@ export class WecomPluginQueryService {
|
||||
skip,
|
||||
take,
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
status: true,
|
||||
auditStatus: true,
|
||||
cityName: true,
|
||||
district: true,
|
||||
address: true,
|
||||
contactPhone: true,
|
||||
phone: true,
|
||||
rating: true,
|
||||
createdAt: true,
|
||||
partnerAccount: { select: { id: true, name: true, companyName: true } },
|
||||
_count: { select: { redeemRecords: true } },
|
||||
},
|
||||
}),
|
||||
this.prisma.store.count({ where }),
|
||||
]);
|
||||
const redeemSums =
|
||||
rows.length > 0
|
||||
? await this.prisma.redeemRecord.groupBy({
|
||||
by: ['storeId'],
|
||||
where: { storeId: { in: rows.map((s) => s.id) } },
|
||||
_sum: { amount: true },
|
||||
})
|
||||
: [];
|
||||
const redeemedByStore = new Map(
|
||||
redeemSums.map((r) => [r.storeId.toString(), asNumber(r._sum.amount)]),
|
||||
);
|
||||
return {
|
||||
total,
|
||||
items: rows.map((s) => ({
|
||||
id: s.id.toString(),
|
||||
name: s.name,
|
||||
status: s.status,
|
||||
auditStatus: s.auditStatus,
|
||||
cityName: s.cityName,
|
||||
district: s.district,
|
||||
address: s.address,
|
||||
contactPhone: maskContactPhone(s.contactPhone || s.phone),
|
||||
rating: s.rating != null ? asNumber(s.rating) : null,
|
||||
redeemCount: s._count.redeemRecords,
|
||||
totalRedeemedBenefitAmount: redeemedByStore.get(s.id.toString()) ?? 0,
|
||||
partnerName: s.partnerAccount.companyName || s.partnerAccount.name,
|
||||
partnerId: s.partnerAccount.id.toString(),
|
||||
createdAt: s.createdAt.toISOString(),
|
||||
})),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
queryRedeems(wecomUserId: string, q?: string, page?: string, pageSize?: string) {
|
||||
queryRedeems(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
q?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'redeem.read');
|
||||
const keyword = requireQuery(q);
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const skip = (clampWecomPluginPage(page) - 1) * take;
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: WECOM_PLUGIN_AUDIT_BOT,
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.redeem.read',
|
||||
permission: 'redeem.read',
|
||||
@@ -249,15 +323,23 @@ export class WecomPluginQueryService {
|
||||
);
|
||||
}
|
||||
|
||||
queryPromoCodes(wecomUserId: string, q?: string, page?: string, pageSize?: string) {
|
||||
queryPromoCodes(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
q?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'promo.read');
|
||||
const keyword = requireQuery(q);
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const skip = (clampWecomPluginPage(page) - 1) * take;
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: WECOM_PLUGIN_AUDIT_BOT,
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.promo.read',
|
||||
permission: 'promo.read',
|
||||
inputSummary: keyword,
|
||||
},
|
||||
async () => {
|
||||
@@ -289,13 +371,15 @@ export class WecomPluginQueryService {
|
||||
);
|
||||
}
|
||||
|
||||
queryPromoCodeStats(wecomUserId: string, code?: string) {
|
||||
queryPromoCodeStats(plugin: WecomPluginRuntime, wecomUserId: string, code?: string) {
|
||||
this.requirePerm(plugin, 'promo.read');
|
||||
const keyword = requireQuery(code);
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: WECOM_PLUGIN_AUDIT_BOT,
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.promo.stats',
|
||||
permission: 'promo.read',
|
||||
inputSummary: keyword,
|
||||
},
|
||||
async () => {
|
||||
@@ -315,16 +399,18 @@ export class WecomPluginQueryService {
|
||||
);
|
||||
}
|
||||
|
||||
queryMetrics(wecomUserId: string, kindRaw?: string) {
|
||||
queryMetrics(plugin: WecomPluginRuntime, wecomUserId: string, kindRaw?: string) {
|
||||
this.requirePerm(plugin, 'metrics.read');
|
||||
const kind = parseWecomPluginMetricsKind(kindRaw);
|
||||
if (!kind) {
|
||||
throw new BadRequestException('kind 须为 today | daily | weekly | monthly');
|
||||
}
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: WECOM_PLUGIN_AUDIT_BOT,
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.metrics.read',
|
||||
permission: 'metrics.read',
|
||||
inputSummary: kind,
|
||||
},
|
||||
async () => {
|
||||
@@ -336,7 +422,7 @@ export class WecomPluginQueryService {
|
||||
rangeLabel: period.rangeLabel,
|
||||
incrementLabel: period.incrementLabel,
|
||||
periodKey: period.periodKey,
|
||||
stats,
|
||||
stats: toWecomPluginMetricsView(stats),
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -415,4 +501,555 @@ export class WecomPluginQueryService {
|
||||
redeemAmountIncrement: asNumber(redeemAmountIncrement._sum.amount),
|
||||
};
|
||||
}
|
||||
|
||||
queryStoreAudits(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
q?: string,
|
||||
status?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'store.audit.read');
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const skip = (clampWecomPluginPage(page) - 1) * take;
|
||||
const auditStatus = (status?.trim() || 'PENDING') as 'PENDING' | 'APPROVED' | 'REJECTED';
|
||||
const where: Prisma.StoreWhereInput = { auditStatus };
|
||||
const keyword = String(q ?? '').trim();
|
||||
if (keyword) where.name = { contains: keyword };
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.store.audit.list',
|
||||
permission: 'store.audit.read',
|
||||
inputSummary: keyword || auditStatus,
|
||||
},
|
||||
async () => {
|
||||
const [rows, total] = await Promise.all([
|
||||
this.prisma.store.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip,
|
||||
take,
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
status: true,
|
||||
auditStatus: true,
|
||||
rejectReason: true,
|
||||
cityName: true,
|
||||
district: true,
|
||||
address: true,
|
||||
contactPhone: true,
|
||||
phone: true,
|
||||
createdAt: true,
|
||||
partnerAccount: { select: { id: true, name: true, companyName: true } },
|
||||
},
|
||||
}),
|
||||
this.prisma.store.count({ where }),
|
||||
]);
|
||||
return {
|
||||
total,
|
||||
items: rows.map((s) => ({
|
||||
id: s.id.toString(),
|
||||
name: s.name,
|
||||
status: s.status,
|
||||
auditStatus: s.auditStatus,
|
||||
rejectReason: s.rejectReason,
|
||||
cityName: s.cityName,
|
||||
district: s.district,
|
||||
address: s.address,
|
||||
contactPhone: maskContactPhone(s.contactPhone || s.phone),
|
||||
partnerName: s.partnerAccount.companyName || s.partnerAccount.name,
|
||||
partnerId: s.partnerAccount.id.toString(),
|
||||
createdAt: s.createdAt.toISOString(),
|
||||
})),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
queryStoreInfoAudits(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
status?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'store.audit.read');
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const skip = (clampWecomPluginPage(page) - 1) * take;
|
||||
const where: Prisma.StoreInfoChangeRequestWhereInput = {};
|
||||
if (status?.trim()) {
|
||||
where.status = status.trim() as Prisma.EnumStoreInfoChangeStatusFilter['equals'];
|
||||
} else {
|
||||
where.status = 'PENDING';
|
||||
}
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.store.info_audit.list',
|
||||
permission: 'store.audit.read',
|
||||
inputSummary: where.status as string,
|
||||
},
|
||||
async () => {
|
||||
const [rows, total] = await Promise.all([
|
||||
this.prisma.storeInfoChangeRequest.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip,
|
||||
take,
|
||||
include: { store: { select: { name: true } } },
|
||||
}),
|
||||
this.prisma.storeInfoChangeRequest.count({ where }),
|
||||
]);
|
||||
return {
|
||||
total,
|
||||
items: rows.map((r) => this.mapStoreInfoAuditRow(r)),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
queryStoreInfoAuditDetail(plugin: WecomPluginRuntime, wecomUserId: string, id: string) {
|
||||
this.requirePerm(plugin, 'store.audit.read');
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.store.info_audit.detail',
|
||||
permission: 'store.audit.read',
|
||||
inputSummary: id,
|
||||
},
|
||||
async () => {
|
||||
const row = await this.prisma.storeInfoChangeRequest.findUnique({
|
||||
where: { id: BigInt(id) },
|
||||
include: { store: { select: { name: true } } },
|
||||
});
|
||||
if (!row) throw new NotFoundException('信息变更审核不存在');
|
||||
return this.mapStoreInfoAuditRow(row, true);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
queryStorePackageAudits(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
status?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'store.audit.read');
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const skip = (clampWecomPluginPage(page) - 1) * take;
|
||||
const where: Prisma.StorePackageChangeRequestWhereInput = {};
|
||||
if (status?.trim()) {
|
||||
where.status = status.trim() as Prisma.EnumStorePackageChangeStatusFilter['equals'];
|
||||
} else {
|
||||
where.status = 'PENDING';
|
||||
}
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.store.package_audit.list',
|
||||
permission: 'store.audit.read',
|
||||
inputSummary: where.status as string,
|
||||
},
|
||||
async () => {
|
||||
const [rows, total] = await Promise.all([
|
||||
this.prisma.storePackageChangeRequest.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip,
|
||||
take,
|
||||
include: { store: { select: { id: true, name: true } } },
|
||||
}),
|
||||
this.prisma.storePackageChangeRequest.count({ where }),
|
||||
]);
|
||||
return {
|
||||
total,
|
||||
items: rows.map((r) => ({
|
||||
id: r.id.toString(),
|
||||
storeId: r.storeId.toString(),
|
||||
storeName: r.store.name,
|
||||
status: r.status,
|
||||
packageCount: Array.isArray(r.packagesJson) ? r.packagesJson.length : 0,
|
||||
submitterType: r.submitterType,
|
||||
rejectReason: r.rejectReason,
|
||||
createdAt: r.createdAt.toISOString(),
|
||||
})),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
queryStorePackageAuditDetail(plugin: WecomPluginRuntime, wecomUserId: string, id: string) {
|
||||
this.requirePerm(plugin, 'store.audit.read');
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.store.package_audit.detail',
|
||||
permission: 'store.audit.read',
|
||||
inputSummary: id,
|
||||
},
|
||||
async () => {
|
||||
const req = await this.prisma.storePackageChangeRequest.findUnique({
|
||||
where: { id: BigInt(id) },
|
||||
include: { store: { select: { name: true } } },
|
||||
});
|
||||
if (!req) throw new NotFoundException('套餐审核不存在');
|
||||
const livePackages = await this.prisma.storePackage.findMany({
|
||||
where: { storeId: req.storeId },
|
||||
orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }],
|
||||
select: { name: true, price: true, dishes: true, usableTime: true, otherNotes: true },
|
||||
});
|
||||
return {
|
||||
id: req.id.toString(),
|
||||
storeId: req.storeId.toString(),
|
||||
storeName: req.store.name,
|
||||
status: req.status,
|
||||
proposedPackages: req.packagesJson,
|
||||
livePackages: livePackages.map((p) => ({
|
||||
name: p.name,
|
||||
price: asNumber(p.price),
|
||||
dishes: p.dishes,
|
||||
usableTime: p.usableTime,
|
||||
otherNotes: p.otherNotes,
|
||||
})),
|
||||
submitterType: req.submitterType,
|
||||
rejectReason: req.rejectReason,
|
||||
reviewedAt: req.reviewedAt?.toISOString() ?? null,
|
||||
createdAt: req.createdAt.toISOString(),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
queryPartners(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
q?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'partner.read');
|
||||
const keyword = requireQuery(q);
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const skip = (clampWecomPluginPage(page) - 1) * take;
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.partner.read',
|
||||
permission: 'partner.read',
|
||||
inputSummary: keyword,
|
||||
},
|
||||
async () => {
|
||||
const or: Prisma.PartnerAccountWhereInput[] = [
|
||||
{ name: { contains: keyword } },
|
||||
{ companyName: { contains: keyword } },
|
||||
{ phone: { contains: keyword } },
|
||||
];
|
||||
if (/^\d+$/.test(keyword)) or.push({ id: BigInt(keyword) });
|
||||
const where: Prisma.PartnerAccountWhereInput = { isPrimary: 1, OR: or };
|
||||
const [rows, total] = await Promise.all([
|
||||
this.prisma.partnerAccount.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip,
|
||||
take,
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
companyName: true,
|
||||
phone: true,
|
||||
status: true,
|
||||
city: { select: { name: true } },
|
||||
_count: { select: { stores: true, assocUsers: true } },
|
||||
},
|
||||
}),
|
||||
this.prisma.partnerAccount.count({ where }),
|
||||
]);
|
||||
return {
|
||||
total,
|
||||
items: rows.map((p) => ({
|
||||
id: p.id.toString(),
|
||||
name: p.name,
|
||||
companyName: p.companyName,
|
||||
phone: maskContactPhone(p.phone),
|
||||
status: p.status,
|
||||
cityName: p.city?.name ?? null,
|
||||
storeCount: p._count.stores,
|
||||
userCount: p._count.assocUsers,
|
||||
})),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
queryPartnerUsers(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
partnerId: string,
|
||||
from?: string,
|
||||
to?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'partner.read');
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const skip = (clampWecomPluginPage(page) - 1) * take;
|
||||
const createdAt = parseWecomPluginDateRange(from, to);
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.partner.users',
|
||||
permission: 'partner.read',
|
||||
inputSummary: `${partnerId}:${from || ''}-${to || ''}`,
|
||||
},
|
||||
async () => {
|
||||
const primary = await this.resolvePartnerPrimary(partnerId);
|
||||
const where: Prisma.UserWhereInput = {
|
||||
assocPartnerAccountId: primary.id,
|
||||
mergedIntoUserId: null,
|
||||
...(createdAt ? { createdAt } : {}),
|
||||
};
|
||||
const [rows, total] = await Promise.all([
|
||||
this.prisma.user.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip,
|
||||
take,
|
||||
select: {
|
||||
userNo: true,
|
||||
nickname: true,
|
||||
phone: true,
|
||||
status: true,
|
||||
createdAt: true,
|
||||
_count: { select: { orders: true } },
|
||||
},
|
||||
}),
|
||||
this.prisma.user.count({ where }),
|
||||
]);
|
||||
return {
|
||||
partnerId: primary.id.toString(),
|
||||
partnerName: primary.companyName || primary.name,
|
||||
total,
|
||||
items: rows.map((u) => ({
|
||||
...toWecomPluginUserView(u),
|
||||
status: u.status,
|
||||
orderCount: u._count.orders,
|
||||
createdAt: u.createdAt.toISOString(),
|
||||
})),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
queryPartnerStores(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
partnerId: string,
|
||||
from?: string,
|
||||
to?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'partner.read');
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const skip = (clampWecomPluginPage(page) - 1) * take;
|
||||
const createdAt = parseWecomPluginDateRange(from, to);
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.partner.stores',
|
||||
permission: 'partner.read',
|
||||
inputSummary: `${partnerId}:${from || ''}-${to || ''}`,
|
||||
},
|
||||
async () => {
|
||||
const primary = await this.resolvePartnerPrimary(partnerId);
|
||||
const where: Prisma.StoreWhereInput = {
|
||||
partnerAccountId: primary.id,
|
||||
...(createdAt ? { createdAt } : {}),
|
||||
};
|
||||
const [rows, total] = await Promise.all([
|
||||
this.prisma.store.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip,
|
||||
take,
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
status: true,
|
||||
auditStatus: true,
|
||||
cityName: true,
|
||||
rating: true,
|
||||
createdAt: true,
|
||||
_count: { select: { redeemRecords: true } },
|
||||
},
|
||||
}),
|
||||
this.prisma.store.count({ where }),
|
||||
]);
|
||||
const redeemSums =
|
||||
rows.length > 0
|
||||
? await this.prisma.redeemRecord.groupBy({
|
||||
by: ['storeId'],
|
||||
where: { storeId: { in: rows.map((s) => s.id) } },
|
||||
_sum: { amount: true },
|
||||
})
|
||||
: [];
|
||||
const redeemedByStore = new Map(
|
||||
redeemSums.map((r) => [r.storeId.toString(), asNumber(r._sum.amount)]),
|
||||
);
|
||||
return {
|
||||
partnerId: primary.id.toString(),
|
||||
partnerName: primary.companyName || primary.name,
|
||||
total,
|
||||
items: rows.map((s) => ({
|
||||
id: s.id.toString(),
|
||||
name: s.name,
|
||||
status: s.status,
|
||||
auditStatus: s.auditStatus,
|
||||
cityName: s.cityName,
|
||||
rating: s.rating != null ? asNumber(s.rating) : null,
|
||||
redeemCount: s._count.redeemRecords,
|
||||
totalRedeemedBenefitAmount: redeemedByStore.get(s.id.toString()) ?? 0,
|
||||
createdAt: s.createdAt.toISOString(),
|
||||
})),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
queryPartnerOrders(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
partnerId: string,
|
||||
from?: string,
|
||||
to?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'partner.read');
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const skip = (clampWecomPluginPage(page) - 1) * take;
|
||||
const createdAt = parseWecomPluginDateRange(from, to);
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.partner.orders',
|
||||
permission: 'partner.read',
|
||||
inputSummary: `${partnerId}:${from || ''}-${to || ''}`,
|
||||
},
|
||||
async () => {
|
||||
const primary = await this.resolvePartnerPrimary(partnerId);
|
||||
const where: Prisma.OrderWhereInput = {
|
||||
partnerAccountIdAtPay: primary.id,
|
||||
...(createdAt ? { createdAt } : {}),
|
||||
};
|
||||
const [rows, total] = await Promise.all([
|
||||
this.prisma.order.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip,
|
||||
take,
|
||||
select: {
|
||||
orderNo: true,
|
||||
status: true,
|
||||
payStatus: true,
|
||||
productName: true,
|
||||
quantity: true,
|
||||
payAmount: true,
|
||||
createdAt: true,
|
||||
user: { select: { userNo: true, nickname: true, phone: true } },
|
||||
},
|
||||
}),
|
||||
this.prisma.order.count({ where }),
|
||||
]);
|
||||
return {
|
||||
partnerId: primary.id.toString(),
|
||||
partnerName: primary.companyName || primary.name,
|
||||
total,
|
||||
items: rows.map((o) => ({
|
||||
orderNo: o.orderNo,
|
||||
status: o.status,
|
||||
payStatus: o.payStatus,
|
||||
productName: o.productName,
|
||||
quantity: o.quantity,
|
||||
payAmount: asNumber(o.payAmount),
|
||||
user: toWecomPluginUserView({
|
||||
userNo: o.user?.userNo || '—',
|
||||
nickname: o.user?.nickname,
|
||||
phone: o.user?.phone,
|
||||
}),
|
||||
createdAt: o.createdAt.toISOString(),
|
||||
})),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private mapStoreInfoAuditRow(
|
||||
row: {
|
||||
id: bigint;
|
||||
storeId: bigint;
|
||||
status: string;
|
||||
liveSnapshot: unknown;
|
||||
proposedSnapshot: unknown;
|
||||
changedFields: unknown;
|
||||
submitterType: string;
|
||||
submitterId: bigint;
|
||||
rejectReason: string | null;
|
||||
reviewedAt: Date | null;
|
||||
createdAt: Date;
|
||||
store?: { name?: string | null };
|
||||
},
|
||||
withDiffs = false,
|
||||
) {
|
||||
const changed = Array.isArray(row.changedFields)
|
||||
? (row.changedFields as StoreInfoChangeableField[])
|
||||
: [];
|
||||
const live = (row.liveSnapshot || {}) as Record<string, unknown>;
|
||||
const proposed = (row.proposedSnapshot || {}) as Record<string, unknown>;
|
||||
const base = {
|
||||
id: row.id.toString(),
|
||||
storeId: row.storeId.toString(),
|
||||
storeName: row.store?.name ?? null,
|
||||
status: row.status,
|
||||
changedFields: changed,
|
||||
changedFieldLabels: changed.map((f) => STORE_INFO_CHANGEABLE_FIELD_LABELS[f] ?? f),
|
||||
submitterType: row.submitterType,
|
||||
submitterId: row.submitterId.toString(),
|
||||
rejectReason: row.rejectReason,
|
||||
reviewedAt: row.reviewedAt?.toISOString() ?? null,
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
};
|
||||
if (!withDiffs) return base;
|
||||
return {
|
||||
...base,
|
||||
diffs: changed.map((field) => ({
|
||||
field,
|
||||
label: STORE_INFO_CHANGEABLE_FIELD_LABELS[field] ?? field,
|
||||
live: live[field] ?? null,
|
||||
proposed: proposed[field] ?? null,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
private async resolvePartnerPrimary(partnerId: string) {
|
||||
const id = BigInt(partnerId);
|
||||
const account = await this.prisma.partnerAccount.findUnique({ where: { id } });
|
||||
if (!account) throw new NotFoundException('合伙人不存在');
|
||||
if (account.isPrimary === 1) return account;
|
||||
if (!account.parentAccountId) throw new NotFoundException('合伙人账号无效');
|
||||
return this.prisma.partnerAccount.findUniqueOrThrow({ where: { id: account.parentAccountId } });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user