refactor(data): 收口关系表运行时数据源
Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled
Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled
- 移除已迁移业务 AppData 运行时 fallback,改走领域 API 和关系表快读 - 补齐需求产品负责人、版本计划任务 JSON 和成员 username 回填迁移 - 统一治理字典入口,并补充 AI provider、数据源契约和领域服务测试 Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
@@ -7,6 +7,9 @@ describe('DevTaskService domain writes', () => {
|
||||
record: jest.fn().mockResolvedValue({ id: 'activity-1' }),
|
||||
};
|
||||
const prisma = {
|
||||
user: {
|
||||
findFirst: createUserFindFirstMock(),
|
||||
},
|
||||
version: {
|
||||
findUnique: jest.fn(),
|
||||
},
|
||||
@@ -26,6 +29,58 @@ describe('DevTaskService domain writes', () => {
|
||||
};
|
||||
};
|
||||
|
||||
it('resolves assignee and creator names to user ids before relation writes', async () => {
|
||||
const { prisma, service } = makeService();
|
||||
prisma.user.findFirst = createUserFindFirstMock({ 张三: 'dev-1', 产品经理: 'pm-1' });
|
||||
prisma.version.findUnique.mockResolvedValue({ id: 'version-1', productId: 'product-1', projectId: 'project-1' });
|
||||
prisma.devTask.create.mockResolvedValue({ id: 'task-1', versionId: 'version-1', title: '开发登录' });
|
||||
|
||||
await service.create('version-1', {
|
||||
title: '开发登录',
|
||||
assigneeId: '张三',
|
||||
createdBy: '产品经理',
|
||||
} as any);
|
||||
|
||||
expect(prisma.user.findFirst).toHaveBeenCalledTimes(2);
|
||||
expect(prisma.devTask.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({
|
||||
assigneeId: 'dev-1',
|
||||
creatorId: 'pm-1',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves transfer assignee names before updating foreign keys', async () => {
|
||||
const { prisma, workActivity, service } = makeService();
|
||||
prisma.user.findFirst = createUserFindFirstMock({ 李四: 'dev-2' });
|
||||
prisma.devTask.findFirst.mockResolvedValue({
|
||||
id: 'task-1',
|
||||
versionId: 'version-1',
|
||||
productId: 'product-1',
|
||||
projectId: 'project-1',
|
||||
title: '开发登录',
|
||||
assigneeId: 'dev-1',
|
||||
});
|
||||
prisma.devTask.update.mockResolvedValue({
|
||||
id: 'task-1',
|
||||
versionId: 'version-1',
|
||||
productId: 'product-1',
|
||||
projectId: 'project-1',
|
||||
title: '开发登录',
|
||||
assigneeId: 'dev-2',
|
||||
});
|
||||
|
||||
await service.transfer('version-1', 'task-1', '李四');
|
||||
|
||||
expect(prisma.devTask.update).toHaveBeenCalledWith({
|
||||
where: { id_versionId: { id: 'task-1', versionId: 'version-1' } },
|
||||
data: { assigneeId: 'dev-2' },
|
||||
});
|
||||
expect(workActivity.record).toHaveBeenCalledWith(expect.objectContaining({
|
||||
metadata: expect.objectContaining({ fromAssigneeId: 'dev-1', toAssigneeId: 'dev-2' }),
|
||||
}));
|
||||
});
|
||||
|
||||
it('creates dev tasks directly under a version partition', async () => {
|
||||
const { prisma, workActivity, service } = makeService();
|
||||
prisma.version.findUnique.mockResolvedValue({ id: 'version-1', productId: 'product-1', projectId: 'project-1' });
|
||||
@@ -151,3 +206,13 @@ describe('DevTaskService domain writes', () => {
|
||||
expect(prisma.devTask.update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
function createUserFindFirstMock(mapping: Record<string, string> = {}) {
|
||||
return jest.fn(({ where }: any) => {
|
||||
const refs = (where?.OR ?? [])
|
||||
.flatMap((condition: Record<string, string>) => Object.values(condition))
|
||||
.filter(Boolean);
|
||||
const ref = refs[0];
|
||||
return Promise.resolve(ref ? { id: mapping[ref] ?? ref } : null);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { resolveUserReference } from '../../common/user-reference';
|
||||
import { WorkActivityService } from '../work-activity/work-activity.service';
|
||||
import { CreateDevTaskDto } from './dto/create-dev-task.dto';
|
||||
import { UpdateDevTaskDto } from './dto/update-dev-task.dto';
|
||||
@@ -17,9 +18,10 @@ export class DevTaskService {
|
||||
if (!version.projectId) {
|
||||
throw new BadRequestException('开发任务所属版本必须归属于项目');
|
||||
}
|
||||
const taskData = await this.toTaskData(dto);
|
||||
const item = await this.prisma.devTask.create({
|
||||
data: {
|
||||
...this.toTaskData(dto),
|
||||
...taskData,
|
||||
versionId,
|
||||
productId: version.productId,
|
||||
projectId: version.projectId,
|
||||
@@ -44,7 +46,7 @@ export class DevTaskService {
|
||||
await this.ensureTaskInVersion(versionId, id);
|
||||
const item = await this.prisma.devTask.update({
|
||||
where: { id_versionId: { id, versionId } },
|
||||
data: this.toTaskData(dto),
|
||||
data: await this.toTaskData(dto),
|
||||
});
|
||||
await this.workActivity.markXiaobaoSummaryDirty(versionId);
|
||||
return { item, activities: [] };
|
||||
@@ -84,16 +86,17 @@ export class DevTaskService {
|
||||
|
||||
async transfer(versionId: string, id: string, assigneeId: string) {
|
||||
const current = await this.ensureTaskInVersion(versionId, id);
|
||||
const resolvedAssigneeId = await resolveUserReference(this.prisma, assigneeId);
|
||||
const item = await this.prisma.devTask.update({
|
||||
where: { id_versionId: { id, versionId } },
|
||||
data: { assigneeId },
|
||||
data: { assigneeId: resolvedAssigneeId },
|
||||
});
|
||||
const activity = await this.recordTaskActivity(
|
||||
item,
|
||||
'dev_task_transferred',
|
||||
'progress',
|
||||
`转派开发任务:${item.title}`,
|
||||
{ fromAssigneeId: current.assigneeId, toAssigneeId: assigneeId },
|
||||
{ fromAssigneeId: current.assigneeId, toAssigneeId: resolvedAssigneeId },
|
||||
);
|
||||
return { item, activities: [activity] };
|
||||
}
|
||||
@@ -105,7 +108,7 @@ export class DevTaskService {
|
||||
return item;
|
||||
}
|
||||
|
||||
private toTaskData(dto: Partial<CreateDevTaskDto>) {
|
||||
private async toTaskData(dto: Partial<CreateDevTaskDto>) {
|
||||
return {
|
||||
...(dto.requirementId !== undefined && { requirementId: emptyToNull(dto.requirementId) }),
|
||||
...(dto.requirementProductId !== undefined && { requirementProductId: emptyToNull(dto.requirementProductId) }),
|
||||
@@ -115,8 +118,10 @@ export class DevTaskService {
|
||||
...(dto.description !== undefined && { description: dto.description ?? '' }),
|
||||
...(dto.status !== undefined && { status: dto.status }),
|
||||
...(dto.priority !== undefined && { priority: parsePriority(dto.priority) ?? 0 }),
|
||||
...(dto.assigneeId !== undefined && { assigneeId: emptyToNull(dto.assigneeId) }),
|
||||
...(dto.creatorId !== undefined || dto.createdBy !== undefined ? { creatorId: emptyToNull(dto.creatorId ?? dto.createdBy) } : {}),
|
||||
...(dto.assigneeId !== undefined && { assigneeId: await resolveUserReference(this.prisma, dto.assigneeId) }),
|
||||
...(dto.creatorId !== undefined || dto.createdBy !== undefined
|
||||
? { creatorId: await resolveUserReference(this.prisma, dto.creatorId ?? dto.createdBy) }
|
||||
: {}),
|
||||
...(dto.isBlocked !== undefined && { isBlocked: dto.isBlocked }),
|
||||
...(dto.blockReason !== undefined && { blockReason: emptyToNull(dto.blockReason) }),
|
||||
...(dto.expectedStartAt !== undefined && { expectedStartAt: parseOptionalDate(dto.expectedStartAt) }),
|
||||
|
||||
Reference in New Issue
Block a user