FaKrosnoAngular
* Added project
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
import { Component, ElementRef, inject, OnInit, signal, viewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { SelectableRow, TableModule, TableRowSelectEvent } from 'primeng/table';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import { InputTextModule } from 'primeng/inputtext';
|
||||
import { DialogModule } from 'primeng/dialog';
|
||||
import { TransactionModel, WzHeaderDto, 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',
|
||||
standalone: true,
|
||||
imports: [TableModule, SelectableRow, ButtonDirective, InputTextModule, DialogModule, FormsModule, PageShellComponent, ContentSectionComponent],
|
||||
template: `
|
||||
<app-page-shell
|
||||
title="Packing List"
|
||||
subtitle="Packing list Meyle — widok szczegółowy z edycją pozycji"
|
||||
icon="pi pi-box"
|
||||
>
|
||||
<div actions>
|
||||
<button pButton type="button" (click)="changeView()"><i class="pi pi-eye"></i> Zmień widok</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="Pozycje packing list" icon="pi pi-list" [flush]="true">
|
||||
<div headerActions>
|
||||
<button pButton type="button" (click)="saveChanges()"><i class="pi pi-save"></i> Zapisz zmiany</button>
|
||||
<button pButton type="button" [disabled]="!selectedRow()" (click)="showSplitDialog.set(true)"><i class="pi pi-arrows-h"></i> Podziel Linię</button>
|
||||
<button pButton type="button" (click)="exportXls()"><i class="pi pi-file-excel"></i> Generuj XLS i Wyślij</button>
|
||||
</div>
|
||||
<p-table
|
||||
[value]="rows()"
|
||||
[(selection)]="selectedRowsList"
|
||||
dataKey="id"
|
||||
[paginator]="true"
|
||||
[rows]="10"
|
||||
selectionMode="single"
|
||||
(onRowSelect)="onRowSelect($event)"
|
||||
styleClass="p-datatable-sm"
|
||||
[showGridlines]="true"
|
||||
[stripedRows]="true"
|
||||
>
|
||||
<ng-template #header>
|
||||
<tr>
|
||||
<th>Numer Zamówienia Meyle</th>
|
||||
<th>Numer Indeksu FA</th>
|
||||
<th>Numer Indeksu Meyle</th>
|
||||
<th>Ilość w Dostawie</th>
|
||||
<th>Nr Palety</th>
|
||||
<th>Nr Partii SL</th>
|
||||
<th>Nr Partii Meyle</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template #body let-row>
|
||||
<tr [pSelectableRow]="row">
|
||||
<td>{{ row.orderNumber }}</td>
|
||||
<td>{{ row.faIndex }}</td>
|
||||
<td>{{ row.itemNumber }}</td>
|
||||
<td>{{ row.quantity }}</td>
|
||||
<td><input pInputText type="number" [(ngModel)]="row.palletNumber" (ngModelChange)="markChanged(row)" /></td>
|
||||
<td><input pInputText [(ngModel)]="row.partNumberSl" (ngModelChange)="markChanged(row)" /></td>
|
||||
<td><input pInputText [(ngModel)]="row.partNumber" (ngModelChange)="markChanged(row)" /></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
||||
</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>
|
||||
|
||||
<p-dialog header="Podziel Linię" [(visible)]="showSplitDialog" [modal]="true" [style]="{ width: '500px' }">
|
||||
<label class="field-label">Podziel linię <b>{{ selectedRow()?.faIndex }}</b> podając ilość sztuk dla nowej linii:</label>
|
||||
<input pInputText type="number" class="w-full" [(ngModel)]="newQuantity" />
|
||||
<ng-template #footer>
|
||||
<button pButton type="button" (click)="splitLine()">Zapisz</button>
|
||||
<button pButton type="button" severity="secondary" (click)="showSplitDialog.set(false)">Anuluj</button>
|
||||
</ng-template>
|
||||
</p-dialog>
|
||||
`,
|
||||
})
|
||||
export class MeylePackListComponent implements OnInit {
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly router = inject(Router);
|
||||
private readonly warehouseService = inject(WarehouseService);
|
||||
|
||||
readonly scanner = viewChild<ElementRef<HTMLInputElement>>('scanner');
|
||||
|
||||
wzHeaderId = '';
|
||||
emailAddresses = '';
|
||||
wzNumber = '';
|
||||
palletNumber = '0';
|
||||
scannedValue = '';
|
||||
lastScannedValue = '';
|
||||
newQuantity = '0';
|
||||
|
||||
readonly rows = signal<WzRowMeyleDto[]>([]);
|
||||
readonly selectedRow = signal<WzRowMeyleDto | null>(null);
|
||||
selectedRowsList: WzRowMeyleDto[] = [];
|
||||
changedRecords: WzRowMeyleDto[] = [];
|
||||
transactionModels: Record<string, TransactionModel[]> = {};
|
||||
|
||||
showInfoDialog = false;
|
||||
showValidationDialog = false;
|
||||
showPalletDialog = false;
|
||||
readonly showSplitDialog = signal(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.set(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, 'Simple']);
|
||||
}
|
||||
|
||||
onRowSelect(event: TableRowSelectEvent<WzRowMeyleDto>): void {
|
||||
const data = event.data;
|
||||
if (data && !Array.isArray(data)) this.selectedRow.set(data);
|
||||
}
|
||||
|
||||
markChanged(row: WzRowMeyleDto): void {
|
||||
if (!this.changedRecords.includes(row)) this.changedRecords.push(row);
|
||||
}
|
||||
|
||||
hideModal(): void {
|
||||
this.showInfoDialog = false;
|
||||
this.showValidationDialog = false;
|
||||
this.showPalletDialog = false;
|
||||
this.showSplitDialog.set(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.updateRows(this.changedRecords);
|
||||
this.changedRecords = [];
|
||||
}
|
||||
}
|
||||
|
||||
async exportXls(): Promise<void> {
|
||||
const missingPallet = this.rows().some((x) => x.palletNumber == null);
|
||||
const valid = !missingPallet && !!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) return;
|
||||
if (parseInt(this.palletNumber, 10) <= 0) {
|
||||
this.showPalletDialog = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.transactionModels[value]?.[0];
|
||||
const materialTransaction = this.transactionModels[value]?.[0];
|
||||
|
||||
if (!materialTransaction && isValidMeyleScannedValue(value)) {
|
||||
await this.fillMeylePartNumber(value);
|
||||
this.lastScannedValue = value;
|
||||
this.scannedValue = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (materialTransaction) {
|
||||
this.selectedRowsList = [];
|
||||
await this.fillFaPartNumberAndPalletNumber(materialTransaction, 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> {
|
||||
const selected = this.selectedRow();
|
||||
if (selected && !this.selectedRowsList.length) {
|
||||
selected.partNumber = scanned;
|
||||
this.changedRecords.push(selected);
|
||||
}
|
||||
for (const row of this.selectedRowsList) {
|
||||
row.partNumber = scanned;
|
||||
this.changedRecords.push(row);
|
||||
}
|
||||
await this.saveChanges();
|
||||
this.changedRecords = [];
|
||||
this.selectedRowsList = [];
|
||||
setTimeout(() => this.scanner()?.nativeElement.focus());
|
||||
}
|
||||
|
||||
private async fillFaPartNumberAndPalletNumber(material: TransactionModel, scanned: string): Promise<void> {
|
||||
const palletNum = parseInt(this.palletNumber, 10);
|
||||
const currentRows = this.rows();
|
||||
let rowIndex = currentRows.findIndex((x) => x.faIndex === material.itemNumber && x.quantity === material.quantity);
|
||||
|
||||
if (rowIndex === -1) {
|
||||
const candidates = currentRows.filter((x) => x.faIndex === material.itemNumber);
|
||||
if (!candidates.length) {
|
||||
this.showValidationDialog = true;
|
||||
return;
|
||||
}
|
||||
rowIndex = currentRows.findIndex((x) => x.faIndex === candidates[0].faIndex && x.quantity === candidates[0].quantity);
|
||||
const validCombinations = findCombinations(candidates, material.quantity ?? 0);
|
||||
for (const combination of validCombinations) {
|
||||
for (const record of combination) {
|
||||
record.partNumberSl = scanned;
|
||||
record.palletNumber = palletNum;
|
||||
this.changedRecords.push(record);
|
||||
}
|
||||
}
|
||||
this.selectedRowsList = [...this.changedRecords];
|
||||
this.selectedRow.set(this.selectedRowsList[0] ?? null);
|
||||
} else {
|
||||
const row = currentRows[rowIndex];
|
||||
this.selectedRow.set(row);
|
||||
row.partNumberSl = scanned;
|
||||
row.palletNumber = palletNum;
|
||||
if (!this.changedRecords.some((x) => x.transactionNumber === row.transactionNumber)) {
|
||||
this.changedRecords.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
await this.saveChanges();
|
||||
this.changedRecords = [];
|
||||
setTimeout(() => this.scanner()?.nativeElement.focus());
|
||||
}
|
||||
|
||||
async splitLine(): Promise<void> {
|
||||
const qty = parseInt(this.newQuantity, 10);
|
||||
const selected = this.selectedRow();
|
||||
if (qty <= 0 || !selected) return;
|
||||
|
||||
const splitRow: WzRowMeyleDto = {
|
||||
id: crypto.randomUUID(),
|
||||
fk_Header: selected.fk_Header,
|
||||
quantity: qty,
|
||||
faIndex: selected.faIndex,
|
||||
itemNumber: selected.itemNumber,
|
||||
orderNumber: selected.orderNumber,
|
||||
palletNumber: selected.palletNumber,
|
||||
wzNumber: selected.wzNumber,
|
||||
transactionNumber: (selected.transactionNumber ?? 0) + 10000,
|
||||
partNumberSl: selected.partNumberSl,
|
||||
partNumber: selected.partNumber,
|
||||
};
|
||||
|
||||
selected.quantity = (selected.quantity ?? 0) - qty;
|
||||
await this.warehouseService.createWzRowsMeyle([splitRow]);
|
||||
await this.updateRows([selected]);
|
||||
this.showSplitDialog.set(false);
|
||||
}
|
||||
|
||||
private async updateRows(changed: WzRowMeyleDto[]): Promise<void> {
|
||||
await this.warehouseService.updateWzRowsMeyle(changed);
|
||||
this.rows.set(await this.warehouseService.getWzRowsMeyleByWzHeaderId(this.wzHeaderId));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user