191 lines
5.1 KiB
TypeScript
191 lines
5.1 KiB
TypeScript
import type { Priority } from './derive';
|
||
|
||
export type RequirementStatus = 'pending_review' | 'adopted' | 'rejected' | 'planned' | 'developing' | 'testing' | 'released' | 'closed';
|
||
export type Effort = 'S' | 'M' | 'L' | 'XL';
|
||
export type SourceType = 'customer' | 'internal' | 'operation' | 'aftersale' | 'market' | 'competitor' | 'management';
|
||
|
||
export interface DictItem {
|
||
id: string;
|
||
name: string;
|
||
createdAt: string;
|
||
}
|
||
|
||
export interface SourceTarget {
|
||
id: string;
|
||
name: string;
|
||
sourceType: SourceType;
|
||
createdAt: string;
|
||
}
|
||
|
||
export function parseSourceTargetSelection(value?: string | null): string[] {
|
||
if (!value) return [];
|
||
return uniqueNonEmptyNames(value.split(/[、,,]/));
|
||
}
|
||
|
||
export function formatSourceTargetSelection(names: string[]): string {
|
||
return uniqueNonEmptyNames(names).join('、');
|
||
}
|
||
|
||
export function filterSourceTargetsByKeyword(
|
||
targets: SourceTarget[],
|
||
sourceType: SourceType,
|
||
keyword: string,
|
||
): SourceTarget[] {
|
||
const query = keyword.trim().toLowerCase();
|
||
return targets.filter((target) => {
|
||
if (target.sourceType !== sourceType) return false;
|
||
if (!query) return true;
|
||
return target.name.toLowerCase().includes(query);
|
||
});
|
||
}
|
||
|
||
export interface RequirementProjectOption {
|
||
id: string;
|
||
name: string;
|
||
productId: string;
|
||
}
|
||
|
||
export interface RequirementVersionOption {
|
||
id: string;
|
||
name: string;
|
||
projectId: string;
|
||
}
|
||
|
||
export function filterRequirementProjectOptions(
|
||
projects: RequirementProjectOption[],
|
||
productId: string,
|
||
keyword: string,
|
||
): RequirementProjectOption[] {
|
||
if (!productId) return [];
|
||
const query = keyword.trim().toLowerCase();
|
||
return projects.filter((project) => {
|
||
if (project.productId !== productId) return false;
|
||
if (!query) return true;
|
||
return project.name.toLowerCase().includes(query);
|
||
});
|
||
}
|
||
|
||
export function filterRequirementVersionOptions(
|
||
versions: RequirementVersionOption[],
|
||
projectId: string,
|
||
keyword: string,
|
||
): RequirementVersionOption[] {
|
||
if (!projectId) return [];
|
||
const query = keyword.trim().toLowerCase();
|
||
return versions.filter((version) => {
|
||
if (version.projectId !== projectId) return false;
|
||
if (!query) return true;
|
||
return version.name.toLowerCase().includes(query);
|
||
});
|
||
}
|
||
|
||
function uniqueNonEmptyNames(names: string[]): string[] {
|
||
const seen = new Set<string>();
|
||
const out: string[] = [];
|
||
for (const name of names) {
|
||
const trimmed = name.trim();
|
||
if (!trimmed || seen.has(trimmed)) continue;
|
||
seen.add(trimmed);
|
||
out.push(trimmed);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
export interface Requirement {
|
||
id: string;
|
||
code: string;
|
||
title: string;
|
||
description: string;
|
||
productId: string;
|
||
projectId: string;
|
||
versionId?: string;
|
||
sourceType: SourceType;
|
||
sourceTarget: string;
|
||
platforms: string[];
|
||
typeId: string;
|
||
status: RequirementStatus;
|
||
priority: Priority;
|
||
effort: Effort;
|
||
currentStage?: string;
|
||
productOwner?: string;
|
||
creator: string;
|
||
createdAt: string;
|
||
parentId?: string;
|
||
addedToVersionBy?: string;
|
||
reqType?: 'original' | 'change';
|
||
changeReason?: ChangeReason;
|
||
changeBy?: string;
|
||
}
|
||
|
||
export type ChangeReason = 'misunderstanding' | 'feedback_rework' | 'difficulty_exceeded' | 'upstream_delay';
|
||
|
||
export const CHANGE_REASON_LABEL: Record<ChangeReason, string> = {
|
||
misunderstanding: '需求理解偏差导致返工',
|
||
feedback_rework: '反馈导致返工',
|
||
difficulty_exceeded: '实现难度超预期',
|
||
upstream_delay: '上游交付延误',
|
||
};
|
||
|
||
export const SOURCE_TYPE_LABEL: Record<SourceType, string> = {
|
||
customer: '客户',
|
||
internal: '内部',
|
||
operation: '运营',
|
||
aftersale: '售后',
|
||
market: '市场',
|
||
competitor: '竞品',
|
||
management: '管理层',
|
||
};
|
||
|
||
export const SOURCE_TARGET_LABEL: Record<SourceType, string> = {
|
||
customer: '来源客户',
|
||
internal: '提出部门',
|
||
operation: '提出部门',
|
||
aftersale: '提出部门',
|
||
market: '来源渠道',
|
||
competitor: '竞品名称',
|
||
management: '提出人',
|
||
};
|
||
|
||
export const REQ_STATUS_LABEL: Record<RequirementStatus, string> = {
|
||
pending_review: '待评审',
|
||
adopted: '已采纳',
|
||
rejected: '已拒绝',
|
||
planned: '已规划',
|
||
developing: '开发中',
|
||
testing: '测试中',
|
||
released: '已上线',
|
||
closed: '已关闭',
|
||
};
|
||
|
||
export const REQ_STATUS_COLOR: Record<RequirementStatus, string> = {
|
||
pending_review: 'bg-zinc-100 text-zinc-700 border border-zinc-200',
|
||
adopted: 'bg-emerald-50 text-emerald-700 border border-emerald-200',
|
||
rejected: 'bg-red-50 text-red-600 border border-red-200',
|
||
planned: 'bg-blue-50 text-blue-700 border border-blue-200',
|
||
developing: 'bg-indigo-50 text-indigo-700 border border-indigo-200',
|
||
testing: 'bg-purple-50 text-purple-700 border border-purple-200',
|
||
released: 'bg-emerald-50 text-emerald-700 border border-emerald-200',
|
||
closed: 'bg-zinc-50 text-zinc-500 border border-zinc-200',
|
||
};
|
||
|
||
export const EFFORT_LABEL: Record<Effort, string> = {
|
||
S: 'S (1-2天)',
|
||
M: 'M (3-5天)',
|
||
L: 'L (5-10天)',
|
||
XL: 'XL (10+天)',
|
||
};
|
||
|
||
export const EFFORT_SHORT: Record<Effort, string> = {
|
||
S: 'S',
|
||
M: 'M',
|
||
L: 'L',
|
||
XL: 'XL',
|
||
};
|
||
|
||
export const EFFORT_COLOR: Record<Effort, string> = {
|
||
S: 'bg-emerald-50 text-emerald-700',
|
||
M: 'bg-blue-50 text-blue-700',
|
||
L: 'bg-orange-50 text-orange-700',
|
||
XL: 'bg-red-50 text-red-700',
|
||
};
|