# 简单方案：通过 department.leader 显示负责人

**日期**: 2025-10-24  
**核心思路**: 不修改数据库，只通过 `department.leader` 动态获取组长信息

---

## 🎯 方案对比

### 方案1: 在 Project 表存储 assignee（之前的方案）

```
Project表
├─ assignee: Pointer<Profile>  ← 需要迁移数据
├─ department: Pointer<Department>
└─ ...
```

**缺点**:
- ❌ 需要迁移现有项目数据
- ❌ 数据冗余（assignee 和 department.leader 重复）
- ❌ 数据一致性问题（组长变更时需要同步更新）

---

### 方案2: 动态从 department.leader 获取（推荐）✅

```
Project表
├─ department: Pointer<Department>  ← 只需要这个
└─ ...

Department表
├─ leader: Pointer<Profile>  ← 从这里获取组长
└─ ...
```

**优点**:
- ✅ **不需要迁移数据**
- ✅ 数据一致性好（组长只存在一个地方）
- ✅ 逻辑清晰（项目属于项目组，项目组有组长）

---

## ✅ 实现方案

### 修改1: 确保查询时包含 department.leader

**文件**: `src/app/pages/admin/services/project.service.ts`

已修改（第27行）：
```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'], // ✅ 包含leader
    skip: options?.skip || 0,
    limit: options?.limit || 20,
    descending: 'updatedAt',
    // ...
  });
}
```

---

### 修改2: toJSON 方法优先使用 department.leader

**文件**: `src/app/pages/admin/services/project.service.ts`

已修改（第243-256行）：
```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') {
    // 如果有明确指定的assignee，使用它
    json.assigneeName = json.assignee.name || '';
    json.assigneeId = json.assignee.objectId;
    json.assigneeRole = json.assignee.roleName || '';
  } else if (json.department && typeof json.department === 'object') {
    // 如果没有assignee，使用department的leader（组长）
    const leader = json.department.leader;
    if (leader && typeof leader === 'object') {
      json.assigneeName = leader.name || '';
      json.assigneeId = leader.objectId;
      json.assigneeRole = '组长';  // ✅ 标记为组长
    }
  }

  return json;
}
```

---

## 🔧 需要做的事情

### 唯一需要做的：确保项目有 department 字段

**检查方法**（在浏览器控制台执行）：

```javascript
(async function() {
  const Parse = window.FmodeParse.with('nova');
  const company = localStorage.getItem('company');
  
  const query = new Parse.Query('Project');
  query.equalTo('company', company);
  query.notEqualTo('isDeleted', true);
  query.include('department', 'department.leader');
  query.limit(20);
  
  const projects = await query.find();
  
  let hasDept = 0;
  let noDept = 0;
  
  projects.forEach(p => {
    if (p.get('department')) {
      hasDept++;
    } else {
      noDept++;
    }
  });
  
  console.log(`✅ 有department: ${hasDept} 个`);
  console.log(`❌ 没有department: ${noDept} 个`);
  
  if (noDept > 0) {
    console.log('\n⚠️ 有项目没有department，需要分配项目组');
  } else {
    console.log('\n🎉 所有项目都有department，可以直接显示组长！');
  }
})();
```

---

### 如果有项目没有 department，执行以下脚本分配默认项目组：

```javascript
(async function() {
  const Parse = window.FmodeParse.with('nova');
  const company = localStorage.getItem('company');
  
  console.log('🚀 开始为项目分配默认项目组...');
  
  // 1. 查找没有department的项目
  const projectQuery = new Parse.Query('Project');
  projectQuery.equalTo('company', company);
  projectQuery.notEqualTo('isDeleted', true);
  projectQuery.doesNotExist('department');
  projectQuery.limit(1000);
  
  const projects = await projectQuery.find();
  console.log(`📊 找到 ${projects.length} 个没有项目组的项目`);
  
  if (projects.length === 0) {
    console.log('✅ 所有项目都已有项目组！');
    return;
  }
  
  // 2. 获取默认项目组（第一个项目组）
  const deptQuery = new Parse.Query('Department');
  deptQuery.equalTo('company', company);
  deptQuery.equalTo('type', 'project');
  deptQuery.notEqualTo('isDeleted', true);
  deptQuery.include('leader');
  deptQuery.ascending('createdAt');
  
  const dept = await deptQuery.first();
  
  if (!dept) {
    console.error('❌ 没有找到项目组，请先创建项目组');
    return;
  }
  
  const leader = dept.get('leader');
  console.log(`✅ 使用默认项目组: ${dept.get('name')}, 组长: ${leader?.get('name') || '无'}`);
  
  // 3. 批量分配
  let success = 0;
  let failed = 0;
  
  for (let i = 0; i < projects.length; i++) {
    const project = projects[i];
    const title = project.get('title') || '未命名项目';
    
    try {
      project.set('department', dept);
      await project.save();
      success++;
      console.log(`✅ [${i+1}/${projects.length}] "${title}" 已分配到 ${dept.get('name')}`);
    } catch (error) {
      failed++;
      console.error(`❌ [${i+1}/${projects.length}] "${title}" 失败:`, error);
    }
  }
  
  console.log('\n' + '='.repeat(60));
  console.log('🎉 分配完成！');
  console.log('='.repeat(60));
  console.log(`📊 总计: ${projects.length} | ✅ 成功: ${success} | ❌ 失败: ${failed}`);
  console.log('='.repeat(60));
  console.log('\n💡 请刷新页面（Ctrl+Shift+R）查看结果');
})();
```

---

## 🎯 完整流程

### 步骤1: 检查项目状态

打开 `http://localhost:4200/admin/project-management`，按 F12，执行：

```javascript
(async function() {
  const Parse = window.FmodeParse.with('nova');
  const company = localStorage.getItem('company');
  
  const query = new Parse.Query('Project');
  query.equalTo('company', company);
  query.include('department', 'department.leader');
  query.limit(20);
  
  const projects = await query.find();
  
  console.table(projects.map(p => ({
    '项目名称': p.get('title'),
    '有项目组': p.get('department') ? '✅' : '❌',
    '组长': p.get('department')?.get('leader')?.get('name') || '无'
  })));
})();
```

---

### 步骤2: 如果有项目缺少 department，执行分配脚本

复制上面的"为项目分配默认项目组"脚本，粘贴到控制台执行。

---

### 步骤3: 刷新页面验证

按 `Ctrl+Shift+R` 刷新页面，查看项目列表的"负责人"列。

**预期结果**:
- 所有项目的"负责人"列应显示组长名字
- 不再显示"未分配"

---

## 📊 数据结构

### Project 表（需要的字段）

```javascript
{
  objectId: "APwk78jnrh",
  title: "张家界凤凰城三期项目",
  department: Pointer<Department> { objectId: "xxx" },  // ✅ 只需要这个
  company: "cDL6R1hgSi",
  status: "进行中",
  // 不需要 assignee 字段
}
```

### Department 表

```javascript
{
  objectId: "xxx",
  name: "汪奥组",
  type: "project",
  leader: Pointer<Profile> { objectId: "yyy" },  // ✅ 组长信息存在这里
  company: "cDL6R1hgSi"
}
```

### Profile 表

```javascript
{
  objectId: "yyy",
  name: "汪奥",
  roleName: "组长",
  company: "cDL6R1hgSi"
}
```

---

## 🎉 优势总结

### 方案2（推荐）vs 方案1（之前）

| 对比项 | 方案1（存储assignee） | 方案2（使用leader）✅ |
|--------|---------------------|---------------------|
| 需要迁移数据 | ❌ 是 | ✅ 否 |
| 数据一致性 | ❌ 可能不一致 | ✅ 始终一致 |
| 逻辑复杂度 | ❌ 较复杂 | ✅ 简单 |
| 查询性能 | ✅ 稍快 | ✅ 同样快 |
| 维护成本 | ❌ 高 | ✅ 低 |

---

## 💡 为什么这个方案更好？

1. **逻辑清晰**
   - 项目 → 属于项目组
   - 项目组 → 有组长
   - 项目的负责人 = 项目组的组长

2. **数据一致性**
   - 组长信息只存在一个地方（Department.leader）
   - 组长变更时，所有项目的负责人自动更新

3. **无需迁移**
   - 只需确保项目有 `department` 字段
   - 不需要为每个项目设置 `assignee` 字段

4. **符合业务逻辑**
   - 项目负责人就是项目组的组长
   - 不需要额外的 `assignee` 字段

---

**现在执行检查脚本，看看是否所有项目都有 department！** 🚀

