CompanyInventoryList sorting and tagging

This commit is contained in:
Frede Hundewadt 2023-06-01 17:28:51 +02:00
parent 8cd924e817
commit 1afbf20b15
56 changed files with 643 additions and 1028 deletions

View file

@ -23,37 +23,74 @@
<div class="list-group mt-2">
<div class="list-group-item d-print-none">
<div class="row">
<div class="col-sm-4 action-link-element" @onclick="@(() => SortProducts(ProductSort.Desc))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-3 action-link-element" @onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-2 text-center action-link-element" @onclick="@(() => SortProducts(ProductSort.Qty))"><i class="bi-sort-numeric-down"></i> Antal <i class="bi-sort-numeric-up-alt"></i></div>
<div class="col-sm-2"></div>
<div class="col-sm-1"></div>
@*
<div class="col-sm-4 action-link-element" onclick="@(() => SortProducts(ProductSort.Description))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-3 action-link-element" onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div>
*@
<div class="col-sm-7 text-end">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="sortOrder" checked @onclick="@SetSortOrder"/>
<label class="form-check-label" for="sortOrder"><i class="@(Descending ? "bi-sort-up" : "bi-sort-down-alt") "></i></label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="description" value="description"
onclick="@(() => SortProducts(ProductSort.Description))">
<label class="form-check-label" for="description">Navn</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="itemNumber" value="itemNumber"
onclick="@(() => SortProducts(ProductSort.Sku))">
<label class="form-check-label" for="itemNumber">Vare Nr.</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="lastInvoiceDate" value="itemDate"
onclick="@(() => SortProducts(ProductSort.LastInvoiceDate))" checked>
<label class="form-check-label" for="lastInvoiceDate">Sidst leveret</label>
</div>
</div>
<div class="col-sm-5">
<SearchPhraseComponent OnChanged="OnSearchChanged"/>
</div>
</div>
</div>
@foreach (var product in Inventory)
@foreach (var product in FilteredList)
{
<div class="list-group-item">
<div class="row align-items-center">
<div class="col-sm-2">
<div class="position-relative">
@product.LastInvoiceDate
@if (product.AgedProduct() && !product.Discontinued)
{
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
Længe siden
<span class="visually-hidden">Længe siden</span>
</span>
}
</div>
</div>
<div class="col-sm-4">
<div class="position-relative">
@product.Description
@if (product.Discontinued)
{
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">Udgået</span>
<span class="visually-hidden">Produktet er udgået</span>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
Udgået
<span class="visually-hidden">Produktet er udgået</span>
</span>
}
</div>
</div>
<div class="col-sm-3">
<div class="col-sm-2 text-sm-start">
@product.Sku
</div>
<div class="col-sm-2 text-center">
<div class="col-sm-1 text-center">
@product.Quantity
</div>
<div class="col-sm-2 d-print-none">
<button type="button" class="btn btn-info d-block" @onclick="@(() => CallShowReorderModal(product.Sku))"><i class="bi-cart"></i> Genbestil</button>
<button type="button" class="btn btn-info d-block" @onclick="@(() => CallShowReorderModal(product.Sku))"><i class="bi-cart"></i> Genbestil</button>
</div>
<div class="col-sm-1 d-print-none" @onclick="@(() => ProductCheck(product.Sku))">
<div class="col-sm-1 d-print-none" @onclick="@(() => SetProductCheckmark(product.Sku))">
<input type="checkbox" class="btn-check" id="btn-@product.Sku.Replace(",", "")" autocomplete="off"/>
@if (product.Check)
{

View file

@ -16,6 +16,7 @@
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components;
using Wonky.Client.Enums;
using Wonky.Client.Helpers;
using Wonky.Client.Models;
using Wonky.Entity.Views;
@ -24,71 +25,68 @@ namespace Wonky.Client.Components;
public partial class CustomerInventoryListComponent
{
// ##############################################3333############
[Inject] public ILocalStorageService Storage { get; set; }
// Parameters
[Inject] public ILogger<CustomerInventoryListComponent> Logger { get; set; }
// ##############################################3333############
[Parameter] public List<ProductInventoryItemView> Inventory { get; set; } = new();
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public EventCallback<string> OnReorderSelected { get; set; }
// ##############################################3333############
// private variables
private bool Descending { get; set; }
protected override void OnParametersSet()
private bool Descending { get; set; } = true;
private string SearchTerm { get; set; } = "";
private List<ProductInventoryItemView> FilteredList { get; set; }
private ProductSort SortColumn { get; set; } = ProductSort.LastInvoiceDate;
protected override void OnInitialized()
{
if(Inventory.Any())
Inventory = Inventory.OrderBy(x => x.Description).ToList();
// sort base list
Inventory = Utils.SortInventory(Inventory, SortColumn, Descending);
// initialize FilteredList
FilterItems(SearchTerm);
}
private void SetSortOrder()
{
Descending = !Descending;
FilteredList = Utils.SortInventory(FilteredList, SortColumn, Descending);
}
private void OnSearchChanged(string searchTerm)
{
// use search input to filter list
FilterItems(searchTerm);
}
private void FilterItems(string filter)
{
SearchTerm = filter;
FilteredList = string.IsNullOrWhiteSpace(filter)
? Inventory
: Inventory.Where(i => i.Description.ToLower().Contains(filter.ToLower())).ToList();
}
private void SortProducts(ProductSort column)
{
Descending = !Descending;
switch (column)
{
case ProductSort.Desc:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Description).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Description).ToList();
break;
case ProductSort.Sku:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Sku).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Sku).ToList();
break;
case ProductSort.Qty:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Quantity).ToList();
break;
case ProductSort.None:
Inventory = Inventory.OrderBy(x => x.Description).ToList();
break;
case ProductSort.Abbr:
break;
default:
Inventory = Inventory.OrderBy(x => x.Description).ToList();
break;
}
FilteredList = Utils.SortInventory(FilteredList, column, Descending);
}
private async Task CallShowReorderModal(string sku)
{
await ProductCheck(sku);
await SetProductCheckmark(sku);
await OnReorderSelected.InvokeAsync(sku);
}
private async Task ProductCheck(string sku)
private async Task SetProductCheckmark(string sku)
{
var x = Inventory.First(x => x.Sku == sku);
x.Check = !x.Check;

View file

@ -22,24 +22,50 @@
<div class="list-group mt-2">
<div class="list-group-item">
<div class="row">
<div class="col-sm-4" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Desc))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div>
@*
<div class="col-sm-4" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Description))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-3" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-2 text-center" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Qty))"><i class="bi-sort-numeric-down"></i> Antal <i class="bi-sort-numeric-up-alt"></i></div>
<div class="col-sm-2"></div>
<div class="col-sm-1"></div>
*@
<div class="col-sm-7 text-end">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="sortOrder" checked @onclick="@SetSortOrder"/>
<label class="form-check-label" for="sortOrder"><i class="@(Descending ? "bi-sort-up" : "bi-sort-down") "></i></label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="description" value="description"
onclick="@(() => SortProducts(ProductSort.Description))">
<label class="form-check-label" for="description">Navn</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="itemNumber" value="itemNumber"
onclick="@(() => SortProducts(ProductSort.Sku))">
<label class="form-check-label" for="itemNumber">Vare Nr.</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="lastInvoiceDate" value="itemDate"
onclick="@(() => SortProducts(ProductSort.LastInvoiceDate))" checked>
<label class="form-check-label" for="lastInvoiceDate">Sidst leveret</label>
</div>
</div>
<div class="col-sm-5">
<SearchPhraseComponent OnChanged="OnSearchChanged"/>
</div>
</div>
</div>
@foreach (var product in Inventory)
{
<div class="list-group-item">
<div class="row align-items-center">
<div class="col-sm-2">
@product.LastInvoiceDate
</div>
<div class="col-sm-4">
@product.Description
</div>
<div class="col-sm-3">
<div class="col-sm-2">
@product.Sku
</div>
<div class="col-sm-2 text-center">
<div class="col-sm-1 text-center">
@product.Quantity
</div>
<div class="col-sm-2">

View file

@ -13,9 +13,11 @@
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using System.Security.Policy;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components;
using Wonky.Client.Enums;
using Wonky.Client.Helpers;
using Wonky.Client.Models;
using Wonky.Entity.Views;
#pragma warning disable CS8618
@ -27,57 +29,48 @@ public partial class CustomerProductCheckListComponent
[Parameter] public string CompanyId { get; set; } = "";
[Inject] public ILocalStorageService Storage { get; set; }
// private variables
private bool Descending { get; set; }
private bool Descending { get; set; } = true;
private string SearchTerm { get; set; } = "";
private List<ProductInventoryItemView> FilteredList { get; set; } = new();
protected override void OnParametersSet()
{
if(Inventory.Any())
Inventory = Inventory.OrderBy(x => x.Description).ToList();
Inventory = Utils.SortInventory(Inventory, ProductSort.LastInvoiceDate, Descending);
FilterItems(SearchTerm);
}
private void SetSortOrder()
{
Descending = !Descending;
FilteredList = Utils.SortInventory(FilteredList, ProductSort.LastInvoiceDate, Descending);
}
private void OnSearchChanged(string searchTerm)
{
FilterItems(searchTerm);
}
private void FilterItems(string filter)
{
SearchTerm = filter;
FilteredList = string.IsNullOrWhiteSpace(filter)
? Inventory
: Inventory.Where(i => i.Description.ToLower().Contains(filter.ToLower())).ToList();
}
private void SortProducts(ProductSort column)
{
Descending = !Descending;
switch (column)
{
case ProductSort.Desc:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Description).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Description).ToList();
break;
case ProductSort.Sku:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Sku).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Sku).ToList();
break;
case ProductSort.Qty:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Quantity).ToList();
break;
case ProductSort.None:
break;
case ProductSort.Abbr:
break;
default:
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
FilteredList = Utils.SortInventory(Inventory, column, Descending);
}
private async Task ProductCheck(string sku)
private void ProductCheck(string sku)
{
var x = Inventory.First(x => x.Sku == sku);
x.Check = !x.Check;
await Storage.SetItemAsync($"{CompanyId}-products", Inventory);
}
}

View file

@ -1,60 +0,0 @@
@* Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@
@using Wonky.Client.Components
<h3>Pakning / Forsendelse</h3>
<table class="table">
<thead>
<tr>
<th>Symbol</th>
<th>Betydning</th>
</tr>
</thead>
<tbody>
<tr>
<td class="align-middle">
<div class="color-code">
<DisplayStateComponent StateClass="the-good"/>
</div>
</td>
<td class="align-middle">Ubehandlet</td>
</tr>
<tr>
<td class="align-middle">
<div class="color-code">
<DisplayStateComponent StateClass="the-bad"/>
</div>
</td>
<td class="align-middle">Varer er plukket</td>
</tr>
<tr>
<td class="align-middle">
<div class="color-code">
<DisplayStateComponent StateClass="the-ugly"/>
</div>
</td>
<td class="align-middle">Varer er pakket</td>
</tr>
<tr>
<td class="align-middle">
<div class="color-code">
<DisplayStateComponent StateClass="the-dead"/>
</div>
</td>
<td class="align-middle">Varer er afsendt</td>
</tr>
</tbody>
</table>

View file

@ -36,6 +36,7 @@
<td><i class="bi-printer" style="font-size:1.3rem"></i></td>
<td>Udskrevet</td>
</tr>
@*
<tr>
<td><i class="bi-file-earmark-check" style="font-size:1.3rem"></i></td>
<td>Plukket</td>
@ -48,5 +49,6 @@
<td><i class="bi-truck" style="font-size:1.3rem"></i></td>
<td>Leveret</td>
</tr>
*@
</thead>
</table>

View file

@ -70,7 +70,7 @@
</div>
<OfficeCustomerInvoiceListOverlay Company="@SelectedCompany" InvoiceList="@InvoiceList" @ref="InvoiceListOverlay" />
<OfficeCustomerActivityListOverlay Company="SelectedCompany" ActivityList="ActivityList" @ref="ActivityListOverlay" />
<OfficeCustomerProductListOverlay Company="SelectedCompany" Inventory="ProductList" @ref="ProductListOverlay" />
<OfficeCustomerInventoryListOverlay Company="SelectedCompany" Inventory="ProductList" @ref="InventoryListOverlay" />
}
else
{

View file

@ -48,7 +48,7 @@ public partial class OfficeCountryCustomerListComponent
// overlays
private OfficeCustomerInvoiceListOverlay InvoiceListOverlay { get; set; } = new();
private OfficeCustomerActivityListOverlay ActivityListOverlay { get; set; } = new();
private OfficeCustomerProductListOverlay ProductListOverlay { get; set; } = new();
private OfficeCustomerInventoryListOverlay InventoryListOverlay { get; set; } = new();
// ******************************************************
// variables
@ -108,7 +108,7 @@ public partial class OfficeCountryCustomerListComponent
SelectedCompany.HistorySync = newSyncDate;
}
ProductList = await HistoryRepo.GetInventory(SelectedCompany.CountryCode, SelectedCompany.CompanyId);
ProductListOverlay.Show();
InventoryListOverlay.Show();
}
private void ShowOrder(string companyId)

View file

@ -24,11 +24,32 @@
<div class="list-group mt-2">
<div class="list-group-item">
<div class="row">
<div class="col-sm-4" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Desc))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-3" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-2 text-center" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Qty))"><i class="bi-sort-numeric-down"></i> Antal <i class="bi-sort-numeric-up-alt"></i></div>
<div class="col-sm-2"></div>
<div class="col-sm-1"></div>
@* <div class="col-sm-4" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Description))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div> *@
@* <div class="col-sm-3" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div> *@
<div class="col-sm-7 text-end">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="sortOrder" checked @onclick="@SetSortOrder"/>
<label class="form-check-label" for="sortOrder"><i class="@(Descending ? "bi-sort-up" : "bi-sort-down") "></i></label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="description" value="description"
onclick="@(() => SortProducts(ProductSort.Description))">
<label class="form-check-label" for="description">Navn</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="itemNumber" value="itemNumber"
onclick="@(() => SortProducts(ProductSort.Sku))">
<label class="form-check-label" for="itemNumber">Vare Nr.</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="lastInvoiceDate" value="itemDate"
onclick="@(() => SortProducts(ProductSort.LastInvoiceDate))" checked>
<label class="form-check-label" for="lastInvoiceDate">Sidst leveret</label>
</div>
</div>
<div class="col-sm-5">
<SearchPhraseComponent OnChanged="OnSearchChanged" />
</div>
</div>
</div>
@foreach (var product in Inventory)

View file

@ -15,72 +15,70 @@
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components;
using Microsoft.VisualBasic.CompilerServices;
using Wonky.Client.Enums;
using Wonky.Client.Models;
using Wonky.Entity.Views;
using Utils = Wonky.Client.Helpers.Utils;
namespace Wonky.Client.Components;
#pragma warning disable CS8618
public partial class OfficeInventoryListComponent
public partial class OfficeCustomerInventoryListComponent
{
// *************************************************************
// Injections
[Inject] public ILocalStorageService Storage { get; set; }
// *************************************************************
// Parameters
[Parameter] public List<ProductInventoryItemView> Inventory { get; set; } = new();
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public EventCallback<string> OnReorderSelected { get; set; }
// *************************************************************
// private variables
private bool Descending { get; set; }
private bool Descending { get; set; } = true;
private string SearchTerm { get; set; } = "";
private List<ProductInventoryItemView> FilteredList { get; set; } = new();
private ProductSort SortColumn { get; set; } = ProductSort.LastInvoiceDate;
protected override void OnParametersSet()
{
if(Inventory.Any())
Inventory = Inventory.OrderBy(x => x.Description).ToList();
Inventory = Utils.SortInventory(Inventory, SortColumn, Descending);
FilterItems(SearchTerm);
}
private void SetSortOrder()
{
Descending = !Descending;
FilteredList = Utils.SortInventory(FilteredList, SortColumn, Descending);
}
private void OnSearchChanged(string searchTerm)
{
FilterItems(searchTerm);
}
private void FilterItems(string filter)
{
SearchTerm = filter;
FilteredList = string.IsNullOrWhiteSpace(filter)
? Inventory
: Inventory.Where(i => i.Description.ToLower().Contains(filter.ToLower())).ToList();
}
private void SortProducts(ProductSort column)
{
Descending = !Descending;
switch (column)
{
case ProductSort.Desc:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Description).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Description).ToList();
break;
case ProductSort.Sku:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Sku).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Sku).ToList();
break;
case ProductSort.Qty:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Quantity).ToList();
break;
case ProductSort.None:
break;
case ProductSort.Abbr:
break;
default:
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
FilteredList = Utils.SortInventory(FilteredList, column, Descending);
}
private async Task CallShowReorderModal(string sku)
{
// await ProductCheck(sku);

View file

@ -13,20 +13,9 @@
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@
<h3>Lager</h3>
<table class="table">
<thead>
<tr>
<th>Symbol</th>
<th>Betydning</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<i class="bi-box" style="font-size:1.3rem"></i>
</td>
<td>Pakning / Forsendelse</td>
</tr>
</tbody>
</table>
<div class="input-group">
<input id="search-input" type="text" class="form-control" placeholder="Søg ..." aria-described-by="search-addon"
@bind-value="SearchTerm" @bind-value:event="oninput" onkeyup="@OnSearchChanged" />
<span class="input-group-text" id="search-addon"><i class="oi oi-delete" @onclick="@ClearSearch"></i></span>
</div>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2022 FCS Frede's Computer Services.
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
@ -11,10 +11,27 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*/
.pictogram {
max-width: 30px;
}
.color-code {
max-width: 30px;
//
using System.Timers;
using Microsoft.AspNetCore.Components;
using Timer = System.Timers.Timer;
namespace Wonky.Client.Components;
public partial class SearchPhraseComponent
{
private string SearchTerm { get; set; } = "";
[Parameter] public EventCallback<string> OnChanged { get; set; }
private void ClearSearch()
{
SearchTerm = "";
OnChanged.InvokeAsync("");
}
private void OnSearchChanged()
{
OnChanged.InvokeAsync(SearchTerm);
}
}

View file

@ -1,102 +0,0 @@
@* Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@
@using Wonky.Client.Models
@using Wonky.Client.Enums
@if (OrderList != null)
{
<div class="row mb-3">
<div class="col-sm-4">
<div class="btn-group" role="group" aria-label="Ordre status">
<input type="radio" class="btn-check" name="btn-order" id="btn-order1" @onchange="() => GetWithStatus(ProcessStatus.None)" autocomplete="off" checked="checked"/>
<label class="btn btn-success" for="btn-order1">Ubehandlet</label>
<input type="radio" class="btn-check" name="btn-order" id="btn-order2" @onchange="() => GetWithStatus(ProcessStatus.Picked)" autocomplete="off"/>
<label class="btn btn-warning" for="btn-order2">Plukket</label>
<input type="radio" class="btn-check" name="btn-order" id="btn-order3" @onchange="() => GetWithStatus(ProcessStatus.Packed)" autocomplete="off"/>
<label class="btn btn-danger" for="btn-order3">Pakket</label>
</div>
</div>
<div class="col-sm-4 text-end">
<label class="btn btn-outline-dark position-relative">
@Header
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-info">@Orders.Count</span>
<span class="visually-hidden">ordrer i listen</span>
</label>
</div>
<div class="col-sm-4 text-end">
@if (ReadyToShip && Orders.Any())
{
<button type="button" class="btn btn-primary text-sm-center" @onclick="@SetShipStatus">Sæt alle afsendt</button>
}
</div>
</div>
<div class="list-group list-group-flush">
<div class="list-group-item">
<div class="row">
<div class="col-sm-4">
<h4>Modtager</h4>
</div>
<div class="col-sm-3">
<h5>Post By</h5>
</div>
<div class="col-sm-2">
<h5>Dato</h5>
</div>
<div class="col-sm-3 text-end">
<h5><i class="bi-lightning-fill"></i> Status</h5>
</div>
</div>
</div>
@foreach (var order in Orders)
{
<div class="list-group-item list-group-item-action" style="cursor: hand;">
<div class="row">
<div class="col-sm-4">
@order.Company.Name
</div>
<div class="col-sm-3">
@order.Company.ZipCode @order.Company.City
</div>
<div class="col-sm-2">
@order.OrderDate
</div>
<div class="col-sm-3 d-flex">
@if (order.Express)
{
<i class="bi-lightning-fill text-warning"></i>
}
@switch (order.ProcessStatusEnum.ToLower())
{
case "none":
<button class="btn btn-outline-danger me-4" @onclick="@(() => QuickPak(order.OrderId))">QuickPak</button>
<a class="btn btn-warning" href="warehouse/orders/process/@order.OrderId">Pluk varer</a>
break;
case "picked":
<a class="btn btn-warning" href="warehouse/orders/process/@order.OrderId">Pak varer</a>
break;
case "packed":
<a class="btn btn-warning" href="warehouse/orders/process/@order.OrderId">Afhent varer</a>
break;
}
</div>
</div>
</div>
}
</div>
}

View file

@ -1,56 +0,0 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using Microsoft.AspNetCore.Components;
using Wonky.Client.Enums;
using Wonky.Client.Models;
using Wonky.Entity.Views;
namespace Wonky.Client.Components;
public partial class WarehouseListComponent
{
[Parameter] public string Header { get; set; } = "";
[Parameter] public List<WarehouseOrderView> OrderList { get; set; } = new();
[Parameter] public bool ReadyToShip { get; set; }
[Parameter] public EventCallback<ProcessStatus> OnGetStatus { get; set; }
[Parameter] public EventCallback OnSetShipped { get; set; }
[Parameter] public EventCallback<string> OnQPak { get; set; }
private List<WarehouseOrderView> Orders { get; set; } = new();
protected override async Task OnParametersSetAsync()
{
while (OrderList == null)
await Task.Delay(500);
Orders = OrderList;
}
private async Task SetShipStatus()
{
await OnSetShipped.InvokeAsync();
}
private async Task GetWithStatus(ProcessStatus status)
{
await OnGetStatus.InvokeAsync(status);
}
private async Task QuickPak(string orderId)
{
await OnQPak.InvokeAsync(orderId);
}
}

View file

@ -18,9 +18,7 @@ namespace Wonky.Client.Enums;
public enum ProductSort
{
None,
Desc,
Description,
Sku,
Qty,
Abbr
LastInvoiceDate
}

View file

@ -27,11 +27,37 @@ namespace Wonky.Client.Helpers;
/// </summary>
public static class Utils
{
public static List<ProductInventoryItemView> SortInventory(IEnumerable<ProductInventoryItemView> inventory,
ProductSort column, bool descending)
{
return column switch
{
ProductSort.Description => descending
? inventory.OrderByDescending(x => x.Description).ToList()
: inventory.OrderBy(x => x.Description).ToList(),
ProductSort.Sku => descending
? inventory.OrderByDescending(x => x.Sku).ToList()
: inventory.OrderBy(x => x.Sku).ToList(),
ProductSort.LastInvoiceDate => descending
? inventory.OrderByDescending(x => x.LastInvoiceDate).ToList()
: inventory.OrderBy(x => x.LastInvoiceDate).ToList(),
_ => inventory.OrderByDescending(x => x.LastInvoiceDate).ToList()
};
}
public static List<ProductVariant> GenerateVariantListDto(IEnumerable<DocView> items)
{
return items.Select(item => new ProductVariant { VariantId = item.VariantId, S5A = item.S5A, S9A = item.S9A }).ToList();
return items.Select(item => new ProductVariant
{
VariantId = item.VariantId,
S5A = item.S5A,
S9A = item.S9A
})
.ToList();
}
public static List<DocView> GenerateRevListView(IEnumerable<WorkplaceProduct> products)
{
var result = new List<DocView>();
@ -64,17 +90,19 @@ public static class Utils
break;
}
}
result.Add(newDoc);
}
}
return result.OrderBy(x => x.VariantName).ToList();
}
public static List<DocView> GenerateDocListView(IEnumerable<WorkplaceProduct> products)
{
var result = new List<DocView>();
var docProducts = products.OrderBy(x => x.TradingName).ToList();
foreach (var product in docProducts)
@ -104,45 +132,47 @@ public static class Utils
break;
}
}
result.Add(newDoc);
}
}
return result.OrderBy(x => x.VariantName).ToList();
}
public static List<EmailContact> ParseRecipientsFromString(string recipients)
{
var addresses = recipients
.Replace(" ", ",")
.Replace(",,",",")
.Replace(",,", ",")
.Split(",");
return (from address
in addresses
where IsValidEmail(address)
return (from address
in addresses
where IsValidEmail(address)
select new EmailContact
{
Name = address, Email = address
}).ToList();
}
public static List<UserRoleAssignment> MapSaveAssignedRoles(RoleAssignment model)
{
return new List<UserRoleAssignment>()
{
new (){ Name = "Admin", Assigned = model.Admin},
new (){ Name = "Advisor", Assigned = model.Advisor},
new (){ Name = "EDoc", Assigned = model.EDoc},
new (){ Name = "EShop", Assigned = model.EShop},
new (){ Name = "Management", Assigned = model.Management},
new (){ Name = "Office", Assigned = model.Office},
new (){ Name = "Supervisor", Assigned = model.Supervisor},
new (){ Name = "Warehouse", Assigned = model.Warehouse}
new() { Name = "Admin", Assigned = model.Admin },
new() { Name = "Advisor", Assigned = model.Advisor },
new() { Name = "EDoc", Assigned = model.EDoc },
new() { Name = "EShop", Assigned = model.EShop },
new() { Name = "Management", Assigned = model.Management },
new() { Name = "Office", Assigned = model.Office },
new() { Name = "Supervisor", Assigned = model.Supervisor },
new() { Name = "Warehouse", Assigned = model.Warehouse }
};
}
public static RoleAssignment MapEditAssignedRoles(UserManagerEditView model)
{
@ -177,9 +207,10 @@ public static class Utils
break;
}
}
return x;
}
public static string StringToDigits(string digitString)
{
@ -195,7 +226,7 @@ public static class Utils
return check.All(c => c is >= '0' and <= '9');
}
public static bool Validate(ValidateType validateType, string toValidate)
{
return validateType switch
@ -231,7 +262,7 @@ public static class Utils
{
validConditions++;
}
return validConditions == 3;
}

View file

@ -69,7 +69,8 @@ public class HttpInterceptorService
var absolutePath = e.Request.RequestUri.AbsolutePath;
if (!absolutePath.Contains("token"))
{
// call TryRefreshToken
// we are using the tryRefreshToken
// the service returns the current access token or a new token using refreshToken
var token = await _refreshTokenService.TryRefreshToken();
if (!string.IsNullOrEmpty(token))
{

View file

@ -34,6 +34,7 @@ public class CrmCustomerHistoryRepository : ICrmCustomerHistoryRepository
private readonly HttpClient _client;
private readonly ApiConfig _api;
public CrmCustomerHistoryRepository(
HttpClient client, ILogger<CrmCustomerHistoryRepository> logger,
NavigationManager navigation, IOptions<ApiConfig> configuration)
@ -44,92 +45,80 @@ public class CrmCustomerHistoryRepository : ICrmCustomerHistoryRepository
_api = configuration.Value;
}
/// <summary>
/// Fetch Invoice LIst
/// </summary>
/// <param name="companyId"></param>
/// <returns></returns>
public async Task<InvoiceListView> FetchInvoiceList(string companyId)
{
var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/invoices");
var content = await response.Content.ReadAsStringAsync();
return response.IsSuccessStatusCode
? JsonSerializer.Deserialize<InvoiceListView>(content, _options)
: new InvoiceListView();
if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(content))
{
return new InvoiceListView();
}
return JsonSerializer.Deserialize<InvoiceListView>(content, _options) ?? new InvoiceListView();
}
/// <summary>
/// Fetch given invoice for given customer
/// </summary>
/// <param name="companyId"></param>
/// <param name="invoiceId"></param>
/// <returns></returns>
public async Task<InvoiceView> FetchInvoice(string companyId, string invoiceId)
{
return await _client
var content = await _client
.GetFromJsonAsync<InvoiceView>($"{_api.CrmCustomers}/{companyId}/invoices/{invoiceId}", _options);
return content ?? new InvoiceView();
}
/// <summary>
/// Fetch inventory from given customer
/// </summary>
/// <param name="companyId"></param>
/// <returns></returns>
public async Task<List<ProductInventoryItemView>> FetchInventory(string companyId)
{
var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/{_api.CrmInventoryExt}");
if (!response.IsSuccessStatusCode)
return new List<ProductInventoryItemView>();
var content = await response.Content.ReadAsStringAsync();
return string.IsNullOrWhiteSpace(content)
? new List<ProductInventoryItemView>()
: JsonSerializer.Deserialize<List<ProductInventoryItemView>>(content, _options);
if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(content))
return new List<ProductInventoryItemView>();
return JsonSerializer.Deserialize<List<ProductInventoryItemView>>(content, _options) ?? new List<ProductInventoryItemView>();
}
/// <summary>
/// Fetch History for given customer
/// </summary>
/// <param name="companyId"></param>
/// <returns></returns>
public async Task<List<ProductHistoryView>> FetchHistory(string companyId)
public async Task<List<ProductHistoryView>> GetProductInvoiceLines(string companyId)
{
var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/{_api.CrmProductExt}");
if (!response.IsSuccessStatusCode)
return new List<ProductHistoryView>();
var content = await response.Content.ReadAsStringAsync();
return string.IsNullOrWhiteSpace(content)
? new List<ProductHistoryView>()
: JsonSerializer.Deserialize<List<ProductHistoryView>>(content, _options);
if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(content))
{
return new List<ProductHistoryView>();
}
return JsonSerializer.Deserialize<List<ProductHistoryView>>(content, _options) ?? new List<ProductHistoryView>();
}
/// <summary>
/// Fetch history for given customer and a given product
/// </summary>
/// <param name="companyId"></param>
/// <param name="sku"></param>
/// <returns></returns>
public async Task<List<ProductHistoryView>> FetchHistory(string companyId, string sku)
public async Task<List<ProductHistoryView>> GetProductInvoiceLines(string companyId, int months)
{
var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/{_api.CrmProductExt}/statistic?months={months}");
var content = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(content))
{
return new List<ProductHistoryView>();
}
return JsonSerializer.Deserialize<List<ProductHistoryView>>(content, _options) ?? new List<ProductHistoryView>();
}
public async Task<List<ProductHistoryView>> GetProductInvoiceLines(string companyId, string sku)
{
var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/{_api.CrmProductExt}/{sku}");
if (!response.IsSuccessStatusCode)
return new List<ProductHistoryView>();
var content = await response.Content.ReadAsStringAsync();
return string.IsNullOrWhiteSpace(content)
? new List<ProductHistoryView>()
: JsonSerializer.Deserialize<List<ProductHistoryView>>(content, _options);
if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(content))
{
return new List<ProductHistoryView>();
}
return JsonSerializer.Deserialize<List<ProductHistoryView>>(content, _options) ?? new List<ProductHistoryView>();
}
/// <summary>
/// RPC call to initiate remote server sync for given customer
/// </summary>
/// <param name="companyId"></param>
/// <param name="syncDate"></param>
/// <returns></returns>
public async Task<string> InvoiceErpToCrmRpc(string companyId, string syncDate)
{
var x = await _client.GetAsync($"{_api.SyncRpc}/companies/{companyId}/invoices/{syncDate}");
if (!x.IsSuccessStatusCode)
{
return string.Empty;
}
return await x.Content.ReadAsStringAsync();
}
}

View file

@ -49,15 +49,23 @@ public interface ICrmCustomerHistoryRepository
/// </summary>
/// <param name="companyId"></param>
/// <returns></returns>
Task<List<ProductHistoryView>> FetchHistory(string companyId);
Task<List<ProductHistoryView>> GetProductInvoiceLines(string companyId);
/// <summary>
/// Fetch history for customer for the past months. A hard limit on 24 months is enforced by the backed
/// </summary>
/// <param name="companyId"></param>
/// <param name="months"></param>
/// <returns></returns>
Task<List<ProductHistoryView>> GetProductInvoiceLines(string companyId, int months);
/// <summary>
/// Fetch history for given customer and a given product
/// </summary>
/// <param name="companyId"></param>
/// <param name="sku"></param>
/// <returns></returns>
Task<List<ProductHistoryView>> FetchHistory(string companyId, string sku);
Task<List<ProductHistoryView>> GetProductInvoiceLines(string companyId, string sku);
/// <summary>
/// RPC call to initiate remote server sync for given customer

View file

@ -150,11 +150,22 @@ public class AuthenticationService : IAuthenticationService
((AuthStateProvider)_authStateProvider).NotifyUserLogout();
}
public async Task<string> AccessToken()
{
return await _infoService.GetAccessToken();
}
public async Task<UserManagerEditView> UserInfo(bool write = false)
{
// use http to fetch userInfo for authenticated user
var response = await _client.GetAsync(_apiConfig.Value.UserInfoAuth).ConfigureAwait(true);
var content = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(content))
{
return new UserManagerEditView();
}
var userInfo = JsonSerializer.Deserialize<UserManagerEditView>(content, _options) ?? new UserManagerEditView();
if (write)
{

View file

@ -23,6 +23,7 @@ public interface IAuthenticationService
{
Task<AuthResponseView> Login(CredentialDto credentials);
Task Logout();
Task<string> AccessToken();
Task<string> RefreshToken();
Task<UserManagerEditView> UserInfo(bool write = false);
}

View file

@ -39,14 +39,13 @@ public class RefreshTokenService
var user = authState.User;
var expClaim = user.FindFirst(c => c.Type.Contains("exp"))?.Value;
//var expTime = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(expClaim));
var expTime = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(expClaim));
var diff = expTime - DateTime.UtcNow;
return diff.TotalMinutes <= 2
? await _authService.RefreshToken() //.ConfigureAwait(true)
: string.Empty;
return diff.TotalMinutes <= 10
? await _authService.RefreshToken()
: await _authService.AccessToken();
}
}

View file

@ -47,7 +47,7 @@ public partial class CustomerInventoryReorderOverlay
if (string.IsNullOrWhiteSpace(SalesItem.Sku))
return;
History = await HistoryRepo.FetchHistory(CompanyId, SalesItem.Sku);
History = await HistoryRepo.GetProductInvoiceLines(CompanyId, SalesItem.Sku);
if (!History.Any())
await Task.Delay(1000);
SelectedItem.Item = SalesItem;

View file

@ -24,10 +24,15 @@ namespace Wonky.Client.OverlayCustomer;
public partial class CustomerInvoiceViewOverlay : IDisposable
{
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string InvoiceId { get; set; } = "";
// ##############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public ICrmCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string InvoiceId { get; set; } = "";
// ##############################################################
private string _modalDisplay = "";
private bool _showBackdrop;
private InvoiceView Invoice { get; set; } = new();

View file

@ -31,22 +31,13 @@ public partial class ProductSelectionOverlay
private List<DocView> FilteredList { get; set; } = new();
private string SearchTerm { get; set; } = "";
protected override void OnInitialized()
{
FilterItems(SearchTerm);
}
private void ClearSearch()
{
SearchTerm = "";
FilterItems(SearchTerm);
}
private void OnSearchChanged()
{
FilterItems(SearchTerm);
}
private async Task SelectItem(DocView item)
{
item.Added = !item.Added;
@ -54,6 +45,19 @@ public partial class ProductSelectionOverlay
}
private void ClearSearch()
{
SearchTerm = "";
FilterItems(SearchTerm);
}
private void OnSearchChanged()
{
FilterItems(SearchTerm);
}
private void FilterItems(string filter)
{
SearchTerm = filter;

View file

@ -22,9 +22,11 @@ namespace Wonky.Client.OverlayOffice;
public partial class OfficeCustomerActivityListOverlay
{
// ##############################################################
[Parameter] public CompanyDto Company { get; set; } = new();
[Parameter] public List<ReportItemView> ActivityList { get; set; } = new();
// ##############################################################
private string _modalDisplay = "";
private bool _showBackdrop;

View file

@ -21,7 +21,10 @@ namespace Wonky.Client.OverlayOffice;
public partial class OfficeCustomerActivityViewOverlay
{
// ##############################################################
[Parameter] public ReportItemView Activity { get; set; } = new();
// ##############################################################
private string _modalDisplay = "";
private bool _showBackdrop;

View file

@ -30,11 +30,36 @@
<div class="list-group mt-2">
<div class="list-group-item bg-dark text-white">
<div class="row">
<div class="col-sm-4" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Desc))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div>
@*
<div class="col-sm-4" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Description))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-3" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-2 text-center" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Qty))"><i class="bi-sort-numeric-down"></i> Antal <i class="bi-sort-numeric-up-alt"></i></div>
<div class="col-sm-2"></div>
<div class="col-sm-1"></div>
*@
<div class="col-sm-7 text-end">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="sortOrder" checked @onclick="@SetSortOrder"/>
<label class="form-check-label" for="sortOrder">
<i class="@(Descending ? "bi-sort-up" : "bi-sort-down-alt")"></i>
</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="description" value="description"
onclick="@(() => SortProducts(ProductSort.Description))">
<label class="form-check-label" for="description">Navn</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="itemNumber" value="itemNumber"
onclick="@(() => SortProducts(ProductSort.Sku))">
<label class="form-check-label" for="itemNumber">Vare Nr.</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="lastInvoiceDate" value="itemDate"
onclick="@(() => SortProducts(ProductSort.LastInvoiceDate))" checked>
<label class="form-check-label" for="lastInvoiceDate">Sidst leveret</label>
</div>
</div>
<div class="col-sm-5">
<SearchPhraseComponent OnChanged="OnSearchChanged"/>
</div>
</div>
</div>
@foreach (var product in Inventory)

View file

@ -16,6 +16,7 @@
using Microsoft.AspNetCore.Components;
using Wonky.Client.Enums;
using Wonky.Client.Helpers;
using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Wonky.Client.Models;
@ -26,81 +27,75 @@ using Wonky.Entity.Views;
namespace Wonky.Client.OverlayOffice;
public partial class OfficeCustomerProductListOverlay : IDisposable
public partial class OfficeCustomerInventoryListOverlay : IDisposable
{
// ##############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public ICountryCatalogRepository CatalogRepo { get; set; }
[Inject] public ILogger<OfficeCustomerProductListOverlay> Logger { get; set; }
[Inject] public ILogger<OfficeCustomerInventoryListOverlay> Logger { get; set; }
// ##############################################################
[Parameter] public CompanyDto Company { get; set; } = new();
[Parameter] public List<ProductInventoryItemView> Inventory { get; set; } = new();
// ##############################################################
private string _modalDisplay = "";
private bool _showBackdrop;
private DraftItem DraftItem { get; set; } = new();
private SalesItemView SalesItem { get; set; } = new();
private OfficeCustomerInventoryReorderOverlay ReorderOverlay { get; set; } = new();
private bool Descending { get; set; }
private bool Descending { get; set; } = true;
private string SearchTerm { get; set; } = "";
private List<ProductInventoryItemView> FilteredList { get; set; } = new();
private ProductSort SortColumn { get; set; } = ProductSort.LastInvoiceDate;
[Parameter] public CompanyDto Company { get; set; } = new();
[Parameter] public List<ProductInventoryItemView> Inventory { get; set; } = new();
protected override void OnParametersSet()
{
if(Inventory.Any())
Inventory = Inventory.OrderBy(x => x.Description).ToList();
}
protected override void OnInitialized()
{
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
Inventory = Utils.SortInventory(Inventory, SortColumn, Descending);
FilterItems(SearchTerm);
StateHasChanged();
}
private void OnSearchChanged(string searchTerm)
{
FilterItems(searchTerm);
}
private void SortProducts(ProductSort column)
{
Descending = !Descending;
FilteredList = Utils.SortInventory(FilteredList, column, Descending);
}
private void SetSortOrder()
{
Descending = !Descending;
FilteredList = Utils.SortInventory(FilteredList, SortColumn, Descending);
}
private void FilterItems(string filter)
{
SearchTerm = filter;
FilteredList = string.IsNullOrWhiteSpace(filter)
? Inventory
: Inventory.Where(i => i.Description.ToLower().Contains(filter.ToLower())).ToList();
}
private async Task ShowSkuReorder(string sku)
{
SalesItem = await CatalogRepo.GetSalesItemSku(Company.CountryCode.ToLower(), sku);
ReorderOverlay.Show();
}
private void SortProducts(ProductSort column)
{
Descending = !Descending;
switch (column)
{
case ProductSort.Desc:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Description).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Description).ToList();
break;
case ProductSort.Sku:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Sku).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Sku).ToList();
break;
case ProductSort.Qty:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Quantity).ToList();
break;
case ProductSort.None:
break;
case ProductSort.Abbr:
break;
default:
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
}
public void Show()
{
_modalDisplay = "block;";
@ -108,6 +103,7 @@ public partial class OfficeCustomerProductListOverlay : IDisposable
StateHasChanged();
}
private void Hide()
{
_modalDisplay = "none;";
@ -115,9 +111,9 @@ public partial class OfficeCustomerProductListOverlay : IDisposable
StateHasChanged();
}
public void Dispose()
{
Interceptor.DisposeEvent();
}
}

View file

@ -26,10 +26,15 @@ namespace Wonky.Client.OverlayOffice;
public partial class OfficeCustomerInventoryReorderOverlay
{
// ##############################################################
[Parameter] public CompanyDto Company { get; set; }
[Parameter] public SalesItemView SalesItem { get; set; } = new();
[Inject] public ICountryCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
[Parameter] public EventCallback<DraftItem> OrderItemCallback { get; set; }
// ##############################################################
private List<ProductHistoryView> History { get; set; } = new();
private DraftItem SelectedItem { get; set; } = new();
private string ProductName { get; set; } = "";

View file

@ -24,12 +24,16 @@ namespace Wonky.Client.OverlayOffice;
public partial class OfficeCustomerInvoiceViewOverlay : IDisposable
{
// ##############################################################
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string InvoiceId { get; set; } = "";
[Parameter] public string CountryCode { get; set; } = "";
// ##############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public ICountryCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
private string _modalDisplay = "";
private bool _showBackdrop;
private InvoiceView Invoice { get; set; } = new();

View file

@ -23,7 +23,7 @@
<button type="button" class="btn-close" @onclick="@Hide" data-bs-dismiss="modal" aria-label="Luk"></button>
</div>
<div class="modal-body">
<OfficeInventoryListComponent OnReorderSelected="OnReorderCallback" CompanyId="@Company.CompanyId" Inventory="@Inventory"/>
<OfficeCustomerInventoryListComponent OnReorderSelected="OnReorderCallback" CompanyId="@Company.CompanyId" Inventory="@Inventory"/>
</div>
</div>
</div>
@ -33,4 +33,4 @@
<div class="modal-backdrop fade show"></div>
}
<OfficeOrderInventoryReorderOverlay Company="@Company" SalesItem="SalesItem" OnSelected="OnSelected" @ref="ReorderOverlay" />
<OfficeCustomerOrderInventoryReorderOverlay Company="@Company" SalesItem="SalesItem" OnSelected="OnSelected" @ref="ReorderOverlay" />

View file

@ -24,23 +24,26 @@ using Wonky.Entity.Views;
namespace Wonky.Client.OverlayOffice;
public partial class OfficeOrderInventoryListOverlay : IDisposable
public partial class OfficeCustomerOrderInventoryListOverlay : IDisposable
{
// ##############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public ICountryCatalogRepository CatalogRepo { get; set; }
[Inject] public ILogger<OfficeOrderInventoryListOverlay> Logger { get; set; }
[Inject] public ILogger<OfficeCustomerOrderInventoryListOverlay> Logger { get; set; }
// ##############################################################
[Parameter] public CompanyDto Company { get; set; } = new();
[Parameter] public List<ProductInventoryItemView> Inventory { get; set; } = new();
[Parameter] public EventCallback<DraftItem> OnSelected { get; set; }
// ##############################################################
private string _modalDisplay = "";
private bool _showBackdrop;
// private List<ProductInventoryView> ProductList { get; set; } = new();
private DraftItem DraftItem { get; set; } = new();
private SalesItemView SalesItem { get; set; } = new();
private OfficeOrderInventoryReorderOverlay ReorderOverlay { get; set; } = new();
private OfficeCustomerOrderInventoryReorderOverlay ReorderOverlay { get; set; } = new();
protected override void OnParametersSet()
{

View file

@ -24,12 +24,17 @@ using Wonky.Entity.Views;
namespace Wonky.Client.OverlayOffice;
public partial class OfficeOrderInventoryReorderOverlay
public partial class OfficeCustomerOrderInventoryReorderOverlay
{
// ##############################################################
[Inject] public ICountryCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
[Parameter] public CompanyDto Company { get; set; } = new();
[Parameter] public SalesItemView SalesItem { get; set; } = new();
[Parameter] public EventCallback<DraftItem> OnSelected { get; set; }
// ##############################################################
private List<ProductHistoryView>? ProductHistory { get; set; } = new();
private DraftItem SelectedItem { get; set; } = new();
private string ProductName { get; set; } = "";

View file

@ -29,17 +29,17 @@ namespace Wonky.Client.OverlayOrderCreate;
public partial class CatalogPagedOverlay : IDisposable
{
// injections
// ##############################################################
[Inject] public ICountryCatalogRepository CatalogRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public UserPreferenceService PreferenceService { get; set; }
[Inject] public ILogger<CatalogPagedOverlay> Logger { get; set; }
// parameters
// ##############################################################
[Parameter] public string CountryCode { get; set; } = "";
[Parameter] public EventCallback<SelectedSku> OnSelected { get; set; }
// variables
// ##############################################################
private string _modalDisplay = "";
private bool _showBackdrop;
private List<SalesItemView> Items { get; set; } = new();

View file

@ -21,12 +21,15 @@ namespace Wonky.Client.OverlayOrderCreate;
public partial class ProductCheckConfirmationOverlay
{
private string _modalDisplay = "";
private bool _showBackdrop;
// ##############################################################
[Parameter] public string BodyMessage { get; set; } = "";
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public List<ProductInventoryItemView> Products { get; set; } = new();
[Parameter] public EventCallback OnOkClicked { get; set; }
// ##############################################################
private string _modalDisplay = "";
private bool _showBackdrop;
public void Show()
{

View file

@ -24,10 +24,14 @@ namespace Wonky.Client.OverlayOrderCreate;
public partial class ProductHistoryOverlay
{
// [Parameter] public EventCallback<decimal> OnSelected { get; set; }
// ##############################################################
[Inject] public ICrmCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string ItemSku { get; set; } = "";
[Inject] public ICrmCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
private List<ProductHistoryView>? History { get; set; }
private string ProductName { get; set; } = "";
private string _modalDisplay = "";
@ -35,18 +39,13 @@ public partial class ProductHistoryOverlay
protected override async Task OnParametersSetAsync()
{
History = await HistoryRepo.FetchHistory(CompanyId, ItemSku);
History = await HistoryRepo.GetProductInvoiceLines(CompanyId, ItemSku);
if (History.Any())
{
ProductName = History[0].Description;
}
}
// private void SelectPrice(decimal price)
// {
// OnSelected.InvokeAsync(price);
// Hide();
// }
public void Show()
{
@ -55,6 +54,7 @@ public partial class ProductHistoryOverlay
StateHasChanged();
}
private void Hide()
{
_modalDisplay = "none;";

View file

@ -24,10 +24,15 @@ namespace Wonky.Client.OverlayOrderCreate;
public partial class ProductPriceHistoryOverlay
{
// ##############################################################
[Inject] public ICrmCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
[Parameter] public EventCallback<decimal> OnSelected { get; set; }
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string Sku { get; set; } = "";
[Inject] public ICrmCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
private List<ProductHistoryView>? ProductHistory { get; set; }
private string ProductName { get; set; } = "";
private string _modalDisplay = "";
@ -38,19 +43,21 @@ public partial class ProductPriceHistoryOverlay
if (string.IsNullOrWhiteSpace(Sku))
return;
ProductHistory = await HistoryRepo.FetchHistory(CompanyId, Sku);
ProductHistory = await HistoryRepo.GetProductInvoiceLines(CompanyId, Sku);
if (ProductHistory.Any())
{
ProductName = ProductHistory[0].Description;
}
}
private void SelectPrice(decimal price)
{
OnSelected.InvokeAsync(price);
Hide();
}
public void Show()
{
_modalDisplay = "block;";
@ -58,6 +65,7 @@ public partial class ProductPriceHistoryOverlay
StateHasChanged();
}
private void Hide()
{
_modalDisplay = "none;";

View file

@ -22,7 +22,7 @@
<PageTitle>Produkt oversigt for @Company.Name</PageTitle>
<div class="row ps-3 pt-2 pb-1 rounded-2 bg-dark text-white">
<div class="col-sm-6">
<h4 class="pt-1">Produkt oversigt @Company.Name</h4>
<h4 class="pt-1">Produkt køb @Company.Name</h4>
</div>
<div class="col-sm-3 align-content-end d-print-none">
<a class="btn btn-primary d-block" href="/advisor/customers/@CompanyId"><i class="bi-chevron-left"></i> Stamkort</a>
@ -33,10 +33,9 @@
</div>
<CustomerInventoryListComponent OnReorderSelected="OnReorderCallback" CompanyId="@CompanyId" Inventory="@Inventory"/>
<CustomerInventoryReorderOverlay OnSelected="@OnSelectedItem" CompanyId="@CompanyId" SalesItem="@SalesItem" @ref="ReorderOverlay"/>
@if (Working)
{
<WorkingThreeDots/>
}
<CustomerInventoryReorderOverlay OnSelected="@OnSelectedItem" CompanyId="@CompanyId" SalesItem="@SalesItem" @ref="ReorderOverlay"/>
}

View file

@ -19,6 +19,8 @@ using System.Xml;
using Blazored.LocalStorage;
using Blazored.Toast.Services;
using Microsoft.AspNetCore.Components;
using Wonky.Client.Enums;
using Wonky.Client.Helpers;
using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Wonky.Client.Models;
@ -65,7 +67,8 @@ public partial class AdvisorCustomerInventoryListPage : IDisposable
Working = false;
}
private async Task OnReorderCallback(string sku)
{
// fetch item from http repo

View file

@ -311,12 +311,9 @@
<button type="button" class="btn btn-outline-dark" @onclick="@ToggleVisibility">@ToggleButtonText</button>
</div>
<div class="col-sm-4 d-grid">
@if (AppInfo!.Value!.Rc)
@if (UserInfo.CountryCode is "DK")
{
@if (UserInfo.CountryCode is "DK")
{
<a class="btn btn-info" href="@($"/advisor/customers/{CompanyId}/workplaces")">Kemi Dokumentation</a>
}
<a class="btn btn-info" href="@($"/advisor/customers/{CompanyId}/workplaces")">Kemi Dokumentation</a>
}
</div>
</div>

View file

@ -33,12 +33,11 @@
<div class="col-sm-12 col-md-6">
<InfoColorCustomerComponent/>
</div>
@*
<div class="col-sm-12 col-md-6">
<InfoColorPackageComponent/>
</div>
<div class="col-sm-12 col-md-6">
<InfoCommonComponent />
</div>
*@
<div class="col-sm-12 col-md-6">
<InfoProcessStateComponent />
</div>
@ -48,12 +47,17 @@
<div class="col-sm-12 col-md-6">
<InfoQuoteToolbarComponent/>
</div>
<div class="col-sm-12 col-md-6">
<InfoCommonComponent />
</div>
<div class="col-sm-12 col-md-6">
<InfoOfficeComponent />
</div>
@*
<div class="col-sm-12 col-md-6">
<InfoWarehouseComponent />
</div>
*@
<div class="col-sm-12 col-md-6">
<InfoBrowserComponent />
</div>

View file

@ -245,4 +245,4 @@
<CatalogPagedOverlay @ref="CatalogOverlay" CountryCode="@CountryCode" OnSelected="PriceListCallback"/>
<OfficeCustomerInvoiceListOverlay @ref="InvoiceListOverlay" Company="Company" InvoiceList="CompanyInvoices"/>
<OfficeCustomerActivityListOverlay @ref="ActivityListOverlay" Company="Company" ActivityList="CompanyActivities"/>
<OfficeOrderInventoryListOverlay @ref="InventoryListOverlay" Company="Company" Inventory="CompanyInventory" OnSelected="InventoryCallback"/>
<OfficeCustomerOrderInventoryListOverlay @ref="InventoryListOverlay" Company="Company" Inventory="CompanyInventory" OnSelected="InventoryCallback"/>

View file

@ -80,7 +80,7 @@ public partial class OfficeOrderCreatePage : IDisposable
private CatalogPagedOverlay CatalogOverlay { get; set; } = new();
private OfficeCustomerInvoiceListOverlay InvoiceListOverlay { get; set; } = new();
private OfficeCustomerActivityListOverlay ActivityListOverlay { get; set; } = new();
private OfficeOrderInventoryListOverlay InventoryListOverlay { get; set; } = new();
private OfficeCustomerOrderInventoryListOverlay InventoryListOverlay { get; set; } = new();
// #############################################################
// lists

View file

@ -37,6 +37,7 @@ public partial class SupervisorDocumentNewPage : IDisposable
[Inject] public ILogger<SupervisorDocumentNewPage> Logger { get; set; }
[Inject] public IToastService Toaster { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public ISystemSendMailService SendMail { get; set; }
// ############################################################
@ -102,6 +103,25 @@ public partial class SupervisorDocumentNewPage : IDisposable
else
{
Toaster.ShowSuccess("Ok");
var sendTo = new List<EmailContact>
{
new () { Email = "paul@innotec.dk", Name = "Paul Vendelbo" },
new () { Email = "eddie@innotec.dk", Name = "Eddie Broch" }
};
var msg = new EmailMessage
{
Body = Document.Content,
Subject = Document.Description,
To = sendTo,
IsBodyHtml = false
};
Toaster.ShowInfo("Sender email ...");
var result = await SendMail.SendMail("system", msg);
if (result.IsSuccess)
{
Toaster.ClearAll();
Toaster.ShowInfo("Email sendt.");
}
Navigator.NavigateTo($"/supervisor/advisors/{AdvisorId}/documents");
}
Toaster.ClearAll();

View file

@ -82,7 +82,7 @@
<div class="card-footer">
<div class="row">
<div class="col text-start">
<button type="button" class="btn btn-warning" nclick="@RemoveDocument"><i class="bi-trash"></i> Slet</button>
<button type="button" class="btn btn-warning" @onclick="@RemoveDocument"><i class="bi-trash"></i> Slet</button>
</div>
<div class="col text-end">
<button type="submit" class="btn btn-primary"><i class="bi-cloud-upload"></i> Gem</button>

View file

@ -1,36 +0,0 @@
@* Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@
@using Wonky.Client.Components
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Roles = "Admin,Office,Warehouse")]
@page "/warehouse/orders/{Status}"
<div class="row">
<div class="col">
<h2>Forsendelser</h2>
</div>
<div class="col">
<div class="busy-signal" style="display:@(Working ? "block" : "none")">
<div class="spinner-grow text-info" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
<WarehouseListComponent Header="@Header" OrderList="@OrderList" ReadyToShip="@ReadyToShip"
OnGetStatus="GetStatusCallback" OnSetShipped="@SetShippedCallback" OnQPak="QPakCallback" />

View file

@ -1,133 +0,0 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using Microsoft.AspNetCore.Components;
using Wonky.Client.Enums;
using Wonky.Client.Helpers;
using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Wonky.Client.Models;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages;
public partial class WarehouseOrderListPage : IDisposable
{
// #############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public IOrderProcessRepository OrderProcessRepo { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
// #############################################################
[Parameter] public string Status { get; set; } = "none";
// #############################################################
private List<WarehouseOrderView> OrderList { get; set; } = new();
private bool Working { get; set; } = true;
private string Header { get; set; } = "Ubehandlet";
private bool ReadyToShip { get; set; }
protected override async Task OnParametersSetAsync()
{
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
OrderList = await FetchOrders(Status);
}
private async Task GetStatusCallback(ProcessStatus status)
{
Working = true;
OrderList = new List<WarehouseOrderView>();
switch (status)
{
case ProcessStatus.None or ProcessStatus.Printed:
Header = "Ubehandlede ordrer";
ReadyToShip = false;
break;
case ProcessStatus.Picked:
Header = "Plukkede ordrer";
ReadyToShip = false;
break;
case ProcessStatus.Packed:
Header = "Pakkede ordrer";
ReadyToShip = true;
break;
case ProcessStatus.Shipped:
break;
case ProcessStatus.All:
break;
case ProcessStatus.Express:
break;
default:
throw new ArgumentOutOfRangeException(nameof(status), status, null);
}
Status = Utils.EnumToString(status).ToLower();
OrderList = await FetchOrders(Status);
Working = false;
}
/// <summary>
/// QPak - quick mark an order as shipped
/// </summary>
/// <param name="orderId"></param>
private async Task QPakCallback(string orderId)
{
Working = true;
var order = OrderList.First(x => x.OrderId == orderId);
order.ProcessStatusEnum = "packed";
var process = new OrderProcessState
{
OrderId = order.OrderId,
ProcessStatusEnum = "packed"
};
await OrderProcessRepo.UpdateWarehouseOrderStatus(process);
OrderList.Remove(order);
Working = false;
}
/// <summary>
/// Set status shipped where status is packed
/// </summary>
private async Task SetShippedCallback()
{
Working = true;
foreach (var order in OrderList.Where(order => order.ProcessStatusEnum.ToLower() == "packed"))
{
order.ProcessStatusEnum = "shipped";
var process = new OrderProcessState
{
OrderId = order.OrderId,
ProcessStatusEnum = "shipped"
};
await OrderProcessRepo.UpdateWarehouseOrderStatus(process);
}
Working = false;
}
private async Task<List<WarehouseOrderView>> FetchOrders(string status)
{
Working = true;
var orderList = await OrderProcessRepo.GetWarehouseOrderListByStatus(status.ToLower());
if(orderList.Any(x => x.Express))
orderList = orderList.OrderByDescending(x => x.Express).ToList();
Working = false;
return orderList;
}
public void Dispose() => Interceptor.DisposeEvent();
}

View file

@ -1,129 +0,0 @@
@* Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@
@using Microsoft.AspNetCore.Authorization
@using Wonky.Client.Components
@attribute [Authorize(Roles = "Admin,Office,Warehouse")]
@page "/warehouse/orders/process/{OrderId}"
@if (!string.IsNullOrWhiteSpace(Order.OrderDate))
{
<table class="table">
<thead>
<tr>
<th colspan="4">
@if (Order.Express)
{
<h2 class="text-center fw-bold">HASTE ORDRE</h2>
}
<h2 class="text-center">@Order.Company.Name</h2>
</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Konto</th>
<td>@Order.Company.Account</td>
<th scope="row">Telefon</th>
<td>@Order.Company.Phone</td>
</tr>
<tr>
<th scope="row">Kunde</th>
<td>@Order.Company.Name</td>
<th scope="row">Lev.Navn</th>
<td>@Order.DlvName</td>
</tr>
<tr>
<th scope="row">Adresse</th>
<td>@Order.Company.Address1</td>
<th scope="row">Lev.Adresse</th>
<td>@Order.DlvAddress1</td>
</tr>
<tr>
<th scope="row">Adresse</th>
<td>@Order.Company.Address2</td>
<th scope="row">Lev.Adresse</th>
<td>@Order.DlvAddress2</td>
</tr>
<tr>
<th scope="row">Postnr By</th>
<td>@Order.Company.ZipCode @Order.Company.City</td>
<th scope="row">Lev.Postnr By</th>
<td>@Order.DlvZipCity</td>
</tr>
</tbody>
</table>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Placering</th>
<th scope="col">Antal</th>
<th scope="col">Varenr</th>
<th scope="col">Beskrivelse</th>
</tr>
</thead>
<tbody>
@foreach (var line in Order.Lines)
{
<tr>
<td class="fw-bold">@line.Location</td>
<td>@line.Quantity</td>
<td>@line.Sku</td>
<td>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="@line.LineNumber"/>
<label class="form-check-label" for="@line.LineNumber">@line.Description</label>
</div>
</td>
</tr>
}
</tbody>
</table>
@if (!string.IsNullOrWhiteSpace(Order.OfficeNote))
{
<div class="alert bg-light border-dark border-2">
<h3 class="text-center">@Order.OfficeNote</h3>
</div>
}
<div class="row">
<div class="col-md-3">
<a class="btn btn-outline-success text-nowrap" href="/warehouse/orders/none">Ubehandlet</a>
</div>
<div class="col-md-3">
</div>
<div class="col-md-4">
@if (Order.ProcessStatusEnum.ToLower() is "none" or "printed")
{
<button class="btn btn-lg btn-warning text-nowrap" type="button" @onclick="@SetProcessStatusPicked" disabled="@Working">Sæt status plukket</button>
}
@if (Order.ProcessStatusEnum.ToLower() == "picked")
{
<button class="btn btn-lg btn-danger text-nowrap" type="button" @onclick="@SetProcessStatusPacked" disabled="@Working">Sæt status pakket</button>
}
@if (Order.ProcessStatusEnum.ToLower() == "packed")
{
<button class="btn btn-lg btn-primary text-nowrap" type="button" @onclick="@SetProcessStatusShipped" disabled="@Working">Sæt status afsendt</button>
}
</div>
</div>
}
@if (Working)
{
<WorkingThreeDots/>
}

View file

@ -1,103 +0,0 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using System.Text.Json;
using Blazored.Toast.Services;
using Microsoft.AspNetCore.Components;
using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages;
public partial class WarehouseOrderViewPage : IDisposable
{
// #############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public IOrderProcessRepository OrderProcessRepo { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IToastService Toast { get; set; }
[Inject] public ILogger<WarehouseOrderViewPage> Logger { get; set; }
// #############################################################
[Parameter] public string OrderId { get; set; } = "";
// #############################################################
private WarehouseOrderView Order { get; set; } = new();
private bool Working { get; set; } = true;
protected override async Task OnParametersSetAsync()
{
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
if (!string.IsNullOrWhiteSpace(OrderId))
Order = await OrderProcessRepo.GetWarehouseOrder(OrderId);
Logger.LogDebug("Warehouse OrderView =>\n{}", JsonSerializer.Serialize(Order));
Working = false;
}
private async Task SetProcessStatusPicked()
{
if (Working)
return;
Working = true;
var process = new OrderProcessState
{
OrderId = Order.OrderId,
ProcessStatusEnum = "picked"
};
await OrderProcessRepo.UpdateWarehouseOrderStatus(process);
Navigator.NavigateTo("/warehouse/orders/none");
}
private async Task SetProcessStatusPacked()
{
if (Working)
return;
Working = true;
var process = new OrderProcessState
{
OrderId = Order.OrderId,
ProcessStatusEnum = "packed"
};
await OrderProcessRepo.UpdateWarehouseOrderStatus(process);
Navigator.NavigateTo("/warehouse/orders/picked");
}
private async Task SetProcessStatusShipped()
{
if (Working)
return;
Working = true;
var process = new OrderProcessState
{
OrderId = Order.OrderId,
ProcessStatusEnum = "shipped"
};
await OrderProcessRepo.UpdateWarehouseOrderStatus(process);
Navigator.NavigateTo("/warehouse/orders/none");
}
public void Dispose()
{
Interceptor.DisposeEvent();
}
}

View file

@ -15,6 +15,7 @@
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Json;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
using Wonky.Client.Local.Services;
@ -26,35 +27,43 @@ namespace Wonky.Client.Shared;
public class AuthStateProvider : AuthenticationStateProvider
{
private readonly HttpClient _client;
// private readonly ILocalStorageService _storage;
private readonly AuthenticationState _anonymous;
private readonly IUserInfoService _infoService;
public AuthStateProvider(HttpClient client, IUserInfoService infoService)
private readonly ILogger<AuthStateProvider> _logger;
public AuthStateProvider(HttpClient client, IUserInfoService infoService, ILogger<AuthStateProvider> logger)
{
_client = client;
_infoService = infoService;
_logger = logger;
_anonymous = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = await _infoService.GetAccessToken();
_logger.LogDebug("accessToken {}", token);
if (string.IsNullOrEmpty(token))
{
return _anonymous;
}
var userInfo = await _infoService.GetUserInfo();
if (string.IsNullOrWhiteSpace(userInfo.UserId))
_logger.LogDebug("userInfo {}", JsonSerializer.Serialize(userInfo));
if (userInfo == null || string.IsNullOrWhiteSpace(userInfo.FirstName))
{
NotifyUserLogout();
return _anonymous;
}
_logger.LogDebug("userInfo.FirstName {}", userInfo.FirstName);
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
var exp = await _infoService.GetExpiration();
_logger.LogDebug("expiration {}", exp);
var claims = new List<Claim>
{
new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"),
@ -64,30 +73,37 @@ public class AuthStateProvider : AuthenticationStateProvider
new(ClaimTypes.Expiration, exp.ToString())
};
claims.AddRange(
from role in userInfo.AssignedRoles
where role.Assigned select new Claim(ClaimTypes.Role, role.Name)
);
claims.Add( new Claim(ClaimTypes.Role, userInfo.CountryCode));
from role in userInfo.AssignedRoles
where role.Assigned
select new Claim(ClaimTypes.Role, role.Name)
);
// return the authState for the user
return new AuthenticationState(
new ClaimsPrincipal(
new ClaimsIdentity(claims, "token")));
new ClaimsIdentity(claims, "token"))
);
}
public async void NotifyUserAuthenticationAsync(string token)
{
if (string.IsNullOrEmpty(token))
{
NotifyUserLogout();
return;
}
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
var userInfo = await _infoService.GetUserInfo();
if (string.IsNullOrWhiteSpace(userInfo.UserId))
{
NotifyUserLogout();
return;
}
var exp = await _infoService.GetExpiration();
var claims = new List<Claim>
{
new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"),
@ -97,18 +113,22 @@ public class AuthStateProvider : AuthenticationStateProvider
new(ClaimTypes.Expiration, exp.ToString())
};
claims.AddRange(
from role in userInfo.AssignedRoles
where role.Assigned select new Claim(ClaimTypes.Role, role.Name));
from role in userInfo.AssignedRoles
where role.Assigned
select new Claim(ClaimTypes.Role, role.Name)
);
var authState = Task.FromResult(
new AuthenticationState(
new ClaimsPrincipal(
new ClaimsIdentity(claims, "token"))));
new ClaimsIdentity(claims, "token"))
)
);
NotifyAuthenticationStateChanged(authState);
}
public void NotifyUserLogout()
{
_client.DefaultRequestHeaders.Authorization = null;

View file

@ -66,11 +66,13 @@
<i class="bi-building pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Kunder SE
</NavLink>
</div>
@*
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/warehouse/orders/none">
<i class="bi-box pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Forsendelse
</NavLink>
</div>
*@
</AuthorizeView>
<AuthorizeView Roles="Advisor">

View file

@ -1,15 +1,15 @@
{
"appInfo": {
"name": "Wonky Online",
"version": "153.0",
"version": "157.1",
"rc": true,
"sandBox": false,
"image": "grumpy-coder.png"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Information",
"Default": "Debug",
"System": "Debug",
"Microsoft": "Information"
},
"Debug": {
@ -19,7 +19,7 @@
}
},
"apiConfig": {
"baseUrl": "https://zeta.innotec.dk",
"baseUrl": "https://dev.innotec.dk",
"catalog": "api/v2/catalog/country",
"crmCustomers": "api/v2/crm/companies",
"crmInventoryExt": "history/inventory",

View file

@ -16,6 +16,14 @@ body {
.workDate {
font-variant: small-caps;
}
.action-link-element {
cursor: pointer;
}
.i-larger {
font-size: 1.3rem;
}
.btn.btn-edit {
color: #030303;
background-color: #a2a2ec;
@ -219,13 +227,6 @@ a, .btn-link {
}
/* end led elements */
.action-link-element {
cursor: pointer;
}
.i-larger {
font-size: 1.3rem;
}
.footer {
border-top: 2px solid orange;
position: relative;

View file

@ -17,11 +17,17 @@ namespace Wonky.Entity.Views;
public class ProductInventoryItemView
{
public virtual bool AgedProduct()
{
return DateTime.Parse(LastInvoiceDate) < DateTime.Now.AddMonths(-12);
}
public virtual bool Check { get; set; }
public string Description { get; set; } = "";
public bool Discontinued { get; set; }
public string PictureLink { get; set; } = "";
public int Quantity { get; set; }
public string LastInvoiceDate { get; set; } = "";
public string Sku { get; set; } = "";
public string VendorItemNo { get; set; } = "";
public string VendorItemNo { get; set; } = "";
}