import type { WecomPluginPermission } from '@dukang/shared-types'; import { allowedWecomPluginOpenApiPaths } from '@dukang/shared-types'; const envelope = (dataSchema: Record) => ({ type: 'object', properties: { code: { type: 'integer', example: 0 }, message: { type: 'string', example: 'ok' }, data: dataSchema, }, required: ['code', 'message', 'data'], }); const qParam = { name: 'q', in: 'query', required: true, schema: { type: 'string' }, description: '查询关键词', }; const pageParams = [ { name: 'page', in: 'query', required: false, schema: { type: 'integer', default: 1, minimum: 1 }, }, { name: 'pageSize', in: 'query', required: false, schema: { type: 'integer', default: 5, minimum: 1, maximum: 10 }, description: '默认 5,最大 10', }, ]; const dateRangeParams = [ { name: 'from', in: 'query', required: false, schema: { type: 'string' }, description: '起始日期 YYYY-MM-DD 或 ISO', }, { name: 'to', in: 'query', required: false, schema: { type: 'string' }, description: '结束日期 YYYY-MM-DD 或 ISO(含当天)', }, ]; const unauthorized = { description: '缺少或错误的 X-Api-Key,或插件未启用', content: { 'application/json': { schema: { type: 'object', properties: { code: { type: 'integer' }, message: { type: 'string' }, }, }, }, }, }; function listPath(summary: string, description: string, qDescription: string) { return { get: { summary, description, operationId: summary, parameters: [{ ...qParam, description: qDescription }, ...pageParams], responses: { 200: { description: '查询结果', content: { 'application/json': { schema: envelope({ type: 'object', properties: { total: { type: 'integer' }, items: { type: 'array', items: { type: 'object' } }, }, }), }, }, }, 401: unauthorized, }, }, }; } /** 按实例权限过滤 paths,供企微第 2 步对照配置 */ export function filterWecomPluginOpenApi( spec: typeof WECOM_PLUGIN_OPENAPI, permissions: WecomPluginPermission[], title?: string, ) { const allowedPaths = new Set(allowedWecomPluginOpenApiPaths(permissions)); const paths: Record = {}; for (const [path, def] of Object.entries(spec.paths)) { if (allowedPaths.has(path)) paths[path] = def; } return { ...spec, info: { ...spec.info, title: title || spec.info.title, }, paths, }; } /** OpenAPI 3.0:企微「添加插件工具」可导入。须原样返回,不要套 {code,message,data}。 */ export const WECOM_PLUGIN_OPENAPI = { openapi: '3.0.3', info: { title: '杜康好客运营查询', description: '企业内部只读查询。手机号已脱敏。鉴权:Header X-Api-Key。响应除本文件外均为 { code, message, data }。', version: '1.0.0', }, servers: [ { url: 'https://api.dukanghaoke.com/api/v1/wecom/plugin', description: '生产' }, { url: 'https://api-test.dukanghaoke.com/api/v1/wecom/plugin', description: '测试' }, ], security: [{ ApiKeyAuth: [] }], components: { securitySchemes: { ApiKeyAuth: { type: 'apiKey', in: 'header', name: 'X-Api-Key', }, }, }, paths: { '/orders': listPath('查询订单', '按订单号模糊查询', '订单号,如 DK20260903xxxx'), '/users': listPath('查询用户', '按用户号或 11 位手机号查询;手机号脱敏', '用户号或手机号'), '/stores': listPath( '查询门店', '按门店名称模糊查询,返回经营数据:评分、核销笔数、累计核销好客权益、合伙人、审核状态', '门店名称关键词', ), '/store-audits': { get: { summary: '门店入驻审核列表', description: '默认 status=PENDING;可按门店名筛选', operationId: '查询门店入驻审核', parameters: [ { name: 'q', in: 'query', required: false, schema: { type: 'string' }, description: '门店名称' }, { name: 'status', in: 'query', required: false, schema: { type: 'string', enum: ['PENDING', 'APPROVED', 'REJECTED'], default: 'PENDING' }, }, ...pageParams, ], responses: { 200: { description: '审核列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } }, 401: unauthorized, }, }, }, '/store-info-audits': { get: { summary: '门店信息变更审核列表', operationId: '查询门店信息变更审核', parameters: [ { name: 'status', in: 'query', required: false, schema: { type: 'string', enum: ['PENDING', 'APPROVED', 'REJECTED'], default: 'PENDING' }, }, ...pageParams, ], responses: { 200: { description: '列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } }, 401: unauthorized, }, }, }, '/store-info-audits/{id}': { get: { summary: '门店信息变更审核对比', description: '返回变更字段 live vs proposed 对照', operationId: '查询门店信息变更详情', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], responses: { 200: { description: '详情含 diffs', content: { 'application/json': { schema: envelope({ type: 'object' }) } } }, 401: unauthorized, }, }, }, '/store-package-audits': { get: { summary: '门店套餐审核列表', operationId: '查询门店套餐审核', parameters: [ { name: 'status', in: 'query', required: false, schema: { type: 'string', enum: ['PENDING', 'APPROVED', 'REJECTED'], default: 'PENDING' }, }, ...pageParams, ], responses: { 200: { description: '列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } }, 401: unauthorized, }, }, }, '/store-package-audits/{id}': { get: { summary: '门店套餐审核对比', description: '返回 proposedPackages 与 livePackages 对照', operationId: '查询门店套餐审核详情', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], responses: { 200: { description: '详情含套餐对比', content: { 'application/json': { schema: envelope({ type: 'object' }) } } }, 401: unauthorized, }, }, }, '/partners': listPath('查询合伙人', '按姓名、公司名、手机号或 ID 搜索主账号', '合伙人关键词'), '/partners/{partnerId}/users': { get: { summary: '合伙人关联用户', operationId: '查询合伙人关联用户', parameters: [ { name: 'partnerId', in: 'path', required: true, schema: { type: 'string' } }, ...dateRangeParams, ...pageParams, ], responses: { 200: { description: '关联用户列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } }, 401: unauthorized, }, }, }, '/partners/{partnerId}/stores': { get: { summary: '合伙人名下门店', operationId: '查询合伙人门店', parameters: [ { name: 'partnerId', in: 'path', required: true, schema: { type: 'string' } }, ...dateRangeParams, ...pageParams, ], responses: { 200: { description: '门店及经营数据', content: { 'application/json': { schema: envelope({ type: 'object' }) } } }, 401: unauthorized, }, }, }, '/partners/{partnerId}/orders': { get: { summary: '合伙人相关订单', description: '佣金归属 partnerAccountIdAtPay 的订单', operationId: '查询合伙人订单', parameters: [ { name: 'partnerId', in: 'path', required: true, schema: { type: 'string' } }, ...dateRangeParams, ...pageParams, ], responses: { 200: { description: '订单列表', content: { 'application/json': { schema: envelope({ type: 'object' }) } } }, 401: unauthorized, }, }, }, '/redeems': listPath('查询核销', '按核销单号或门店名查询', '核销单号或门店名'), '/promo-codes': listPath('查询推广码', '按推广码 code 或名称查询', '推广码或名称'), '/promo-codes/{code}/stats': { get: { summary: '推广码统计', operationId: '查询推广码统计', parameters: [ { name: 'code', in: 'path', required: true, schema: { type: 'string' }, description: '推广码 code', }, ], responses: { 200: { description: '扫码/成交统计', content: { 'application/json': { schema: envelope({ type: 'object' }) } }, }, 401: unauthorized, }, }, }, '/metrics': { get: { summary: '经营指标', description: 'today=今日截至当前;daily/weekly/monthly 与企微经营报告同一口径。stats 含 users/partners/stores/orders 存量与增量;storesIncrement 与 newStores 均为新增门店数。', operationId: '查询经营指标', parameters: [ { name: 'kind', in: 'query', required: false, schema: { type: 'string', enum: ['today', 'daily', 'weekly', 'monthly'], default: 'today', }, }, ], responses: { 200: { description: '存量与新增', content: { 'application/json': { schema: envelope({ type: 'object' }) } }, }, 401: unauthorized, }, }, }, }, } as const;