Dev #59
@@ -50,6 +50,7 @@ const STATUS_COLOR: Record<DevPlanTaskStatusDto, string> = {
|
|||||||
IN_PROGRESS: 'orange',
|
IN_PROGRESS: 'orange',
|
||||||
DEVELOPED: 'blue',
|
DEVELOPED: 'blue',
|
||||||
RELEASED: 'green',
|
RELEASED: 'green',
|
||||||
|
STOPPED: 'red',
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_DISPATCH_SUPPLEMENT = '请按以下任务进入开发流程';
|
const DEFAULT_DISPATCH_SUPPLEMENT = '请按以下任务进入开发流程';
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const STATUS_COLOR: Record<DevPlanVersionStatusDto, string> = {
|
|||||||
IN_PROGRESS: 'processing',
|
IN_PROGRESS: 'processing',
|
||||||
TESTING: 'purple',
|
TESTING: 'purple',
|
||||||
RELEASED: 'green',
|
RELEASED: 'green',
|
||||||
|
STOPPED: 'red',
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatDuration(minutes?: number | null) {
|
function formatDuration(minutes?: number | null) {
|
||||||
|
|||||||
+1
-1
@@ -64,7 +64,7 @@ C 端购酒核销 · 门店扫码核销+打款 · 合伙人拓店履约 · WebAd
|
|||||||
|
|
||||||
**主链路 15 项**:登录、4 SKU、起购、支付+权益、双通道核销、payout、关店不可见、拓店审核、配送完成、退款、T+1/T+30…
|
**主链路 15 项**:登录、4 SKU、起购、支付+权益、双通道核销、payout、关店不可见、拓店审核、配送完成、退款、T+1/T+30…
|
||||||
**后台 8 项**:商品/门店/订单/权益/核销/工单/日志/财务。
|
**后台 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 仍出账(展示无需打款)。
|
**财务账单日**:出账当天北京日历日(门店/酒厂 `billDate`;合伙人/物流账期按北京自然月)。禁止 `toISOString().slice(0,10)` 或服务器本地 `Date` 午夜当账单日。酒厂应付为 0 仍出账(展示无需打款)。
|
||||||
|
|
||||||
## 6. 技术债(摘要)
|
## 6. 技术债(摘要)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ describe('taskStatusFromVersionStatus', () => {
|
|||||||
expect(taskStatusFromVersionStatus('IN_PROGRESS')).toBe('IN_PROGRESS');
|
expect(taskStatusFromVersionStatus('IN_PROGRESS')).toBe('IN_PROGRESS');
|
||||||
expect(taskStatusFromVersionStatus('TESTING')).toBe('DEVELOPED');
|
expect(taskStatusFromVersionStatus('TESTING')).toBe('DEVELOPED');
|
||||||
expect(taskStatusFromVersionStatus('RELEASED')).toBe('RELEASED');
|
expect(taskStatusFromVersionStatus('RELEASED')).toBe('RELEASED');
|
||||||
|
expect(taskStatusFromVersionStatus('STOPPED')).toBe('STOPPED');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -22,4 +23,11 @@ describe('resolveTaskStatusFromVersions', () => {
|
|||||||
expect(resolveTaskStatusFromVersions(['PENDING', 'TESTING'])).toBe('DEVELOPED');
|
expect(resolveTaskStatusFromVersions(['PENDING', 'TESTING'])).toBe('DEVELOPED');
|
||||||
expect(resolveTaskStatusFromVersions(['IN_PROGRESS', 'RELEASED'])).toBe('RELEASED');
|
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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export type DevPlanTaskStatus = 'TODO' | 'IN_PROGRESS' | 'DEVELOPED' | 'RELEASED';
|
export type DevPlanTaskStatus = 'TODO' | 'IN_PROGRESS' | 'DEVELOPED' | 'RELEASED' | 'STOPPED';
|
||||||
export type DevPlanVersionStatus = 'PENDING' | 'IN_PROGRESS' | 'TESTING' | 'RELEASED';
|
export type DevPlanVersionStatus = 'PENDING' | 'IN_PROGRESS' | 'TESTING' | 'RELEASED' | 'STOPPED';
|
||||||
|
|
||||||
export function computeDurationMinutes(
|
export function computeDurationMinutes(
|
||||||
devStartedAt: Date | null | undefined,
|
devStartedAt: Date | null | undefined,
|
||||||
@@ -63,13 +63,14 @@ export function taskCompletedAtOnStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TASK_STATUS_RANK: Record<DevPlanTaskStatus, number> = {
|
const TASK_STATUS_RANK: Record<DevPlanTaskStatus, number> = {
|
||||||
|
STOPPED: -1,
|
||||||
TODO: 0,
|
TODO: 0,
|
||||||
IN_PROGRESS: 1,
|
IN_PROGRESS: 1,
|
||||||
DEVELOPED: 2,
|
DEVELOPED: 2,
|
||||||
RELEASED: 3,
|
RELEASED: 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 版本状态 → 任务状态:待开始/开发中/测试中/已发布 ↔ 待处理/进行中/已开发/已发布 */
|
/** 版本状态 → 任务状态:待开始/开发中/测试中/已发布/已停止 ↔ 待处理/进行中/已开发/已发布/已停止 */
|
||||||
export function taskStatusFromVersionStatus(status: DevPlanVersionStatus): DevPlanTaskStatus {
|
export function taskStatusFromVersionStatus(status: DevPlanVersionStatus): DevPlanTaskStatus {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'PENDING':
|
case 'PENDING':
|
||||||
@@ -80,15 +81,19 @@ export function taskStatusFromVersionStatus(status: DevPlanVersionStatus): DevPl
|
|||||||
return 'DEVELOPED';
|
return 'DEVELOPED';
|
||||||
case 'RELEASED':
|
case 'RELEASED':
|
||||||
return 'RELEASED';
|
return 'RELEASED';
|
||||||
|
case 'STOPPED':
|
||||||
|
return 'STOPPED';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 任务挂多个版本时取进度最高的对应状态 */
|
/** 任务挂多个版本时取进度最高的对应状态;仅当全部版本已停止时任务才为已停止 */
|
||||||
export function resolveTaskStatusFromVersions(
|
export function resolveTaskStatusFromVersions(
|
||||||
versionStatuses: DevPlanVersionStatus[],
|
versionStatuses: DevPlanVersionStatus[],
|
||||||
): DevPlanTaskStatus | null {
|
): DevPlanTaskStatus | null {
|
||||||
if (!versionStatuses.length) return null;
|
if (!versionStatuses.length) return null;
|
||||||
return versionStatuses
|
const active = versionStatuses.filter((s) => s !== 'STOPPED');
|
||||||
|
if (!active.length) return 'STOPPED';
|
||||||
|
return active
|
||||||
.map(taskStatusFromVersionStatus)
|
.map(taskStatusFromVersionStatus)
|
||||||
.reduce((highest, current) =>
|
.reduce((highest, current) =>
|
||||||
TASK_STATUS_RANK[current] > TASK_STATUS_RANK[highest] ? current : highest,
|
TASK_STATUS_RANK[current] > TASK_STATUS_RANK[highest] ? current : highest,
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ export const DEV_PLAN_TASK_TYPE_LABELS: Record<DevPlanTaskTypeDto, string> = {
|
|||||||
|
|
||||||
/** 开发计划任务状态 */
|
/** 开发计划任务状态 */
|
||||||
|
|
||||||
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,17 +40,19 @@ export const DEV_PLAN_TASK_STATUS_LABELS: Record<DevPlanTaskStatusDto, string> =
|
|||||||
|
|
||||||
RELEASED: '已上线',
|
RELEASED: '已上线',
|
||||||
|
|
||||||
|
STOPPED: '已停止',
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** 开发计划版本状态 */
|
/** 开发计划版本状态 */
|
||||||
|
|
||||||
export type DevPlanVersionStatusDto = 'PENDING' | 'IN_PROGRESS' | 'TESTING' | 'RELEASED';
|
export type DevPlanVersionStatusDto = 'PENDING' | 'IN_PROGRESS' | 'TESTING' | 'RELEASED' | 'STOPPED';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const DEV_PLAN_VERSION_STATUSES = ['PENDING', 'IN_PROGRESS', 'TESTING', 'RELEASED'] as const;
|
export const DEV_PLAN_VERSION_STATUSES = ['PENDING', 'IN_PROGRESS', 'TESTING', 'RELEASED', 'STOPPED'] as const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -64,6 +66,8 @@ export const DEV_PLAN_VERSION_STATUS_LABELS: Record<DevPlanVersionStatusDto, str
|
|||||||
|
|
||||||
RELEASED: '已上线',
|
RELEASED: '已上线',
|
||||||
|
|
||||||
|
STOPPED: '已停止',
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -129,6 +129,7 @@ enum DevPlanTaskStatus {
|
|||||||
IN_PROGRESS
|
IN_PROGRESS
|
||||||
DEVELOPED
|
DEVELOPED
|
||||||
RELEASED
|
RELEASED
|
||||||
|
STOPPED
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DevPlanVersionStatus {
|
enum DevPlanVersionStatus {
|
||||||
@@ -136,6 +137,7 @@ enum DevPlanVersionStatus {
|
|||||||
IN_PROGRESS
|
IN_PROGRESS
|
||||||
TESTING
|
TESTING
|
||||||
RELEASED
|
RELEASED
|
||||||
|
STOPPED
|
||||||
}
|
}
|
||||||
|
|
||||||
enum InvoiceTitleType {
|
enum InvoiceTitleType {
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ const TOOL_INSTRUCTION = [
|
|||||||
'- finance_payout · finance_withdrawal',
|
'- finance_payout · finance_withdrawal',
|
||||||
'- dev_plan_tasks [状态] · dev_plan_versions [版本号]',
|
'- dev_plan_tasks [状态] · dev_plan_versions [版本号]',
|
||||||
'- dev_plan_task_create <BUG|REQUIREMENT|OPTIMIZATION> <描述>',
|
'- dev_plan_task_create <BUG|REQUIREMENT|OPTIMIZATION> <描述>',
|
||||||
'- dev_plan_task_update_status <任务编号> <TODO|IN_PROGRESS|DEVELOPED|RELEASED>',
|
'- dev_plan_task_update_status <任务编号> <TODO|IN_PROGRESS|DEVELOPED|RELEASED|STOPPED>',
|
||||||
'- dev_plan_version_create <版本号>',
|
'- dev_plan_version_create <版本号>',
|
||||||
'- dev_plan_version_update_status <版本号> <PENDING|IN_PROGRESS|TESTING|RELEASED>',
|
'- dev_plan_version_update_status <版本号> <PENDING|IN_PROGRESS|TESTING|RELEASED|STOPPED>',
|
||||||
'- dev_plan_version_link_tasks <版本号> <任务编号1,任务编号2>',
|
'- dev_plan_version_link_tasks <版本号> <任务编号1,任务编号2>',
|
||||||
'- server_logs [关键词] · handbook_read [关键词]',
|
'- server_logs [关键词] · handbook_read [关键词]',
|
||||||
'禁止输出 JSON、api 字段、「请稍等正在检索」等占位话术。',
|
'禁止输出 JSON、api 字段、「请稍等正在检索」等占位话术。',
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export class WecomBotCapabilityService {
|
|||||||
) {
|
) {
|
||||||
const cmds: string[] = [];
|
const cmds: string[] = [];
|
||||||
if (wecomBotHasPermission(bot, 'dev_plan.task.create')) cmds.push('`新建任务 BUG|需求|优化 <描述>`');
|
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(' · '), '');
|
lines.push('**开发任务管理**', cmds.join(' · '), '');
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -1234,10 +1234,10 @@ export class WecomBotCapabilityService {
|
|||||||
private async updateDevPlanTaskStatus(bot: WecomBotRuntimeConfig, wecomUserId: string, input: string) {
|
private async updateDevPlanTaskStatus(bot: WecomBotRuntimeConfig, wecomUserId: string, input: string) {
|
||||||
this.requirePerm(bot, 'dev_plan.task.update_status');
|
this.requirePerm(bot, 'dev_plan.task.update_status');
|
||||||
const trimmed = input.trim();
|
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);
|
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 [taskNo, rawStatus] = parts;
|
||||||
const STATUS_ALIAS: Record<string, string> = {
|
const STATUS_ALIAS: Record<string, string> = {
|
||||||
@@ -1245,9 +1245,10 @@ export class WecomBotCapabilityService {
|
|||||||
IN_PROGRESS: 'IN_PROGRESS', in_progress: 'IN_PROGRESS', 开发中: 'IN_PROGRESS',
|
IN_PROGRESS: 'IN_PROGRESS', in_progress: 'IN_PROGRESS', 开发中: 'IN_PROGRESS',
|
||||||
DEVELOPED: 'DEVELOPED', developed: 'DEVELOPED', 已开发: 'DEVELOPED',
|
DEVELOPED: 'DEVELOPED', developed: 'DEVELOPED', 已开发: 'DEVELOPED',
|
||||||
RELEASED: 'RELEASED', released: 'RELEASED', 已发布: 'RELEASED', 已上线: 'RELEASED',
|
RELEASED: 'RELEASED', released: 'RELEASED', 已发布: 'RELEASED', 已上线: 'RELEASED',
|
||||||
|
STOPPED: 'STOPPED', stopped: 'STOPPED', STOPED: 'STOPPED', stoped: 'STOPPED', 已停止: 'STOPPED',
|
||||||
};
|
};
|
||||||
const status = STATUS_ALIAS[rawStatus];
|
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 } });
|
const row = await this.prisma.devPlanTask.findFirst({ where: { taskNo } });
|
||||||
if (!row) return `未找到任务「${taskNo}」`;
|
if (!row) return `未找到任务「${taskNo}」`;
|
||||||
@@ -1271,7 +1272,7 @@ export class WecomBotCapabilityService {
|
|||||||
if (!trimmed) return '请提供版本号和状态,格式:`修改版本 v3.6.0 <状态>`';
|
if (!trimmed) return '请提供版本号和状态,格式:`修改版本 v3.6.0 <状态>`';
|
||||||
|
|
||||||
const parts = trimmed.split(/\s+/).filter(Boolean);
|
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 [versionNo, rawStatus] = parts;
|
||||||
const STATUS_ALIAS: Record<string, string> = {
|
const STATUS_ALIAS: Record<string, string> = {
|
||||||
@@ -1279,9 +1280,10 @@ export class WecomBotCapabilityService {
|
|||||||
IN_PROGRESS: 'IN_PROGRESS', in_progress: 'IN_PROGRESS', 开发中: 'IN_PROGRESS',
|
IN_PROGRESS: 'IN_PROGRESS', in_progress: 'IN_PROGRESS', 开发中: 'IN_PROGRESS',
|
||||||
TESTING: 'TESTING', testing: 'TESTING', 测试中: 'TESTING',
|
TESTING: 'TESTING', testing: 'TESTING', 测试中: 'TESTING',
|
||||||
RELEASED: 'RELEASED', released: 'RELEASED', 已发布: 'RELEASED',
|
RELEASED: 'RELEASED', released: 'RELEASED', 已发布: 'RELEASED',
|
||||||
|
STOPPED: 'STOPPED', stopped: 'STOPPED', STOPED: 'STOPPED', stoped: 'STOPPED', 已停止: 'STOPPED',
|
||||||
};
|
};
|
||||||
const status = STATUS_ALIAS[rawStatus];
|
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 } });
|
const row = await this.prisma.devPlanVersion.findUnique({ where: { versionNo } });
|
||||||
if (!row) return `未找到版本「${versionNo}」`;
|
if (!row) return `未找到版本「${versionNo}」`;
|
||||||
|
|||||||
@@ -826,9 +826,9 @@ export class DevPlanService {
|
|||||||
|
|
||||||
versionStatusTimestamps(
|
versionStatusTimestamps(
|
||||||
|
|
||||||
existing.status as 'PENDING' | 'IN_PROGRESS' | 'TESTING' | 'RELEASED',
|
existing.status,
|
||||||
|
|
||||||
dto.status as 'PENDING' | 'IN_PROGRESS' | 'TESTING' | 'RELEASED',
|
dto.status as DevPlanVersionStatus,
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user