|
@@ -0,0 +1,277 @@
|
|
|
+import { Injectable } from '@angular/core';
|
|
|
+import { FmodeParse, FmodeObject } from 'fmode-ng/core';
|
|
|
+
|
|
|
+const Parse: any = FmodeParse.with('nova');
|
|
|
+
|
|
|
+export interface DesignerData {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ avatar?: string;
|
|
|
+ teamId: string;
|
|
|
+ teamName: string;
|
|
|
+ isTeamLeader: boolean;
|
|
|
+ status: 'idle' | 'busy' | 'reviewing';
|
|
|
+ idleDays: number;
|
|
|
+ workload: number;
|
|
|
+ skills: string[];
|
|
|
+ roleName: string;
|
|
|
+ // 为兼容现有组件增加别名字段
|
|
|
+ groupId: string;
|
|
|
+ groupName: string;
|
|
|
+ isLeader: boolean;
|
|
|
+ currentProjects: number;
|
|
|
+ isInStagnantProject: boolean;
|
|
|
+ availableDates: string[];
|
|
|
+ reviewDates: string[];
|
|
|
+ recentOrders: number;
|
|
|
+ lastOrderDate?: string;
|
|
|
+}
|
|
|
+
|
|
|
+export interface TeamData {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ leaderId: string;
|
|
|
+ leaderName: string;
|
|
|
+ members: DesignerData[];
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 设计师数据服务
|
|
|
+ * 从Parse加载实际的项目组和设计师数据
|
|
|
+ */
|
|
|
+@Injectable({
|
|
|
+ providedIn: 'root'
|
|
|
+})
|
|
|
+export class DesignerDataService {
|
|
|
+ private readonly COMPANY_ID = 'cDL6R1hgSi'; // 映三色帐套
|
|
|
+
|
|
|
+ constructor() {}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取公司指针
|
|
|
+ */
|
|
|
+ private getCompanyPointer(): any {
|
|
|
+ return {
|
|
|
+ __type: 'Pointer',
|
|
|
+ className: '_Company',
|
|
|
+ objectId: this.COMPANY_ID
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加载所有项目组及其成员
|
|
|
+ */
|
|
|
+ async loadTeamsWithMembers(): Promise<TeamData[]> {
|
|
|
+ try {
|
|
|
+ // 1. 查询所有项目组
|
|
|
+ const deptQuery = new Parse.Query('Department');
|
|
|
+ deptQuery.equalTo('company', this.getCompanyPointer());
|
|
|
+ deptQuery.equalTo('type', 'project');
|
|
|
+ deptQuery.notEqualTo('isDeleted', true);
|
|
|
+ deptQuery.include(['leader']);
|
|
|
+
|
|
|
+ const departments = await deptQuery.find();
|
|
|
+
|
|
|
+ // 2. 为每个项目组加载成员
|
|
|
+ const teams: TeamData[] = await Promise.all(
|
|
|
+ departments.map(async (dept: any) => {
|
|
|
+ const deptId = dept.id;
|
|
|
+ const deptName = dept.get('name') || '未命名项目组';
|
|
|
+ const leader = dept.get('leader');
|
|
|
+ const leaderId = leader?.id || '';
|
|
|
+ const leaderName = leader?.get('name') || '未分配';
|
|
|
+
|
|
|
+ // 查询项目组成员
|
|
|
+ const memberQuery = new Parse.Query('Profile');
|
|
|
+ memberQuery.equalTo('company', this.getCompanyPointer());
|
|
|
+ memberQuery.equalTo('department', dept.toPointer());
|
|
|
+ memberQuery.notEqualTo('isDeleted', true);
|
|
|
+ memberQuery.equalTo('roleName', '组员'); // 只查询组员角色
|
|
|
+
|
|
|
+ const members = await memberQuery.find();
|
|
|
+
|
|
|
+ // 转换成员数据
|
|
|
+ const memberList: DesignerData[] = members.map((m: any) =>
|
|
|
+ this.transformToDesignerData(m, deptId, deptName)
|
|
|
+ );
|
|
|
+
|
|
|
+ // 如果组长也在Profile中,也加入成员列表
|
|
|
+ if (leader) {
|
|
|
+ const leaderData = this.transformToDesignerData(
|
|
|
+ leader,
|
|
|
+ deptId,
|
|
|
+ deptName
|
|
|
+ );
|
|
|
+ leaderData.isTeamLeader = true;
|
|
|
+ leaderData.isLeader = true;
|
|
|
+ memberList.unshift(leaderData);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ id: deptId,
|
|
|
+ name: deptName,
|
|
|
+ leaderId,
|
|
|
+ leaderName,
|
|
|
+ members: memberList
|
|
|
+ };
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ return teams;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载项目组失败:', error);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将Profile转换为DesignerData
|
|
|
+ */
|
|
|
+ private transformToDesignerData(
|
|
|
+ profile: FmodeObject,
|
|
|
+ teamId: string,
|
|
|
+ teamName: string
|
|
|
+ ): DesignerData {
|
|
|
+ const data = profile.get('data') || {};
|
|
|
+ const roleName = profile.get('roleName') || '组员';
|
|
|
+ const isLeader = roleName === '组长';
|
|
|
+
|
|
|
+ // 计算工作负载(简化逻辑,实际应从项目分配中统计)
|
|
|
+ const workload = data.workload || 0;
|
|
|
+
|
|
|
+ // 计算空闲天数(简化逻辑)
|
|
|
+ const lastOrderDate = data.lastOrderDate;
|
|
|
+ let idleDays = 0;
|
|
|
+ if (lastOrderDate) {
|
|
|
+ const last = new Date(lastOrderDate);
|
|
|
+ const now = new Date();
|
|
|
+ idleDays = Math.floor(
|
|
|
+ (now.getTime() - last.getTime()) / (1000 * 60 * 60 * 24)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断状态
|
|
|
+ let status: 'idle' | 'busy' | 'reviewing' = 'idle';
|
|
|
+ if (workload > 70) {
|
|
|
+ status = 'busy';
|
|
|
+ } else if (data.isReviewing) {
|
|
|
+ status = 'reviewing';
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ id: profile.id || '',
|
|
|
+ name: profile.get('name') || '未知设计师',
|
|
|
+ avatar: data.avatar || '',
|
|
|
+ teamId,
|
|
|
+ teamName,
|
|
|
+ isTeamLeader: isLeader,
|
|
|
+ status,
|
|
|
+ idleDays,
|
|
|
+ workload,
|
|
|
+ skills: data.skills || [],
|
|
|
+ roleName,
|
|
|
+ // 别名字段
|
|
|
+ groupId: teamId,
|
|
|
+ groupName: teamName,
|
|
|
+ isLeader,
|
|
|
+ currentProjects: data.currentProjects || 0,
|
|
|
+ isInStagnantProject: false,
|
|
|
+ availableDates: data.availableDates || [],
|
|
|
+ reviewDates: data.reviewDates || [],
|
|
|
+ recentOrders: data.recentOrders || 0,
|
|
|
+ lastOrderDate: data.lastOrderDate
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据设计师ID列表获取设计师信息
|
|
|
+ */
|
|
|
+ async getDesignersByIds(designerIds: string[]): Promise<DesignerData[]> {
|
|
|
+ if (!designerIds || designerIds.length === 0) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const query = new Parse.Query('Profile');
|
|
|
+ query.containedIn('objectId', designerIds);
|
|
|
+ query.include(['department']);
|
|
|
+
|
|
|
+ const profiles = await query.find();
|
|
|
+
|
|
|
+ return profiles.map((p: any) => {
|
|
|
+ const dept = p.get('department');
|
|
|
+ const teamId = dept?.id || '';
|
|
|
+ const teamName = dept?.get('name') || '未分配';
|
|
|
+ return this.transformToDesignerData(p, teamId, teamName);
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取设计师信息失败:', error);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存项目的设计师分配
|
|
|
+ */
|
|
|
+ async saveProjectAssignment(
|
|
|
+ projectId: string,
|
|
|
+ assignment: {
|
|
|
+ primaryTeamId: string;
|
|
|
+ assignedDesignerIds: string[];
|
|
|
+ crossTeamCollaborators: string[];
|
|
|
+ }
|
|
|
+ ): Promise<boolean> {
|
|
|
+ try {
|
|
|
+ const projectQuery = new Parse.Query('Project');
|
|
|
+ const project = await projectQuery.get(projectId);
|
|
|
+
|
|
|
+ // 主要负责人(取第一个设计师)
|
|
|
+ if (assignment.assignedDesignerIds.length > 0) {
|
|
|
+ const mainDesignerId = assignment.assignedDesignerIds[0];
|
|
|
+ project.set('assignee', {
|
|
|
+ __type: 'Pointer',
|
|
|
+ className: 'Profile',
|
|
|
+ objectId: mainDesignerId
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存所有分配的设计师到data字段
|
|
|
+ const projectData = project.get('data') || {};
|
|
|
+ projectData.assignedDesigners = assignment.assignedDesignerIds;
|
|
|
+ projectData.crossTeamCollaborators = assignment.crossTeamCollaborators;
|
|
|
+ projectData.primaryTeamId = assignment.primaryTeamId;
|
|
|
+ project.set('data', projectData);
|
|
|
+
|
|
|
+ await project.save();
|
|
|
+ return true;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存设计师分配失败:', error);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取项目的设计师分配信息
|
|
|
+ */
|
|
|
+ async getProjectAssignment(projectId: string): Promise<{
|
|
|
+ primaryTeamId: string;
|
|
|
+ assignedDesignerIds: string[];
|
|
|
+ crossTeamCollaborators: string[];
|
|
|
+ } | null> {
|
|
|
+ try {
|
|
|
+ const projectQuery = new Parse.Query('Project');
|
|
|
+ const project = await projectQuery.get(projectId);
|
|
|
+
|
|
|
+ const data = project.get('data') || {};
|
|
|
+
|
|
|
+ return {
|
|
|
+ primaryTeamId: data.primaryTeamId || '',
|
|
|
+ assignedDesignerIds: data.assignedDesigners || [],
|
|
|
+ crossTeamCollaborators: data.crossTeamCollaborators || []
|
|
|
+ };
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取项目分配失败:', error);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|