employee.service.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. import { Injectable } from '@angular/core';
  2. import { AdminDataService } from './admin-data.service';
  3. import { FmodeObject, FmodeParse } from 'fmode-ng/core';
  4. /**
  5. * 员工管理数据服务 (Profile表)
  6. */
  7. @Injectable({
  8. providedIn: 'root'
  9. })
  10. export class EmployeeService {
  11. constructor(private adminData: AdminDataService) {}
  12. /**
  13. * 查询员工列表
  14. */
  15. async findEmployees(options?: {
  16. roleName?: string;
  17. departmentId?: string;
  18. keyword?: string;
  19. skip?: number;
  20. limit?: number;
  21. }): Promise<FmodeObject[]> {
  22. return await this.adminData.findAll('Profile', {
  23. include: ['department'],
  24. skip: options?.skip || 0,
  25. limit: options?.limit || 100,
  26. descending: 'createdAt',
  27. additionalQuery: query => {
  28. if (options?.roleName) {
  29. query.equalTo('roleName', options.roleName);
  30. }
  31. if (options?.departmentId) {
  32. query.equalTo('department', {
  33. __type: 'Pointer',
  34. className: 'Department',
  35. objectId: options.departmentId
  36. });
  37. }
  38. if (options?.keyword) {
  39. const kw = options.keyword.trim();
  40. if (kw) {
  41. query.matches('name', new RegExp(kw, 'i'));
  42. }
  43. }
  44. }
  45. });
  46. }
  47. /**
  48. * 统计员工数量
  49. */
  50. async countEmployees(roleName?: string): Promise<number> {
  51. return await this.adminData.count('Profile', query => {
  52. if (roleName) {
  53. query.equalTo('roleName', roleName);
  54. }
  55. });
  56. }
  57. /**
  58. * 根据ID获取员工
  59. */
  60. async getEmployee(objectId: string): Promise<FmodeObject | null> {
  61. return await this.adminData.getById('Profile', objectId, ['department']);
  62. }
  63. /**
  64. * 创建员工 (注意: 员工从企微同步,实际可能不需要创建功能)
  65. */
  66. async createEmployee(data: {
  67. name: string;
  68. mobile?: string;
  69. userId?: string;
  70. roleName: string;
  71. departmentId?: string;
  72. data?: any;
  73. }): Promise<FmodeObject> {
  74. const employeeData: any = {
  75. name: data.name,
  76. mobile: data.mobile || '',
  77. userId: data.userId || '',
  78. roleName: data.roleName
  79. };
  80. if (data.departmentId) {
  81. employeeData.department = {
  82. __type: 'Pointer',
  83. className: 'Department',
  84. objectId: data.departmentId
  85. };
  86. }
  87. if (data.data) {
  88. employeeData.data = data.data;
  89. }
  90. const employee = this.adminData.createObject('Profile', employeeData);
  91. return await this.adminData.save(employee);
  92. }
  93. /**
  94. * 更新员工
  95. */
  96. async updateEmployee(
  97. objectId: string,
  98. updates: {
  99. name?: string;
  100. mobile?: string;
  101. roleName?: string;
  102. departmentId?: string;
  103. isDisabled?: boolean;
  104. data?: any;
  105. }
  106. ): Promise<FmodeObject | null> {
  107. const employee = await this.getEmployee(objectId);
  108. if (!employee) {
  109. return null;
  110. }
  111. if (updates.name !== undefined) {
  112. employee.set('name', updates.name);
  113. }
  114. if (updates.mobile !== undefined) {
  115. employee.set('mobile', updates.mobile);
  116. }
  117. if (updates.roleName !== undefined) {
  118. employee.set('roleName', updates.roleName);
  119. }
  120. if (updates.departmentId !== undefined) {
  121. employee.set('department', {
  122. __type: 'Pointer',
  123. className: 'Department',
  124. objectId: updates.departmentId
  125. });
  126. }
  127. if (updates.isDisabled !== undefined) {
  128. employee.set('isDisabled', updates.isDisabled);
  129. }
  130. if (updates.data !== undefined) {
  131. const currentData = employee.get('data') || {};
  132. employee.set('data', { ...currentData, ...updates.data });
  133. }
  134. return await this.adminData.save(employee);
  135. }
  136. /**
  137. * 禁用/启用员工
  138. */
  139. async toggleEmployee(objectId: string, isDisabled: boolean): Promise<boolean> {
  140. const employee = await this.getEmployee(objectId);
  141. if (!employee) {
  142. return false;
  143. }
  144. employee.set('isDisabled', isDisabled);
  145. await this.adminData.save(employee);
  146. return true;
  147. }
  148. /**
  149. * 转换为JSON
  150. */
  151. toJSON(employee: FmodeObject): any {
  152. const json = this.adminData.toJSON(employee);
  153. // 处理部门关联
  154. if (json.department && typeof json.department === 'object') {
  155. json.departmentName = json.department.name || '';
  156. json.departmentId = json.department.objectId;
  157. }
  158. return json;
  159. }
  160. /**
  161. * 批量转换
  162. */
  163. toJSONArray(employees: FmodeObject[]): any[] {
  164. return employees.map(e => this.toJSON(e));
  165. }
  166. /**
  167. * 获取员工的项目负载数据
  168. * 🔥 核心修复:完全参照组长端实现,通过 ProjectTeam 表查询(避免 OR 查询导致的 500 错误)
  169. * @param employeeId 员工ID(Profile表的objectId)
  170. * @returns 员工的项目统计信息
  171. */
  172. async getEmployeeWorkload(employeeId: string): Promise<{
  173. currentProjects: number;
  174. completedProjects: number;
  175. ongoingProjects: any[];
  176. completedProjectsList: any[];
  177. }> {
  178. try {
  179. const Parse = FmodeParse.with("nova");
  180. if (!Parse) {
  181. console.error('❌ [EmployeeService] Parse未初始化');
  182. return { currentProjects: 0, completedProjects: 0, ongoingProjects: [], completedProjectsList: [] };
  183. }
  184. const companyId = this.adminData.getCompanyPointer().objectId || 'cDL6R1hgSi';
  185. const employeePointer = {
  186. __type: 'Pointer',
  187. className: 'Profile',
  188. objectId: employeeId
  189. };
  190. console.log(`🔍 [EmployeeService] 查询员工项目负载:`, {
  191. 员工ID: employeeId,
  192. 公司ID: companyId
  193. });
  194. // 🔥 方案1:优先通过 ProjectTeam 表查询(组长端的方式,最稳定)
  195. console.log(`🔍 [EmployeeService] 通过 ProjectTeam 查询员工 ${employeeId} 的项目...`);
  196. const projectQuery = new Parse.Query('Project');
  197. projectQuery.equalTo('company', companyId);
  198. projectQuery.notEqualTo('isDeleted', true);
  199. projectQuery.select('title', 'status', 'currentStage', 'deadline', 'createdAt', 'completedAt', 'updatedAt');
  200. const teamQuery = new Parse.Query('ProjectTeam');
  201. teamQuery.equalTo('profile', employeePointer);
  202. teamQuery.notEqualTo('isDeleted', true);
  203. teamQuery.matchesQuery('project', projectQuery);
  204. teamQuery.include('project');
  205. teamQuery.limit(1000);
  206. const teamRecords = await teamQuery.find();
  207. const allProjects = teamRecords
  208. .map((r: any) => r.get('project'))
  209. .filter((p: any) => !!p);
  210. console.log(`✅ [EmployeeService] 通过 ProjectTeam 找到 ${allProjects.length} 个项目`);
  211. // 🔥 方案2:补充查询 Project.assignee(兼容旧数据/未用 ProjectTeam 的项目)
  212. console.log(`🔍 [EmployeeService] 补充查询 Project.assignee 字段...`);
  213. let assigneeProjects: any[] = [];
  214. try {
  215. const assigneeQuery = new Parse.Query('Project');
  216. assigneeQuery.equalTo('company', companyId);
  217. assigneeQuery.equalTo('assignee', employeePointer);
  218. assigneeQuery.notEqualTo('isDeleted', true);
  219. assigneeQuery.select('title', 'status', 'currentStage', 'deadline', 'createdAt', 'completedAt', 'updatedAt');
  220. assigneeQuery.limit(1000);
  221. assigneeProjects = await assigneeQuery.find();
  222. console.log(`✅ [EmployeeService] 通过 assignee 找到 ${assigneeProjects.length} 个项目`);
  223. } catch (assigneeErr) {
  224. console.warn('⚠️ [EmployeeService] 查询 assignee 失败(忽略):', assigneeErr);
  225. }
  226. // 合并两种途径的项目(去重)
  227. const projectMap = new Map<string, any>();
  228. [...allProjects, ...assigneeProjects].forEach(p => {
  229. if (p && p.id) {
  230. projectMap.set(p.id, p);
  231. }
  232. });
  233. const mergedProjects = Array.from(projectMap.values());
  234. console.log(`✅ [EmployeeService] 合并去重后共 ${mergedProjects.length} 个项目`);
  235. // 按状态和阶段分类
  236. const ongoingProjects: any[] = [];
  237. const completedProjects: any[] = [];
  238. for (const p of mergedProjects) {
  239. const status = p.get('status');
  240. const stage = p.get('currentStage');
  241. // 进行中:待分配、进行中
  242. if (['待分配', '进行中'].includes(status)) {
  243. ongoingProjects.push(p);
  244. }
  245. // 已完成:已完成、已归档,或售后归档阶段
  246. else if (['已完成', '已归档'].includes(status) || stage === '售后归档') {
  247. completedProjects.push(p);
  248. }
  249. }
  250. console.log(`✅ [EmployeeService] 项目分类: 进行中 ${ongoingProjects.length} 个, 已完成 ${completedProjects.length} 个`);
  251. // 转换为简单对象
  252. const ongoingList = ongoingProjects.map(p => ({
  253. id: p.id,
  254. name: p.get('title') || '未命名项目',
  255. status: p.get('status'),
  256. currentStage: p.get('currentStage'),
  257. deadline: p.get('deadline'),
  258. createdAt: p.get('createdAt')
  259. }));
  260. const completedList = completedProjects.map(p => ({
  261. id: p.id,
  262. name: p.get('title') || '未命名项目',
  263. status: p.get('status'),
  264. currentStage: p.get('currentStage'),
  265. completedAt: p.get('completedAt') || p.get('updatedAt'),
  266. createdAt: p.get('createdAt')
  267. }));
  268. return {
  269. currentProjects: ongoingList.length,
  270. completedProjects: completedList.length,
  271. ongoingProjects: ongoingList,
  272. completedProjectsList: completedList
  273. };
  274. } catch (error: any) {
  275. console.error('❌ [EmployeeService] 获取员工项目负载失败:', error);
  276. console.error('错误详情:', {
  277. message: error?.message,
  278. code: error?.code,
  279. stack: error?.stack?.split('\n').slice(0, 3)
  280. });
  281. return { currentProjects: 0, completedProjects: 0, ongoingProjects: [], completedProjectsList: [] };
  282. }
  283. }
  284. }