diff --git a/apps/admin-web/src/pages/DevPlanTasksPage.tsx b/apps/admin-web/src/pages/DevPlanTasksPage.tsx index 64cce71..dfeac61 100644 --- a/apps/admin-web/src/pages/DevPlanTasksPage.tsx +++ b/apps/admin-web/src/pages/DevPlanTasksPage.tsx @@ -50,6 +50,7 @@ const STATUS_COLOR: Record = { IN_PROGRESS: 'orange', DEVELOPED: 'blue', RELEASED: 'green', + STOPPED: 'red', }; const DEFAULT_DISPATCH_SUPPLEMENT = '请按以下任务进入开发流程'; diff --git a/docs/杜康好客-v3编码手册.md b/docs/杜康好客-v3编码手册.md index 0d73a3b..045d614 100644 --- a/docs/杜康好客-v3编码手册.md +++ b/docs/杜康好客-v3编码手册.md @@ -64,7 +64,7 @@ C 端购酒核销 · 门店扫码核销+打款 · 合伙人拓店履约 · WebAd **主链路 15 项**:登录、4 SKU、起购、支付+权益、双通道核销、payout、关店不可见、拓店审核、配送完成、退款、T+1/T+30… **后台 8 项**:商品/门店/订单/权益/核销/工单/日志/财务。 -**开发计划任务**:状态 `TODO` 待开发 / `IN_PROGRESS` 开发中 / `DEVELOPED` 已开发 / `RELEASED` 已上线;任务列表「来源工单」链到 `/tickets/support?id=`。 +**开发计划任务**:状态 `TODO` 待开发 / `IN_PROGRESS` 开发中 / `DEVELOPED` 已开发 / `RELEASED` 已上线 / `STOPPED` 已停止;版本状态含 `STOPPED` 已停止,关联任务随版本同步(仅当全部关联版本已停止时任务才为已停止)。任务列表「来源工单」链到 `/tickets/support?id=`。 **财务账单日**:出账当天北京日历日(门店/酒厂 `billDate`;合伙人/物流账期按北京自然月)。禁止 `toISOString().slice(0,10)` 或服务器本地 `Date` 午夜当账单日。酒厂应付为 0 仍出账(展示无需打款)。 ## 6. 技术债(摘要) diff --git a/packages/domain/src/dev-plan.test.ts b/packages/domain/src/dev-plan.test.ts index a14c7b6..b2849c3 100644 --- a/packages/domain/src/dev-plan.test.ts +++ b/packages/domain/src/dev-plan.test.ts @@ -10,6 +10,7 @@ describe('taskStatusFromVersionStatus', () => { expect(taskStatusFromVersionStatus('IN_PROGRESS')).toBe('IN_PROGRESS'); expect(taskStatusFromVersionStatus('TESTING')).toBe('DEVELOPED'); expect(taskStatusFromVersionStatus('RELEASED')).toBe('RELEASED'); + expect(taskStatusFromVersionStatus('STOPPED')).toBe('STOPPED'); }); }); @@ -22,4 +23,11 @@ describe('resolveTaskStatusFromVersions', () => { expect(resolveTaskStatusFromVersions(['PENDING', 'TESTING'])).toBe('DEVELOPED'); expect(resolveTaskStatusFromVersions(['IN_PROGRESS', 'RELEASED'])).toBe('RELEASED'); }); + + it('syncs STOPPED only when every linked version is stopped', () => { + expect(resolveTaskStatusFromVersions(['STOPPED'])).toBe('STOPPED'); + expect(resolveTaskStatusFromVersions(['STOPPED', 'STOPPED'])).toBe('STOPPED'); + expect(resolveTaskStatusFromVersions(['STOPPED', 'IN_PROGRESS'])).toBe('IN_PROGRESS'); + expect(resolveTaskStatusFromVersions(['STOPPED', 'RELEASED'])).toBe('RELEASED'); + }); }); diff --git a/packages/domain/src/dev-plan.ts b/packages/domain/src/dev-plan.ts index bbd270e..0e6435c 100644 --- a/packages/domain/src/dev-plan.ts +++ b/packages/domain/src/dev-plan.ts @@ -1,5 +1,5 @@ -export type DevPlanTaskStatus = 'TODO' | 'IN_PROGRESS' | 'DEVELOPED' | 'RELEASED'; -export type DevPlanVersionStatus = 'PENDING' | 'IN_PROGRESS' | 'TESTING' | 'RELEASED'; +export type DevPlanTaskStatus = 'TODO' | 'IN_PROGRESS' | 'DEVELOPED' | 'RELEASED' | 'STOPPED'; +export type DevPlanVersionStatus = 'PENDING' | 'IN_PROGRESS' | 'TESTING' | 'RELEASED' | 'STOPPED'; export function computeDurationMinutes( devStartedAt: Date | null | undefined, @@ -63,13 +63,14 @@ export function taskCompletedAtOnStatus( } const TASK_STATUS_RANK: Record = { + STOPPED: -1, TODO: 0, IN_PROGRESS: 1, DEVELOPED: 2, RELEASED: 3, }; -/** 版本状态 → 任务状态:待开始/开发中/测试中/已发布 ↔ 待处理/进行中/已开发/已发布 */ +/** 版本状态 → 任务状态:待开始/开发中/测试中/已发布/已停止 ↔ 待处理/进行中/已开发/已发布/已停止 */ export function taskStatusFromVersionStatus(status: DevPlanVersionStatus): DevPlanTaskStatus { switch (status) { case 'PENDING': @@ -80,15 +81,19 @@ export function taskStatusFromVersionStatus(status: DevPlanVersionStatus): DevPl return 'DEVELOPED'; case 'RELEASED': return 'RELEASED'; + case 'STOPPED': + return 'STOPPED'; } } -/** 任务挂多个版本时取进度最高的对应状态 */ +/** 任务挂多个版本时取进度最高的对应状态;仅当全部版本已停止时任务才为已停止 */ export function resolveTaskStatusFromVersions( versionStatuses: DevPlanVersionStatus[], ): DevPlanTaskStatus | null { if (!versionStatuses.length) return null; - return versionStatuses + const active = versionStatuses.filter((s) => s !== 'STOPPED'); + if (!active.length) return 'STOPPED'; + return active .map(taskStatusFromVersionStatus) .reduce((highest, current) => TASK_STATUS_RANK[current] > TASK_STATUS_RANK[highest] ? current : highest, diff --git a/packages/shared-types/src/dev-plan.ts b/packages/shared-types/src/dev-plan.ts index 773b06c..3d1a136 100644 --- a/packages/shared-types/src/dev-plan.ts +++ b/packages/shared-types/src/dev-plan.ts @@ -22,11 +22,11 @@ export const DEV_PLAN_TASK_TYPE_LABELS: Record = { /** 开发计划任务状态 */ -export type DevPlanTaskStatusDto = 'TODO' | 'IN_PROGRESS' | 'DEVELOPED' | 'RELEASED'; +export type DevPlanTaskStatusDto = 'TODO' | 'IN_PROGRESS' | 'DEVELOPED' | 'RELEASED' | 'STOPPED'; -export const DEV_PLAN_TASK_STATUSES = ['TODO', 'IN_PROGRESS', 'DEVELOPED', 'RELEASED'] as const; +export const DEV_PLAN_TASK_STATUSES = ['TODO', 'IN_PROGRESS', 'DEVELOPED', 'RELEASED', 'STOPPED'] as const; @@ -40,6 +40,8 @@ export const DEV_PLAN_TASK_STATUS_LABELS: Record = RELEASED: '已上线', + STOPPED: '已停止', + }; diff --git a/server/dukang-api/prisma/migrate-dev-plan-stopped.sql b/server/dukang-api/prisma/migrate-dev-plan-stopped.sql new file mode 100644 index 0000000..cece2a2 --- /dev/null +++ b/server/dukang-api/prisma/migrate-dev-plan-stopped.sql @@ -0,0 +1,6 @@ +-- 开发计划版本/任务增加「已停止」状态 +ALTER TABLE `dev_plan_version` + MODIFY COLUMN `status` ENUM('PENDING', 'IN_PROGRESS', 'TESTING', 'RELEASED', 'STOPPED') NOT NULL DEFAULT 'PENDING'; + +ALTER TABLE `dev_plan_task` + MODIFY COLUMN `status` ENUM('TODO', 'IN_PROGRESS', 'DEVELOPED', 'RELEASED', 'STOPPED') NOT NULL DEFAULT 'TODO'; diff --git a/server/dukang-api/prisma/schema.prisma b/server/dukang-api/prisma/schema.prisma index 3697518..6a92e58 100644 --- a/server/dukang-api/prisma/schema.prisma +++ b/server/dukang-api/prisma/schema.prisma @@ -129,6 +129,7 @@ enum DevPlanTaskStatus { IN_PROGRESS DEVELOPED RELEASED + STOPPED } enum DevPlanVersionStatus { @@ -136,6 +137,7 @@ enum DevPlanVersionStatus { IN_PROGRESS TESTING RELEASED + STOPPED } enum InvoiceTitleType { diff --git a/server/dukang-api/src/integrations/wecom/wecom-bot-ai.service.ts b/server/dukang-api/src/integrations/wecom/wecom-bot-ai.service.ts index e17e009..f6b5102 100644 --- a/server/dukang-api/src/integrations/wecom/wecom-bot-ai.service.ts +++ b/server/dukang-api/src/integrations/wecom/wecom-bot-ai.service.ts @@ -28,9 +28,9 @@ const TOOL_INSTRUCTION = [ '- finance_payout · finance_withdrawal', '- dev_plan_tasks [状态] · dev_plan_versions [版本号]', '- dev_plan_task_create <描述>', - '- dev_plan_task_update_status <任务编号> ', + '- dev_plan_task_update_status <任务编号> ', '- dev_plan_version_create <版本号>', - '- dev_plan_version_update_status <版本号> ', + '- dev_plan_version_update_status <版本号> ', '- dev_plan_version_link_tasks <版本号> <任务编号1,任务编号2>', '- server_logs [关键词] · handbook_read [关键词]', '禁止输出 JSON、api 字段、「请稍等正在检索」等占位话术。', diff --git a/server/dukang-api/src/integrations/wecom/wecom-bot-capability.service.ts b/server/dukang-api/src/integrations/wecom/wecom-bot-capability.service.ts index f1ed4de..da632a0 100644 --- a/server/dukang-api/src/integrations/wecom/wecom-bot-capability.service.ts +++ b/server/dukang-api/src/integrations/wecom/wecom-bot-capability.service.ts @@ -117,7 +117,7 @@ export class WecomBotCapabilityService { ) { const cmds: string[] = []; if (wecomBotHasPermission(bot, 'dev_plan.task.create')) cmds.push('`新建任务 BUG|需求|优化 <描述>`'); - if (wecomBotHasPermission(bot, 'dev_plan.task.update_status')) cmds.push('`修改任务 <编号> TODO|IN_PROGRESS|DEVELOPED|RELEASED`'); + if (wecomBotHasPermission(bot, 'dev_plan.task.update_status')) cmds.push('`修改任务 <编号> TODO|IN_PROGRESS|DEVELOPED|RELEASED|STOPPED`'); lines.push('**开发任务管理**', cmds.join(' · '), ''); } if ( @@ -1234,10 +1234,10 @@ export class WecomBotCapabilityService { private async updateDevPlanTaskStatus(bot: WecomBotRuntimeConfig, wecomUserId: string, input: string) { this.requirePerm(bot, 'dev_plan.task.update_status'); const trimmed = input.trim(); - if (!trimmed) return '请提供任务编号和状态,格式:`修改任务 STxxx TODO|IN_PROGRESS|DEVELOPED|RELEASED`'; + if (!trimmed) return '请提供任务编号和状态,格式:`修改任务 STxxx TODO|IN_PROGRESS|DEVELOPED|RELEASED|STOPPED`'; const parts = trimmed.split(/\s+/).filter(Boolean); - if (parts.length < 2) return '格式:`修改任务 <任务编号> <状态>`\n状态:TODO(待开发)· IN_PROGRESS(开发中)· DEVELOPED(已开发)· RELEASED(已上线)'; + if (parts.length < 2) return '格式:`修改任务 <任务编号> <状态>`\n状态:TODO(待开发)· IN_PROGRESS(开发中)· DEVELOPED(已开发)· RELEASED(已上线)· STOPPED(已停止)'; const [taskNo, rawStatus] = parts; const STATUS_ALIAS: Record = { @@ -1245,9 +1245,10 @@ export class WecomBotCapabilityService { IN_PROGRESS: 'IN_PROGRESS', in_progress: 'IN_PROGRESS', 开发中: 'IN_PROGRESS', DEVELOPED: 'DEVELOPED', developed: 'DEVELOPED', 已开发: 'DEVELOPED', RELEASED: 'RELEASED', released: 'RELEASED', 已发布: 'RELEASED', 已上线: 'RELEASED', + STOPPED: 'STOPPED', stopped: 'STOPPED', STOPED: 'STOPPED', stoped: 'STOPPED', 已停止: 'STOPPED', }; const status = STATUS_ALIAS[rawStatus]; - if (!status) return `未知状态「${rawStatus}」。可用:TODO(待开发)/ IN_PROGRESS(开发中)/ DEVELOPED(已开发)/ RELEASED(已上线)`; + if (!status) return `未知状态「${rawStatus}」。可用:TODO(待开发)/ IN_PROGRESS(开发中)/ DEVELOPED(已开发)/ RELEASED(已上线)/ STOPPED(已停止)`; const row = await this.prisma.devPlanTask.findFirst({ where: { taskNo } }); if (!row) return `未找到任务「${taskNo}」`; @@ -1271,7 +1272,7 @@ export class WecomBotCapabilityService { if (!trimmed) return '请提供版本号和状态,格式:`修改版本 v3.6.0 <状态>`'; const parts = trimmed.split(/\s+/).filter(Boolean); - if (parts.length < 2) return '格式:`修改版本 <版本号> <状态>`\n状态:PENDING · IN_PROGRESS · TESTING · RELEASED'; + if (parts.length < 2) return '格式:`修改版本 <版本号> <状态>`\n状态:PENDING · IN_PROGRESS · TESTING · RELEASED · STOPPED'; const [versionNo, rawStatus] = parts; const STATUS_ALIAS: Record = { @@ -1279,9 +1280,10 @@ export class WecomBotCapabilityService { IN_PROGRESS: 'IN_PROGRESS', in_progress: 'IN_PROGRESS', 开发中: 'IN_PROGRESS', TESTING: 'TESTING', testing: 'TESTING', 测试中: 'TESTING', RELEASED: 'RELEASED', released: 'RELEASED', 已发布: 'RELEASED', + STOPPED: 'STOPPED', stopped: 'STOPPED', STOPED: 'STOPPED', stoped: 'STOPPED', 已停止: 'STOPPED', }; const status = STATUS_ALIAS[rawStatus]; - if (!status) return `未知状态「${rawStatus}」。可用:PENDING / IN_PROGRESS / TESTING / RELEASED`; + if (!status) return `未知状态「${rawStatus}」。可用:PENDING / IN_PROGRESS / TESTING / RELEASED / STOPPED`; const row = await this.prisma.devPlanVersion.findUnique({ where: { versionNo } }); if (!row) return `未找到版本「${versionNo}」`; diff --git a/server/dukang-api/src/modules/dev-plan/dev-plan.service.ts b/server/dukang-api/src/modules/dev-plan/dev-plan.service.ts index f56cf8f..f17fba7 100644 --- a/server/dukang-api/src/modules/dev-plan/dev-plan.service.ts +++ b/server/dukang-api/src/modules/dev-plan/dev-plan.service.ts @@ -826,9 +826,9 @@ export class DevPlanService { versionStatusTimestamps( - existing.status as 'PENDING' | 'IN_PROGRESS' | 'TESTING' | 'RELEASED', + existing.status, - dto.status as 'PENDING' | 'IN_PROGRESS' | 'TESTING' | 'RELEASED', + dto.status as DevPlanVersionStatus, {