Преглед на файлове

componente de analisis de costos, recupera y pinta data ya formateada, permite registro de costo para operarios y muestra totales en tiempo real. Funcionando

EmilianoOrtiz преди 2 дни
родител
ревизия
ad0e946df6

+ 2 - 0
src/app/app.module.ts

@@ -461,6 +461,7 @@ import { MultipleCorrectiveUploadComponent } from './components/initial-data-upl
 import { PreviewDocumentsComponent } from './components/initial-data-upload/preview-documents/preview-documents.component';
 import { PreviewFileContentComponent } from './components/initial-data-upload/preview-file-content/preview-file-content.component';
 import { StaffSelectionComponent } from './components/corrective-maintenance/operations-management/staff-selection/staff-selection.component';
+import { CostAnalysisModalComponent } from './components/corrective-maintenance/operations-management/cost-analysis-modal/cost-analysis-modal.component';
 import { SignatureViewComponent } from './components/template/notification-dialog/signature-view/signature-view.component';
 import { CommentsViewComponent } from './components/template/notification-dialog/comments-view/comments-view.component';
 import { AgreeOrderComponent } from './components/template/notification-dialog/agree-order/agree-order.component';
@@ -856,6 +857,7 @@ import { HelpDialogComponent } from './components/template/help-dialog/help-dial
     MultipleUploadComponent,
     PreviewExcelDocumentComponent,
     StaffSelectionComponent,
+    CostAnalysisModalComponent,
     ZipfileUploadComponent,
     IndividualPreventiveUploadComponent,
     MultiplePreventiveUploadComponent,

+ 74 - 0
src/app/components/corrective-maintenance/operations-management/cost-analysis-modal/cost-analysis-modal.component.css

@@ -0,0 +1,74 @@
+.full-width {
+  width: 100%;
+}
+
+.cost-analysis-content {
+  max-height: 600px;
+  overflow-y: auto;
+}
+
+.users-section {
+  margin-bottom: 20px;
+}
+
+.user-row {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 10px;
+}
+
+.user-name {
+  min-width: 120px;
+  font-weight: 500;
+}
+
+.cost-input {
+  width: 100px;
+}
+
+.currency-select {
+  width: 80px;
+}
+
+.items-table {
+  margin-bottom: 10px;
+}
+
+.item-row {
+  display: flex;
+  justify-content: space-between;
+  padding: 8px 0;
+  border-bottom: 1px solid #eee;
+}
+
+.item-name {
+  flex: 2;
+  font-weight: 500;
+}
+
+.item-cost, .item-qty, .item-subtotal {
+  flex: 1;
+  text-align: right;
+}
+
+.subtotal-section, .totals-section {
+  margin: 15px 0;
+  padding: 10px;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+}
+
+.subtotal-row, .total-row {
+  text-align: right;
+  margin: 5px 0;
+}
+
+.comments-field {
+  margin-top: 20px;
+}
+
+h3 {
+  margin: 20px 0 10px 0;
+  color: #333;
+}

+ 93 - 0
src/app/components/corrective-maintenance/operations-management/cost-analysis-modal/cost-analysis-modal.component.html

@@ -0,0 +1,93 @@
+<h1 mat-dialog-title class="prevent-select">Análisis de costos</h1>
+
+<div mat-dialog-content class="cost-analysis-content">
+  @if (costData) {
+    <!-- Usuarios -->
+    @if (costData.USUARIOS && costData.USUARIOS.length > 0) {
+      <h3>Usuarios</h3>
+      <div class="users-section">
+        @for (user of costData.USUARIOS; track user.ID) {
+          <div class="user-row">
+            <span class="user-name">{{ user.NOMBRE }}</span>
+            <mat-form-field appearance="outline" class="cost-input">
+              <mat-label>Costo</mat-label>
+              <input matInput type="number" [(ngModel)]="userCosts[user.ID].amount" (ngModelChange)="onUserCostChange()">
+            </mat-form-field>
+            <mat-form-field appearance="outline" class="currency-select">
+              <mat-label>Moneda</mat-label>
+              <mat-select [(ngModel)]="userCosts[user.ID].currency" (selectionChange)="onUserCostChange()">
+                @for (currency of currencies; track currency) {
+                  <mat-option [value]="currency">{{ currency }}</mat-option>
+                }
+              </mat-select>
+            </mat-form-field>
+          </div>
+        }
+      </div>
+    }
+
+    <!-- Herramientas -->
+    @if (costData.HERRAMIENTAS && costData.HERRAMIENTAS.length > 0) {
+      <h3>Herramientas</h3>
+      <div class="items-table">
+        @for (tool of costData.HERRAMIENTAS; track tool.ID) {
+          <div class="item-row">
+            <span class="item-name">{{ tool.NOMBRE }}</span>
+            <span class="item-cost">{{ tool.PRECIO_UNITARIO }} {{ tool.MONEDA_SIMBOLO }}</span>
+            <span class="item-qty">Cant: {{ tool.CANTIDAD }}</span>
+            <span class="item-subtotal">{{ tool.SUBTOTAL }} {{ tool.MONEDA_SIMBOLO }}</span>
+          </div>
+        }
+      </div>
+      <div class="subtotal-section">
+        @for (subtotal of costData.SUBTOTAL_HERRAMIENTAS; track subtotal.SIMBOLO) {
+          <div class="subtotal-row">
+            <strong>Subtotal Herramientas: {{ subtotal.SUBTOTAL }} {{ subtotal.SIMBOLO }}</strong>
+          </div>
+        }
+      </div>
+    }
+
+    <!-- Refacciones -->
+    @if (costData.REFACCIONES && costData.REFACCIONES.length > 0) {
+      <h3>Refacciones</h3>
+      <div class="items-table">
+        @for (part of costData.REFACCIONES; track part.ID) {
+          <div class="item-row">
+            <span class="item-name">{{ part.NOMBRE }}</span>
+            <span class="item-cost">{{ part.PRECIO_UNITARIO }} {{ part.MONEDA_SIMBOLO }}</span>
+            <span class="item-qty">Cant: {{ part.CANTIDAD }}</span>
+            <span class="item-subtotal">{{ part.SUBTOTAL }} {{ part.MONEDA_SIMBOLO }}</span>
+          </div>
+        }
+      </div>
+      <div class="subtotal-section">
+        @for (subtotal of costData.SUBTOTAL_REFACCIONES; track subtotal.SIMBOLO) {
+          <div class="subtotal-row">
+            <strong>Subtotal Refacciones: {{ subtotal.SUBTOTAL }} {{ subtotal.SIMBOLO }}</strong>
+          </div>
+        }
+      </div>
+    }
+
+    <!-- Totales -->
+    <div class="totals-section">
+      <h3>Total</h3>
+      @for (total of totals | keyvalue; track total.key) {
+        <div class="total-row">
+          <strong>Total {{ total.key }}: {{ total.value }}</strong>
+        </div>
+      }
+    </div>
+  }
+
+  <mat-form-field appearance="outline" class="full-width comments-field">
+    <mat-label>Comentarios</mat-label>
+    <textarea matInput [(ngModel)]="comments" rows="3" placeholder="Ingrese sus comentarios..."></textarea>
+  </mat-form-field>
+</div>
+
+<div mat-dialog-actions align="end">
+  <button mat-button (click)="onCancel()">Cancelar</button>
+  <button mat-button color="primary" (click)="onSave()">Guardar</button>
+</div>

+ 102 - 0
src/app/components/corrective-maintenance/operations-management/cost-analysis-modal/cost-analysis-modal.component.ts

@@ -0,0 +1,102 @@
+import { Component, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+
+@Component({
+  selector: 'app-cost-analysis-modal',
+  templateUrl: './cost-analysis-modal.component.html',
+  styleUrl: './cost-analysis-modal.component.css',
+  standalone: false
+})
+export class CostAnalysisModalComponent {
+  comments: string = '';
+  costData: any = null;
+  currencies = ['MXN', 'USD'];
+  userCosts: { [key: string]: { amount: number, currency: string } } = {};
+  totals: { [key: string]: number } = {};
+
+  constructor(
+    public dialogRef: MatDialogRef<CostAnalysisModalComponent>,
+    @Inject(MAT_DIALOG_DATA) public data: { idOrder: string, costData?: any }
+  ) {
+    this.costData = data.costData;
+    this.initializeUserCosts();
+    this.calculateTotals();
+  }
+
+  initializeUserCosts(): void {
+    if (this.costData?.USUARIOS) {
+      this.costData.USUARIOS.forEach((user: any) => {
+        this.userCosts[user.ID] = { amount: user.COSTO || 0, currency: 'MXN' };
+      });
+    }
+  }
+
+  calculateTotals(): void {
+    this.totals = {};
+    
+    // Add tools subtotals
+    if (this.costData?.SUBTOTAL_HERRAMIENTAS) {
+      this.costData.SUBTOTAL_HERRAMIENTAS.forEach((item: any) => {
+        this.totals[item.SIMBOLO] = (this.totals[item.SIMBOLO] || 0) + item.SUBTOTAL;
+      });
+    }
+    
+    // Add parts subtotals
+    if (this.costData?.SUBTOTAL_REFACCIONES) {
+      this.costData.SUBTOTAL_REFACCIONES.forEach((item: any) => {
+        this.totals[item.SIMBOLO] = (this.totals[item.SIMBOLO] || 0) + item.SUBTOTAL;
+      });
+    }
+    
+    // Add user costs
+    Object.values(this.userCosts).forEach((userCost: any) => {
+      this.totals[userCost.currency] = (this.totals[userCost.currency] || 0) + userCost.amount;
+    });
+  }
+
+  onUserCostChange(): void {
+    this.calculateTotals();
+  }
+
+  onCancel(): void {
+    this.dialogRef.close();
+  }
+
+  onSave(): void {
+    const payload = this.prepareSavePayload();
+    this.dialogRef.close({ payload, comments: this.comments, userCosts: this.userCosts });
+  }
+
+  prepareSavePayload(): any {
+    const userSubtotals: { [key: string]: number } = {};
+    const usuarios = this.costData?.USUARIOS?.map((user: any) => ({
+      ID: user.ID,
+      COSTO: this.userCosts[user.ID]?.amount || 0,
+      MONEDA: this.userCosts[user.ID]?.currency || 'MXN'
+    })) || [];
+
+    // Calculate user subtotals by currency
+    Object.values(this.userCosts).forEach((userCost: any) => {
+      userSubtotals[userCost.currency] = (userSubtotals[userCost.currency] || 0) + userCost.amount;
+    });
+
+    const subtotalUsuarios = Object.keys(userSubtotals).map(currency => ({
+      SIMBOLO: currency,
+      SUBTOTAL: userSubtotals[currency]
+    }));
+
+    return {
+      COMMENTS: this.comments,
+      USUARIOS: usuarios,
+      HERRAMIENTAS: this.costData?.HERRAMIENTAS || [],
+      REFACCIONES: this.costData?.REFACCIONES || [],
+      SUBTOTAL_USUARIOS: subtotalUsuarios,
+      SUBTOTAL_HERRAMIENTAS: this.costData?.SUBTOTAL_HERRAMIENTAS || [],
+      SUBTOTAL_REFACCIONES: this.costData?.SUBTOTAL_REFACCIONES || [],
+      TOTAL: Object.keys(this.totals).map(currency => ({
+        SIMBOLO: currency,
+        TOTAL: this.totals[currency]
+      }))
+    };
+  }
+}

+ 1 - 1
src/app/components/corrective-maintenance/operations-management/operations-management.component.html

@@ -153,7 +153,7 @@
                   <span>Ver detalles</span>
                 </button>
                 @if (row.ESTADO != 'Cerrado') {
-                  <button mat-menu-item>
+                  <button mat-menu-item (click)="openCostAnalysisModal(row.ID_ORDEN)">
                     <mat-icon>attach_money</mat-icon>
                     <span>Asignar análisis de costos</span>
                   </button>

+ 49 - 0
src/app/components/corrective-maintenance/operations-management/operations-management.component.ts

@@ -30,6 +30,7 @@ import { NoMeasuresOrderReportComponent } from './no-measures-order-report/no-me
 import { GdelService } from '../../../services/gdel.service';
 
 import { StaffSelectionComponent } from './staff-selection/staff-selection.component';
+import { CostAnalysisModalComponent } from './cost-analysis-modal/cost-analysis-modal.component';
 
 const ID_MODULE = "S002V01M09GMCO";
 @Component({
@@ -1098,6 +1099,54 @@ export class OperationsManagementComponent implements OnInit {
     });
   }
 
+  async openCostAnalysisModal(idOrder: string): Promise<void> {
+    let idOrderStr = idOrder.replace('Orden #', '').trim();
+    let idOrderEnc = await this._encService.encrypt(idOrderStr);
+    let idUser = localStorage.getItem('idusuario')!;
+    let costData = null;
+    
+    try {
+      let costAnalysisResponse = await lastValueFrom(this._correctiveMaintenanceService.getCostAnalysis(idOrderEnc, idUser, 1));
+      console.log('Cost analysis data:', costAnalysisResponse);
+      costData = costAnalysisResponse.response;
+    } catch(error) {
+      console.error('Error getting cost analysis:', error);
+    }
+
+    let dialogRef = this._dialog.open(CostAnalysisModalComponent, {
+      width: '800px',
+      maxWidth: '800px',
+      data: {
+        idOrder: idOrderStr,
+        costData: costData
+      }
+    });
+
+    dialogRef.afterClosed().subscribe(async res => {
+      if(res?.payload){
+        try {
+          const payload = {
+            id_user: idUser,
+            linea: 1,
+            id_order: idOrderEnc,
+            ...res.payload
+          };
+          
+          const response = await lastValueFrom(this._correctiveMaintenanceService.saveCostAnalysis(payload));
+          this._resourcesService.openSnackBar('Análisis de costos guardado correctamente.');
+          console.log('Save cost analysis response:', response);
+        } catch(error: any) {
+          if(error.error?.msg) {
+            this._resourcesService.openSnackBar(error.error.msg);
+          } else {
+            this._resourcesService.openSnackBar('Error al guardar el análisis de costos.');
+          }
+          console.error('Error saving cost analysis:', error);
+        }
+      }
+    });
+  }
+
   private async generateNoMeasuresReport(idOrder: string, reportType: string, reportData: string){
     try{
       this._resourcesService.openSnackBar('Generando reporte...');

+ 8 - 0
src/app/services/corrective-maintenance.service.ts

@@ -82,6 +82,14 @@ export class CorrectiveMaintenanceService {
     return this.getQuery(`corrective-maintenance/get-work-order-attendance/${idOrder}/${idUser}/${line}`).pipe(map((data: any) => data))
   }
 
+  getCostAnalysis(idOrder: string, idUser: string, line: number){
+    return this.getQuery(`corrective-maintenance/get-cost-analysis/${idOrder}/${idUser}/${line}`).pipe(map((data: any) => data))
+  }
+
+  saveCostAnalysis(body: any){
+    return this.postQuery("corrective-maintenance/save-cost-analysis", body).pipe(map((data: any) => data))
+  }
+
   registerWorkOrder(body: any){
     return this.postQuery("corrective-maintenance/register-work-order", body).pipe(map((data: any) => data))
   }