merge(dev_jacy): 企微 MCP 增加开发计划版本与任务查询
This commit is contained in:
@@ -130,6 +130,7 @@ HQ 页也会展示 Base URL 与 Header 名(不含密钥)。
|
||||
| `promo.read` | `/promo-codes`、`/promo-codes/{code}/stats` | `query_promo_codes`、`query_promo_code_stats` |
|
||||
| `store.audit.read` | `/store-audits`、`/store-info-audits`、`/store-info-audits/{id}`、`/store-package-audits`、`/store-package-audits/{id}` | `query_store_audits` 等 5 个 |
|
||||
| `partner.read` | `/partners`、`/partners/{partnerId}/users|stores|orders` | `query_partners` 等 4 个 |
|
||||
| `dev_plan.read` | `/versions`、`/tasks` | `query_versions`、`query_tasks` |
|
||||
|
||||
**实例建议:**
|
||||
|
||||
@@ -138,6 +139,7 @@ HQ 页也会展示 Base URL 与 Header 名(不含密钥)。
|
||||
| 日常运营 | metrics + order + user + store + promo + redeem |
|
||||
| 审核值班 | 上表 + store.audit.read |
|
||||
| 合伙人分析 | 上表 + partner.read |
|
||||
| 开发计划 | `dev_plan.read`(已有实例需在 HQ 补勾才会出现这两个工具) |
|
||||
|
||||
---
|
||||
|
||||
@@ -379,6 +381,49 @@ HQ 页也会展示 Base URL 与 Header 名(不含密钥)。
|
||||
|
||||
---
|
||||
|
||||
### 5.16 查询开发版本
|
||||
|
||||
| 项 | 值 |
|
||||
|----|-----|
|
||||
| 权限 | `dev_plan.read` |
|
||||
| 路径 | `/versions` |
|
||||
| MCP | `query_versions` |
|
||||
|
||||
**输入(均可选):**
|
||||
|
||||
| 参数 | 位置 | 类型 | 模型可见 | 说明 |
|
||||
|------|------|------|----------|------|
|
||||
| q | Query | String | 是 | 版本号或说明,如 `v3.5.18` |
|
||||
| status | Query | String | 是 | `PENDING` 待启动 / `IN_PROGRESS` 开发中 / `TESTING` 测试 / `RELEASED` 已上线 / `STOPPED` 已停止 |
|
||||
| page / pageSize | Query | Integer | 否 | 分页 |
|
||||
|
||||
**`data.items[]`:** versionNo、content、status、statusLabel、createdAt、releasedAt
|
||||
|
||||
---
|
||||
|
||||
### 5.17 查询开发任务
|
||||
|
||||
| 项 | 值 |
|
||||
|----|-----|
|
||||
| 权限 | `dev_plan.read` |
|
||||
| 路径 | `/tasks` |
|
||||
| MCP | `query_tasks` |
|
||||
|
||||
按**任务状态**筛选。可同时用 `q`、`versionNo`。
|
||||
|
||||
**输入(均可选):**
|
||||
|
||||
| 参数 | 位置 | 类型 | 模型可见 | 说明 |
|
||||
|------|------|------|----------|------|
|
||||
| status | Query | String | **是** | `TODO` 待开发 / `IN_PROGRESS` 开发中 / `DEVELOPED` 已开发 / `RELEASED` 已上线 / `STOPPED` 已停止 |
|
||||
| q | Query | String | 是 | 任务编号或内容 |
|
||||
| versionNo | Query | String | 是 | 精确版本号,如 `v3.5.18`;传入则只返回该版本下任务 |
|
||||
| page / pageSize | Query | Integer | 否 | 分页 |
|
||||
|
||||
**`data.items[]`:** taskNo、content、type、typeLabel、status、statusLabel、creatorName、supportTicketNo、versions[]、createdAt、completedAt。不含附件 URL。
|
||||
|
||||
---
|
||||
|
||||
## 7. OpenAPI 导入
|
||||
|
||||
| 项 | 值 |
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
|
||||
| 日期 | 说明 |
|
||||
|------|------|
|
||||
| 2026-09-08 | 企微 MCP/REST 插件增加 `dev_plan.read`:`query_versions` / `query_tasks`(按任务状态筛选) |
|
||||
| 2026-09-08 | v3.5.18:企微插件增加 Streamable HTTP MCP 端点 `/api/v1/wecom/plugin/mcp`,与 REST/OpenAPI 并存 |
|
||||
| 2026-09-08 | HQ「门店账户」主账号详情可 CRUD 店员子账号(`/admin/store-accounts/:id/staff`) |
|
||||
| 2026-09-06 | v3.5.17:企微 API 插件迁入 HQ「企微机器人 → API 插件」;多实例 Key + 工具权限;不再依赖 `WECOM_PLUGIN_*` env |
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
| `store.read` | `/stores`(名称搜索 + 评分/核销/累计核销/合伙人) |
|
||||
| `store.audit.read` | `/store-audits`、`/store-info-audits`、`/store-package-audits` 及 `/{id}` 对比详情 |
|
||||
| `partner.read` | `/partners`、`/partners/{id}/users|stores|orders`(可选 `from`/`to` 时间筛选) |
|
||||
| `dev_plan.read` | `/versions`、`/tasks`(MCP:`query_versions`、`query_tasks`,任务可按 status 筛选) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
| 响应 | JSON-RPC;**无** `{code,message,data}` 信封 |
|
||||
| 工具结果 | `JSON.stringify(查询结果)` |
|
||||
| 权限 | `tools/list` 只注册已勾选权限;QueryService 仍 `requirePerm` |
|
||||
| 开发计划 | `dev_plan.read` → `query_versions`、`query_tasks`(任务按 TODO/IN_PROGRESS/DEVELOPED/RELEASED/STOPPED 筛选) |
|
||||
|
||||
生产:`https://api.dukanghaoke.com/api/v1/wecom/plugin/mcp`
|
||||
测试:`https://api-test.dukanghaoke.com/api/v1/wecom/plugin/mcp`
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ C 端购酒核销 · 门店扫码核销+打款 · 合伙人拓店履约 · WebAd
|
||||
|
||||
**HQ 企微报告(v3.5.15)**:企微机器人下「报告」与「消息推送」分开。日报/周报/月报各配 Webhook 与发送时刻;走群机器人 markdown。账期截在发送日北京 0 点(前一天 24 点),不含发送当天:日报=昨日存量+当日新增;周报/月报=上一自然周/月期末存量+本期新增。用户=有效未合并;合伙人=主账号;订单笔数与金额均排除待支付 / 已取消 / 退款中;订单金额=已付 `payAmount`(`paidAt`);核销=`RedeemRecord`。
|
||||
|
||||
**企微 API 插件(v3.5.17 / MCP v3.5.18)**:实例在 HQ「企微机器人 → API 插件」维护;共用 Header `X-Api-Key`。REST `GET /api/v1/wecom/plugin/*` 与 MCP `POST /api/v1/wecom/plugin/mcp`(Streamable HTTP 无状态)并存;MCP 按权限自动 `tools/list`。经营指标含新增门店(`newStores`);门店搜索含经营数据;支持门店/信息/套餐审核对照只读查询;支持按合伙人查关联用户/门店/订单(`from`/`to` 时间筛选)。
|
||||
**企微 API 插件(v3.5.17 / MCP v3.5.18)**:实例在 HQ「企微机器人 → API 插件」维护;共用 Header `X-Api-Key`。REST `GET /api/v1/wecom/plugin/*` 与 MCP `POST /api/v1/wecom/plugin/mcp`(Streamable HTTP 无状态)并存;MCP 按权限自动 `tools/list`。经营指标含新增门店(`newStores`);门店搜索含经营数据;支持门店/信息/套餐审核对照只读查询;支持按合伙人查关联用户/门店/订单(`from`/`to` 时间筛选);`dev_plan.read` 可查开发版本(`query_versions`)与按任务状态筛选的任务列表(`query_tasks`)。
|
||||
|
||||
**HQ 列表(v3.5.9)**:主表不省略号、可横滑;最左序号;列设置(显隐/顺序)与列宽(拖表头)存 `hq_account.list_column_prefs`。主展示列下划线,点击进编辑或详情。门店列表「累计核销好客权益」= 该店 `RedeemRecord.amount` 合计。用户列表昵称只读(点击进详情);双击「备注」离开即保存(`hq_remark`);列表手机号不脱敏。
|
||||
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@ HQ 账号/角色(`hq-permissions`,生效=(角色∪追加)−撤销;可绑
|
||||
|------|------|
|
||||
| 智能机器人 | `/wecom/bots` 长连接指令 |
|
||||
| API 插件 | HQ `/wecom/plugins` 多实例;`/api/v1/wecom/plugin` Header `X-Api-Key` 只读查询(与长连接独立) |
|
||||
| MCP 插件 | 同上 Key;`POST /api/v1/wecom/plugin/mcp` Streamable HTTP;`tools/list` 按权限过滤 |
|
||||
| MCP 插件 | 同上 Key;`POST /api/v1/wecom/plugin/mcp` Streamable HTTP;`tools/list` 按权限过滤;`dev_plan.read` → `query_versions` / `query_tasks` |
|
||||
| 消息推送 | `/wecom/pushes` Webhook+eventKey |
|
||||
| 日志 | `/logs/wecom-bots` |
|
||||
| C 端微信客服 | 系统设置 `CUSTOMER_SERVICE_WECOM_URL` + `WECOM_CORP_ID`;小程序须已关联该企业微信客服 |
|
||||
|
||||
@@ -103,5 +103,7 @@ describe('maskWecomPluginApiKey / path permission map', () => {
|
||||
]);
|
||||
expect(allowedWecomPluginMcpTools(['partner.read'])).toContain('query_partners');
|
||||
expect(allowedWecomPluginMcpTools(['metrics.read'])).not.toContain('query_orders');
|
||||
expect(allowedWecomPluginOpenApiPaths(['dev_plan.read'])).toEqual(['/versions', '/tasks']);
|
||||
expect(allowedWecomPluginMcpTools(['dev_plan.read'])).toEqual(['query_versions', 'query_tasks']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,6 +26,7 @@ export const WECOM_PLUGIN_PERMISSIONS = [
|
||||
'metrics.read',
|
||||
'store.audit.read',
|
||||
'partner.read',
|
||||
'dev_plan.read',
|
||||
] as const;
|
||||
|
||||
export type WecomPluginPermission = (typeof WECOM_PLUGIN_PERMISSIONS)[number];
|
||||
@@ -39,6 +40,7 @@ export const WECOM_PLUGIN_PERMISSION_LABELS: Record<WecomPluginPermission, strin
|
||||
'metrics.read': '经营指标',
|
||||
'store.audit.read': '门店/套餐审核',
|
||||
'partner.read': '合伙人关联查询',
|
||||
'dev_plan.read': '查询版本与任务',
|
||||
};
|
||||
|
||||
export const WECOM_PLUGIN_PERMISSION_GROUPS: Array<{
|
||||
@@ -64,6 +66,11 @@ export const WECOM_PLUGIN_PERMISSION_GROUPS: Array<{
|
||||
label: '审核对照',
|
||||
permissions: ['store.audit.read'],
|
||||
},
|
||||
{
|
||||
key: 'dev_plan',
|
||||
label: '开发计划',
|
||||
permissions: ['dev_plan.read'],
|
||||
},
|
||||
];
|
||||
|
||||
/** OpenAPI path → 工具权限 */
|
||||
@@ -84,6 +91,8 @@ export const WECOM_PLUGIN_PATH_PERMISSION: Record<string, WecomPluginPermission>
|
||||
'/partners/{partnerId}/users': 'partner.read',
|
||||
'/partners/{partnerId}/stores': 'partner.read',
|
||||
'/partners/{partnerId}/orders': 'partner.read',
|
||||
'/versions': 'dev_plan.read',
|
||||
'/tasks': 'dev_plan.read',
|
||||
};
|
||||
|
||||
export const WECOM_PLUGIN_MCP_TOOL_NAMES = [
|
||||
@@ -103,6 +112,8 @@ export const WECOM_PLUGIN_MCP_TOOL_NAMES = [
|
||||
'query_partner_users',
|
||||
'query_partner_stores',
|
||||
'query_partner_orders',
|
||||
'query_versions',
|
||||
'query_tasks',
|
||||
] as const;
|
||||
|
||||
export type WecomPluginMcpToolName = (typeof WECOM_PLUGIN_MCP_TOOL_NAMES)[number];
|
||||
@@ -142,6 +153,15 @@ export const WECOM_PLUGIN_MCP_TOOL_META: Record<
|
||||
permission: 'partner.read',
|
||||
description: '查询佣金归属该合伙人的订单,可选 from/to',
|
||||
},
|
||||
query_versions: {
|
||||
permission: 'dev_plan.read',
|
||||
description: '按版本号关键词查询开发计划版本;可选 status 筛选版本状态 PENDING/IN_PROGRESS/TESTING/RELEASED/STOPPED',
|
||||
},
|
||||
query_tasks: {
|
||||
permission: 'dev_plan.read',
|
||||
description:
|
||||
'查询开发任务列表。status 按任务状态筛选:TODO 待开发、IN_PROGRESS 开发中、DEVELOPED 已开发、RELEASED 已上线、STOPPED 已停止;可选 q 搜任务编号/内容,versionNo 限定某版本',
|
||||
},
|
||||
};
|
||||
|
||||
export const WECOM_PLUGIN_MCP_TOOLS: Record<WecomPluginPermission, WecomPluginMcpToolName[]> = {
|
||||
@@ -159,6 +179,7 @@ export const WECOM_PLUGIN_MCP_TOOLS: Record<WecomPluginPermission, WecomPluginMc
|
||||
'query_store_package_audit_detail',
|
||||
],
|
||||
'partner.read': ['query_partners', 'query_partner_users', 'query_partner_stores', 'query_partner_orders'],
|
||||
'dev_plan.read': ['query_versions', 'query_tasks'],
|
||||
};
|
||||
|
||||
export const WECOM_PLUGIN_TOOL_PATHS: Record<WecomPluginPermission, string[]> = {
|
||||
@@ -181,6 +202,7 @@ export const WECOM_PLUGIN_TOOL_PATHS: Record<WecomPluginPermission, string[]> =
|
||||
'/partners/{partnerId}/stores',
|
||||
'/partners/{partnerId}/orders',
|
||||
],
|
||||
'dev_plan.read': ['/versions', '/tasks'],
|
||||
};
|
||||
|
||||
export function parseWecomPluginPermissions(
|
||||
|
||||
@@ -43,4 +43,33 @@ describe('WECOM_PLUGIN_MCP_TOOL_DEFS', () => {
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0]).toMatchObject({ type: 'text', text: '请提供查询关键词 q' });
|
||||
});
|
||||
|
||||
it('invokes query_versions and query_tasks', async () => {
|
||||
const query = {
|
||||
queryVersions: vi.fn().mockResolvedValue({ total: 1, items: [{ versionNo: 'v3.5.18' }] }),
|
||||
queryTasks: vi.fn().mockResolvedValue({ total: 1, items: [{ taskNo: 'T1', status: 'TODO' }] }),
|
||||
} as unknown as WecomPluginQueryService;
|
||||
const versions = WECOM_PLUGIN_MCP_TOOL_DEFS.find((d) => d.name === 'query_versions');
|
||||
const tasks = WECOM_PLUGIN_MCP_TOOL_DEFS.find((d) => d.name === 'query_tasks');
|
||||
expect(versions).toBeTruthy();
|
||||
expect(tasks).toBeTruthy();
|
||||
await versions!.invoke(
|
||||
{ query, plugin, wecomUserId: 'alice' },
|
||||
{ q: 'v3.5', status: 'IN_PROGRESS', page: 1, pageSize: 5 },
|
||||
);
|
||||
expect(query.queryVersions).toHaveBeenCalledWith(plugin, 'alice', 'v3.5', 'IN_PROGRESS', '1', '5');
|
||||
await tasks!.invoke(
|
||||
{ query, plugin, wecomUserId: 'alice' },
|
||||
{ status: 'TODO', q: '核销', versionNo: 'v3.5.18', page: 1, pageSize: 5 },
|
||||
);
|
||||
expect(query.queryTasks).toHaveBeenCalledWith(
|
||||
plugin,
|
||||
'alice',
|
||||
'核销',
|
||||
'TODO',
|
||||
'v3.5.18',
|
||||
'1',
|
||||
'5',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -201,4 +201,47 @@ export const WECOM_PLUGIN_MCP_TOOL_DEFS: ToolDef[] = [
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'query_versions',
|
||||
description: WECOM_PLUGIN_MCP_TOOL_META.query_versions.description,
|
||||
inputSchema: {
|
||||
q: z.string().optional().describe('版本号关键词,如 v3.5.18'),
|
||||
status: z
|
||||
.enum(['PENDING', 'IN_PROGRESS', 'TESTING', 'RELEASED', 'STOPPED'])
|
||||
.optional()
|
||||
.describe('版本状态:待启动/开发中/测试/已上线/已停止'),
|
||||
page,
|
||||
pageSize,
|
||||
},
|
||||
invoke: (ctx, args) =>
|
||||
jsonResult(() =>
|
||||
ctx.query.queryVersions(ctx.plugin, ctx.wecomUserId, str(args, 'q'), str(args, 'status'), str(args, 'page'), str(args, 'pageSize')),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'query_tasks',
|
||||
description: WECOM_PLUGIN_MCP_TOOL_META.query_tasks.description,
|
||||
inputSchema: {
|
||||
status: z
|
||||
.enum(['TODO', 'IN_PROGRESS', 'DEVELOPED', 'RELEASED', 'STOPPED'])
|
||||
.optional()
|
||||
.describe('任务状态:TODO 待开发、IN_PROGRESS 开发中、DEVELOPED 已开发、RELEASED 已上线、STOPPED 已停止'),
|
||||
q: z.string().optional().describe('任务编号或内容关键词'),
|
||||
versionNo: z.string().optional().describe('限定某版本号,如 v3.5.18'),
|
||||
page,
|
||||
pageSize,
|
||||
},
|
||||
invoke: (ctx, args) =>
|
||||
jsonResult(() =>
|
||||
ctx.query.queryTasks(
|
||||
ctx.plugin,
|
||||
ctx.wecomUserId,
|
||||
str(args, 'q'),
|
||||
str(args, 'status'),
|
||||
str(args, 'versionNo'),
|
||||
str(args, 'page'),
|
||||
str(args, 'pageSize'),
|
||||
),
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
Inject,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
clampWecomPluginPage,
|
||||
clampWecomPluginPageSize,
|
||||
@@ -19,11 +25,21 @@ import {
|
||||
allowedWecomPluginMcpTools,
|
||||
wecomPluginHasPermission,
|
||||
STORE_INFO_CHANGEABLE_FIELD_LABELS,
|
||||
DEV_PLAN_TASK_STATUSES,
|
||||
DEV_PLAN_TASK_STATUS_LABELS,
|
||||
DEV_PLAN_TASK_TYPE_LABELS,
|
||||
DEV_PLAN_VERSION_STATUSES,
|
||||
DEV_PLAN_VERSION_STATUS_LABELS,
|
||||
type StoreInfoChangeableField,
|
||||
type WecomPluginPermission,
|
||||
type DevPlanTaskDto,
|
||||
type DevPlanVersionDto,
|
||||
type DevPlanTaskStatusDto,
|
||||
type DevPlanVersionStatusDto,
|
||||
} from '@dukang/shared-types';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { DevPlanService } from '../../modules/dev-plan/dev-plan.service';
|
||||
import { PromoCodeService } from '../../modules/promo/promo-code.service';
|
||||
import { WecomBotAuditService } from './wecom-bot-audit.service';
|
||||
import { wecomPluginAuditBot, type WecomPluginRuntime } from './wecom-plugin.types';
|
||||
@@ -41,18 +57,68 @@ function requireQuery(q?: string): string {
|
||||
return v;
|
||||
}
|
||||
|
||||
function parseDevPlanTaskStatus(raw?: string | null): DevPlanTaskStatusDto | null {
|
||||
const v = String(raw ?? '').trim().toUpperCase();
|
||||
if (!v) return null;
|
||||
if (!(DEV_PLAN_TASK_STATUSES as readonly string[]).includes(v)) {
|
||||
throw new BadRequestException(
|
||||
`任务状态须为 ${DEV_PLAN_TASK_STATUSES.join(' | ')}(待开发/开发中/已开发/已上线/已停止)`,
|
||||
);
|
||||
}
|
||||
return v as DevPlanTaskStatusDto;
|
||||
}
|
||||
|
||||
function parseDevPlanVersionStatus(raw?: string | null): DevPlanVersionStatusDto | null {
|
||||
const v = String(raw ?? '').trim().toUpperCase();
|
||||
if (!v) return null;
|
||||
if (!(DEV_PLAN_VERSION_STATUSES as readonly string[]).includes(v)) {
|
||||
throw new BadRequestException(
|
||||
`版本状态须为 ${DEV_PLAN_VERSION_STATUSES.join(' | ')}(待启动/开发中/测试/已上线/已停止)`,
|
||||
);
|
||||
}
|
||||
return v as DevPlanVersionStatusDto;
|
||||
}
|
||||
|
||||
function toWecomPluginTaskView(t: DevPlanTaskDto) {
|
||||
return {
|
||||
taskNo: t.taskNo,
|
||||
content: t.content,
|
||||
type: t.type,
|
||||
typeLabel: DEV_PLAN_TASK_TYPE_LABELS[t.type] ?? t.type,
|
||||
status: t.status,
|
||||
statusLabel: DEV_PLAN_TASK_STATUS_LABELS[t.status] ?? t.status,
|
||||
creatorName: t.creatorName?.trim() || '—',
|
||||
supportTicketNo: t.supportTicketNo ?? null,
|
||||
versions: (t.versions ?? []).map((v) => v.versionNo),
|
||||
createdAt: t.createdAt,
|
||||
completedAt: t.completedAt ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function toWecomPluginVersionView(v: DevPlanVersionDto) {
|
||||
return {
|
||||
versionNo: v.versionNo,
|
||||
content: v.content ?? null,
|
||||
status: v.status,
|
||||
statusLabel: DEV_PLAN_VERSION_STATUS_LABELS[v.status] ?? v.status,
|
||||
createdAt: v.createdAt,
|
||||
releasedAt: v.releasedAt ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class WecomPluginQueryService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly promo: PromoCodeService,
|
||||
private readonly audit: WecomBotAuditService,
|
||||
@Inject(DevPlanService) private readonly devPlan: DevPlanService,
|
||||
) {}
|
||||
|
||||
info(plugin: WecomPluginRuntime) {
|
||||
return {
|
||||
name: plugin.name,
|
||||
description: '企微智能机器人只读插件。查询订单、用户、门店经营、核销、推广码、经营指标、审核对照与合伙人关联数据。',
|
||||
description: '企微智能机器人只读插件。查询订单、用户、门店经营、核销、推广码、经营指标、审核对照、合伙人关联数据、开发计划版本与任务。',
|
||||
auth: { header: 'X-Api-Key' },
|
||||
permissions: plugin.permissions,
|
||||
tools: plugin.permissions.flatMap((p) => WECOM_PLUGIN_TOOL_PATHS[p]),
|
||||
@@ -1007,6 +1073,106 @@ export class WecomPluginQueryService {
|
||||
);
|
||||
}
|
||||
|
||||
queryVersions(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
q?: string,
|
||||
status?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'dev_plan.read');
|
||||
const versionStatus = parseDevPlanVersionStatus(status);
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const pg = clampWecomPluginPage(page);
|
||||
const keyword = String(q ?? '').trim();
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.dev_plan.versions',
|
||||
permission: 'dev_plan.read',
|
||||
inputSummary: `${keyword || '*'} ${versionStatus || ''}`.trim(),
|
||||
},
|
||||
async () => {
|
||||
const data = (await this.devPlan.listVersions({
|
||||
keyword: keyword || undefined,
|
||||
status: versionStatus ?? undefined,
|
||||
page: pg,
|
||||
pageSize: take,
|
||||
})) as { items: DevPlanVersionDto[]; total: number };
|
||||
return {
|
||||
total: data.total,
|
||||
items: data.items.map(toWecomPluginVersionView),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
queryTasks(
|
||||
plugin: WecomPluginRuntime,
|
||||
wecomUserId: string,
|
||||
q?: string,
|
||||
status?: string,
|
||||
versionNo?: string,
|
||||
page?: string,
|
||||
pageSize?: string,
|
||||
) {
|
||||
this.requirePerm(plugin, 'dev_plan.read');
|
||||
const taskStatus = parseDevPlanTaskStatus(status);
|
||||
const take = clampWecomPluginPageSize(pageSize);
|
||||
const pg = clampWecomPluginPage(page);
|
||||
const keyword = String(q ?? '').trim();
|
||||
const ver = String(versionNo ?? '').trim();
|
||||
return this.audit.run(
|
||||
{
|
||||
bot: wecomPluginAuditBot(plugin),
|
||||
wecomUserId,
|
||||
action: 'plugin.dev_plan.tasks',
|
||||
permission: 'dev_plan.read',
|
||||
inputSummary: `${ver || keyword || '*'} ${taskStatus || ''}`.trim(),
|
||||
},
|
||||
async () => {
|
||||
if (ver) {
|
||||
const listed = (await this.devPlan.listVersions({
|
||||
keyword: ver,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
})) as { items: DevPlanVersionDto[] };
|
||||
const match = listed.items.find((v) => v.versionNo.toLowerCase() === ver.toLowerCase());
|
||||
if (!match) throw new NotFoundException(`未找到版本:${ver}`);
|
||||
const detail = await this.devPlan.getVersion(BigInt(match.id));
|
||||
let tasks = (detail.tasks ?? []).map(toWecomPluginTaskView);
|
||||
if (taskStatus) tasks = tasks.filter((t) => t.status === taskStatus);
|
||||
if (keyword) {
|
||||
const kw = keyword.toLowerCase();
|
||||
tasks = tasks.filter(
|
||||
(t) => t.taskNo.toLowerCase().includes(kw) || t.content.toLowerCase().includes(kw),
|
||||
);
|
||||
}
|
||||
const total = tasks.length;
|
||||
const start = (pg - 1) * take;
|
||||
return {
|
||||
versionNo: match.versionNo,
|
||||
versionStatus: match.status,
|
||||
total,
|
||||
items: tasks.slice(start, start + take),
|
||||
};
|
||||
}
|
||||
const data = (await this.devPlan.listTasks({
|
||||
status: taskStatus ?? undefined,
|
||||
keyword: keyword || undefined,
|
||||
page: pg,
|
||||
pageSize: take,
|
||||
})) as { items: DevPlanTaskDto[]; total: number };
|
||||
return {
|
||||
total: data.total,
|
||||
items: data.items.map(toWecomPluginTaskView),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private mapStoreInfoAuditRow(
|
||||
row: {
|
||||
id: bigint;
|
||||
|
||||
@@ -219,4 +219,29 @@ export class WecomPluginController {
|
||||
) {
|
||||
return this.query.queryMetrics(plugin, wecomPluginCaller(req), kind);
|
||||
}
|
||||
|
||||
@Get('versions')
|
||||
versions(
|
||||
@CurrentWecomPlugin() plugin: WecomPluginRuntime,
|
||||
@Req() req: Request,
|
||||
@Query('q') q?: string,
|
||||
@Query('status') status?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
) {
|
||||
return this.query.queryVersions(plugin, wecomPluginCaller(req), q, status, page, pageSize);
|
||||
}
|
||||
|
||||
@Get('tasks')
|
||||
tasks(
|
||||
@CurrentWecomPlugin() plugin: WecomPluginRuntime,
|
||||
@Req() req: Request,
|
||||
@Query('q') q?: string,
|
||||
@Query('status') status?: string,
|
||||
@Query('versionNo') versionNo?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
) {
|
||||
return this.query.queryTasks(plugin, wecomPluginCaller(req), q, status, versionNo, page, pageSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,5 +328,52 @@ export const WECOM_PLUGIN_OPENAPI = {
|
||||
},
|
||||
},
|
||||
},
|
||||
'/versions': {
|
||||
get: {
|
||||
summary: '查询开发版本',
|
||||
description: '按版本号或说明模糊查询;status 为版本状态',
|
||||
operationId: '查询开发版本',
|
||||
parameters: [
|
||||
{ name: 'q', in: 'query', required: false, schema: { type: 'string' }, description: '版本号关键词,如 v3.5.18' },
|
||||
{
|
||||
name: 'status',
|
||||
in: 'query',
|
||||
required: false,
|
||||
schema: { type: 'string', enum: ['PENDING', 'IN_PROGRESS', 'TESTING', 'RELEASED', 'STOPPED'] },
|
||||
},
|
||||
...pageParams,
|
||||
],
|
||||
responses: {
|
||||
200: { description: '版本列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
|
||||
401: unauthorized,
|
||||
},
|
||||
},
|
||||
},
|
||||
'/tasks': {
|
||||
get: {
|
||||
summary: '查询开发任务',
|
||||
description:
|
||||
'按任务状态筛选。status:TODO 待开发 / IN_PROGRESS 开发中 / DEVELOPED 已开发 / RELEASED 已上线 / STOPPED 已停止。可选 q、versionNo。',
|
||||
operationId: '查询开发任务',
|
||||
parameters: [
|
||||
{ name: 'q', in: 'query', required: false, schema: { type: 'string' }, description: '任务编号或内容' },
|
||||
{
|
||||
name: 'status',
|
||||
in: 'query',
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'string',
|
||||
enum: ['TODO', 'IN_PROGRESS', 'DEVELOPED', 'RELEASED', 'STOPPED'],
|
||||
},
|
||||
},
|
||||
{ name: 'versionNo', in: 'query', required: false, schema: { type: 'string' }, description: '限定某版本号' },
|
||||
...pageParams,
|
||||
],
|
||||
responses: {
|
||||
200: { description: '任务列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } },
|
||||
401: unauthorized,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user