diff --git a/docs/企微API插件-配置手册.md b/docs/企微API插件-配置手册.md index eb3b5c9..5643119 100644 --- a/docs/企微API插件-配置手册.md +++ b/docs/企微API插件-配置手册.md @@ -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 导入 | 项 | 值 | diff --git a/docs/杜康好客-v3-现状对照.md b/docs/杜康好客-v3-现状对照.md index 7dee69d..d5226ce 100644 --- a/docs/杜康好客-v3-现状对照.md +++ b/docs/杜康好客-v3-现状对照.md @@ -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 | diff --git a/docs/杜康好客-v3.5.17-开发文档.md b/docs/杜康好客-v3.5.17-开发文档.md index 0dcf739..3e84554 100644 --- a/docs/杜康好客-v3.5.17-开发文档.md +++ b/docs/杜康好客-v3.5.17-开发文档.md @@ -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 筛选) | --- diff --git a/docs/杜康好客-v3.5.18-开发文档.md b/docs/杜康好客-v3.5.18-开发文档.md index f550e8c..09aa917 100644 --- a/docs/杜康好客-v3.5.18-开发文档.md +++ b/docs/杜康好客-v3.5.18-开发文档.md @@ -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` diff --git a/docs/杜康好客-v3编码手册.md b/docs/杜康好客-v3编码手册.md index 2ed2149..01f36c6 100644 --- a/docs/杜康好客-v3编码手册.md +++ b/docs/杜康好客-v3编码手册.md @@ -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`);列表手机号不脱敏。 diff --git a/docs/杜康好客-知识库.md b/docs/杜康好客-知识库.md index dff7034..6cbe379 100644 --- a/docs/杜康好客-知识库.md +++ b/docs/杜康好客-知识库.md @@ -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`;小程序须已关联该企业微信客服 | diff --git a/packages/shared-types/src/wecom-plugin.test.ts b/packages/shared-types/src/wecom-plugin.test.ts index a3556aa..8b2d1d2 100644 --- a/packages/shared-types/src/wecom-plugin.test.ts +++ b/packages/shared-types/src/wecom-plugin.test.ts @@ -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']); }); }); diff --git a/packages/shared-types/src/wecom-plugin.ts b/packages/shared-types/src/wecom-plugin.ts index 7f32357..5e8ca14 100644 --- a/packages/shared-types/src/wecom-plugin.ts +++ b/packages/shared-types/src/wecom-plugin.ts @@ -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 '/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 = { @@ -159,6 +179,7 @@ export const WECOM_PLUGIN_MCP_TOOLS: Record = { @@ -181,6 +202,7 @@ export const WECOM_PLUGIN_TOOL_PATHS: Record = '/partners/{partnerId}/stores', '/partners/{partnerId}/orders', ], + 'dev_plan.read': ['/versions', '/tasks'], }; export function parseWecomPluginPermissions( diff --git a/server/dukang-api/src/integrations/wecom/wecom-plugin-mcp.tools.test.ts b/server/dukang-api/src/integrations/wecom/wecom-plugin-mcp.tools.test.ts index 3f40e9a..89892b8 100644 --- a/server/dukang-api/src/integrations/wecom/wecom-plugin-mcp.tools.test.ts +++ b/server/dukang-api/src/integrations/wecom/wecom-plugin-mcp.tools.test.ts @@ -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', + ); + }); }); diff --git a/server/dukang-api/src/integrations/wecom/wecom-plugin-mcp.tools.ts b/server/dukang-api/src/integrations/wecom/wecom-plugin-mcp.tools.ts index 49250ec..d457058 100644 --- a/server/dukang-api/src/integrations/wecom/wecom-plugin-mcp.tools.ts +++ b/server/dukang-api/src/integrations/wecom/wecom-plugin-mcp.tools.ts @@ -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'), + ), + ), + }, ]; diff --git a/server/dukang-api/src/integrations/wecom/wecom-plugin-query.service.ts b/server/dukang-api/src/integrations/wecom/wecom-plugin-query.service.ts index 64488dc..9163b83 100644 --- a/server/dukang-api/src/integrations/wecom/wecom-plugin-query.service.ts +++ b/server/dukang-api/src/integrations/wecom/wecom-plugin-query.service.ts @@ -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; diff --git a/server/dukang-api/src/integrations/wecom/wecom-plugin.controller.ts b/server/dukang-api/src/integrations/wecom/wecom-plugin.controller.ts index 7356df8..3ba2f4f 100644 --- a/server/dukang-api/src/integrations/wecom/wecom-plugin.controller.ts +++ b/server/dukang-api/src/integrations/wecom/wecom-plugin.controller.ts @@ -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); + } } diff --git a/server/dukang-api/src/integrations/wecom/wecom-plugin.openapi.ts b/server/dukang-api/src/integrations/wecom/wecom-plugin.openapi.ts index 791bbb1..708e397 100644 --- a/server/dukang-api/src/integrations/wecom/wecom-plugin.openapi.ts +++ b/server/dukang-api/src/integrations/wecom/wecom-plugin.openapi.ts @@ -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;