temp.txt 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { Router, ActivatedRoute, RouterModule } from '@angular/router';
  4. import { IonicModule } from '@ionic/angular';
  5. import { WxworkSDK, WxworkCorp, WxworkAuth } from 'fmode-ng/core';
  6. import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
  7. import { ProfileService } from '../../../../app/services/profile.service';
  8. import { ProjectBottomCardComponent } from '../../components/project-bottom-card/project-bottom-card.component';
  9. import { ProjectFilesModalComponent } from '../../components/project-files-modal/project-files-modal.component';
  10. import { ProjectMembersModalComponent } from '../../components/project-members-modal/project-members-modal.component';
  11. import { ProjectIssuesModalComponent } from '../../components/project-issues-modal/project-issues-modal.component';
  12. import { ProjectIssueService } from '../../services/project-issue.service';
  13. import { FormsModule } from '@angular/forms';
  14. import { CustomerSelectorComponent } from '../../components/contact-selector/contact-selector.component';
  15. import { OrderApprovalPanelComponent } from '../../../../app/shared/components/order-approval-panel/order-approval-panel.component';
  16. import { GroupChatSummaryComponent } from '../../components/group-chat-summary/group-chat-summary.component';
  17. const Parse = FmodeParse.with('nova');
  18. /**
  19. * 憿寧𤌍霂行��詨�蝏�辣
  20. *
  21. * �蠘�嚗? * 1. 撅閧內�偦𧫴畾萄紡�迎�霈W������&霈日�瘙���漱隞䀹�銵䎚��睸�𤾸�獢��
  22. * 2. �寞旿閫坿𠧧�批����
  23. * 3. 摮鞱楝�勗��a𧫴畾萄�摰? * 4. �舀�@Input�諹楝�勗��唬舅蝘齿㺭�桀�頧賣䲮撘? *
  24. * 頝舐眏嚗?wxwork/:cid/project/:projectId
  25. */
  26. @Component({
  27. selector: 'app-project-detail',
  28. standalone: true,
  29. imports: [
  30. CommonModule,
  31. IonicModule,
  32. RouterModule,
  33. ProjectBottomCardComponent,
  34. ProjectFilesModalComponent,
  35. ProjectMembersModalComponent,
  36. ProjectIssuesModalComponent,
  37. CustomerSelectorComponent,
  38. OrderApprovalPanelComponent,
  39. GroupChatSummaryComponent
  40. ],
  41. templateUrl: './project-detail.component.html',
  42. styleUrls: ['./project-detail.component.scss']
  43. })
  44. export class ProjectDetailComponent implements OnInit, OnDestroy {
  45. // 颲枏���㺭嚗�𣈲���隞嗅��剁�
  46. @Input() project: FmodeObject | null = null;
  47. @Input() groupChat: FmodeObject | null = null;
  48. @Input() currentUser: FmodeObject | null = null;
  49. // �桅�蝏蠘恣
  50. issueCount: number = 0;
  51. // 頝舐眏��㺭
  52. cid: string = '';
  53. projectId: string = '';
  54. groupId: string = '';
  55. profileId: string = '';
  56. chatId: string = ''; // 隞𦒘�敺株��交𧒄�?chat_id
  57. // 隡�凝SDK
  58. wxwork: WxworkSDK | null = null;
  59. wecorp: WxworkCorp | null = null;
  60. wxAuth: WxworkAuth | null = null; // WxworkAuth 摰硺�
  61. // �㰘蝸�嗆�? loading: boolean = true;
  62. error: string | null = null;
  63. // 憿寧𤌍�唳旿
  64. contact: FmodeObject | null = null;
  65. assignee: FmodeObject | null = null;
  66. // 敶枏��嗆挾
  67. currentStage: string = 'order'; // order | requirements | delivery | aftercare
  68. stages = [
  69. { id: 'order', name: '霈W����', icon: 'document-text-outline', number: 1 },
  70. { id: 'requirements', name: '蝖株恕��瘙?, icon: 'checkmark-circle-outline', number: 2 },
  71. { id: 'delivery', name: '鈭支��扯�', icon: 'rocket-outline', number: 3 },
  72. { id: 'aftercare', name: '�桀�敶埝﹝', icon: 'archive-outline', number: 4 }
  73. ];
  74. // ���
  75. canEdit: boolean = false;
  76. canViewCustomerPhone: boolean = false;
  77. role: string = '';
  78. // 璅⊥����嗆�? showFilesModal: boolean = false;
  79. showMembersModal: boolean = false;
  80. showIssuesModal: boolean = false;
  81. // �啣�嚗𡁜恥�瑁祕��儒�誯𢒰�輻𠶖�? showContactPanel: boolean = false;
  82. // �桀㭘�嗆�? surveyStatus: {
  83. filled: boolean;
  84. text: string;
  85. icon: string;
  86. surveyLog?: FmodeObject;
  87. contact?: FmodeObject;
  88. } = {
  89. filled: false,
  90. text: '�煾��䔮�?,
  91. icon: 'document-text-outline'
  92. };
  93. // �睃�嚗𡁻★�桀抅�砌縑�? showProjectInfoCollapsed: boolean = true;
  94. // 鈭衤辣�穃𨯬�典��? private stageCompletedListener: any = null;
  95. constructor(
  96. private router: Router,
  97. private route: ActivatedRoute,
  98. private profileService: ProfileService,
  99. private issueService: ProjectIssueService
  100. ) {}
  101. async ngOnInit() {
  102. // �瑕�頝舐眏��㺭
  103. this.cid = this.route.snapshot.paramMap.get('cid') || '';
  104. this.projectId = this.route.snapshot.paramMap.get('projectId') || '';
  105. this.groupId = this.route.snapshot.queryParamMap.get('groupId') || '';
  106. this.profileId = this.route.snapshot.queryParamMap.get('profileId') || '';
  107. this.chatId = this.route.snapshot.queryParamMap.get('chatId') || '';
  108. // �穃𨯬頝舐眏�睃�
  109. this.route.firstChild?.url.subscribe((segments) => {
  110. if (segments.length > 0) {
  111. this.currentStage = segments[0].path;
  112. console.log('�� 敶枏��嗆挾撌脫凒�?', this.currentStage);
  113. }
  114. });
  115. // �嘥��碶�敺格����銝漤獈憛鮋△�W�頧踝�
  116. await this.initWxworkAuth();
  117. await this.loadData();
  118. // �嘥��硋極雿𨀣��嗆挾嚗�𥅾蝻箏仃�蹱覔�桀歇摰峕�霈啣��冽鱏嚗? this.ensureWorkflowStage();
  119. // �穃𨯬��𧫴畾萄��𣂷�隞塚��芸𢆡�刻��唬�銝��航�
  120. this.stageCompletedListener = async (e: any) => {
  121. const stageId = e?.detail?.stage as string;
  122. if (!stageId) return;
  123. console.log('�?�交𤣰�圈𧫴畾萄��𣂷�隞?', stageId);
  124. await this.advanceToNextStage(stageId);
  125. };
  126. document.addEventListener('stage:completed', this.stageCompletedListener);
  127. }
  128. /**
  129. * 蝏�辣��瘥�𧒄皜��鈭衤辣�穃𨯬�? */
  130. ngOnDestroy() {
  131. if (this.stageCompletedListener) {
  132. document.removeEventListener('stage:completed', this.stageCompletedListener);
  133. console.log('�完 撌脫���𧫴畾萄��𣂷�隞嗥��砍膥');
  134. }
  135. }
  136. /**
  137. * �嘥��碶�敺格����銝漤獈憛鮋△�g�
  138. */
  139. async initWxworkAuth() {
  140. try {
  141. let cid = this.cid || localStorage.getItem("company") || "";
  142. // 憒��瘝⊥�cid嚗諹扇敶閗郎�𠹺�銝齿��粹�霂? if (!cid) {
  143. console.warn('�𩤃� �芣𪄳�軏ompany ID (cid)嚗䔶�敺桀��賢�銝滚虾�?);
  144. return;
  145. }
  146. this.wxAuth = new WxworkAuth({ cid: cid });
  147. this.wxwork = new WxworkSDK({ cid: cid, appId: 'crm' });
  148. this.wecorp = new WxworkCorp(cid);
  149. console.log('�?隡�凝SDK�嘥��𡝗����cid:', cid);
  150. } catch (error) {
  151. console.error('�?隡�凝SDK�嘥��硋仃韐?', error);
  152. // 銝漤獈憛鮋△�W�頧? }
  153. }
  154. /**
  155. * �睃�/撅訫� 憿寧𤌍�箸𧋦靽⊥�
  156. */
  157. toggleProjectInfo(): void {
  158. this.showProjectInfoCollapsed = !this.showProjectInfoCollapsed;
  159. }
  160. /**
  161. * 頝唾蓮�唳�摰𡁻𧫴畾? */
  162. goToStage(stageId: 'order'|'requirements'|'delivery'|'aftercare') {
  163. // 摮鞱楝�梧�敶枏�銝?/.../project-detail/:projectId/:stage
  164. this.currentStage = stageId;
  165. // 雿輻鍂銝羓漣�詨笆頝臬�嚗𣬚&靽嘥銁隞颱�撋��頝舐眏銝钅��賣迤蝖桀��? this.router.navigate(['../', stageId], { relativeTo: this.route });
  166. }
  167. /**
  168. * 隞𡒊�摰𡁻𧫴畾菜綫餈𥕦�銝衤�銝芷𧫴畾? */
  169. async advanceToNextStage(current: string) {
  170. const order = ['order','requirements','delivery','aftercare'];
  171. const idx = order.indexOf(current);
  172. console.log('�� �刻��嗆挾:', { current, idx, currentStage: this.currentStage });
  173. if (idx === -1) {
  174. console.warn('�𩤃� �芣𪄳�啣��漤𧫴畾?', current);
  175. return;
  176. }
  177. if (idx >= order.length - 1) {
  178. console.log('�?撌脣�颲暹��𡡞𧫴畾?);
  179. window?.fmode?.alert('���厰𧫴畾萄歇摰峕�嚗?);
  180. return;
  181. }
  182. const next = order[idx + 1];
  183. console.log('�∴� 頝唾蓮�唬�銝��嗆挾:', next);
  184. // ����吔���扇敶枏��嗆挾摰峕�撟嗉挽蝵桐�銝��嗆挾銝箏��? await this.persistStageProgress(current, next);
  185. // 撖潸⏛�唬�銝��嗆挾嚗���孵�撌乩�瘚���脣ế摰𡄯�隞���W�摰對�
  186. this.goToStage(next as any);
  187. const nextStageName = this.stages.find(s => s.id === next)?.name || next;
  188. window?.fmode?.alert(`撌脰䌊�刻歲頧砍�銝衤��嗆挾: ${nextStageName}`);
  189. }
  190. /**
  191. * 蝖桐�摮睃銁撌乩�瘚���漤𧫴畾萸���蝻箏仃�蹱覔�桀��鞱扇敶閗恣蝞? */
  192. ensureWorkflowStage() {
  193. if (!this.project) return;
  194. const order = ['order','requirements','delivery','aftercare'];
  195. const data = this.project.get('data') || {};
  196. const statuses = data.stageStatuses || {};
  197. let current = this.project.get('currentStage');
  198. if (!current) {
  199. // �曉�蝚砌�銝芣𧊋摰峕���𧫴畾? current = order.find(s => statuses[s] !== 'completed') || 'aftercare';
  200. this.project.set('currentStage', current);
  201. }
  202. }
  203. /**
  204. * ����㚚𧫴畾菜綫餈𨥈���扇敶枏�摰峕���挽蝵桐�銝��嗆挾嚗? */
  205. private async persistStageProgress(current: string, next: string) {
  206. if (!this.project) {
  207. console.warn('�𩤃� 憿寧𤌍撖寡情銝滚��剁��䭾�����?);
  208. return;
  209. }
  210. console.log('�𠒣 撘�憪𧢲�銋���嗆挾:', { current, next });
  211. const data = this.project.get('data') || {};
  212. data.stageStatuses = data.stageStatuses || {};
  213. data.stageStatuses[current] = 'completed';
  214. this.project.set('data', data);
  215. this.project.set('currentStage', next);
  216. console.log('�𠒣 霈曄蔭�嗆挾�嗆�?', {
  217. currentStage: next,
  218. stageStatuses: data.stageStatuses
  219. });
  220. try {
  221. await this.project.save();
  222. console.log('�?�嗆挾�嗆���銋���𣂼�');
  223. } catch (e) {
  224. console.warn('�𩤃� �嗆挾�嗆���銋��憭梯揖嚗�蕭�乩誑靽肽�瘚���舐誧蝏哨�:', e);
  225. }
  226. }
  227. /**
  228. * �㰘蝸�唳旿
  229. */
  230. async loadData() {
  231. try {
  232. this.loading = true;
  233. // 2. �瑕�敶枏��冽�嚗������典��滚𦛚�瑕�嚗? if (!this.currentUser?.id && this.wxAuth) {
  234. try {
  235. this.currentUser = await this.wxAuth.currentProfile();
  236. } catch (error) {
  237. console.warn('�𩤃� �瑕�敶枏��冽�Profile憭梯揖:', error);
  238. }
  239. }
  240. // 霈曄蔭���
  241. this.role = this.currentUser?.get('roleName') || '';
  242. this.canEdit = ['摰X�', '蝏��', '蝏�鵭', '蝞∠��?, '霈曇恣撣?, '摰X�銝餌恣'].includes(this.role);
  243. this.canViewCustomerPhone = ['摰X�', '蝏�鵭', '蝞∠��?].includes(this.role);
  244. const companyId = this.currentUser?.get('company')?.id || localStorage?.getItem("company");
  245. // 3. �㰘蝸憿寧𤌍
  246. if (!this.project) {
  247. if (this.projectId) {
  248. // �朞� projectId �㰘蝸嚗���𤾸蝱餈𥕦�嚗? const query = new Parse.Query('Project');
  249. query.include('contact', 'assignee','department','department.leader');
  250. this.project = await query.get(this.projectId);
  251. } else if (this.chatId) {
  252. // �朞� chat_id �交𪄳憿寧𤌍嚗��隡�凝蝢方�餈𥕦�嚗? if (companyId) {
  253. // ��䰻�?GroupChat
  254. const gcQuery = new Parse.Query('GroupChat');
  255. gcQuery.equalTo('chat_id', this.chatId);
  256. gcQuery.equalTo('company', companyId);
  257. let groupChat = await gcQuery.first();
  258. if (groupChat) {
  259. this.groupChat = groupChat;
  260. const projectPointer = groupChat.get('project');
  261. if (projectPointer) {
  262. const pQuery = new Parse.Query('Project');
  263. pQuery.include('contact', 'assignee','department','department.leader');
  264. this.project = await pQuery.get(projectPointer.id);
  265. }
  266. }
  267. if (!this.project) {
  268. throw new Error('霂亦黎�𠰴��芸��娪★�殷�霂瑕��典��啣�撱粹★�?);
  269. }
  270. }
  271. }
  272. }
  273. if(!this.groupChat?.id){
  274. const gcQuery2 = new Parse.Query('GroupChat');
  275. gcQuery2.equalTo('project', this.projectId);
  276. gcQuery2.equalTo('company', companyId);
  277. this.groupChat = await gcQuery2.first();
  278. }
  279. this.wxwork?.syncGroupChat(this.groupChat?.toJSON())
  280. if (!this.project) {
  281. throw new Error('�䭾��㰘蝸憿寧𤌍靽⊥�');
  282. }
  283. this.contact = this.project.get('contact');
  284. this.assignee = this.project.get('assignee');
  285. // �㰘蝸�桀㭘�嗆�? await this.loadSurveyStatus();
  286. // �湔鰵�桅�霈⊥㺭
  287. try {
  288. if (this.project?.id) {
  289. this.issueService.seed(this.project.id!);
  290. const counts = this.issueService.getCounts(this.project.id!);
  291. this.issueCount = counts.total;
  292. }
  293. } catch (e) {
  294. console.warn('蝏蠘恣�桅��圈�憭梯揖:', e);
  295. }
  296. // 4. �㰘蝸蝢方�嚗���𨀣瓷�劐��乩��斉roupId嚗? if (!this.groupChat && this.groupId) {
  297. try {
  298. const gcQuery = new Parse.Query('GroupChat');
  299. this.groupChat = await gcQuery.get(this.groupId);
  300. } catch (err) {
  301. console.warn('�㰘蝸蝢方�憭梯揖:', err);
  302. }
  303. }
  304. // 5. �寞旿憿寧𤌍敶枏��嗆挾霈曄蔭暺䁅恕頝舐眏
  305. const projectStage = this.project.get('currentStage');
  306. const stageMap: any = {
  307. '霈W����': 'order',
  308. '蝖株恕��瘙?: 'requirements',
  309. '�寞�蝖株恕': 'requirements',
  310. '�寞�瘛勗�': 'requirements',
  311. '鈭支��扯�': 'delivery',
  312. '撱箸芋': 'delivery',
  313. '頧航�': 'delivery',
  314. '皜脫�': 'delivery',
  315. '�擧�': 'delivery',
  316. '撠暹狡蝏梶�': 'aftercare',
  317. '摰X�霂�遠': 'aftercare',
  318. '�閗�憭��': 'aftercare'
  319. };
  320. const targetStage = stageMap[projectStage] || 'order';
  321. // 憒��敶枏�瘝⊥�摮鞱楝�梧�頝唾蓮�啣笆摨娪𧫴畾? if (!this.route.firstChild) {
  322. this.router.navigate([targetStage], { relativeTo: this.route, replaceUrl: true });
  323. }
  324. } catch (err: any) {
  325. console.error('�㰘蝸憭梯揖:', err);
  326. this.error = err.message || '�㰘蝸憭梯揖';
  327. } finally {
  328. this.loading = false;
  329. }
  330. }
  331. /**
  332. * ��揢�嗆挾
  333. */
  334. switchStage(stageId: string) {
  335. this.currentStage = stageId;
  336. this.router.navigate([stageId], { relativeTo: this.route });
  337. }
  338. /**
  339. * �瑕��嗆挾�嗆�? */
  340. getStageStatus(stageId: string): 'completed' | 'active' | 'pending' {
  341. // 憸𡏭𠧧�曄內隞���?撌乩�瘚�𠶖�?嚗䔶��𦯀葩�嗆�閫�楝�勗蔣�? const data = this.project?.get('data') || {};
  342. const statuses = data.stageStatuses || {};
  343. const workflowCurrent = this.project?.get('currentStage') || 'order';
  344. console.log('�綫 霈∠��嗆挾�嗆�?', {
  345. stageId,
  346. workflowCurrent,
  347. statuses,
  348. result: statuses[stageId] === 'completed' ? 'completed' : (workflowCurrent === stageId ? 'active' : 'pending')
  349. });
  350. if (statuses[stageId] === 'completed') return 'completed';
  351. if (workflowCurrent === stageId) return 'active';
  352. return 'pending';
  353. }
  354. /**
  355. * 餈𥪜�
  356. */
  357. goBack() {
  358. let ua = navigator.userAgent.toLowerCase();
  359. let isWeixin = ua.indexOf("micromessenger") != -1;
  360. if(isWeixin){
  361. this.router.navigate(['/wxwork', this.cid, 'project-loader']);
  362. }else{
  363. history.back();
  364. }
  365. }
  366. /**
  367. * �湔鰵憿寧𤌍�嗆挾
  368. */
  369. async updateProjectStage(stage: string) {
  370. if (!this.project || !this.canEdit) return;
  371. try {
  372. this.project.set('currentStage', stage);
  373. await this.project.save();
  374. // 瘛餃��嗆挾��蟮
  375. const data = this.project.get('data') || {};
  376. const stageHistory = data.stageHistory || [];
  377. stageHistory.push({
  378. stage,
  379. startTime: new Date(),
  380. status: 'current',
  381. operator: {
  382. id: this.currentUser!.id,
  383. name: this.currentUser!.get('name'),
  384. role: this.role
  385. }
  386. });
  387. this.project.set('data', { ...data, stageHistory });
  388. await this.project.save();
  389. } catch (err) {
  390. console.error('�湔鰵�嗆挾憭梯揖:', err);
  391. window?.fmode?.alert('�湔鰵憭梯揖');
  392. }
  393. }
  394. /**
  395. * �煾���敺格��? */
  396. async sendWxMessage(message: string) {
  397. if (!this.groupChat || !this.wecorp) return;
  398. try {
  399. const chatId = this.groupChat.get('chat_id');
  400. await this.wecorp.appchat.sendText(chatId, message);
  401. } catch (err) {
  402. console.error('�煾����臬仃韐?', err);
  403. }
  404. }
  405. /**
  406. * �㗇𥋘摰X�嚗��蝢方��𣂼�銝剝�㗇𥋘憭㚚��𠉛頂鈭綽�
  407. */
  408. async selectCustomer() {
  409. console.log(this.canEdit, this.groupChat)
  410. if (!this.groupChat) return;
  411. try {
  412. const memberList = this.groupChat.get('member_list') || [];
  413. const externalMembers = memberList.filter((m: any) => m.type === 2);
  414. if (externalMembers.length === 0) {
  415. window?.fmode?.alert('敶枏�蝢方�銝剜瓷�匧��刻�蝟颱犖');
  416. return;
  417. }
  418. console.log(externalMembers)
  419. // 蝞��訫��堆��㗇𥋘蝚砌�銝芸��刻�蝟颱犖
  420. // TODO: 摰䂿緵�㗇𥋘�沃I
  421. const selectedMember = externalMembers[0];
  422. await this.setCustomerFromMember(selectedMember);
  423. } catch (err) {
  424. console.error('�㗇𥋘摰X�憭梯揖:', err);
  425. window?.fmode?.alert('�㗇𥋘摰X�憭梯揖');
  426. }
  427. }
  428. /**
  429. * 隞𡒊黎�𣂼�霈曄蔭摰X�
  430. */
  431. async setCustomerFromMember(member: any) {
  432. if (!this.wecorp) return;
  433. try {
  434. const companyId = this.currentUser?.get('company')?.id || localStorage.getItem("company");
  435. if (!companyId) throw new Error('�䭾��瑕�隡��靽⊥�');
  436. // 1. �亥砭�臬炏撌脣��?ContactInfo
  437. const query = new Parse.Query('ContactInfo');
  438. query.equalTo('external_userid', member.userid);
  439. query.equalTo('company', companyId);
  440. let contactInfo = await query.first();
  441. // 2. 憒��銝滚��剁��朞�隡�凝API�瑕�撟嗅�撱? if (!contactInfo) {
  442. contactInfo = new Parse.Object("ContactInfo");
  443. }
  444. const externalContactData = await this.wecorp.externalContact.get(member.userid);
  445. console.log("externalContactData",externalContactData)
  446. const ContactInfo = Parse.Object.extend('ContactInfo');
  447. contactInfo.set('name', externalContactData.name);
  448. contactInfo.set('external_userid', member.userid);
  449. const company = new Parse.Object('Company');
  450. company.id = companyId;
  451. const companyPointer = company.toPointer();
  452. contactInfo.set('company', companyPointer);
  453. contactInfo.set('data', externalContactData);
  454. await contactInfo.save();
  455. // 3. 霈曄蔭銝粹★�桀恥�? if (this.project) {
  456. this.project.set('contact', contactInfo.toPointer());
  457. await this.project.save();
  458. this.contact = contactInfo;
  459. window?.fmode?.alert('摰X�霈曄蔭�𣂼�');
  460. }
  461. } catch (err) {
  462. console.error('霈曄蔭摰X�憭梯揖:', err);
  463. throw err;
  464. }
  465. }
  466. /**
  467. * �曄內��辣璅⊥���
  468. */
  469. showFiles() {
  470. this.showFilesModal = true;
  471. }
  472. /**
  473. * �曄內�𣂼�璅⊥���
  474. */
  475. showMembers() {
  476. this.showMembersModal = true;
  477. }
  478. /** �曄內�桅�璅⊥��� */
  479. showIssues() {
  480. this.showIssuesModal = true;
  481. }
  482. /**
  483. * �喲𡡒��辣璅⊥���
  484. */
  485. closeFilesModal() {
  486. this.showFilesModal = false;
  487. }
  488. /**
  489. * �喲𡡒�𣂼�璅⊥���
  490. */
  491. closeMembersModal() {
  492. this.showMembersModal = false;
  493. }
  494. /** �曄內摰X�霂行��X踎 */
  495. openContactPanel() {
  496. if (this.contact) {
  497. this.showContactPanel = true;
  498. }
  499. }
  500. /** �喲𡡒摰X�霂行��X踎 */
  501. closeContactPanel() {
  502. this.showContactPanel = false;
  503. }
  504. /** �喲𡡒�桅�璅⊥��� */
  505. closeIssuesModal() {
  506. this.showIssuesModal = false;
  507. if (this.project?.id) {
  508. const counts = this.issueService.getCounts(this.project.id!);
  509. this.issueCount = counts.total;
  510. }
  511. }
  512. /** 摰X��㗇𥋘鈭衤辣�噼�嚗�𦻖�嗅�蝏�辣颲枏枂嚗?*/
  513. onContactSelected(evt: { contact: FmodeObject; isNewCustomer: boolean; action: 'selected' | 'created' | 'updated' }) {
  514. this.contact = evt.contact;
  515. // �齿鰵�㰘蝸�桀㭘�嗆�? this.loadSurveyStatus();
  516. }
  517. /**
  518. * �㰘蝸�桀㭘�嗆�? */
  519. async loadSurveyStatus() {
  520. if (!this.project?.id) return;
  521. try {
  522. const query = new Parse.Query('SurveyLog');
  523. query.equalTo('project', this.project.toPointer());
  524. query.equalTo('type', 'survey-project');
  525. query.equalTo('isCompleted', true);
  526. query.include("contact")
  527. const surveyLog = await query.first();
  528. if (surveyLog) {
  529. this.surveyStatus = {
  530. filled: true,
  531. text: '�亦��桀㭘',
  532. icon: 'checkmark-circle',
  533. surveyLog,
  534. contact:surveyLog?.get("contact")
  535. };
  536. console.log('�?�桀㭘撌脣‵�?);
  537. } else {
  538. this.surveyStatus = {
  539. filled: false,
  540. text: '�煾��䔮�?,
  541. icon: 'document-text-outline'
  542. };
  543. console.log('�?�桀㭘�芸‵�?);
  544. }
  545. } catch (err) {
  546. console.error('�?�亥砭�桀㭘�嗆��仃韐?', err);
  547. }
  548. }
  549. /**
  550. * �煾��䔮�? */
  551. async sendSurvey() {
  552. if (!this.groupChat || !this.wxwork) {
  553. window?.fmode?.alert('�䭾��煾��䔮�?�芣𪄳�啁黎�𦠜�隡�凝SDK�芸�憪见�');
  554. return;
  555. }
  556. try {
  557. const chatId = this.groupChat.get('chat_id');
  558. const surveyUrl = `${document.baseURI}/wxwork/${this.cid}/survey/project/${this.project?.id}`;
  559. await this.wxwork.ww.openExistedChatWithMsg({
  560. chatId: chatId,
  561. msg: {
  562. msgtype: 'link',
  563. link: {
  564. title: '�𠰴振鋆���𨅯㦛�滚𦛚��瘙���亥”�?,
  565. desc: '銝箄悟�祆活�滚𦛚�渲斐������瘙?霂瑁�3-5���憛怠�蝞��剝䔮�?�蠘陝�舀�!',
  566. url: surveyUrl,
  567. imgUrl: `${document.baseURI}/assets/logo.jpg`
  568. }
  569. }
  570. });
  571. window?.fmode?.alert('�桀㭘撌脣����蝢方�!');
  572. } catch (err) {
  573. console.error('�?�煾��䔮�瑕仃韐?', err);
  574. window?.fmode?.alert('�煾��仃韐?霂琿�霂?);
  575. }
  576. }
  577. /**
  578. * �亦��桀㭘蝏𤘪�
  579. */
  580. async viewSurvey() {
  581. if (!this.surveyStatus.surveyLog) return;
  582. // 頝唾蓮�圈䔮�琿△�X䰻�讠��? this.router.navigate(['/wxwork', this.cid, 'survey', 'project', this.project?.id]);
  583. }
  584. /**
  585. * 憭���桀㭘�孵稬
  586. */
  587. async handleSurveyClick(event: Event) {
  588. event.stopPropagation();
  589. if (this.surveyStatus.filled) {
  590. // 撌脣‵�?�亦�蝏𤘪�
  591. await this.viewSurvey();
  592. } else {
  593. // �芸‵�?�煾��䔮�? await this.sendSurvey();
  594. }
  595. }
  596. /**
  597. * �臬炏�曄內摰⊥鸌�X踎
  598. * �∩辣嚗𡁜��滨鍂�瑟糓蝏�鵭 + 憿寧𤌍憭��霈W�����嗆挾 + 摰⊥鸌�嗆��蛹敺�恣�? * �𩤃� 銝湔𧒄�曉����嚗𡁜�霈豢��㕑��脫䰻�见恣�寥𢒰�選�瘚贝��剁�
  599. */
  600. get showApprovalPanel(): boolean {
  601. if (!this.project || !this.currentUser) {
  602. console.log('�� 摰⊥鸌�X踎璉��? 蝻箏�憿寧𤌍�𣇉鍂�瑟㺭�?);
  603. return false;
  604. }
  605. const userRole = this.currentUser.get('roleName') || '';
  606. // �𩤃� 銝湔𧒄瘜券�閫坿𠧧璉��伐���捂���㕑��脰挪�? // const isTeamLeader = userRole === '霈曇恣蝏�鵭' || userRole === 'team-leader';
  607. const isTeamLeader = true; // 銝湔𧒄�曉����
  608. const currentStage = this.project.get('currentStage') || '';
  609. const isOrderStage = currentStage === '霈W����' || currentStage === 'order';
  610. const data = this.project.get('data') || {};
  611. const approvalStatus = data.approvalStatus;
  612. const isPending = approvalStatus === 'pending';
  613. console.log('�� 摰⊥鸌�X踎璉��?[銝湔𧒄�曉����]:', {
  614. userRole,
  615. isTeamLeader,
  616. currentStage,
  617. isOrderStage,
  618. approvalStatus,
  619. isPending,
  620. result: isTeamLeader && isOrderStage && isPending
  621. });
  622. return isTeamLeader && isOrderStage && isPending;
  623. }
  624. /**
  625. * 憭��摰⊥鸌摰峕�鈭衤辣
  626. */
  627. async onApprovalCompleted(event: { action: 'approved' | 'rejected'; reason?: string; comment?: string }) {
  628. if (!this.project) return;
  629. try {
  630. const data = this.project.get('data') || {};
  631. const approvalHistory = data.approvalHistory || [];
  632. const latestRecord = approvalHistory[approvalHistory.length - 1];
  633. if (latestRecord) {
  634. latestRecord.status = event.action;
  635. latestRecord.approver = {
  636. id: this.currentUser?.id,
  637. name: this.currentUser?.get('name'),
  638. role: this.currentUser?.get('roleName')
  639. };
  640. latestRecord.approvalTime = new Date();
  641. latestRecord.comment = event.comment;
  642. latestRecord.reason = event.reason;
  643. }
  644. if (event.action === 'approved') {
  645. // �朞�摰⊥鸌嚗𡁏綫餈𥕦�蝖株恕��瘙�𧫴畾? data.approvalStatus = 'approved';
  646. this.project.set('currentStage', '蝖株恕��瘙?);
  647. this.project.set('data', data);
  648. await this.project.save();
  649. alert('�?摰⊥鸌�朞�嚗屸★�桀歇餈𥕦�蝖株恕��瘙�𧫴畾?);
  650. // �瑟鰵憿菟𢒰�唳旿
  651. await this.loadData();
  652. } else {
  653. // 撽喳�嚗帋���銁霈W�����嗆挾嚗諹扇敶閖底�𧼮��? data.approvalStatus = 'rejected';
  654. data.lastRejectionReason = event.reason || '�芣�靘𥕦��?;
  655. this.project.set('data', data);
  656. await this.project.save();
  657. alert('�?撌脤底�噼恥�𤏪�摰X�撠�𤣰�圈�𡁶䰻');
  658. // �瑟鰵憿菟𢒰�唳旿
  659. await this.loadData();
  660. }
  661. } catch (err) {
  662. console.error('憭��摰⊥鸌憭梯揖:', err);
  663. alert('摰⊥鸌�滢�憭梯揖嚗諹窈�滩�');
  664. }
  665. }
  666. }
  667. // duplicate inline CustomerSelectorComponent removed (we keep single declaration above)