|
@@ -14,6 +14,7 @@ import {
|
|
|
calculateFinalPrice,
|
|
|
getDefaultProcesses
|
|
|
} from '../../../config/quotation-rules';
|
|
|
+import { QuotationEditorComponent } from '../../../components/quotation-editor.component';
|
|
|
|
|
|
const Parse = FmodeParse.with('nova');
|
|
|
|
|
@@ -29,7 +30,7 @@ const Parse = FmodeParse.with('nova');
|
|
|
@Component({
|
|
|
selector: 'app-stage-order',
|
|
|
standalone: true,
|
|
|
- imports: [CommonModule, FormsModule],
|
|
|
+ imports: [CommonModule, FormsModule, QuotationEditorComponent],
|
|
|
templateUrl: './stage-order.component.html',
|
|
|
styleUrls: ['./stage-order.component.scss']
|
|
|
})
|
|
@@ -123,10 +124,19 @@ export class StageOrderComponent implements OnInit {
|
|
|
departmentMembers: FmodeObject[] = [];
|
|
|
selectedDesigner: FmodeObject | null = null;
|
|
|
|
|
|
+ // 已分配的项目团队成员
|
|
|
+ projectTeams: FmodeObject[] = [];
|
|
|
+
|
|
|
+ // 设计师分配对话框
|
|
|
+ showAssignDialog: boolean = false;
|
|
|
+ assigningDesigner: FmodeObject | null = null;
|
|
|
+ selectedSpaces: string[] = [];
|
|
|
+
|
|
|
// 加载状态
|
|
|
loading: boolean = true;
|
|
|
saving: boolean = false;
|
|
|
loadingMembers: boolean = false;
|
|
|
+ loadingTeams: boolean = false;
|
|
|
|
|
|
// 路由参数
|
|
|
cid: string = '';
|
|
@@ -223,6 +233,9 @@ export class StageOrderComponent implements OnInit {
|
|
|
deptQuery.ascending('name');
|
|
|
this.departments = await deptQuery.find();
|
|
|
|
|
|
+ // 加载已分配的项目团队
|
|
|
+ await this.loadProjectTeams();
|
|
|
+
|
|
|
} catch (err) {
|
|
|
console.error('加载失败:', err);
|
|
|
} finally {
|
|
@@ -417,13 +430,14 @@ export class StageOrderComponent implements OnInit {
|
|
|
* 选择项目组(Department)
|
|
|
*/
|
|
|
async selectDepartment(department: FmodeObject) {
|
|
|
- if (!this.canEdit && this.project?.get('assignee')?.id) return;
|
|
|
-
|
|
|
- this.selectedDepartment = department;
|
|
|
- this.selectedDesigner = null;
|
|
|
- this.departmentMembers = [];
|
|
|
-
|
|
|
- await this.loadDepartmentMembers(department);
|
|
|
+ if (this.canEdit || this.project?.get('assignee')?.id){
|
|
|
+
|
|
|
+ this.selectedDepartment = department;
|
|
|
+ this.selectedDesigner = null;
|
|
|
+ this.departmentMembers = [];
|
|
|
+
|
|
|
+ await this.loadDepartmentMembers(department);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -454,10 +468,151 @@ export class StageOrderComponent implements OnInit {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 选择设计师
|
|
|
+ * 加载已分配的ProjectTeam
|
|
|
+ */
|
|
|
+ async loadProjectTeams() {
|
|
|
+ if (!this.project) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.loadingTeams = true;
|
|
|
+
|
|
|
+ const query = new Parse.Query('ProjectTeam');
|
|
|
+ query.equalTo('project', this.project.toPointer());
|
|
|
+ query.include('profile');
|
|
|
+ query.notEqualTo('isDeleted', true);
|
|
|
+
|
|
|
+ this.projectTeams = await query.find();
|
|
|
+ } catch (err) {
|
|
|
+ console.error('加载项目团队失败:', err);
|
|
|
+ } finally {
|
|
|
+ this.loadingTeams = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 选择设计师 - 弹出分配对话框
|
|
|
*/
|
|
|
selectDesigner(designer: FmodeObject) {
|
|
|
- this.selectedDesigner = designer;
|
|
|
+ // 检查是否已分配
|
|
|
+ const isAssigned = this.projectTeams.some(team => team.get('profile')?.id === designer.id);
|
|
|
+ if (isAssigned) {
|
|
|
+ alert('该设计师已分配到此项目');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.assigningDesigner = designer;
|
|
|
+ this.selectedSpaces = [];
|
|
|
+
|
|
|
+ // 如果只有一个空间,默认选中
|
|
|
+ if (this.quotation.spaces.length === 1) {
|
|
|
+ this.selectedSpaces = [this.quotation.spaces[0].name];
|
|
|
+ }
|
|
|
+
|
|
|
+ this.showAssignDialog = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 切换空间选择
|
|
|
+ */
|
|
|
+ toggleSpaceSelection(spaceName: string) {
|
|
|
+ const index = this.selectedSpaces.indexOf(spaceName);
|
|
|
+ if (index > -1) {
|
|
|
+ this.selectedSpaces.splice(index, 1);
|
|
|
+ } else {
|
|
|
+ this.selectedSpaces.push(spaceName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 确认分配设计师
|
|
|
+ */
|
|
|
+ async confirmAssignDesigner() {
|
|
|
+ if (!this.assigningDesigner || !this.project) return;
|
|
|
+
|
|
|
+ if (this.selectedSpaces.length === 0) {
|
|
|
+ alert('请至少选择一个空间场景');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.saving = true;
|
|
|
+
|
|
|
+ // 创建 ProjectTeam
|
|
|
+ const ProjectTeam = Parse.Object.extend('ProjectTeam');
|
|
|
+ const team = new ProjectTeam();
|
|
|
+ team.set('project', this.project.toPointer());
|
|
|
+ team.set('profile', this.assigningDesigner.toPointer());
|
|
|
+ team.set('role', '组员');
|
|
|
+ team.set('data', {
|
|
|
+ spaces: this.selectedSpaces,
|
|
|
+ assignedAt: new Date(),
|
|
|
+ assignedBy: this.currentUser?.id
|
|
|
+ });
|
|
|
+
|
|
|
+ await team.save();
|
|
|
+
|
|
|
+ // 加入群聊(静默执行)
|
|
|
+ await this.addMemberToGroupChat(this.assigningDesigner.get('userId'));
|
|
|
+
|
|
|
+ // 重新加载团队列表
|
|
|
+ await this.loadProjectTeams();
|
|
|
+
|
|
|
+ this.showAssignDialog = false;
|
|
|
+ this.assigningDesigner = null;
|
|
|
+ this.selectedSpaces = [];
|
|
|
+
|
|
|
+ alert('分配成功');
|
|
|
+ } catch (err) {
|
|
|
+ console.error('分配设计师失败:', err);
|
|
|
+ alert('分配失败');
|
|
|
+ } finally {
|
|
|
+ this.saving = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 取消分配对话框
|
|
|
+ */
|
|
|
+ cancelAssignDialog() {
|
|
|
+ this.showAssignDialog = false;
|
|
|
+ this.assigningDesigner = null;
|
|
|
+ this.selectedSpaces = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加成员到群聊(静默执行)
|
|
|
+ */
|
|
|
+ async addMemberToGroupChat(userId: string) {
|
|
|
+ if (!userId) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 从父组件获取groupChat和chatId
|
|
|
+ const groupChat = (this as any).groupChat;
|
|
|
+ if (!groupChat) return;
|
|
|
+
|
|
|
+ const chatId = groupChat.get('chat_id');
|
|
|
+ if (!chatId) return;
|
|
|
+
|
|
|
+ // 调用企微SDK添加成员(需要在前端通过ww对象调用)
|
|
|
+ // 这部分需要在浏览器环境中通过企微JSSDK执行
|
|
|
+ if (typeof (window as any).ww !== 'undefined') {
|
|
|
+ await (window as any).ww.updateEnterpriseChat({
|
|
|
+ chatId: chatId,
|
|
|
+ userIdsToAdd: [userId]
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ // 静默失败,不影响主流程
|
|
|
+ console.warn('添加群成员失败:', err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取团队成员负责的空间
|
|
|
+ */
|
|
|
+ getMemberSpaces(team: FmodeObject): string {
|
|
|
+ const spaces = team.get('data')?.spaces || [];
|
|
|
+ return spaces.join('、') || '未分配';
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -469,23 +624,17 @@ export class StageOrderComponent implements OnInit {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 切换工序启用状态
|
|
|
+ * 报价数据变化回调
|
|
|
*/
|
|
|
- toggleProcess(space: any, processKey: string) {
|
|
|
- const process = space.processes[processKey];
|
|
|
- process.enabled = !process.enabled;
|
|
|
- if (!process.enabled) {
|
|
|
- process.price = 0;
|
|
|
- process.quantity = 0;
|
|
|
- }
|
|
|
- this.calculateTotal();
|
|
|
+ onQuotationChange(updatedQuotation: any) {
|
|
|
+ this.quotation = updatedQuotation;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 工序价格或数量变化
|
|
|
+ * 报价总额变化回调
|
|
|
*/
|
|
|
- onProcessChange() {
|
|
|
- this.calculateTotal();
|
|
|
+ onTotalChange(total: number) {
|
|
|
+ this.quotation.total = total;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -611,75 +760,4 @@ export class StageOrderComponent implements OnInit {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 辅助方法:检查工序是否启用
|
|
|
- */
|
|
|
- isProcessEnabled(space: any, processKey: string): boolean {
|
|
|
- const process = (space.processes as any)[processKey];
|
|
|
- return process?.enabled || false;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 辅助方法:设置工序价格
|
|
|
- */
|
|
|
- setProcessPrice(space: any, processKey: string, value: any): void {
|
|
|
- const process = (space.processes as any)[processKey];
|
|
|
- if (process) {
|
|
|
- process.price = value;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 辅助方法:设置工序数量
|
|
|
- */
|
|
|
- setProcessQuantity(space: any, processKey: string, value: any): void {
|
|
|
- const process = (space.processes as any)[processKey];
|
|
|
- if (process) {
|
|
|
- process.quantity = value;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 辅助方法:获取工序价格
|
|
|
- */
|
|
|
- getProcessPrice(space: any, processKey: string): number {
|
|
|
- const process = (space.processes as any)[processKey];
|
|
|
- return process?.price || 0;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 辅助方法:获取工序数量
|
|
|
- */
|
|
|
- getProcessQuantity(space: any, processKey: string): number {
|
|
|
- const process = (space.processes as any)[processKey];
|
|
|
- return process?.quantity || 0;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 辅助方法:获取工序单位
|
|
|
- */
|
|
|
- getProcessUnit(space: any, processKey: string): string {
|
|
|
- const process = (space.processes as any)[processKey];
|
|
|
- return process?.unit || '';
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 辅助方法:计算工序小计
|
|
|
- */
|
|
|
- calculateProcessSubtotal(space: any, processKey: string): number {
|
|
|
- const process = (space.processes as any)[processKey];
|
|
|
- return (process?.price || 0) * (process?.quantity || 0);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 辅助方法:获取项目类型图标
|
|
|
- */
|
|
|
- getProjectTypeIcon(type: string): string {
|
|
|
- const iconMap: any = {
|
|
|
- '家装': 'home-outline',
|
|
|
- '工装': 'business-outline',
|
|
|
- '软装设计': 'color-palette-outline'
|
|
|
- };
|
|
|
- return iconMap[type] || 'document-outline';
|
|
|
- }
|
|
|
}
|