|
|
@@ -0,0 +1,432 @@
|
|
|
+# 项目负责人和空间场景问题 - 实现总结
|
|
|
+
|
|
|
+**日期**: 2025-10-24
|
|
|
+**完成状态**: ✅ 已完成
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## ✅ 已完成的修改
|
|
|
+
|
|
|
+### 修改1: team-assign组件 - 自动设置组长为负责人
|
|
|
+
|
|
|
+**文件**: `src/modules/project/components/team-assign/team-assign.component.ts`
|
|
|
+**位置**: 第128-151行
|
|
|
+
|
|
|
+**修改内容**:
|
|
|
+
|
|
|
+```typescript
|
|
|
+async selectDepartment(department: FmodeObject) {
|
|
|
+ this.selectedDepartment = department;
|
|
|
+ this.selectedDesigner = null;
|
|
|
+ this.departmentMembers = [];
|
|
|
+
|
|
|
+ // ✅ 自动设置组长为项目负责人
|
|
|
+ const leader = department.get('leader');
|
|
|
+ if (leader && this.project) {
|
|
|
+ try {
|
|
|
+ // 更新项目的assignee字段为组长
|
|
|
+ this.project.set('assignee', leader);
|
|
|
+ this.project.set('department', department);
|
|
|
+ await this.project.save();
|
|
|
+ console.log('✅ 项目负责人已设置为组长:', leader.get('name'));
|
|
|
+
|
|
|
+ // 触发界面更新
|
|
|
+ this.cdr.markForCheck();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('❌ 设置项目负责人失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ await this.loadDepartmentMembers(department);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**效果**:
|
|
|
+- 在项目详情页选择项目组时,自动将组长设置为项目的 `assignee`
|
|
|
+- 同时设置项目的 `department` 字段
|
|
|
+- 保存到数据库,刷新后数据持久化
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 修改2: ProjectService - 项目创建逻辑
|
|
|
+
|
|
|
+**文件**: `src/app/pages/admin/services/project.service.ts`
|
|
|
+**位置**: 第4-6行(导入)、第70-137行(createProject方法)
|
|
|
+
|
|
|
+**2.1 添加Parse导入**:
|
|
|
+
|
|
|
+```typescript
|
|
|
+import { FmodeParse } from 'fmode-ng/parse';
|
|
|
+
|
|
|
+const Parse = FmodeParse.with('nova');
|
|
|
+```
|
|
|
+
|
|
|
+**2.2 修改createProject方法**:
|
|
|
+
|
|
|
+```typescript
|
|
|
+async createProject(data: {
|
|
|
+ title: string;
|
|
|
+ customerId?: string;
|
|
|
+ assigneeId?: string;
|
|
|
+ departmentId?: string; // ✅ 新增:项目组ID
|
|
|
+ status?: string;
|
|
|
+ currentStage?: string;
|
|
|
+ deadline?: Date;
|
|
|
+ data?: any;
|
|
|
+}): Promise<FmodeObject> {
|
|
|
+ const projectData: any = {
|
|
|
+ title: data.title,
|
|
|
+ status: data.status || '待分配',
|
|
|
+ currentStage: data.currentStage || '订单分配'
|
|
|
+ };
|
|
|
+
|
|
|
+ // 设置客户指针
|
|
|
+ if (data.customerId) {
|
|
|
+ projectData.customer = {
|
|
|
+ __type: 'Pointer',
|
|
|
+ className: 'ContactInfo',
|
|
|
+ objectId: data.customerId
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // ✅ 新增:如果提供了项目组,获取组长作为默认负责人
|
|
|
+ if (data.departmentId) {
|
|
|
+ try {
|
|
|
+ const departmentQuery = new Parse.Query('Department');
|
|
|
+ departmentQuery.include('leader');
|
|
|
+ const department = await departmentQuery.get(data.departmentId);
|
|
|
+
|
|
|
+ if (department) {
|
|
|
+ projectData.department = department.toPointer();
|
|
|
+
|
|
|
+ // 获取组长
|
|
|
+ const leader = department.get('leader');
|
|
|
+ if (leader && !data.assigneeId) {
|
|
|
+ // 如果没有明确指定负责人,使用组长
|
|
|
+ projectData.assignee = leader.toPointer();
|
|
|
+ console.log('✅ 项目负责人默认为组长:', leader.get('name'));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('❌ 获取项目组失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置负责人指针(如果明确指定,覆盖组长)
|
|
|
+ if (data.assigneeId) {
|
|
|
+ projectData.assignee = {
|
|
|
+ __type: 'Pointer',
|
|
|
+ className: 'Profile',
|
|
|
+ objectId: data.assigneeId
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (data.deadline) {
|
|
|
+ projectData.deadline = data.deadline;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (data.data) {
|
|
|
+ projectData.data = data.data;
|
|
|
+ }
|
|
|
+
|
|
|
+ const project = this.adminData.createObject('Project', projectData);
|
|
|
+ return await this.adminData.save(project);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**效果**:
|
|
|
+- 创建项目时可以指定 `departmentId`
|
|
|
+- 自动获取该项目组的组长作为默认负责人
|
|
|
+- 如果明确指定了 `assigneeId`,优先使用指定的人员
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 修改3: ProjectService - 查询时包含department和leader
|
|
|
+
|
|
|
+**文件**: `src/app/pages/admin/services/project.service.ts`
|
|
|
+**位置**: 第17-44行(findProjects)、第57-67行(getProject)
|
|
|
+
|
|
|
+**3.1 修改findProjects**:
|
|
|
+
|
|
|
+```typescript
|
|
|
+async findProjects(options?: {
|
|
|
+ status?: string;
|
|
|
+ keyword?: string;
|
|
|
+ skip?: number;
|
|
|
+ limit?: number;
|
|
|
+}): Promise<FmodeObject[]> {
|
|
|
+ return await this.adminData.findAll('Project', {
|
|
|
+ include: ['customer', 'assignee', 'department', 'department.leader'], // ✅ 添加department和leader
|
|
|
+ skip: options?.skip || 0,
|
|
|
+ limit: options?.limit || 20,
|
|
|
+ descending: 'updatedAt',
|
|
|
+ additionalQuery: query => {
|
|
|
+ if (options?.status) {
|
|
|
+ query.equalTo('status', options.status);
|
|
|
+ }
|
|
|
+ if (options?.keyword) {
|
|
|
+ const kw = options.keyword.trim();
|
|
|
+ if (kw) {
|
|
|
+ // 搜索项目标题
|
|
|
+ query.matches('title', new RegExp(kw, 'i'));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**3.2 修改getProject**:
|
|
|
+
|
|
|
+```typescript
|
|
|
+async getProject(objectId: string): Promise<FmodeObject | null> {
|
|
|
+ return await this.adminData.getById('Project', objectId, [
|
|
|
+ 'customer',
|
|
|
+ 'assignee',
|
|
|
+ 'department',
|
|
|
+ 'department.leader' // ✅ 添加department和leader
|
|
|
+ ]);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**效果**:
|
|
|
+- 查询项目时自动加载 `department` 和 `department.leader` 关联数据
|
|
|
+- 为后续显示组长信息提供数据基础
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 修改4: ProjectService - toJSON方法优化
|
|
|
+
|
|
|
+**文件**: `src/app/pages/admin/services/project.service.ts`
|
|
|
+**位置**: 第231-257行
|
|
|
+
|
|
|
+**修改内容**:
|
|
|
+
|
|
|
+```typescript
|
|
|
+toJSON(project: FmodeObject): any {
|
|
|
+ const json = this.adminData.toJSON(project);
|
|
|
+
|
|
|
+ // 处理关联对象
|
|
|
+ if (json.customer && typeof json.customer === 'object') {
|
|
|
+ json.customerName = json.customer.name || '';
|
|
|
+ json.customerId = json.customer.objectId;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ✅ 处理负责人:优先使用assignee,如果为空则使用department.leader
|
|
|
+ if (json.assignee && typeof json.assignee === 'object') {
|
|
|
+ json.assigneeName = json.assignee.name || '';
|
|
|
+ json.assigneeId = json.assignee.objectId;
|
|
|
+ } else if (json.department && typeof json.department === 'object') {
|
|
|
+ // 如果没有assignee,但有department和leader,使用leader
|
|
|
+ const leader = json.department.leader;
|
|
|
+ if (leader && typeof leader === 'object') {
|
|
|
+ json.assigneeName = leader.name || '';
|
|
|
+ json.assigneeId = leader.objectId;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return json;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**效果**:
|
|
|
+- 项目列表显示负责人时,优先使用 `assignee.name`
|
|
|
+- 如果 `assignee` 为空,自动使用 `department.leader.name`
|
|
|
+- 确保项目列表中不会显示"未分配",而是显示组长名字
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 📊 数据流图
|
|
|
+
|
|
|
+### 创建项目流程
|
|
|
+
|
|
|
+```
|
|
|
+用户创建项目
|
|
|
+ ↓
|
|
|
+指定departmentId
|
|
|
+ ↓
|
|
|
+createProject()查询Department
|
|
|
+ ↓
|
|
|
+获取department.leader
|
|
|
+ ↓
|
|
|
+设置project.assignee = leader
|
|
|
+ ↓
|
|
|
+保存到数据库
|
|
|
+ ↓
|
|
|
+项目列表显示组长名字
|
|
|
+```
|
|
|
+
|
|
|
+### 选择项目组流程
|
|
|
+
|
|
|
+```
|
|
|
+用户在项目详情页
|
|
|
+ ↓
|
|
|
+选择项目组(team-assign组件)
|
|
|
+ ↓
|
|
|
+selectDepartment()自动触发
|
|
|
+ ↓
|
|
|
+获取department.leader
|
|
|
+ ↓
|
|
|
+更新project.assignee = leader
|
|
|
+ ↓
|
|
|
+保存到数据库
|
|
|
+ ↓
|
|
|
+刷新项目列表,负责人更新
|
|
|
+```
|
|
|
+
|
|
|
+### 显示负责人流程
|
|
|
+
|
|
|
+```
|
|
|
+加载项目列表
|
|
|
+ ↓
|
|
|
+include: ['assignee', 'department', 'department.leader']
|
|
|
+ ↓
|
|
|
+toJSON()转换
|
|
|
+ ↓
|
|
|
+如果有assignee → 显示assignee.name
|
|
|
+如果没有assignee但有department.leader → 显示leader.name
|
|
|
+否则 → 显示"未分配"
|
|
|
+ ↓
|
|
|
+项目列表展示
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 🧪 测试验证
|
|
|
+
|
|
|
+### 测试场景1: 在项目详情页选择项目组
|
|
|
+
|
|
|
+**步骤**:
|
|
|
+1. 访问项目详情页:`http://localhost:4200/admin/project-detail/APwk78jnrh/order`
|
|
|
+2. 在"设计师分配"卡片中选择一个项目组
|
|
|
+3. 观察控制台输出:`✅ 项目负责人已设置为组长: xxx`
|
|
|
+4. 刷新项目管理页面:`http://localhost:4200/admin/project-management`
|
|
|
+5. 验证该项目的"负责人"列显示组长名字
|
|
|
+
|
|
|
+**预期结果**:
|
|
|
+- ✅ 选择项目组后,项目的 `assignee` 自动设置为组长
|
|
|
+- ✅ 项目列表中"负责人"列显示组长名字
|
|
|
+- ✅ 不再显示"未分配"
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 测试场景2: 创建新项目并指定项目组
|
|
|
+
|
|
|
+**步骤**:
|
|
|
+1. 调用 `projectService.createProject()` 创建项目:
|
|
|
+ ```typescript
|
|
|
+ await projectService.createProject({
|
|
|
+ title: '测试项目',
|
|
|
+ departmentId: 'xxx项目组ID',
|
|
|
+ status: '待分配'
|
|
|
+ });
|
|
|
+ ```
|
|
|
+2. 查看控制台输出:`✅ 项目负责人默认为组长: xxx`
|
|
|
+3. 在项目管理页面查看新项目
|
|
|
+4. 验证"负责人"列显示组长名字
|
|
|
+
|
|
|
+**预期结果**:
|
|
|
+- ✅ 项目创建时自动设置组长为负责人
|
|
|
+- ✅ `project.assignee` 指向组长的Profile
|
|
|
+- ✅ `project.department` 指向该项目组
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 测试场景3: 验证空间场景问题
|
|
|
+
|
|
|
+**当前状态**:
|
|
|
+- ⚠️ 项目ID `APwk78jnrh` 在 `Product` 表中没有记录
|
|
|
+- ⚠️ 分配设计师时没有空间可选
|
|
|
+
|
|
|
+**解决方案**:
|
|
|
+1. 在订单分配阶段(stage-order)添加空间时,确保调用 `ProductSpaceService.createProductSpace()` 创建 Product 记录
|
|
|
+2. 或者在Parse Dashboard手动添加Product记录:
|
|
|
+ - 进入 `Product` 表
|
|
|
+ - 添加新行
|
|
|
+ - 设置 `project` 字段为项目指针
|
|
|
+ - 设置 `productName` 为 "客厅"、"卧室" 等
|
|
|
+ - 设置 `productType` 为 "living_room"、"bedroom" 等
|
|
|
+ - 保存
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 🎯 核心改进
|
|
|
+
|
|
|
+### 改进1: 自动化负责人分配
|
|
|
+
|
|
|
+**修改前**:
|
|
|
+- 项目创建时 `assignee` 为空
|
|
|
+- 需要手动设置负责人
|
|
|
+- 项目列表显示"未分配"
|
|
|
+
|
|
|
+**修改后**:
|
|
|
+- 选择项目组时自动设置组长为负责人
|
|
|
+- 创建项目时可指定项目组,自动获取组长
|
|
|
+- 项目列表始终显示组长名字
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 改进2: 优雅降级
|
|
|
+
|
|
|
+**数据获取优先级**:
|
|
|
+1. **最高优先级**: 明确指定的 `assignee`
|
|
|
+2. **次优先级**: 项目组的 `department.leader`
|
|
|
+3. **兜底**: 显示"未分配"
|
|
|
+
|
|
|
+**代码实现**:
|
|
|
+```typescript
|
|
|
+// toJSON方法中的逻辑
|
|
|
+if (json.assignee && typeof json.assignee === 'object') {
|
|
|
+ // 优先使用assignee
|
|
|
+ json.assigneeName = json.assignee.name || '';
|
|
|
+} else if (json.department && typeof json.department === 'object') {
|
|
|
+ // 降级使用department.leader
|
|
|
+ const leader = json.department.leader;
|
|
|
+ if (leader && typeof leader === 'object') {
|
|
|
+ json.assigneeName = leader.name || '';
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 📁 修改文件清单
|
|
|
+
|
|
|
+| 文件 | 修改内容 | 状态 |
|
|
|
+|------|---------|------|
|
|
|
+| `src/modules/project/components/team-assign/team-assign.component.ts` | 选择项目组时自动设置组长为负责人 | ✅ 完成 |
|
|
|
+| `src/app/pages/admin/services/project.service.ts` | 添加Parse导入 | ✅ 完成 |
|
|
|
+| `src/app/pages/admin/services/project.service.ts` | 修改createProject方法,支持departmentId | ✅ 完成 |
|
|
|
+| `src/app/pages/admin/services/project.service.ts` | 修改findProjects,include department | ✅ 完成 |
|
|
|
+| `src/app/pages/admin/services/project.service.ts` | 修改getProject,include department | ✅ 完成 |
|
|
|
+| `src/app/pages/admin/services/project.service.ts` | 修改toJSON,优先显示leader | ✅ 完成 |
|
|
|
+| `docs/task/2025102221-fix-project-assignee-and-spaces.md` | 问题分析文档 | ✅ 完成 |
|
|
|
+| `docs/task/2025102221-implementation-summary.md` | 实现总结文档 | ✅ 完成 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 🎉 总结
|
|
|
+
|
|
|
+### 解决的问题
|
|
|
+
|
|
|
+1. ✅ **项目负责人显示"未分配"** → 现在自动显示组长名字
|
|
|
+2. ⚠️ **分配设计师时没有空间场景** → 需要在订单分配阶段创建Product记录
|
|
|
+
|
|
|
+### 核心逻辑
|
|
|
+
|
|
|
+1. **选择项目组** → 自动设置组长为负责人
|
|
|
+2. **创建项目** → 指定项目组时,自动获取组长
|
|
|
+3. **显示负责人** → 优先assignee,降级使用leader
|
|
|
+4. **数据加载** → 始终include department和leader
|
|
|
+
|
|
|
+### 用户体验提升
|
|
|
+
|
|
|
+- **无需手动分配负责人**:选择项目组即可
|
|
|
+- **项目列表清晰**:始终显示真实负责人姓名
|
|
|
+- **数据一致性**:负责人与项目组关联
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+**修改完成!现在刷新浏览器测试,项目列表应该能正确显示组长名字了!** 🚀
|
|
|
+
|
|
|
+
|