* Introduced Marelli packing list

This commit is contained in:
2025-09-09 07:00:05 +02:00
parent 89792d3d28
commit 50a25ff996
12 changed files with 477 additions and 30 deletions

View File

@@ -0,0 +1,419 @@
@page "/Warehouse/Marelli/PackList/{WzHeader:guid}"
@using OrdersManagement.Models
@using Syncfusion.Blazor.Cards
@using Syncfusion.Blazor.Grids
@using SytelineSaAppEfDataModel.Dtos
@using Syncfusion.Blazor.Navigations
@using Syncfusion.Blazor.Popups
@using Syncfusion.Blazor.Inputs
@using FilterType = Syncfusion.Blazor.Grids.FilterType
@using Syncfusion.Blazor.Buttons
@using SelectionType = Syncfusion.Blazor.Grids.SelectionType
@inject NavigationManager NavigationManager
@inject WarehouseService WarehouseService
<div class="h-100 d-flex justify-content-center align-items-start">
<SfCard CssClass="shadow" style="width: 100%; max-width: 1200px;">
<CardHeader>
<h3 class="text-primary">Packing List</h3>
</CardHeader>
<CardContent>
<SfCard CssClass="shadow" style="width: 100%; max-width: 1200px;">
<CardContent>
<SfButton CssClass="e-primary" IsPrimary="true" @onclick="ChangeView">Zmień widok</SfButton>
</CardContent>
</SfCard>
<SfCard CssClass="shadow" style="width: 100%; max-width: 1200px;">
<CardContent>
<label for="textBox" class="form-label">Adresy Email do Wysyłki raportu:</label>
<SfTextBox ID="textBox" Placeholder="Wprowadź adresy..." @bind-Value="@EmailAddresses"
CssClass="e-outline"/>
</CardContent>
</SfCard>
<SfCard CssClass="shadow" style="width: 100%; max-width: 1200px;">
<CardContent>
<label for="textBox" class="form-label">Numer WZ:</label>
<SfTextBox ID="textBox" @bind-Value=@WzNumber CssClass="e-outline"/>
</CardContent>
</SfCard>
<SfCard CssClass="shadow" style="width: 100%; max-width: 1200px;">
<CardContent>
<label for="textBox" class="form-label">Wprowadź numer palety:</label>
<SfTextBox ID="palletNumber" Type="InputType.Number" @bind-Value="@PalletNumber"
CssClass="e-outline"/>
<label for="textBox" class="form-label">Zeskanowana wartość:</label>
<SfTextBox ID="scannedValue" ValueChange="ScanValue" @bind-Value="ScannedValue"
CssClass="e-outline" @ref="_scanner"/>
</CardContent>
</SfCard>
<SfGrid @ref="_grid"
AllowFiltering="true"
AllowPaging="true"
AllowSorting="true"
AllowSelection="true"
TValue="WzRowMarelliDto"
DataSource="@WzRowsMarelli"
EnableAdaptiveUI="true">
<SfToolbar>
<ToolbarItems>
<ToolbarItem Type="ItemType.Button" Text="Zapisz zmiany" Id="SaveButton"
PrefixIcon="e-icons e-save" OnClick="SaveChanges"/>
<ToolbarItem Type="ItemType.Button" Id="Generuj XLS i Wyślij" PrefixIcon="e-icons e-export-xls"
Text="Generuj XLS i Wyślij" OnClick="ExportXls"/>
</ToolbarItems>
</SfToolbar>
<GridColumns>
<GridColumn Field=@nameof(WzRowMarelliDto.ID) IsPrimaryKey="true" Visible="false" AllowEditing="false"
TextAlign="TextAlign.Center" HeaderText="ID" Width="70"></GridColumn>
<GridColumn Field=@nameof(WzRowMarelliDto.OrderNumber) AllowEditing="false"
TextAlign="TextAlign.Center" HeaderText="Numer Zamówienia Meyle"
Width="70"></GridColumn>
<GridColumn Field=@nameof(WzRowMarelliDto.FaIndex) AllowEditing="false" AllowFiltering="true"
TextAlign="TextAlign.Center" HeaderText="Numer Indeksu FA" Width="100"></GridColumn>
<GridColumn Field=@nameof(WzRowMarelliDto.ItemNumber) AllowEditing="false"
TextAlign="TextAlign.Center" HeaderText="Numer Indeksu Meyle" Width="100"></GridColumn>
<GridColumn Field=@nameof(WzRowMarelliDto.Quantity) AllowEditing="false" TextAlign="TextAlign.Center"
HeaderText="Ilość w Dostawie" Width="80"></GridColumn>
<GridColumn Field=@nameof(WzRowMarelliDto.PalletNumber) AllowEditing="true"
TextAlign="TextAlign.Center" HeaderText="Nr Palety" Width="100"></GridColumn>
<GridColumn Field=@nameof(WzRowMarelliDto.PartNumberSl) AllowEditing="true"
TextAlign="TextAlign.Center"
HeaderText="Nr Partii SL" Width="80"></GridColumn>
<GridColumn Field=@nameof(WzRowMarelliDto.PartNumber) AllowEditing="true" TextAlign="TextAlign.Center"
HeaderText="Nr Partii Meyle" Width="80"></GridColumn>
</GridColumns>
<GridEditSettings AllowDeleting="false"
AllowAdding="false"
AllowEditing="true"
AllowNextRowEdit="true"
AllowEditOnDblClick="true"
ShowConfirmDialog="false"
Mode="EditMode.Batch">
</GridEditSettings>
<GridFilterSettings Type="FilterType.Excel"/>
<GridPageSettings PageSize="10"/>
<GridSelectionSettings Mode="SelectionMode.Row" Type="SelectionType.Single"/>
<GridEvents RowSelected="OnRowSelected" OnBatchSave="OnBatchSave" TValue="WzRowMarelliDto"></GridEvents>
</SfGrid>
</CardContent>
<SfDialog Width="500px" Title="Informacja" IsModal="true" @bind-Visible="Visibility" AllowPrerender="true">
<DialogTemplates>
<Content>
@if (_isValid)
{
<p>Packing List został wygenerowany i wysłany!</p>
}
else if (string.IsNullOrWhiteSpace(EmailAddresses))
{
<p>Błąd: Proszę wprowadzić przynajmniej jeden <b>ADRES EMAIL</b> do wysyłki raportu!</p>
}
else if (!_isValid)
{
<p>Błąd: Nie Wszystkie linie mają wypełniony <b>NUMER PALETY</b>.<br/>Packing List nie zostanie
wygenerowany!</p>
}
</Content>
</DialogTemplates>
<DialogButtons>
<DialogButton Content="OK" IsPrimary="true" OnClick="@HideModal"/>
</DialogButtons>
</SfDialog>
<SfDialog Width="500px" Title="Błąd" IsModal="true" @bind-Visible="VisibilityPalletNumber"
AllowPrerender="true">
<DialogTemplates>
<Content>
<p>Błąd skanowania! <b>Wybierz NUMER PALETY większy niż 0</b> (Aktualnie '@PalletNumber'):</p>
</Content>
</DialogTemplates>
<DialogButtons>
<DialogButton Content="OK" IsPrimary="true" OnClick="@HideModal"/>
</DialogButtons>
</SfDialog>
<CardFooter>
<small class="text-muted">FA Krosno Manager © @(DateTime.Now.Year)</small>
</CardFooter>
</SfCard>
</div>
@code {
[Parameter] public Guid WzHeader { get; set; }
private SfGrid<WzRowMarelliDto> _grid;
private List<WzRowMarelliDto> WzRowsMarelli { get; set; } = new();
private IDictionary<string, List<TransactionModel>> TransactionModelsByPartNumber { get; set; } = new Dictionary<string, List<TransactionModel>>();
private List<WzRowMarelliDto> ChangedRecords = new();
private WzHeaderDto _wzHeader;
private SfTextBox _scanner;
private WzRowMarelliDto? SelectedRow { get; set; }
private List<WzRowMarelliDto> SelectedRows { get; set; } = new();
private string WzNumber { get; set; } = string.Empty;
private bool _isValid;
private bool Visibility { get; set; }
public bool VisibilityPalletNumber { get; set; }
private string? EmailAddresses { get; set; } = string.Empty;
private string PalletNumber { get; set; } = "0";
private string LastScannedValue { get; set; } = string.Empty;
private string ScannedValue { get; set; } = string.Empty;
private bool IsDisabled => SelectedRow == null;
private void HideModal()
{
Visibility = false;
VisibilityPalletNumber = false;
LastScannedValue = ScannedValue;
ScannedValue = string.Empty;
_scanner.FocusAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_wzHeader = await WarehouseService.GetWzHeaderByIdAsync(WzHeader);
WzRowsMarelli = (await WarehouseService.GetWzRowsByWzHeaderId(WzHeader)).ToList();
TransactionModelsByPartNumber = await GetTransactionModels();
EmailAddresses = _wzHeader.EmailAddresses;
WzNumber = _wzHeader.WzNumbers ?? string.Empty;
await _scanner.FocusAsync();
StateHasChanged();
}
}
private async Task SaveChanges()
{
if (!string.IsNullOrWhiteSpace(EmailAddresses))
{
await WarehouseService.AddEmailsToWzHeaderAsync(WzHeader, EmailAddresses);
}
if (ChangedRecords.Any())
{
await UpdateRows(ChangedRecords);
}
await _grid.EndEditAsync();
}
private async Task OnBatchSave(BeforeBatchSaveArgs<WzRowMarelliDto> obj)
{
var changes = obj.BatchChanges;
List<WzRowMarelliDto> changedRecords = changes.ChangedRecords;
if (!changedRecords.Any()) return;
await UpdateRows(changedRecords);
}
private async Task ExportXls()
{
int count = WzRowsMarelli.Count(x => x.PalletNumber == null);
_isValid = count == 0;
_isValid = _isValid && !string.IsNullOrWhiteSpace(EmailAddresses);
if (_isValid)
{
await WarehouseService.AddEmailsToWzHeaderAsync(WzHeader, EmailAddresses);
await WarehouseService.GenerateXlsForMarelliAsync(WzHeader);
}
Visibility = true;
}
private void ScanValue(ChangedEventArgs obj)
{
if (string.IsNullOrWhiteSpace(obj.Value)) return;
if (int.Parse(PalletNumber) <= 0)
{
VisibilityPalletNumber = true;
return;
}
ScannedValue = obj.Value.Trim();
StateHasChanged();
TransactionModelsByPartNumber.TryGetValue(obj.Value.Trim(), out List<TransactionModel>? materialTransactionsByPartNumber);
TransactionModel? materialTransactionByPartNumber = materialTransactionsByPartNumber?.FirstOrDefault();
_grid.Query = null;
_grid.ClearFilteringAsync();
if (materialTransactionByPartNumber != null)
{
SelectedRows.Clear();
FillFaPartNumberAndPalletNumber(materialTransactionByPartNumber, obj.Value.Trim());
}
LastScannedValue = ScannedValue;
ScannedValue = string.Empty;
_scanner.FocusAsync();
}
private async Task FillFaPartNumberAndPalletNumber(TransactionModel materialTransactionByPartNumber, string scannedValue)
{
await _grid.ClearSelectionAsync();
await _grid.Refresh();
await InvokeAsync(StateHasChanged);
List<int> selectedIndices = new List<int>();
int palletNumber = int.Parse(PalletNumber);
int rowIndex = WzRowsMarelli.FindIndex(x => x.FaIndex == materialTransactionByPartNumber.ItemNumber && x.Quantity == materialTransactionByPartNumber.Quantity);
switch (rowIndex)
{
case -1:
{
SelectedRows = WzRowsMarelli.Where(x => x.FaIndex == materialTransactionByPartNumber.ItemNumber).ToList();
rowIndex = WzRowsMarelli.FindIndex(x => x.FaIndex == SelectedRows.First().FaIndex && x.Quantity == SelectedRows.First().Quantity);
var validCombinations = FindCombinations(SelectedRows, (int?)materialTransactionByPartNumber.Quantity ?? 0);
foreach (var combination in validCombinations)
{
foreach (var record in combination)
{
record.PalletNumber = palletNumber;
ChangedRecords.Add(record);
int index = WzRowsMarelli.IndexOf(record);
if (index >= 0)
{
selectedIndices.Add(index);
}
}
}
SelectedRows.Clear();
SelectedRows.AddRange(ChangedRecords);
SelectedRow = SelectedRows.FirstOrDefault();
await ApplyFilter(ChangedRecords);
break;
}
default:
{
SelectedRow = WzRowsMarelli[rowIndex];
selectedIndices.Add(rowIndex);
SelectedRow.PalletNumber = palletNumber;
if (ChangedRecords.All(x => x.TransactionNumber != SelectedRow.TransactionNumber))
{
ChangedRecords.Add(SelectedRow);
}
break;
}
}
await SaveChanges();
ChangedRecords.Clear();
if (selectedIndices.Any())
{
await _grid.SelectRowsAsync(selectedIndices.ToArray());
}
await _grid.Refresh();
await InvokeAsync(StateHasChanged);
FocusGridRow(rowIndex);
await _scanner.FocusAsync();
}
private void FocusGridRow(int rowIndex)
{
if (_grid.AllowPaging)
{
int pageSize = _grid.PageSettings.PageSize;
int targetPage = (rowIndex / pageSize) + 1;
_grid.GoToPageAsync(targetPage);
rowIndex %= pageSize;
}
_grid.SelectRowAsync(rowIndex);
_grid.ScrollIntoViewAsync(rowIndex: rowIndex);
_grid.FocusAsync();
}
private async Task<IDictionary<string, List<TransactionModel>>> GetTransactionModels()
{
return await WarehouseService.GetTransactionsModels();
}
private async Task UpdateRows(IList<WzRowMarelliDto> changedRecords)
{
await WarehouseService.UpdateWzRowsMeyleAsync(changedRecords);
WzRowsMarelli = (await WarehouseService.GetWzRowsByWzHeaderId(WzHeader)).ToList();
await InvokeAsync(StateHasChanged);
await _grid.Refresh();
}
private void OnRowSelected(RowSelectEventArgs<WzRowMarelliDto> obj)
{
SelectedRow = obj.Data;
}
private List<List<WzRowMarelliDto>> FindCombinations(List<WzRowMarelliDto> records, int targetSum)
{
var result = new List<List<WzRowMarelliDto>>();
var currentCombination = new List<WzRowMarelliDto>();
void Backtrack(int start, int currentSum)
{
if (currentSum == targetSum)
{
result.Add(new List<WzRowMarelliDto>(currentCombination));
return;
}
for (int i = start; i < records.Count; i++)
{
if (currentSum + records[i].Quantity <= targetSum)
{
currentCombination.Add(records[i]);
Backtrack(i + 1, currentSum + records[i].Quantity ?? 0);
currentCombination.RemoveAt(currentCombination.Count - 1);
}
}
}
Backtrack(0, 0);
return result;
}
private async Task ApplyFilter(IList<WzRowMarelliDto> selectedRecords)
{
await _grid.FilterByColumnAsync(nameof(WzRowMarelliDto.FaIndex), "equal", selectedRecords.First().FaIndex);
}
private void ChangeView()
{
NavigationManager.NavigateTo($"/Warehouse/Marelli/PackList/{WzHeader}/Simple");
}
}

View File

@@ -1,4 +1,5 @@
@page "/Warehouse/PackList/{WzHeader:guid}"
@page "/Warehouse/Meyle/PackList/{WzHeader:guid}"
@using OrdersManagement.Models
@using Syncfusion.Blazor.Cards
@using Syncfusion.Blazor.Grids
@@ -562,6 +563,6 @@
private void ChangeView()
{
NavigationManager.NavigateTo($"/Warehouse/PackList/{WzHeader}/Simple");
NavigationManager.NavigateTo($"/Warehouse/Meyle/PackList/{WzHeader}/Simple");
}
}

View File

@@ -1,4 +1,4 @@
@page "/Warehouse/PackList/{WzHeader:guid}/Simple"
@page "/Warehouse/Meyle/PackList/{WzHeader:guid}/Simple"
@using OrdersManagement.Models
@using Syncfusion.Blazor.Cards
@using Syncfusion.Blazor.Grids

View File

@@ -175,9 +175,11 @@
private void OnRowDoubleClick(RecordDoubleClickEventArgs<WzHeaderDto> obj)
{
if (_selectedClient is null) return;
Guid headerId = obj.RowData.ID;
NavigationManager.NavigateTo("/Warehouse/PackList/" + headerId);
NavigationManager.NavigateTo($"/Warehouse/{_selectedClient.Name}/PackList/" + headerId);
}
private async Task CreatePackingList()
@@ -234,10 +236,11 @@
}
await WarehouseService.CreateWzRowsMeyleAsync(rows);
NavigationManager.NavigateTo("/Warehouse/Meyle/PackList/" + wzHeader.ID);
break;
}
NavigationManager.NavigateTo("/Warehouse/PackList/" + wzHeader.ID);
}
private void HideModal()

View File

@@ -128,11 +128,21 @@ public class WarehouseService(IHttpClientFactory httpClientFactory)
{
var response = await _httpClient.GetAsync($"api/ExcelGenerator/generate-meyle?packListId={wzHeaderId}");
response.EnsureSuccessStatusCode();
if (response.StatusCode != System.Net.HttpStatusCode.OK)
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception("Failed to generate XLS for Mayle");
}
}
public async Task GenerateXlsForMarelliAsync(Guid wzHeaderId)
{
var response = await _httpClient.GetAsync($"api/ExcelGenerator/generate-marelli?packListId={wzHeaderId}");
response.EnsureSuccessStatusCode();
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception("Failed to generate XLS for Marelli");
}
}
public async Task AddEmailsToWzHeaderAsync(Guid wzHeaderId, string? emailAddresses)
{