feat(v2.4): 完成领域主写迁移

This commit is contained in:
2026-07-08 15:41:10 +08:00
parent 2358dbd0b5
commit da71128ccd
42 changed files with 1410 additions and 81 deletions

View File

@@ -2,10 +2,14 @@ import { api } from './api';
import type { Bug, BugSeverity, BugStatus } from './bug';
import type { DevTask, DevTaskStatus, Reference } from './dev-task';
import type { Priority } from './derive';
import type { Member } from './members';
import type { OvertimeRecord } from './overtime';
import type { Requirement, RequirementStatus, SourceType } from './requirement';
import type { TaskCategory } from './task-category';
import type { TaskWorklog } from './task-worklog';
import type { TestCase, TestCaseStatus } from './test-case';
import type { VersionPlan, VersionPlanLog, VersionPlanRequirementCoverage } from './version-plan';
import type { WorkActivity, WorkActivityCategory, WorkActivitySourceType } from './work-activity';
import type { WorkActivity, WorkActivityCategory, WorkActivityDraft, WorkActivitySourceType } from './work-activity';
export interface RootProject {
id: string;
@@ -254,6 +258,51 @@ interface DomainWorkActivityRow {
occurredAt?: string | Date | null;
}
interface DomainMemberRow {
id: string;
name: string;
username?: string | null;
departmentId?: string | null;
roleId?: string | null;
phone?: string | null;
email?: string | null;
password?: string | null;
isSystem?: boolean | null;
createdAt?: string | Date | null;
}
interface DomainTaskCategoryRow {
id: string;
name: string;
code?: string | null;
group: string;
isSystem?: boolean | null;
createdAt?: string | Date | null;
}
interface DomainTaskWorklogRow {
id: string;
userId?: string | null;
sourceId: string;
workDate?: string | Date | null;
hours?: number | null;
content?: string | null;
createdAt?: string | Date | null;
}
interface DomainOvertimeRow {
id: string;
productId?: string | null;
projectId?: string | null;
versionId?: string | null;
userId?: string | null;
reason?: string | null;
startAt?: string | Date | null;
endAt?: string | Date | null;
hours?: number | null;
createdAt?: string | Date | null;
}
interface DomainMutationResponse<T> {
item: T;
activities?: DomainWorkActivityRow[];
@@ -436,6 +485,83 @@ export async function deleteBugByVersionId(versionId: string, bugId: string): Pr
await api.delete(`/versions/${versionId}/bugs/${bugId}`);
}
export async function listMembersDomain(): Promise<Member[]> {
const rows = await api.get<DomainMemberRow[]>('/members');
return rows.map(normalizeMember);
}
export async function createMemberDomain(data: Partial<Member>, _existing?: Member[]): Promise<Member> {
return normalizeMember(await api.post<DomainMemberRow>('/members', toMemberPayload(data)));
}
export async function updateMemberDomain(memberId: string, data: Partial<Member>): Promise<Member> {
return normalizeMember(await api.patch<DomainMemberRow>(`/members/${memberId}`, toMemberPayload(data)));
}
export async function deleteMemberDomain(memberId: string): Promise<void> {
await api.delete(`/members/${memberId}`);
}
export async function listTaskCategoriesDomain(): Promise<TaskCategory[]> {
const rows = await api.get<DomainTaskCategoryRow[]>('/task-categories');
return rows.map((row, index) => normalizeTaskCategory(row, index));
}
export async function createTaskCategoryDomain(data: Partial<TaskCategory>): Promise<TaskCategory> {
return normalizeTaskCategory(await api.post<DomainTaskCategoryRow>('/task-categories', toTaskCategoryPayload(data)), 0);
}
export async function updateTaskCategoryDomain(categoryId: string, data: Partial<TaskCategory>): Promise<TaskCategory> {
return normalizeTaskCategory(await api.patch<DomainTaskCategoryRow>(`/task-categories/${categoryId}`, toTaskCategoryPayload(data)), 0);
}
export async function deleteTaskCategoryDomain(categoryId: string): Promise<void> {
await api.delete(`/task-categories/${categoryId}`);
}
export async function listTaskWorklogsDomain(): Promise<TaskWorklog[]> {
const rows = await api.get<DomainTaskWorklogRow[]>('/task-worklogs');
return rows.map(normalizeTaskWorklog);
}
export async function createTaskWorklogDomain(data: TaskWorklog): Promise<TaskWorklog> {
return normalizeTaskWorklog(await api.post<DomainTaskWorklogRow>('/task-worklogs', toTaskWorklogPayload(data)));
}
export async function deleteTaskWorklogDomain(worklogId: string): Promise<void> {
await api.delete(`/task-worklogs/${worklogId}`);
}
export async function listOvertimeRecordsDomain(): Promise<OvertimeRecord[]> {
const rows = await api.get<DomainOvertimeRow[]>('/overtime');
return rows.map(normalizeOvertimeRecord);
}
export async function createOvertimeRecordDomain(data: OvertimeRecord): Promise<OvertimeRecord> {
return normalizeOvertimeRecord(await api.post<DomainOvertimeRow>('/overtime', toOvertimePayload(data)));
}
export async function updateOvertimeRecordDomain(recordId: string, data: Partial<OvertimeRecord>): Promise<void> {
await api.patch(`/overtime/${recordId}`, toOvertimePayload(data));
}
export async function deleteOvertimeRecordDomain(recordId: string): Promise<void> {
await api.delete(`/overtime/${recordId}`);
}
export async function listWorkActivitiesDomain(): Promise<WorkActivity[]> {
const rows = await api.get<DomainWorkActivityRow[]>('/work-activities');
return rows.map(normalizeWorkActivity);
}
export async function createWorkActivityDomain(data: WorkActivity | WorkActivityDraft): Promise<WorkActivity> {
return normalizeWorkActivity(await api.post<DomainWorkActivityRow>('/work-activities', toWorkActivityPayload(data)));
}
export async function deleteWorkActivityDomain(activityId: string): Promise<void> {
await api.delete(`/work-activities/${activityId}`);
}
function normalizeProductRoot(product: RootProduct): RootProduct {
return {
...product,
@@ -753,6 +879,126 @@ function toBugSeverity(value: string | null | undefined): BugSeverity {
return 'minor';
}
function toMemberPayload(data: Partial<Member>) {
return {
...(data.id !== undefined && { id: data.id }),
...(data.name !== undefined && { name: data.name }),
...(data.username !== undefined && { username: data.username }),
...(data.departmentId !== undefined && { departmentId: data.departmentId }),
...(data.roleId !== undefined && { roleId: data.roleId }),
...(data.phone !== undefined && { phone: data.phone }),
...(data.email !== undefined && { email: data.email }),
...(data.password !== undefined && { password: data.password }),
...(data.isSystem !== undefined && { isSystem: data.isSystem }),
};
}
function normalizeMember(row: DomainMemberRow): Member {
return {
id: row.id,
name: row.name,
username: row.username ?? undefined,
departmentId: row.departmentId ?? '',
roleId: row.roleId ?? 'member',
phone: row.phone ?? '',
email: row.email ?? '',
password: row.password ?? '',
createdAt: isoString(row.createdAt).slice(0, 10),
isSystem: Boolean(row.isSystem),
};
}
function toTaskCategoryPayload(data: Partial<TaskCategory>) {
return {
...(data.name !== undefined && { name: data.name }),
...(data.group !== undefined && { group: data.group }),
...(data.code !== undefined && { code: data.code }),
...(data.isSystem !== undefined && { isSystem: data.isSystem }),
};
}
function normalizeTaskCategory(row: DomainTaskCategoryRow, index: number): TaskCategory {
return {
id: row.id,
code: row.code ?? row.name,
name: row.name,
group: toCategoryGroup(row.group),
sortOrder: index + 1,
isSystem: Boolean(row.isSystem),
};
}
function toCategoryGroup(value: string): TaskCategory['group'] {
if (value === 'development' || value === 'testing' || value === 'implementation') return value;
return 'other';
}
function toTaskWorklogPayload(data: TaskWorklog) {
return {
taskId: data.taskId,
userId: data.userId,
date: data.date,
hours: data.hours,
workContent: data.workContent,
};
}
function normalizeTaskWorklog(row: DomainTaskWorklogRow): TaskWorklog {
return {
id: row.id,
taskId: row.sourceId,
userId: row.userId ?? '',
date: isoString(row.workDate).slice(0, 10),
hours: row.hours ?? 0,
workContent: row.content ?? '',
createdAt: isoString(row.createdAt),
};
}
function toOvertimePayload(data: Partial<OvertimeRecord>) {
return {
...(data.productId !== undefined && { productId: data.productId }),
...(data.projectId !== undefined && { projectId: data.projectId }),
...(data.versionId !== undefined && { versionId: data.versionId }),
...(data.person !== undefined && { person: data.person }),
...(data.startTime !== undefined && { startTime: data.startTime }),
...(data.endTime !== undefined && { endTime: data.endTime }),
...(data.duration !== undefined && { duration: data.duration }),
...(data.reasonId !== undefined && { reasonId: data.reasonId }),
...(data.remark !== undefined && { remark: data.remark }),
};
}
function normalizeOvertimeRecord(row: DomainOvertimeRow): OvertimeRecord {
const [reasonId, ...remarkParts] = (row.reason ?? '').split(':');
return {
id: row.id,
projectId: row.projectId ?? '',
versionId: row.versionId ?? undefined,
person: row.userId ?? '',
startTime: isoString(row.startAt),
endTime: isoString(row.endAt),
duration: row.hours ?? 0,
reasonId,
remark: remarkParts.join(':') || undefined,
createdAt: isoString(row.createdAt).slice(0, 10),
};
}
function toWorkActivityPayload(data: WorkActivity | WorkActivityDraft) {
return {
actorId: data.actorId,
sourceType: data.sourceType,
sourceId: data.sourceId,
action: data.action,
title: data.title,
category: data.category,
summary: data.summary,
metadata: data.metadata,
occurredAt: data.occurredAt,
};
}
function normalizeMutation<Row, Item>(
response: DomainMutationResponse<Row>,
mapper: (row: Row) => Item,