unprogrammed-visits.component.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. import { Component, OnInit, ViewChild } from '@angular/core';
  2. import { MatTableDataSource } from '@angular/material/table';
  3. import {
  4. UnprogrammedVisit,
  5. UnprogrammedVisitsResponse,
  6. } from '../../../interfaces/unprogrammed-visits.interface';
  7. import { MatPaginator } from '@angular/material/paginator';
  8. import { MatSort } from '@angular/material/sort';
  9. import { EncService } from '../../../services/enc.service';
  10. import { PreventiveMaintenanceService } from '../../../services/preventive-maintenance.service';
  11. import { lastValueFrom } from 'rxjs';
  12. import { Router } from '@angular/router';
  13. import { PriorityInterface } from '../../system-admin/system-params/system-params.component';
  14. import { SystemAdminService } from '../../../services/system-admin.service';
  15. import { FunctionsService } from '../../../services/functions.service';
  16. import { MatDialog } from '@angular/material/dialog';
  17. import { PreventiveOrderDetailsComponent } from '../preventive-order-details/preventive-order-details.component';
  18. import { ResourcesService } from '../../../services/resources.service';
  19. import { AlertComponent } from '../../resources/dialogs/alert/alert.component';
  20. import { CommnetsDialogComponent } from '../../corrective-maintenance/operations-management/commnets-dialog/commnets-dialog.component';
  21. @Component({
  22. selector: 'app-unprogrammed-visits',
  23. templateUrl: './unprogrammed-visits.component.html',
  24. styleUrl: './unprogrammed-visits.component.css',
  25. standalone: false,
  26. })
  27. export class UnprogrammedVisitsComponent implements OnInit {
  28. btnSmall: boolean;
  29. txtBuscador: string;
  30. dataSource?: MatTableDataSource<UnprogrammedVisit>;
  31. displayedColumns = [
  32. 'numeracion',
  33. 'equipamiento',
  34. 'descripcion',
  35. 'estado',
  36. 'prioridad',
  37. 'tipoActivacion',
  38. 'fechaRegistro',
  39. 'acciones',
  40. ];
  41. @ViewChild(MatPaginator) paginator?: MatPaginator;
  42. @ViewChild(MatSort) sort?: MatSort;
  43. isLoading: boolean;
  44. hasError: boolean;
  45. errorStr: string;
  46. priorityMap: Map<string, PriorityInterface>;
  47. private readonly activationTypeLabels: Record<string, string> = {
  48. A: 'Automática',
  49. M: 'Manual',
  50. };
  51. private readonly statusLabels: Record<string, string> = {
  52. VA: 'Validado',
  53. EP: 'En proceso',
  54. CP: 'Cerrado pendiente',
  55. CE: 'Cerrado',
  56. P: 'Pendiente',
  57. C: 'Cancelado',
  58. R: 'Rechazado',
  59. A: 'Aprobado',
  60. F: 'Finalizado',
  61. };
  62. constructor(
  63. private _encService: EncService,
  64. private _prevMaintService: PreventiveMaintenanceService,
  65. private _router: Router,
  66. private _sysAdminService: SystemAdminService,
  67. private _functionsService: FunctionsService,
  68. private _dialog: MatDialog,
  69. private _resourcesService: ResourcesService
  70. ) {
  71. this.btnSmall = window.innerWidth <= 1530;
  72. this.txtBuscador = '';
  73. this.isLoading = true;
  74. this.hasError = false;
  75. this.errorStr = '';
  76. this.dataSource = new MatTableDataSource();
  77. this.priorityMap = new Map();
  78. }
  79. ngOnInit(): void {
  80. this.getOrderPriorities();
  81. }
  82. async getOrderPriorities() {
  83. try {
  84. let idUser = localStorage.getItem('idusuario')!;
  85. let priorities = await lastValueFrom(
  86. this._sysAdminService.getOrderPriorities(idUser, 1)
  87. );
  88. if (!priorities.error) {
  89. this.priorityMap.clear();
  90. const prioritiesArr = priorities.response?.order_priorities as
  91. | PriorityInterface[]
  92. | undefined;
  93. if (prioritiesArr?.length) {
  94. for (const item of prioritiesArr) {
  95. const valueKey = `${item.value}`.trim();
  96. const formattedPriority: PriorityInterface = {
  97. ...item,
  98. value: valueKey,
  99. label: `${item.label} (${item.value})`,
  100. };
  101. this.priorityMap.set(valueKey, formattedPriority);
  102. }
  103. }
  104. }
  105. } catch (error) {
  106. console.error('Error loading order priorities', error);
  107. } finally {
  108. this.getUnprogrammedVisits();
  109. }
  110. }
  111. async getUnprogrammedVisits() {
  112. try {
  113. this.isLoading = true;
  114. this.hasError = false;
  115. this.errorStr = '';
  116. let idUser = localStorage.getItem('idusuario')!;
  117. let visits: UnprogrammedVisitsResponse = await lastValueFrom(
  118. this._prevMaintService.getUnprogrammedVisits(idUser, 1)
  119. );
  120. this.hasError = visits.error;
  121. this.errorStr = visits.msg;
  122. if (!this.hasError) {
  123. let visitsArr: UnprogrammedVisit[] = [];
  124. for (const visit of visits.response) {
  125. visit.IDVISITA = await this._encService.decrypt(visit.IDVISITA);
  126. let equipmentCodeDec = await this._encService.decrypt(
  127. visit.EQUIPAMIENTO
  128. );
  129. visit.TIPO_EQUIPAMIENTO = `${equipmentCodeDec} - ${visit.TIPO_EQUIPAMIENTO}`;
  130. let equipmentIDDec = await this._encService.decrypt(
  131. visit.ID_EQUIPAMIENTO
  132. );
  133. visit.MODELO_EQUIPAMIENTO = `${visit.MODELO_EQUIPAMIENTO} (${equipmentIDDec})`;
  134. if (visit.PRIORIDAD) {
  135. const priorityValue = await this.decryptWithFallback(
  136. visit.PRIORIDAD
  137. );
  138. visit.PRIORIDAD = priorityValue;
  139. const priorityInfo = this.priorityMap.get(priorityValue.trim());
  140. if (priorityInfo) {
  141. visit.PRIORIDAD_OBJ = priorityInfo;
  142. }
  143. }
  144. const activationKey = (visit.TIPO_ACTIVACION || '').toUpperCase();
  145. visit.TIPO_ACTIVACION_LABEL =
  146. this.activationTypeLabels[activationKey] ||
  147. visit.TIPO_ACTIVACION ||
  148. 'N/A';
  149. const statusKey = (visit.ESTADO || '').toUpperCase();
  150. visit.ESTADO_LABEL =
  151. this.statusLabels[statusKey] || visit.ESTADO || 'N/A';
  152. if (visit.FECREG) {
  153. visit.FECREG_FMT = this._functionsService.orderDate(visit.FECREG);
  154. if (!visit.FECREG_FMT) {
  155. visit.FECREG_FMT = visit.FECREG;
  156. }
  157. }
  158. visitsArr.push(visit);
  159. }
  160. this.dataSource = new MatTableDataSource(visitsArr);
  161. this.dataSource.paginator = this.paginator!;
  162. this.dataSource.sort = this.sort!;
  163. }
  164. this.isLoading = false;
  165. } catch (error: any) {
  166. if (error.error == undefined) {
  167. this.errorStr = 'Ocurrió un error inesperado.';
  168. } else if (error.error.msg == undefined) {
  169. this.errorStr = 'Ocurrió un error inesperado.';
  170. } else {
  171. this.errorStr = error.error.msg;
  172. }
  173. this.isLoading = false;
  174. this.hasError = true;
  175. }
  176. }
  177. public onResize(): void {
  178. this.btnSmall = window.innerWidth <= 1530;
  179. }
  180. goBack(steps: number) {
  181. window.history.go(steps * -1);
  182. }
  183. applyFilter(event: any, type: string) {
  184. let filterValue: string;
  185. if (type === 'INP') {
  186. filterValue = (event.target as HTMLInputElement).value;
  187. } else {
  188. this.txtBuscador = event;
  189. filterValue = event;
  190. }
  191. this.dataSource!.filter = filterValue.trim().toLowerCase();
  192. if (this.dataSource?.paginator) {
  193. this.dataSource.paginator.firstPage();
  194. }
  195. }
  196. registerVisit() {
  197. this._router.navigate(['sam/GMPR/AOTR/RVTP/register']);
  198. }
  199. async openVisitDetails(idVisit: string) {
  200. try {
  201. const idVisitEnc = await this._encService.encrypt(idVisit);
  202. this._dialog.open(PreventiveOrderDetailsComponent, {
  203. width: '480px',
  204. maxWidth: '480px',
  205. data: {
  206. idOrder: idVisitEnc,
  207. },
  208. });
  209. } catch (error) {
  210. console.error('Error opening visit details dialog', error);
  211. }
  212. }
  213. startVisitExecution(visit: UnprogrammedVisit) {
  214. const visitId = `${visit.IDVISITA ?? ''}`.trim();
  215. if (!visitId) {
  216. console.warn('startVisitExecution: missing visit ID', visit);
  217. this._resourcesService.openSnackBar('No se pudo identificar la visita.');
  218. return;
  219. }
  220. const statusKey = (visit.ESTADO || '').toUpperCase();
  221. const allowedStatuses = ['A', 'VA'];
  222. if (statusKey && !allowedStatuses.includes(statusKey)) {
  223. this._resourcesService.openSnackBar(
  224. 'La visita debe estar aprobada para iniciar la ejecución.'
  225. );
  226. return;
  227. }
  228. const dialogRef = this._dialog.open(AlertComponent, {
  229. width: '420px',
  230. maxWidth: '420px',
  231. disableClose: true,
  232. data: {
  233. title: 'Confirmación',
  234. icon: 'warning',
  235. description: `¿Está seguro de iniciar la ejecución de la visita #${visitId}?`,
  236. },
  237. });
  238. dialogRef.afterClosed().subscribe((res) => {
  239. if (res === true) {
  240. this.verifyVisitStatus(visitId);
  241. }
  242. });
  243. }
  244. private async verifyVisitStatus(idVisit: string) {
  245. try {
  246. this._resourcesService.openSnackBar('Verificando visita...');
  247. const idVisitEnc = await this._encService.encrypt(idVisit);
  248. const idUser = localStorage.getItem('idusuario')!;
  249. const attendanceResp = await lastValueFrom(
  250. this._prevMaintService.getVisitAttendance(idVisitEnc, idUser, 1)
  251. );
  252. if (attendanceResp?.error) {
  253. this._resourcesService.openSnackBar(
  254. attendanceResp.msg || 'No fue posible obtener la asistencia.'
  255. );
  256. return;
  257. }
  258. const attendanceList = Array.isArray(attendanceResp?.response)
  259. ? attendanceResp.response
  260. : [];
  261. if (!attendanceList.length) {
  262. this._resourcesService.openSnackBar(
  263. 'Ninguno de los operarios involucrados ha respondido a la solicitud.'
  264. );
  265. return;
  266. }
  267. const staffResp = await lastValueFrom(
  268. this._prevMaintService.getVisitStaff(idVisitEnc, idUser, 1)
  269. );
  270. if (staffResp?.error) {
  271. this._resourcesService.openSnackBar(
  272. staffResp.msg || 'No fue posible obtener el personal asignado.'
  273. );
  274. return;
  275. }
  276. const staffList = Array.isArray(staffResp?.response)
  277. ? staffResp.response
  278. : [];
  279. const requiredUsers = staffList.reduce((acc: number, item: any) => {
  280. const amount = Number(item?.CANT ?? item?.cant ?? 0);
  281. return acc + (isNaN(amount) ? 0 : amount);
  282. }, 0);
  283. const startedUsers = attendanceList.reduce((acc: number, item: any) => {
  284. const response = (item?.RESPUESTA || item?.respuesta || '').toString();
  285. return acc + (response.toUpperCase() === 'A' ? 1 : 0);
  286. }, 0);
  287. if (requiredUsers > startedUsers) {
  288. this._resourcesService.openSnackBar(
  289. 'Algunos operarios involucrados no han respondido a la solicitud.'
  290. );
  291. return;
  292. }
  293. await this.startVisit(idVisit);
  294. } catch (error: any) {
  295. console.error('verifyVisitStatus error', error);
  296. this._resourcesService.openSnackBar(
  297. error?.error?.msg || 'No fue posible verificar la visita.'
  298. );
  299. }
  300. }
  301. private async startVisit(idVisit: string) {
  302. try {
  303. const idUser = localStorage.getItem('idusuario')!;
  304. const idVisitEnc = await this._encService.encrypt(idVisit);
  305. const formData = new FormData();
  306. formData.append('id_user', idUser);
  307. formData.append('linea', '1');
  308. formData.append('id_visit', idVisitEnc);
  309. const response = await lastValueFrom(
  310. this._prevMaintService.startVisit(formData)
  311. );
  312. if (response?.error) {
  313. this._resourcesService.openSnackBar(
  314. response.msg || 'Ocurrió un error al iniciar la ejecución.'
  315. );
  316. } else {
  317. this._resourcesService.openSnackBar(
  318. response?.msg || 'La ejecución de la visita se inició correctamente.'
  319. );
  320. this.getUnprogrammedVisits();
  321. }
  322. } catch (error: any) {
  323. console.error('startVisit error', error);
  324. const message =
  325. error?.error?.msg ||
  326. 'Ocurrió un error inesperado al iniciar la visita.';
  327. this._resourcesService.openSnackBar(message);
  328. this.getUnprogrammedVisits();
  329. }
  330. }
  331. private async decryptWithFallback(
  332. value: string | null | undefined
  333. ): Promise<string> {
  334. if (!value) {
  335. return '';
  336. }
  337. try {
  338. const decrypted = await this._encService.decrypt(value);
  339. return decrypted.trim();
  340. } catch (error) {
  341. return `${value}`.trim();
  342. }
  343. }
  344. openCloseVisitDialog(visit: UnprogrammedVisit) {
  345. const visitId = `${visit.IDVISITA ?? ''}`.trim();
  346. if (!visitId) {
  347. this._resourcesService.openSnackBar('No se pudo identificar la visita.');
  348. return;
  349. }
  350. const dialogRef = this._dialog.open(CommnetsDialogComponent, {
  351. width: '480px',
  352. maxWidth: '480px',
  353. disableClose: true,
  354. data: {
  355. idOrder: visitId,
  356. status: 'Cerrar',
  357. },
  358. });
  359. dialogRef.afterClosed().subscribe((res) => {
  360. if (res != undefined && res != null && res != '') {
  361. if (res.length < 15) {
  362. this._resourcesService.openSnackBar(
  363. 'Los comentarios deben tener al menos 15 caracteres.'
  364. );
  365. return;
  366. }
  367. this.closeVisit(visitId, res);
  368. }
  369. });
  370. }
  371. openFinalCloseDialog(visit: UnprogrammedVisit) {
  372. const visitId = `${visit.IDVISITA ?? ''}`.trim();
  373. if (!visitId) {
  374. this._resourcesService.openSnackBar('No se pudo identificar la visita.');
  375. return;
  376. }
  377. // Primera confirmación
  378. const confirmDialogRef = this._dialog.open(AlertComponent, {
  379. width: '420px',
  380. maxWidth: '420px',
  381. disableClose: true,
  382. data: {
  383. title: 'Confirmación',
  384. icon: 'warning',
  385. description: `¿Está seguro de realizar el cierre final de la visita #${visitId}?`,
  386. },
  387. });
  388. confirmDialogRef.afterClosed().subscribe((res) => {
  389. if (res === true) {
  390. this.finalCloseVisit(visitId);
  391. }
  392. });
  393. }
  394. private async closeVisit(idVisit: string, comment: string) {
  395. try {
  396. const idUser = localStorage.getItem('idusuario')!;
  397. const idVisitEnc = await this._encService.encrypt(idVisit);
  398. const formData = new FormData();
  399. formData.append('id_user', idUser);
  400. formData.append('linea', '1');
  401. formData.append('id_visit', idVisitEnc);
  402. formData.append('comment', comment);
  403. const response = await lastValueFrom(
  404. this._prevMaintService.registerOperatorClosingComment(formData)
  405. );
  406. if (response?.error) {
  407. this._resourcesService.openSnackBar(
  408. response.msg || 'Ocurrió un error al cerrar la visita.'
  409. );
  410. } else {
  411. this._resourcesService.openSnackBar(
  412. response?.msg || 'La visita se cerró correctamente.'
  413. );
  414. }
  415. this.getUnprogrammedVisits();
  416. } catch (error: any) {
  417. if (error.error == undefined) {
  418. this._resourcesService.openSnackBar('Ocurrió un error inesperado.');
  419. } else if (error.error.msg == undefined) {
  420. this._resourcesService.openSnackBar('Ocurrió un error inesperado.');
  421. } else {
  422. this._resourcesService.openSnackBar(error.error.msg);
  423. }
  424. this.getUnprogrammedVisits();
  425. }
  426. }
  427. private async finalCloseVisit(idVisit: string) {
  428. try {
  429. //Se recupera encriptado
  430. const idUser = localStorage.getItem('idusuario')!;
  431. const idVisitEnc = await this._encService.encrypt(idVisit);
  432. const formData = new FormData();
  433. formData.append('id_user', idUser);
  434. formData.append('linea', '1');
  435. formData.append('id_visit', idVisitEnc);
  436. formData.append('status', 'CE');
  437. const response = await lastValueFrom(
  438. this._prevMaintService.updateVisitStatus(formData)
  439. );
  440. if (response?.error) {
  441. this._resourcesService.openSnackBar(
  442. response.msg || 'Ocurrió un error al realizar el cierre final.'
  443. );
  444. } else {
  445. this._resourcesService.openSnackBar(
  446. response?.msg || 'El cierre final se realizó correctamente.'
  447. );
  448. }
  449. this.getUnprogrammedVisits();
  450. } catch (error: any) {
  451. if (error.error == undefined) {
  452. this._resourcesService.openSnackBar('Ocurrió un error inesperado.');
  453. } else if (error.error.msg == undefined) {
  454. this._resourcesService.openSnackBar('Ocurrió un error inesperado.');
  455. } else {
  456. this._resourcesService.openSnackBar(error.error.msg);
  457. }
  458. this.getUnprogrammedVisits();
  459. }
  460. }
  461. getRowIndex(index: number): number {
  462. const pageIndex = this.paginator?.pageIndex ?? 0;
  463. const pageSize = this.paginator?.pageSize ?? 10;
  464. return pageIndex * pageSize + index + 1;
  465. }
  466. }