Files
FA_WEB/FaKrosnoManagerAngular/src/app/packing-lists/meyle/meyle-pack-list-simple.component.ts
Piotr Kus 89d74428b2 FaKrosnoAngular
* Added project
2026-08-08 13:39:17 +02:00

218 lines
10 KiB
TypeScript

import { Component, ElementRef, inject, OnInit, signal, viewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { ButtonDirective } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext';
import { DialogModule } from 'primeng/dialog';
import { TransactionModel, WzRowMeyleDto } from '../../core/models';
import { WarehouseService } from '../../core/services/warehouse.service';
import { findCombinations, isValidMeyleScannedValue } from '../../shared/utils/business-logic';
import { PageShellComponent } from '../../shared/components/page-shell/page-shell.component';
import { ContentSectionComponent } from '../../shared/components/content-section/content-section.component';
@Component({
selector: 'app-meyle-pack-list-simple',
standalone: true,
imports: [ButtonDirective, InputTextModule, DialogModule, FormsModule, PageShellComponent, ContentSectionComponent],
template: `
<app-page-shell
title="Packing List"
subtitle="Packing list Meyle — widok uproszczony ze skanowaniem"
icon="pi pi-box"
>
<div actions>
<button pButton type="button" (click)="changeView()"><i class="pi pi-eye"></i> Zmień widok</button>
<button pButton type="button" (click)="exportXls()"><i class="pi pi-file-excel"></i> Generuj XLS i Wyślij</button>
</div>
<app-content-section title="Dane wysyłki" icon="pi pi-envelope">
<div class="form-grid form-grid--2">
<div class="field-group">
<label class="field-label">Adresy Email do Wysyłki raportu:</label>
<input pInputText class="w-full" placeholder="Wprowadź adresy..." [(ngModel)]="emailAddresses" />
</div>
<div class="field-group">
<label class="field-label">Numer WZ:</label>
<input pInputText class="w-full" [(ngModel)]="wzNumber" />
</div>
</div>
</app-content-section>
<app-content-section title="Skanowanie" icon="pi pi-barcode">
<div class="form-grid form-grid--2">
<div class="field-group">
<label class="field-label">Wprowadź numer palety:</label>
<input pInputText type="number" class="w-full" [(ngModel)]="palletNumber" />
</div>
<div class="field-group">
<label class="field-label">Zeskanowana wartość:</label>
<input #scanner pInputText class="w-full" [(ngModel)]="scannedValue" (change)="scanValue()" />
</div>
</div>
</app-content-section>
<app-content-section title="Ostatnio zeskanowana pozycja" icon="pi pi-info-circle">
<div class="form-grid form-grid--3">
<div class="field-group"><label class="field-label">Numer Indeksu FA:</label><input pInputText readonly class="w-full" [(ngModel)]="itemNumber" /></div>
<div class="field-group"><label class="field-label">Ilość w Dostawie:</label><input pInputText readonly class="w-full" [(ngModel)]="qty" /></div>
<div class="field-group"><label class="field-label">Numer Palety:</label><input pInputText readonly class="w-full" [(ngModel)]="palletNumberOutput" /></div>
<div class="field-group"><label class="field-label">Nr Partii SL:</label><input pInputText readonly class="w-full" [(ngModel)]="partNumberSl" /></div>
<div class="field-group"><label class="field-label">Numer Partii Meyle:</label><input pInputText readonly class="w-full" [(ngModel)]="partNumberMeyle" /></div>
</div>
</app-content-section>
</app-page-shell>
<p-dialog header="Informacja" [(visible)]="showInfoDialog" [modal]="true" [style]="{ width: '500px' }">
@if (exportValid()) { <p>Packing List został wygenerowany i wysłany!</p> }
@else if (!emailAddresses?.trim()) { <p>Błąd: Proszę wprowadzić przynajmniej jeden <b>ADRES EMAIL</b> do wysyłki raportu!</p> }
@else { <p>Błąd: Nie Wszystkie linie mają wypełniony <b>NUMER PALETY</b>.<br />Packing List nie zostanie wygenerowany!</p> }
<ng-template #footer><button pButton type="button" (click)="hideModal()">OK</button></ng-template>
</p-dialog>
<p-dialog header="Błąd" [(visible)]="showValidationDialog" [modal]="true" [style]="{ width: '500px' }">
<p>Błąd skanowania! Wystąpił jeden z wyjątków (Zeskanowana wartość '<b>{{ lastScannedValue }}</b>'):</p>
<ul>
<li>Zeskanowano niepoprawny Numer Partii SL (nieistniejący w tabeli)</li>
<li>Zeskanowano niepoprawny numer Partii Meyle (niezaczynający się od <b>{{ yearPrefix }}X</b>)</li>
<li>Numer Palety nie jest większy niż 0 (aktualnie wybrany numer palety: '<b>{{ palletNumber }}</b>')</li>
</ul>
<ng-template #footer><button pButton type="button" (click)="hideModal()">OK</button></ng-template>
</p-dialog>
<p-dialog header="Błąd" [(visible)]="showPalletDialog" [modal]="true" [style]="{ width: '500px' }">
<p>Błąd skanowania! <b>Wybierz NUMER PALETY większy niż 0</b> (Aktualnie '{{ palletNumber }}'):</p>
<ng-template #footer><button pButton type="button" (click)="hideModal()">OK</button></ng-template>
</p-dialog>
`,
})
export class MeylePackListSimpleComponent implements OnInit {
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly warehouseService = inject(WarehouseService);
readonly scanner = viewChild<ElementRef<HTMLInputElement>>('scanner');
wzHeaderId = '';
rows: WzRowMeyleDto[] = [];
transactionModels: Record<string, TransactionModel[]> = {};
changedRecords: WzRowMeyleDto[] = [];
selectedRow: WzRowMeyleDto | null = null;
selectedRows: WzRowMeyleDto[] = [];
emailAddresses = '';
wzNumber = '';
palletNumber = '0';
scannedValue = '';
lastScannedValue = '';
itemNumber = '';
qty = '0';
palletNumberOutput = '0';
partNumberSl = '';
partNumberMeyle = '';
showInfoDialog = false;
showValidationDialog = false;
showPalletDialog = false;
readonly exportValid = signal(false);
get yearPrefix(): number { return new Date().getFullYear() - 2000; }
async ngOnInit(): Promise<void> {
this.wzHeaderId = this.route.snapshot.paramMap.get('WzHeader') ?? '';
const header = await this.warehouseService.getWzHeaderById(this.wzHeaderId);
this.rows = await this.warehouseService.getWzRowsMeyleByWzHeaderId(this.wzHeaderId);
this.transactionModels = await this.warehouseService.getTransactionsModels();
this.emailAddresses = header.emailAddresses ?? '';
this.wzNumber = header.wzNumbers ?? '';
setTimeout(() => this.scanner()?.nativeElement.focus());
}
changeView(): void { this.router.navigate(['/Warehouse/Meyle/PackList', this.wzHeaderId]); }
hideModal(): void {
this.showInfoDialog = false;
this.showValidationDialog = false;
this.showPalletDialog = false;
this.lastScannedValue = this.scannedValue;
this.scannedValue = '';
setTimeout(() => this.scanner()?.nativeElement.focus());
}
async saveChanges(): Promise<void> {
if (this.emailAddresses?.trim()) await this.warehouseService.addEmailsToWzHeader(this.wzHeaderId, this.emailAddresses);
if (this.changedRecords.length) {
await this.warehouseService.updateWzRowsMeyle(this.changedRecords);
this.rows = await this.warehouseService.getWzRowsMeyleByWzHeaderId(this.wzHeaderId);
}
}
async exportXls(): Promise<void> {
const valid = !this.rows.some((x) => x.palletNumber == null) && !!this.emailAddresses?.trim();
this.exportValid.set(valid);
if (valid) {
await this.warehouseService.addEmailsToWzHeader(this.wzHeaderId, this.emailAddresses);
await this.warehouseService.generateXlsForMeyle(this.wzHeaderId);
}
this.showInfoDialog = true;
}
async scanValue(): Promise<void> {
const value = this.scannedValue.trim();
if (!value || parseInt(this.palletNumber, 10) <= 0) { this.showPalletDialog = true; return; }
const material = this.transactionModels[value]?.[0];
if (!material && isValidMeyleScannedValue(value)) {
await this.fillMeylePartNumber(value);
this.scannedValue = '';
return;
}
if (material) await this.fillFaPartNumberAndPalletNumber(material, value);
else { this.showValidationDialog = true; this.changedRecords = []; return; }
this.lastScannedValue = value;
this.scannedValue = '';
setTimeout(() => this.scanner()?.nativeElement.focus());
}
private async fillMeylePartNumber(scanned: string): Promise<void> {
if (this.selectedRow && !this.selectedRows.length) {
this.selectedRow.partNumber = scanned;
this.changedRecords.push(this.selectedRow);
this.partNumberMeyle = scanned;
}
for (const row of this.selectedRows) { row.partNumber = scanned; this.changedRecords.push(row); }
await this.saveChanges();
this.changedRecords = [];
this.selectedRows = [];
setTimeout(() => this.scanner()?.nativeElement.focus());
}
private async fillFaPartNumberAndPalletNumber(material: TransactionModel, scanned: string): Promise<void> {
const palletNum = parseInt(this.palletNumber, 10);
let rowIndex = this.rows.findIndex((x) => x.faIndex === material.itemNumber && x.quantity === material.quantity);
if (rowIndex === -1) {
this.selectedRows = this.rows.filter((x) => x.faIndex === material.itemNumber);
if (!this.selectedRows.length) { this.showValidationDialog = true; return; }
const validCombinations = findCombinations(this.selectedRows, material.quantity ?? 0);
for (const combination of validCombinations) {
for (const record of combination) {
record.partNumberSl = scanned;
record.palletNumber = palletNum;
this.changedRecords.push(record);
}
}
this.selectedRows = [...this.changedRecords];
this.selectedRow = this.selectedRows[0] ?? null;
} else {
this.selectedRow = this.rows[rowIndex];
this.selectedRow.partNumberSl = scanned;
this.selectedRow.palletNumber = palletNum;
if (!this.changedRecords.some((x) => x.transactionNumber === this.selectedRow!.transactionNumber)) {
this.changedRecords.push(this.selectedRow);
}
}
this.partNumberSl = this.selectedRow?.partNumberSl ?? '';
this.palletNumberOutput = String(this.selectedRow?.palletNumber ?? 0);
this.itemNumber = this.selectedRow?.faIndex ?? '';
this.qty = String(this.selectedRow?.quantity ?? 0);
await this.saveChanges();
this.changedRecords = [];
setTimeout(() => this.scanner()?.nativeElement.focus());
}
}