toggle edit in customer view/edit page

This commit is contained in:
Frede Hundewadt 2023-02-26 09:17:59 +01:00
parent 1a0255a14a
commit 41eccd64bf
32 changed files with 1523 additions and 1483 deletions

View file

@ -26,31 +26,30 @@ using Wonky.Entity.Views;
#pragma warning disable CS8618 #pragma warning disable CS8618
namespace Wonky.Client.Components namespace Wonky.Client.Components;
public partial class AdvisorCustomerListComponent
{ {
public partial class AdvisorCustomerListComponent [Parameter] public List<CompanyDto> CompanyList { get; set; } = new();
[Parameter] public EventCallback<string> OnDelete { get; set; }
[Parameter] public EventCallback<string> OnSelect { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IJSRuntime JsRuntime { get; set; }
private Lazy<IJSObjectReference> BsTooltip { get; set; } = new();
private InformationModal InformationModal { get; set; } = new();
private string InfoMessage { get; set; } = "";
private string CompanyId { get; set; } = "";
private void ViewCustomer(string companyId)
{ {
[Parameter] public List<CompanyDto> CompanyList { get; set; } = new(); Navigator.NavigateTo($"/advisor/customers/{companyId}");
[Parameter] public EventCallback<string> OnDelete { get; set; } }
[Parameter] public EventCallback<string> OnSelect { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IJSRuntime JsRuntime { get; set; }
private Lazy<IJSObjectReference> BsTooltip { get; set; } = new(); private void CallInformationModal(string info)
{
private InformationModal InformationModal { get; set; } = new(); InfoMessage = info;
private string InfoMessage { get; set; } = ""; InformationModal.Show();
private string CompanyId { get; set; } = ""; }
}
private void ViewCustomer(string companyId)
{
Navigator.NavigateTo($"/advisor/customers/{companyId}");
}
private void CallInformationModal(string info)
{
InfoMessage = info;
InformationModal.Show();
}
}
}

View file

@ -17,35 +17,34 @@ using System.Timers;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Timer = System.Timers.Timer; using Timer = System.Timers.Timer;
namespace Wonky.Client.Components namespace Wonky.Client.Components;
public partial class CatalogSearchPhraseComponent
{ {
public partial class CatalogSearchPhraseComponent private Timer Timer { get; set; } = new();
private string SearchTerm { get; set; } = "";
[Parameter] public EventCallback<string> OnChanged { get; set; }
private void ClearSearch()
{ {
private Timer Timer { get; set; } = new(); SearchTerm = "";
private string SearchTerm { get; set; } = ""; OnChanged.InvokeAsync("");
[Parameter] public EventCallback<string> OnChanged { get; set; } }
private void ClearSearch()
{
SearchTerm = "";
OnChanged.InvokeAsync("");
}
private void OnSearchChanged() private void OnSearchChanged()
{ {
Timer.Dispose(); Timer.Dispose();
Timer = new Timer(500); Timer = new Timer(500);
Timer.AutoReset = false; Timer.AutoReset = false;
Timer.Elapsed += OnTimerElapsed; Timer.Elapsed += OnTimerElapsed;
Timer.Enabled = true; Timer.Enabled = true;
} }
private void OnTimerElapsed(object? sender, ElapsedEventArgs e) private void OnTimerElapsed(object? sender, ElapsedEventArgs e)
{ {
OnChanged.InvokeAsync(SearchTerm); OnChanged.InvokeAsync(SearchTerm);
Timer.Elapsed -= OnTimerElapsed; Timer.Elapsed -= OnTimerElapsed;
Timer.Enabled = false; Timer.Enabled = false;
Timer.Dispose(); Timer.Dispose();
} }
} }
}

View file

@ -19,48 +19,47 @@ using Wonky.Client.Services;
using Timer = System.Timers.Timer; using Timer = System.Timers.Timer;
#pragma warning disable CS8618 #pragma warning disable CS8618
namespace Wonky.Client.Components namespace Wonky.Client.Components;
public partial class CustomerSearchPhraseComponent
{ {
public partial class CustomerSearchPhraseComponent private Timer InputTimer { get; set; } = new();
private string SearchTerm { get; set; } = "";
private UserProfile Profiles { get; set; } = new ();
[Inject] public UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
protected override async Task OnInitializedAsync()
{ {
private Timer InputTimer { get; set; } = new(); Profiles = await ProfileService.GetProfile();
private string SearchTerm { get; set; } = ""; SearchTerm = string.IsNullOrWhiteSpace(Profiles.CompanyFilterPhrase) ? "" : Profiles.CompanyFilterPhrase.Trim();
private UserProfile Profiles { get; set; } = new ();
[Inject] public UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
protected override async Task OnInitializedAsync()
{
Profiles = await ProfileService.GetProfile();
SearchTerm = string.IsNullOrWhiteSpace(Profiles.CompanyFilterPhrase) ? "" : Profiles.CompanyFilterPhrase.Trim();
if(!string.IsNullOrWhiteSpace(SearchTerm)) if(!string.IsNullOrWhiteSpace(SearchTerm))
await OnChanged.InvokeAsync(SearchTerm);
}
private async Task ClearSearch()
{
InputTimer.Dispose();
SearchTerm = "";
await ProfileService.SetCompanyFilterPhrase(SearchTerm.Trim());
await OnChanged.InvokeAsync(SearchTerm); await OnChanged.InvokeAsync(SearchTerm);
} }
private async Task OnSearchChanged()
{
await ProfileService.SetCompanyFilterPhrase(SearchTerm.Trim());
InputTimer.Dispose();
InputTimer = new Timer(500);
InputTimer.AutoReset = false;
InputTimer.Elapsed += OnTimerElapsed;
InputTimer.Enabled = true;
}
private void OnTimerElapsed(object? sender, ElapsedEventArgs e) private async Task ClearSearch()
{ {
InputTimer.Dispose(); InputTimer.Dispose();
OnChanged.InvokeAsync(SearchTerm); SearchTerm = "";
} await ProfileService.SetCompanyFilterPhrase(SearchTerm.Trim());
} await OnChanged.InvokeAsync(SearchTerm);
} }
private async Task OnSearchChanged()
{
await ProfileService.SetCompanyFilterPhrase(SearchTerm.Trim());
InputTimer.Dispose();
InputTimer = new Timer(500);
InputTimer.AutoReset = false;
InputTimer.Elapsed += OnTimerElapsed;
InputTimer.Enabled = true;
}
private void OnTimerElapsed(object? sender, ElapsedEventArgs e)
{
InputTimer.Dispose();
OnChanged.InvokeAsync(SearchTerm);
}
}

View file

@ -20,38 +20,36 @@ using Microsoft.AspNetCore.Components;
using Wonky.Client.Services; using Wonky.Client.Services;
#pragma warning disable CS8618 #pragma warning disable CS8618
namespace Wonky.Client.Components namespace Wonky.Client.Components;
{
public partial class CustomerSortComponent : IDisposable
{
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
private Dictionary<string, string> Items { get; set; } = new();
private UserProfile _profiles = new();
private string SortCol { get; set; } = "name";
protected override async Task OnInitializedAsync()
{
ProfileService.OnChange += ProfileServiceOnOnChange;
_profiles = await ProfileService.GetProfile();
SortCol = _profiles.CompanySort;
}
private async Task OnSelectionChanged(ChangeEventArgs e)
{
var val = e.Value.ToString();
if (val == "-1") return;
await OnChanged.InvokeAsync(val);
await ProfileService.SetCompanySort(val);
}
private void ProfileServiceOnOnChange(UserProfile newUserProfile)
{
_profiles = newUserProfile;
StateHasChanged();
}
public void Dispose()
{
ProfileService.OnChange -= ProfileServiceOnOnChange;
}
}
}
public partial class CustomerSortComponent : IDisposable
{
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
private Dictionary<string, string> Items { get; set; } = new();
private UserProfile _profiles = new();
private string SortCol { get; set; } = "name";
protected override async Task OnInitializedAsync()
{
ProfileService.OnChange += ProfileServiceOnOnChange;
_profiles = await ProfileService.GetProfile();
SortCol = _profiles.CompanySort;
}
private async Task OnSelectionChanged(ChangeEventArgs e)
{
var val = e.Value.ToString();
if (val == "-1") return;
await OnChanged.InvokeAsync(val);
await ProfileService.SetCompanySort(val);
}
private void ProfileServiceOnOnChange(UserProfile newUserProfile)
{
_profiles = newUserProfile;
StateHasChanged();
}
public void Dispose()
{
ProfileService.OnChange -= ProfileServiceOnOnChange;
}
}

View file

@ -27,77 +27,76 @@ using Utils = Wonky.Client.Helpers.Utils;
#pragma warning disable CS8618 #pragma warning disable CS8618
namespace Wonky.Client.Components namespace Wonky.Client.Components;
public partial class OfficeCountryCustomerListComponent
{ {
public partial class OfficeCountryCustomerListComponent // ******************************************************
// parameters
[Parameter] public string CountryCode { get; set; } = "";
[Parameter] public List<CompanyDto> CompanyList { get; set; } = new();
// [Parameter] public EventCallback<DraftItem> OnOrderItem { get; set; }
[CascadingParameter] public DraftStateProvider DraftProvider { get; set; }
// ******************************************************
// injects
[Inject] public ICountryCustomerHistoryRepository HistoryRepo { get; set; }
[Inject] public ICountryActivityRepository ActivityRepo { get; set; }
// ******************************************************
// overlays
private OfficeCustomerInvoiceListOverlay InvoiceListOverlay { get; set; } = new();
private OfficeCustomerActivityListOverlay ActivityListOverlay { get; set; } = new();
private OfficeCustomerProductListOverlay ProductListOverlay { get; set; } = new();
// ******************************************************
// variables
private InvoiceListView InvoiceList { get; set; } = new();
private List<ReportItemView> ActivityList { get; set; } = new();
private List<ProductInventoryView> ProductList { get; set; } = new();
private CompanyDto SelectedCompany { get; set; } = new();
// ******************************************************
// functions
private async Task ShowInvoiceList(string companyId)
{ {
// ****************************************************** // check for console manipulation
// parameters if (!Utils.Validate(VType.Id, companyId)) return;
[Parameter] public string CountryCode { get; set; } = ""; SelectedCompany = CompanyList.First(x => x.CompanyId == companyId);
[Parameter] public List<CompanyDto> CompanyList { get; set; } = new(); // call erp to crm sync before requesting invoices
// [Parameter] public EventCallback<DraftItem> OnOrderItem { get; set; } var newSyncDate = await HistoryRepo.RequestErpToCrmSync(CountryCode, companyId, SelectedCompany.HistorySync);
[CascadingParameter] public DraftStateProvider DraftProvider { get; set; } await Task.Delay(500);
InvoiceList = await HistoryRepo.RequestInvoiceList(CountryCode, companyId);
// ****************************************************** if(!string.IsNullOrWhiteSpace(newSyncDate)) SelectedCompany.HistorySync = newSyncDate;
// injects InvoiceListOverlay.Show();
[Inject] public ICountryCustomerHistoryRepository HistoryRepo { get; set; } }
[Inject] public ICountryActivityRepository ActivityRepo { get; set; }
// ******************************************************
// overlays
private OfficeCustomerInvoiceListOverlay InvoiceListOverlay { get; set; } = new();
private OfficeCustomerActivityListOverlay ActivityListOverlay { get; set; } = new();
private OfficeCustomerProductListOverlay ProductListOverlay { get; set; } = new();
// ******************************************************
// variables
private InvoiceListView InvoiceList { get; set; } = new();
private List<ReportItemView> ActivityList { get; set; } = new();
private List<ProductInventoryView> ProductList { get; set; } = new();
private CompanyDto SelectedCompany { get; set; } = new();
// ****************************************************** private async Task ShowActivityList(string companyId)
// functions {
private async Task ShowInvoiceList(string companyId) // check for console manipulation
{ if (!Utils.Validate(VType.Id, companyId)) return;
// check for console manipulation SelectedCompany = CompanyList.First(x => x.CompanyId == companyId);
if (!Utils.Validate(VType.Id, companyId)) return; ActivityList = await ActivityRepo.RequestActivityList(companyId);
SelectedCompany = CompanyList.First(x => x.CompanyId == companyId); ActivityListOverlay.Show();
// call erp to crm sync before requesting invoices }
var newSyncDate = await HistoryRepo.RequestErpToCrmSync(CountryCode, companyId, SelectedCompany.HistorySync);
await Task.Delay(500);
InvoiceList = await HistoryRepo.RequestInvoiceList(CountryCode, companyId);
if(!string.IsNullOrWhiteSpace(newSyncDate)) SelectedCompany.HistorySync = newSyncDate;
InvoiceListOverlay.Show();
}
private async Task ShowActivityList(string companyId) private async Task ShowInventory(string companyId)
{ {
// check for console manipulation // check for console manipulation
if (!Utils.Validate(VType.Id, companyId)) return; if (!Utils.Validate(VType.Id, companyId)) return;
SelectedCompany = CompanyList.First(x => x.CompanyId == companyId); SelectedCompany = CompanyList.First(x => x.CompanyId == companyId);
ActivityList = await ActivityRepo.RequestActivityList(companyId); // call erp to crm sync before requesting products
ActivityListOverlay.Show(); var newSyncDate = await HistoryRepo.RequestErpToCrmSync(CountryCode, companyId, SelectedCompany.HistorySync);
} await Task.Delay(500);
if(!string.IsNullOrWhiteSpace(newSyncDate)) SelectedCompany.HistorySync = newSyncDate;
ProductList = await HistoryRepo.RequestInventory(SelectedCompany.CountryCode, SelectedCompany.CompanyId);
ProductListOverlay.Show();
}
private async Task ShowInventory(string companyId) private async Task ShowOrder(string companyId)
{ {
// check for console manipulation // check for console manipulation
if (!Utils.Validate(VType.Id, companyId)) return; if (!Utils.Validate(VType.Id, companyId)) return;
SelectedCompany = CompanyList.First(x => x.CompanyId == companyId); SelectedCompany = CompanyList.First(x => x.CompanyId == companyId);
// call erp to crm sync before requesting products
var newSyncDate = await HistoryRepo.RequestErpToCrmSync(CountryCode, companyId, SelectedCompany.HistorySync);
await Task.Delay(500);
if(!string.IsNullOrWhiteSpace(newSyncDate)) SelectedCompany.HistorySync = newSyncDate;
ProductList = await HistoryRepo.RequestInventory(SelectedCompany.CountryCode, SelectedCompany.CompanyId);
ProductListOverlay.Show();
}
private async Task ShowOrder(string companyId)
{
// check for console manipulation
if (!Utils.Validate(VType.Id, companyId)) return;
SelectedCompany = CompanyList.First(x => x.CompanyId == companyId);
}
} }
} }

View file

@ -20,41 +20,42 @@ using Microsoft.AspNetCore.Components;
using Wonky.Client.Services; using Wonky.Client.Services;
#pragma warning disable CS8618 #pragma warning disable CS8618
namespace Wonky.Client.Components namespace Wonky.Client.Components;
public partial class PageSizeComponent : IDisposable
{ {
public partial class PageSizeComponent : IDisposable [Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
private Dictionary<string, string> Items { get; set; } = new();
private UserProfile Profile { get; set; } = new();
private string PageSize { get; set; }
protected override async Task OnInitializedAsync()
{ {
[Inject] public ILocalStorageService Storage { get; set; } ProfileService.OnChange += ProfileServiceOnOnChange;
[Inject] public UserProfileService ProfileService { get; set; } Profile = await ProfileService.GetProfile();
[Parameter] public EventCallback<string> OnChanged { get; set; } PageSize = Profile.PageSize;
private Dictionary<string, string> Items { get; set; } = new();
private UserProfile _profiles = new();
private string PageSize { get; set; } = "";
protected override async Task OnInitializedAsync()
{
ProfileService.OnChange += ProfileServiceOnOnChange;
_profiles = await ProfileService.GetProfile();
PageSize = _profiles.PageSize;
}
private async Task OnSelectChanged(ChangeEventArgs e)
{
var val = e.Value.ToString();
if (val == "-1") return;
await OnChanged.InvokeAsync(val);
await ProfileService.SetPageSize(val);
}
private void ProfileServiceOnOnChange(UserProfile newUserProfile)
{
_profiles = newUserProfile;
StateHasChanged();
}
public void Dispose()
{
ProfileService.OnChange -= ProfileServiceOnOnChange;
}
} }
}
private async Task OnSelectChanged(ChangeEventArgs e)
{
var val = e.Value?.ToString();
if (val == "-1") return;
var cVal = Convert.ToInt32(val);
if (cVal > 50) val = "50"; // mitigate variable manipulation
await OnChanged.InvokeAsync(val);
await ProfileService.SetPageSize(val);
}
private void ProfileServiceOnOnChange(UserProfile newUserProfile)
{
Profile = newUserProfile;
StateHasChanged();
}
public void Dispose()
{
ProfileService.OnChange -= ProfileServiceOnOnChange;
}
}

View file

@ -19,44 +19,43 @@ using Microsoft.AspNetCore.Components;
using Wonky.Client.Features; using Wonky.Client.Features;
using Wonky.Entity.Requests; using Wonky.Entity.Requests;
namespace Wonky.Client.Components namespace Wonky.Client.Components;
public partial class PaginationComponent
{ {
public partial class PaginationComponent [Parameter] public MetaData MetaData { get; set; } = new();
[Parameter] public int Spread { get; set; }
[Parameter] public EventCallback<int> SelectedPage { get; set; }
private List<PagingLink> Links { get; set; } = new();
protected override void OnParametersSet()
{ {
[Parameter] public MetaData MetaData { get; set; } = new(); CreatePaginationLinks();
[Parameter] public int Spread { get; set; } }
[Parameter] public EventCallback<int> SelectedPage { get; set; }
private List<PagingLink> Links { get; set; } = new();
protected override void OnParametersSet() private void CreatePaginationLinks()
{
Links = new List<PagingLink>
{ {
CreatePaginationLinks(); new(MetaData.CurrentPage - 1, MetaData.HasPrevious, "Forrige")
} };
private void CreatePaginationLinks() for (var i = 1; i <= MetaData.TotalPages; i++)
{ {
Links = new List<PagingLink> if (i >= MetaData.CurrentPage - Spread && i <= MetaData.CurrentPage + Spread)
{ {
new(MetaData.CurrentPage - 1, MetaData.HasPrevious, "Forrige") Links.Add(new PagingLink(i, true, i.ToString()) {Active = MetaData.CurrentPage == i});
};
for (var i = 1; i <= MetaData.TotalPages; i++)
{
if (i >= MetaData.CurrentPage - Spread && i <= MetaData.CurrentPage + Spread)
{
Links.Add(new PagingLink(i, true, i.ToString()) {Active = MetaData.CurrentPage == i});
}
} }
Links.Add(new PagingLink(MetaData.CurrentPage + 1, MetaData.HasNext, "Næste"));
} }
private async Task OnSelectedPage(PagingLink link) Links.Add(new PagingLink(MetaData.CurrentPage + 1, MetaData.HasNext, "Næste"));
{ }
if (link.Page == MetaData.CurrentPage || !link.Enabled)
return; private async Task OnSelectedPage(PagingLink link)
MetaData.CurrentPage = link.Page; {
await SelectedPage.InvokeAsync(link.Page); if (link.Page == MetaData.CurrentPage || !link.Enabled)
} return;
MetaData.CurrentPage = link.Page;
await SelectedPage.InvokeAsync(link.Page);
} }
} }

View file

@ -21,57 +21,56 @@ using Wonky.Client.HttpRepository;
using Wonky.Client.Shared; using Wonky.Client.Shared;
using Wonky.Entity.DTO; using Wonky.Entity.DTO;
namespace Wonky.Client.Components namespace Wonky.Client.Components;
public partial class TaskItemTableComponent
{ {
public partial class TaskItemTableComponent [Parameter] public List<TaskItemDto> TaskItemList { get; set; } = new();
[Parameter] public EventCallback<string> OnDeleteTask { get; set; }
[Parameter] public EventCallback<string> OnCompleteTask { get; set; }
[Parameter] public EventCallback<string> OnTaskCompleted { get; set; }
private ConfirmationModal _confirmationModal = new ();
private string _taskItemIdToDelete = "";
/// <summary>
/// Complete task callback
/// </summary>
/// <param name="taskItemId"></param>
private async Task CompleteTask(string taskItemId)
{ {
[Parameter] public List<TaskItemDto> TaskItemList { get; set; } = new(); await OnCompleteTask.InvokeAsync(taskItemId);
[Parameter] public EventCallback<string> OnDeleteTask { get; set; } }
[Parameter] public EventCallback<string> OnCompleteTask { get; set; }
[Parameter] public EventCallback<string> OnTaskCompleted { get; set; }
private ConfirmationModal _confirmationModal = new (); /// <summary>
private string _taskItemIdToDelete = ""; /// Task completed callback
/// </summary>
/// <param name="taskItemId"></param>
private async Task TaskCompleted(string taskItemId)
{
await OnTaskCompleted.InvokeAsync(taskItemId);
}
/// <summary> /// <summary>
/// Complete task callback /// Confirm delete
/// </summary> /// </summary>
/// <param name="taskItemId"></param> /// <param name="taskItemId"></param>
private async Task CompleteTask(string taskItemId) private void CallConfirmationModal(string taskItemId)
{ {
await OnCompleteTask.InvokeAsync(taskItemId); _taskItemIdToDelete = taskItemId;
} _confirmationModal.Show();
}
/// <summary>
/// Task completed callback
/// </summary>
/// <param name="taskItemId"></param>
private async Task TaskCompleted(string taskItemId)
{
await OnTaskCompleted.InvokeAsync(taskItemId);
}
/// <summary>
/// Confirm delete
/// </summary>
/// <param name="taskItemId"></param>
private void CallConfirmationModal(string taskItemId)
{
_taskItemIdToDelete = taskItemId;
_confirmationModal.Show();
}
private void OnCancelCallback() private void OnCancelCallback()
{ {
_confirmationModal.Hide(); _confirmationModal.Hide();
} }
/// <summary> /// <summary>
/// Delete task call back /// Delete task call back
/// </summary> /// </summary>
private async Task DeleteTask() private async Task DeleteTask()
{ {
_confirmationModal.Hide(); _confirmationModal.Hide();
await OnDeleteTask.InvokeAsync(_taskItemIdToDelete); await OnDeleteTask.InvokeAsync(_taskItemIdToDelete);
} }
} }
}

View file

@ -15,6 +15,7 @@
using System.Globalization; using System.Globalization;
using System.Text.Json;
using Blazored.LocalStorage; using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Forms;
@ -69,8 +70,8 @@ public partial class WorkDateComponent : IDisposable
/// <param name="e"></param> /// <param name="e"></param>
private async Task OnDateChanged(ChangeEventArgs e) private async Task OnDateChanged(ChangeEventArgs e)
{ {
var x = DateTime.TryParse(e.Value.ToString(), out var setDate); if (string.IsNullOrWhiteSpace(e.Value.ToString())) return;
if (x) if (DateTime.TryParse(e.Value.ToString(), out var setDate))
{ {
await UserProfile.SetWorkDate(setDate); await UserProfile.SetWorkDate(setDate);
await OnChangedCallback.InvokeAsync($"{setDate:yyyy-MM-dd}"); await OnChangedCallback.InvokeAsync($"{setDate:yyyy-MM-dd}");

View file

@ -26,337 +26,336 @@
using System.Diagnostics; using System.Diagnostics;
namespace Wonky.Client.Helpers namespace Wonky.Client.Helpers;
/// <summary>
/// Squid is guid string shortened and url safe
/// </summary>
[DebuggerDisplay("{" + nameof(Value) + "}")]
public readonly struct Squid : IEquatable<Squid>
{ {
/// <summary> /// <summary>
/// Squid is guid string shortened and url safe /// Return Empty value
/// </summary> /// </summary>
[DebuggerDisplay("{" + nameof(Value) + "}")] // ReSharper disable once MemberCanBePrivate.Global
public readonly struct Squid : IEquatable<Squid> public static readonly Squid Empty = new(Guid.Empty);
/// <summary>
/// Decode Squid to Guid
/// </summary>
/// <param name="value"></param>
public Squid(string value)
{ {
/// <summary> Value = value;
/// Return Empty value Guid = DecodeSquid(value);
/// </summary> }
// ReSharper disable once MemberCanBePrivate.Global
public static readonly Squid Empty = new(Guid.Empty);
/// <summary> /// <summary>
/// Decode Squid to Guid /// Generate Squid from Guid object
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="obj"></param>
public Squid(string value) public Squid(Guid obj)
{
Value = EncodeGuid(obj);
Guid = obj;
}
/// <summary>
/// Guid
/// </summary>
// ReSharper disable once MemberCanBePrivate.Global
public Guid Guid { get; }
public string Value { get; }
/// <summary>
/// ToString() function
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Value;
}
/// <summary>
/// Equality
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object? obj)
{
return obj is Squid other && Equals(other);
}
/// <summary>
/// EQuality
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool Equals(Squid obj)
{
return Guid.Equals(obj.Guid) && Value == obj.Value;
}
/// <summary>
/// Get hashcode
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
unchecked
{ {
Value = value; return (Guid.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0);
Guid = DecodeSquid(value);
} }
}
/// <summary> /// <summary>
/// Generate Squid from Guid object /// Create Squid from new Guid
/// </summary> /// </summary>
/// <param name="obj"></param> /// <returns></returns>
public Squid(Guid obj) public static Squid NewGuid()
{
return new Squid(Guid.NewGuid());
}
/// <summary>
/// Encode string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string EncodeString(string value)
{
var guid = new Guid(value);
return EncodeGuid(guid);
}
/// <summary>
/// Encode Guid
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static string EncodeGuid(Guid obj)
{
var encoded = Convert.ToBase64String(obj.ToByteArray());
encoded = encoded
.Replace("/", "_")
.Replace("+", "-");
return encoded.Substring(0, 22);
}
/// <summary>
/// Decode Squid
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static Guid DecodeSquid(string value)
{
if (!value.Any()) return Empty;
value = value
.Replace("_", "/")
.Replace("-", "+");
var blob = Convert.FromBase64String(value + "==");
return new Guid(blob);
}
/// <summary>
/// Guid From Squid
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static Guid FromSquid(Squid obj)
{
return obj.Guid;
}
/// <summary>
/// Squid From String
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static Squid FromString(string value)
{
if (string.IsNullOrEmpty(value))
return Empty;
return TryParse(value, out Squid obj) ? obj : Empty;
}
/// <summary>
/// TryDecode
/// </summary>
/// <param name="value"></param>
/// <param name="obj"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static bool TryDecode(string value, out Guid obj)
{
try
{ {
Value = EncodeGuid(obj); // Decode as Squid
Guid = obj; obj = DecodeSquid(value);
return true;
} }
catch (Exception)
/// <summary>
/// Guid
/// </summary>
// ReSharper disable once MemberCanBePrivate.Global
public Guid Guid { get; }
public string Value { get; }
/// <summary>
/// ToString() function
/// </summary>
/// <returns></returns>
public override string ToString()
{ {
return Value; // Return empty Guid
}
/// <summary>
/// Equality
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object? obj)
{
return obj is Squid other && Equals(other);
}
/// <summary>
/// EQuality
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool Equals(Squid obj)
{
return Guid.Equals(obj.Guid) && Value == obj.Value;
}
/// <summary>
/// Get hashcode
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
unchecked
{
return (Guid.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0);
}
}
/// <summary>
/// Create Squid from new Guid
/// </summary>
/// <returns></returns>
public static Squid NewGuid()
{
return new Squid(Guid.NewGuid());
}
/// <summary>
/// Encode string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string EncodeString(string value)
{
var guid = new Guid(value);
return EncodeGuid(guid);
}
/// <summary>
/// Encode Guid
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static string EncodeGuid(Guid obj)
{
var encoded = Convert.ToBase64String(obj.ToByteArray());
encoded = encoded
.Replace("/", "_")
.Replace("+", "-");
return encoded.Substring(0, 22);
}
/// <summary>
/// Decode Squid
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static Guid DecodeSquid(string value)
{
if (!value.Any()) return Empty;
value = value
.Replace("_", "/")
.Replace("-", "+");
var blob = Convert.FromBase64String(value + "==");
return new Guid(blob);
}
/// <summary>
/// Guid From Squid
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static Guid FromSquid(Squid obj)
{
return obj.Guid;
}
/// <summary>
/// Squid From String
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static Squid FromString(string value)
{
if (string.IsNullOrEmpty(value))
return Empty;
return TryParse(value, out Squid obj) ? obj : Empty;
}
/// <summary>
/// TryDecode
/// </summary>
/// <param name="value"></param>
/// <param name="obj"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static bool TryDecode(string value, out Guid obj)
{
try
{
// Decode as Squid
obj = DecodeSquid(value);
return true;
}
catch (Exception)
{
// Return empty Guid
obj = Guid.Empty;
return false;
}
}
/// <summary>
/// TryParse
/// </summary>
/// <param name="value"></param>
/// <param name="obj"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static bool TryParse(string value, out Squid obj)
{
// Parse as Squid string.
if (TryDecode(value, out var oGuid))
{
obj = oGuid;
return true;
}
// Parse as Guid string.
if (Guid.TryParse(value, out oGuid))
{
obj = oGuid;
return true;
}
obj = Empty;
return false;
}
/// <summary>
/// TryParse
/// </summary>
/// <param name="value"></param>
/// <param name="obj"></param>
/// <returns></returns>
public static bool TryParse(string value, out Guid obj)
{
// Try a Squid string.
if (TryDecode(value, out obj))
return true;
// Try a Guid string.
if (Guid.TryParse(value, out obj))
return true;
obj = Guid.Empty; obj = Guid.Empty;
return false; return false;
} }
}
/// <summary> /// <summary>
/// Operator /// TryParse
/// </summary> /// </summary>
/// <param name="x"></param> /// <param name="value"></param>
/// <param name="y"></param> /// <param name="obj"></param>
/// <returns></returns> /// <returns></returns>
public static bool operator ==(Squid x, Squid y) // ReSharper disable once MemberCanBePrivate.Global
public static bool TryParse(string value, out Squid obj)
{
// Parse as Squid string.
if (TryDecode(value, out var oGuid))
{ {
return x.Guid == y.Guid; obj = oGuid;
return true;
} }
/// <summary> // Parse as Guid string.
/// Operator if (Guid.TryParse(value, out oGuid))
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator ==(Squid x, Guid y)
{ {
return x.Guid == y; obj = oGuid;
return true;
} }
/// <summary> obj = Empty;
/// Operator return false;
/// </summary> }
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator ==(Guid x, Squid y)
{
return y == x; // NB: order of arguments
}
/// <summary> /// <summary>
/// Operator /// TryParse
/// </summary> /// </summary>
/// <param name="x"></param> /// <param name="value"></param>
/// <param name="y"></param> /// <param name="obj"></param>
/// <returns></returns> /// <returns></returns>
public static bool operator !=(Squid x, Squid y) public static bool TryParse(string value, out Guid obj)
{ {
return !(x == y); // Try a Squid string.
} if (TryDecode(value, out obj))
return true;
// Try a Guid string.
if (Guid.TryParse(value, out obj))
return true;
obj = Guid.Empty;
return false;
}
/// <summary> /// <summary>
/// Operator /// Operator
/// </summary> /// </summary>
/// <param name="x"></param> /// <param name="x"></param>
/// <param name="y"></param> /// <param name="y"></param>
/// <returns></returns> /// <returns></returns>
public static bool operator !=(Squid x, Guid y) public static bool operator ==(Squid x, Squid y)
{ {
return !(x == y); return x.Guid == y.Guid;
} }
/// <summary> /// <summary>
/// Operator /// Operator
/// </summary> /// </summary>
/// <param name="x"></param> /// <param name="x"></param>
/// <param name="y"></param> /// <param name="y"></param>
/// <returns></returns> /// <returns></returns>
public static bool operator !=(Guid x, Squid y) public static bool operator ==(Squid x, Guid y)
{ {
return !(x == y); return x.Guid == y;
} }
/// <summary> /// <summary>
/// Operator /// Operator
/// </summary> /// </summary>
/// <param name="oSquid"></param> /// <param name="x"></param>
/// <returns></returns> /// <param name="y"></param>
public static implicit operator string(Squid oSquid) /// <returns></returns>
{ public static bool operator ==(Guid x, Squid y)
return oSquid.Value; {
} return y == x; // NB: order of arguments
}
/// <summary> /// <summary>
/// Operator /// Operator
/// </summary> /// </summary>
/// <param name="oSquid"></param> /// <param name="x"></param>
/// <returns></returns> /// <param name="y"></param>
public static implicit operator Guid(Squid oSquid) /// <returns></returns>
{ public static bool operator !=(Squid x, Squid y)
return oSquid.Guid; {
} return !(x == y);
}
/// <summary> /// <summary>
/// Operator /// Operator
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="x"></param>
/// <returns></returns> /// <param name="y"></param>
public static implicit operator Squid(string value) /// <returns></returns>
{ public static bool operator !=(Squid x, Guid y)
if (string.IsNullOrEmpty(value)) {
return Empty; return !(x == y);
}
return TryParse(value, out Squid oSquid) ? oSquid : Empty; /// <summary>
} /// Operator
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator !=(Guid x, Squid y)
{
return !(x == y);
}
/// <summary> /// <summary>
/// Operator /// Operator
/// </summary> /// </summary>
/// <param name="oGuid"></param> /// <param name="oSquid"></param>
/// <returns></returns> /// <returns></returns>
public static implicit operator Squid(Guid oGuid) public static implicit operator string(Squid oSquid)
{ {
return oGuid == Guid.Empty ? Empty : new Squid(oGuid); return oSquid.Value;
} }
/// <summary>
/// Operator
/// </summary>
/// <param name="oSquid"></param>
/// <returns></returns>
public static implicit operator Guid(Squid oSquid)
{
return oSquid.Guid;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static implicit operator Squid(string value)
{
if (string.IsNullOrEmpty(value))
return Empty;
return TryParse(value, out Squid oSquid) ? oSquid : Empty;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="oGuid"></param>
/// <returns></returns>
public static implicit operator Squid(Guid oGuid)
{
return oGuid == Guid.Empty ? Empty : new Squid(oGuid);
} }
} }

View file

@ -23,104 +23,102 @@ using Wonky.Client.Services;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Toolbelt.Blazor; using Toolbelt.Blazor;
namespace Wonky.Client.HttpInterceptors namespace Wonky.Client.HttpInterceptors;
public class HttpInterceptorService
{ {
public class HttpInterceptorService private readonly HttpClientInterceptor _interceptor;
private readonly NavigationManager _navigation;
private readonly IToastService _toast;
private readonly RefreshTokenService _refreshTokenService;
private readonly ILogger<HttpInterceptorService> _logger;
private readonly ILocalStorageService _storage;
private readonly IAuthenticationService _authenticationService;
public HttpInterceptorService(HttpClientInterceptor interceptor,
NavigationManager navigation, IToastService toast,
RefreshTokenService refreshTokenService, ILogger<HttpInterceptorService> logger,
ILocalStorageService storage, IAuthenticationService authenticationService)
{ {
private readonly HttpClientInterceptor _interceptor; _interceptor = interceptor;
private readonly NavigationManager _navigation; _navigation = navigation;
private readonly IToastService _toast; _toast = toast;
private readonly RefreshTokenService _refreshTokenService; _refreshTokenService = refreshTokenService;
private readonly ILogger<HttpInterceptorService> _logger; _logger = logger;
private readonly ILocalStorageService _storage; _storage = storage;
private readonly IAuthenticationService _authenticationService; _authenticationService = authenticationService;
}
public HttpInterceptorService(HttpClientInterceptor interceptor, public void RegisterEvent()
NavigationManager navigation, IToastService toast, {
RefreshTokenService refreshTokenService, ILogger<HttpInterceptorService> logger, _interceptor.AfterSend += AfterSend;
ILocalStorageService storage, IAuthenticationService authenticationService) }
{
_interceptor = interceptor;
_navigation = navigation;
_toast = toast;
_refreshTokenService = refreshTokenService;
_logger = logger;
_storage = storage;
_authenticationService = authenticationService;
}
public void RegisterEvent() public void RegisterBeforeSendEvent()
{ {
_interceptor.AfterSend += AfterSend; _interceptor.BeforeSendAsync += InterceptBeforeSend;
} }
public void RegisterBeforeSendEvent()
{
_interceptor.BeforeSendAsync += InterceptBeforeSend;
}
public void DisposeEvent() public void DisposeEvent()
{ {
_interceptor.AfterSend -= AfterSend; _interceptor.AfterSend -= AfterSend;
_interceptor.BeforeSendAsync -= InterceptBeforeSend; _interceptor.BeforeSendAsync -= InterceptBeforeSend;
} }
private async Task InterceptBeforeSend(object sender, HttpClientInterceptorEventArgs e) private async Task InterceptBeforeSend(object sender, HttpClientInterceptorEventArgs e)
{
var absolutePath = e.Request.RequestUri.AbsolutePath;
if (!absolutePath.Contains("token"))
{ {
var absolutePath = e.Request.RequestUri.AbsolutePath; // call TryRefreshToken
if (!absolutePath.Contains("token")) var token = await _refreshTokenService.TryRefreshToken();
if (!string.IsNullOrEmpty(token))
{ {
// call TryRefreshToken // set new token
var token = await _refreshTokenService.TryRefreshToken(); e.Request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
if (!string.IsNullOrEmpty(token))
{
// set new token
e.Request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
} }
} }
}
private void AfterSend (object sender, HttpClientInterceptorEventArgs e) private void AfterSend (object sender, HttpClientInterceptorEventArgs e)
{
if (e.Response == null || e.Response.IsSuccessStatusCode)
return;
var message = $"En fejl er opstået \n {JsonSerializer.Serialize(e)}";
var currDoc = _navigation.ToBaseRelativePath(_navigation.Uri);
if (currDoc.Contains("login/"))
currDoc = "";
switch (e.Response.StatusCode)
{ {
if (e.Response == null || e.Response.IsSuccessStatusCode) case HttpStatusCode.NotFound:
return; _logger.LogDebug("NotFound <= {}", currDoc);
break;
var message = $"En fejl er opstået \n {JsonSerializer.Serialize(e)}"; case HttpStatusCode.BadRequest:
var currDoc = _navigation.ToBaseRelativePath(_navigation.Uri); _logger.LogDebug("BadRequest <= {}", currDoc);
if (currDoc.Contains("login/")) _logger.LogDebug("{}", message);
currDoc = ""; break;
case HttpStatusCode.Unauthorized:
switch (e.Response.StatusCode) _logger.LogDebug("Unauthorized <= {}", currDoc);
{ _logger.LogDebug("{}", message);
case HttpStatusCode.NotFound: _authenticationService.Logout();
_logger.LogDebug("NotFound <= {}", currDoc); _navigation.NavigateTo($"/login/{currDoc}");
break; _toast.ShowInfo("Venligst Login. Tak.");
case HttpStatusCode.BadRequest: break;
_logger.LogDebug("BadRequest <= {}", currDoc); case HttpStatusCode.Conflict:
_logger.LogDebug("{}", message); _logger.LogDebug("Conflict <= {}", currDoc);
break; _logger.LogDebug("{}", message);
case HttpStatusCode.Unauthorized: break;
_logger.LogDebug("Unauthorized <= {}", currDoc); case HttpStatusCode.InternalServerError:
_logger.LogDebug("{}", message); _logger.LogDebug("InternalServerError <= {}", currDoc);
_authenticationService.Logout(); _logger.LogDebug("{}", message);
_navigation.NavigateTo($"/login/{currDoc}"); break;
_toast.ShowInfo("Venligst Login. Tak."); default:
break; _logger.LogDebug("{}", message);
case HttpStatusCode.Conflict: break;
_logger.LogDebug("Conflict <= {}", currDoc);
_logger.LogDebug("{}", message);
break;
case HttpStatusCode.InternalServerError:
_logger.LogDebug("InternalServerError <= {}", currDoc);
_logger.LogDebug("{}", message);
break;
default:
_logger.LogDebug("{}", message);
break;
}
// throw new HttpResponseException(message);
} }
} // throw new HttpResponseException(message);
} }
}

View file

@ -16,26 +16,25 @@
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace Wonky.Client.HttpInterceptors namespace Wonky.Client.HttpInterceptors;
[Serializable]
public class HttpResponseException : Exception
{ {
[Serializable] public HttpResponseException()
public class HttpResponseException : Exception
{ {
public HttpResponseException()
{
}
public HttpResponseException(string message)
: base(message)
{
}
public HttpResponseException(string message, Exception innerException)
: base(message, innerException)
{
}
public HttpResponseException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
} }
} public HttpResponseException(string message)
: base(message)
{
}
public HttpResponseException(string message, Exception innerException)
: base(message, innerException)
{
}
public HttpResponseException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}

View file

@ -30,7 +30,7 @@
<div class="row g-3"> <div class="row g-3">
<label for="date" class="col-form-label-sm col-sm-1">Dato</label> <label for="date" class="col-form-label-sm col-sm-1">Dato</label>
<div class="col-sm-3"> <div class="col-sm-3">
<input id="date" class="form-control" type="text" value="@(DateTime.Parse(ReportItem.CreateTimestamp).ToShortDateString())" readonly/> <input id="date" class="form-control" type="text" value="@ReportItem.CreateTimestamp" readonly/>
</div> </div>
<label for="account" class="col-form-label-sm col-sm-1">Konto</label> <label for="account" class="col-form-label-sm col-sm-1">Konto</label>

View file

@ -26,7 +26,7 @@
<div class="row bg-dark text-white rounded-2 mb-2 py-2 align-items-center"> <div class="row bg-dark text-white rounded-2 mb-2 py-2 align-items-center">
<div class="col"> <div class="col">
<WorkDateComponent OnChangedCallback="WorkDateComponentCallback" /> <WorkDateComponent OnChangedCallback="WorkDateComponentCallback"/>
</div> </div>
</div> </div>
@ -34,7 +34,7 @@
{ {
<div class="alert alert-danger"> <div class="alert alert-danger">
<h4>Ring til kontoret. Denne konto er spærret med kode '@Company.Blocked'</h4> <h4>Ring til kontoret. Denne konto er spærret med kode '@Company.Blocked'</h4>
</div> </div>
} }
<div class="row mb-2 bg-dark text-white rounded-3 p-3"> <div class="row mb-2 bg-dark text-white rounded-3 p-3">
<div class="col"> <div class="col">
@ -46,10 +46,9 @@
{ {
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<h3>Der kan ikke oprettes besøg når der findes rapport for @SelectedDate.ToShortDateString()</h3> <h3>Der kan ikke oprettes besøg når der findes rapport for @SelectedDate.ToShortDateString()</h3>
</div> </div>
</div> </div>
} }
else else
{ {
@ -81,8 +80,8 @@ else
{ {
<option value="order">Bestilling</option> <option value="order">Bestilling</option>
} }
@if(DraftProvider.Draft.DraftType == "offer") @if (DraftProvider.Draft.DraftType == "offer")
{ {
<option selected value="quote">Tilbud</option> <option selected value="quote">Tilbud</option>
} }
@ -137,7 +136,7 @@ else
<InputText id="phone" class="form-control" @bind-Value="Activity.Phone"/> <InputText id="phone" class="form-control" @bind-Value="Activity.Phone"/>
<ValidationMessage For="@(() => Activity.Phone)"></ValidationMessage> <ValidationMessage For="@(() => Activity.Phone)"></ValidationMessage>
</div> </div>
<label for="orderMessage" class="col-sm-2 col-form-label-sm">Note /Kontor</label> <label for="orderMessage" class="col-sm-2 col-form-label-sm">Note /Kontor</label>
<div class="col-sm-4"> <div class="col-sm-4">
<InputTextArea id="orderMessage" class="form-control" @bind-Value="Activity.OrderMessage"/> <InputTextArea id="orderMessage" class="form-control" @bind-Value="Activity.OrderMessage"/>
@ -149,15 +148,15 @@ else
<InputTextArea id="crmNote" class="form-control" @bind-Value="Activity.CrmNote"/> <InputTextArea id="crmNote" class="form-control" @bind-Value="Activity.CrmNote"/>
<ValidationMessage For="@(() => Activity.CrmNote)"></ValidationMessage> <ValidationMessage For="@(() => Activity.CrmNote)"></ValidationMessage>
</div> </div>
<div class="col-sm-6"></div> <div class="col-sm-6"></div>
<label for="vatNumber" class="col-sm-2 col-form-label-sm">Cvr/Org nr.</label> <label for="vatNumber" class="col-sm-2 col-form-label-sm">Cvr/Org nr.</label>
<div class="col-sm-4"> <div class="col-sm-4">
<InputText id="vatNumber" class="form-control" @bind-Value="Activity.VatNumber" /> <InputText id="vatNumber" class="form-control" @bind-Value="Activity.VatNumber"/>
<ValidationMessage For="@(() => Activity.VatNumber)" /> <ValidationMessage For="@(() => Activity.VatNumber)"/>
</div> </div>
</div> </div>
<div class="row g-2 mb-3"> <div class="row g-2 mb-3">
<div class="col-sm-3 d-grid mx-auto"> <div class="col-sm-3 d-grid mx-auto">
@* @*
@ -178,7 +177,7 @@ else
<button class="btn btn-success" disabled="@string.IsNullOrWhiteSpace(Activity.ActivityTypeEnum)" @onclick="ShowInventoryOverlay">Produkter</button> <button class="btn btn-success" disabled="@string.IsNullOrWhiteSpace(Activity.ActivityTypeEnum)" @onclick="ShowInventoryOverlay">Produkter</button>
</div> </div>
</div> </div>
<div id="this-draft" style="@(Activity.ActivityStatusEnum is "order" or "quote" ? "display: block" : "display:none")"> <div id="this-draft" style="@(Activity.ActivityStatusEnum is "order" or "quote" ? "display: block" : "display:none")">
@* Draft lines in draft -----------------------------------------------------*@ @* Draft lines in draft -----------------------------------------------------*@
<div class="row"> <div class="row">
@ -275,7 +274,7 @@ else
</td> </td>
<td class="align-middle" style="min-width:200px;"> <td class="align-middle" style="min-width:200px;">
<div class="input-group"> <div class="input-group">
<input type="number" class="form-control" @bind-value="@Price"/> <input type="number" class="form-control" @bind-value="@Price"/>
@* @*
***************** Price history overlay button ***************************** ***************** Price history overlay button *****************************
*@ *@
@ -285,10 +284,10 @@ else
</div> </div>
</td> </td>
<td class="align-middle" style="min-width:100px;"> <td class="align-middle" style="min-width:100px;">
<input type="number" class="form-control" @bind-value="@Discount"/> <input type="number" class="form-control" @bind-value="@Discount"/>
</td> </td>
<td class="align-middle align-content-center justify-content-center"> <td class="align-middle align-content-center justify-content-center">
<input type="checkbox" class="form-check" @bind-value="@Sas"/> <input type="checkbox" class="form-check" @bind-value="@Sas"/>
</td> </td>
<td class="align-middle">@SelectedItem.Sku</td> <td class="align-middle">@SelectedItem.Sku</td>
<td class="align-middle"> <td class="align-middle">
@ -360,26 +359,27 @@ else
***************** Confirm product check overlay button ***************************** ***************** Confirm product check overlay button *****************************
***************** Continue by submitton order to erp ***************************** ***************** Continue by submitton order to erp *****************************
*@ *@
<button type="button" class="btn btn-warning" @onclick="CallConfirmCheckOverlay" disabled="@(PoFormInvalid || Working)"><i class="bi-cloud-arrow-up"></i> @ButtonText</button> <button type="button" class="btn btn-warning" @onclick="CallConfirmCheckOverlay" disabled="@(PoFormInvalid || Working)">
<i class="bi-cloud-arrow-up"></i> @ButtonText
</button>
</div> </div>
</div> </div>
} }
<ProductCheckConfirmationOverlay BodyMessage="" CompanyId="@CompanyId" Products="CheckList" <ProductCheckConfirmationOverlay BodyMessage="" CompanyId="@CompanyId" Products="CheckList"
OnOkClicked="ConfirmProductCheckCallback" @ref="ConfirmationCheckOverlay" /> OnOkClicked="ConfirmProductCheckCallback" @ref="ConfirmationCheckOverlay"/>
<ConfirmWorkDateModal BodyMessage="@PromptDateConfirm" <ConfirmWorkDateModal BodyMessage="@PromptDateConfirm"
OnOkClicked="WorkDateConfirmCallback" @ref="ConfirmWorkDate"/> OnOkClicked="WorkDateConfirmCallback" @ref="ConfirmWorkDate"/>
<ProductHistoryOverlay CompanyId="@CompanyId" ItemSku="@SelectedItem.Sku" @ref="ProductOverlay"/> <ProductHistoryOverlay CompanyId="@CompanyId" ItemSku="@SelectedItem.Sku" @ref="ProductOverlay"/>
<PriceCatalogOverlay CountryCode="@Company.CountryCode.ToLower()" OnSelected="PriceListCallback" @ref="CatalogOverlay"/> <PriceCatalogOverlay CountryCode="@Company.CountryCode.ToLower()" OnSelected="PriceListCallback" @ref="CatalogOverlay"/>
<ProductPriceHistoryOverlay CompanyId="@CompanyId" Sku="@SelectedItem.Sku" OnSelected="PriceHistoryCallback" @ref="PriceOverlay"/> <ProductPriceHistoryOverlay CompanyId="@CompanyId" Sku="@SelectedItem.Sku" OnSelected="PriceHistoryCallback" @ref="PriceOverlay"/>
<CustomerInvoiceListOverlay CustomerInvoices="CompanyInvoices" @ref="InvoiceListOverlay" /> <CustomerInvoiceListOverlay CustomerInvoices="CompanyInvoices" @ref="InvoiceListOverlay"/>
<CustomerActivityListOverlay Activities="Activities" CompanyName="@Company.Name" @ref="ActivityListOverlay" /> <CustomerActivityListOverlay Activities="Activities" CompanyName="@Company.Name" @ref="ActivityListOverlay"/>
<CustomerInventoryListOverlay CompanyName="@Company.Name" CompanyId="@CompanyId" CountryCode="@Company.CountryCode" <CustomerInventoryListOverlay CompanyName="@Company.Name" CompanyId="@CompanyId" CountryCode="@Company.CountryCode"
OnSelected="OnInventoryCallback" Inventory="Inventory" @ref="InventoryListOverlay" /> OnSelected="OnInventoryCallback" Inventory="Inventory" @ref="InventoryListOverlay"/>

View file

@ -1,5 +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 // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as // it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the // published by the Free Software Foundation, either version 3 of the
@ -30,6 +29,7 @@ using Wonky.Client.Services;
using Wonky.Client.Shared; using Wonky.Client.Shared;
using Wonky.Entity.DTO; using Wonky.Entity.DTO;
using Wonky.Entity.Views; using Wonky.Entity.Views;
#pragma warning disable CS8618 #pragma warning disable CS8618
namespace Wonky.Client.Pages; namespace Wonky.Client.Pages;
@ -49,18 +49,22 @@ public partial class AdvisorActivityCreatePage : IDisposable
[Inject] public IAdvisorActivityRepository ActivityRepo { get; set; } [Inject] public IAdvisorActivityRepository ActivityRepo { get; set; }
[Inject] public IAdvisorReportRepository ReportRepo { get; set; } [Inject] public IAdvisorReportRepository ReportRepo { get; set; }
[Inject] public IAdvisorCustomerHistoryRepository HistoryRepo { get; set; } [Inject] public IAdvisorCustomerHistoryRepository HistoryRepo { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
[Inject] public IUserInfoService UserService { get; set; }
// ************************************************************* // *************************************************************
// Parameters // Parameters
[CascadingParameter] private DraftStateProvider DraftProvider { get; set; } = new(); [CascadingParameter] private DraftStateProvider DraftProvider { get; set; } = new();
[Parameter] public string CompanyId { get; set; } = ""; [Parameter] public string CompanyId { get; set; } = "";
// ************************************************************* // *************************************************************
// Variables // Variables
private readonly JsonSerializerOptions _options = new() {PropertyNameCaseInsensitive = true}; private readonly JsonSerializerOptions _options = new() { PropertyNameCaseInsensitive = true };
private SalesItemView SelectedItem { get; set; } = new(); private SalesItemView SelectedItem { get; set; } = new();
private UserProfile UserProfile { get; set; } = new(); private UserProfile UserProfile { get; set; } = new();
private ActivityDto Activity { get; set; } = new(); private ActivityDto Activity { get; set; } = new();
private CompanyDto Company = new(); private CompanyDto Company { get; set; } = new();
private EditContext? ActivityContext { get; set; } private EditContext? ActivityContext { get; set; }
private bool PoFormInvalid { get; set; } = true; private bool PoFormInvalid { get; set; } = true;
private bool ShowItem { get; set; } private bool ShowItem { get; set; }
@ -69,7 +73,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
private string Discount { get; set; } = "0"; private string Discount { get; set; } = "0";
private bool Sas { get; set; } private bool Sas { get; set; }
private bool InvalidActivityType { get; set; } = true; private bool InvalidActivityType { get; set; } = true;
private bool InvalidActivity { get; set; } = true; private bool InvalidActivity { get; set; } = true;
private bool ReportClosed { get; set; } private bool ReportClosed { get; set; }
private bool Working { get; set; } = true; private bool Working { get; set; } = true;
private UserManagerEditView SalesRep { get; set; } = new(); private UserManagerEditView SalesRep { get; set; } = new();
@ -78,7 +82,9 @@ public partial class AdvisorActivityCreatePage : IDisposable
private string PromptDateConfirm { get; set; } = ""; private string PromptDateConfirm { get; set; } = "";
private string ButtonText { get; set; } = "Gem besøg"; private string ButtonText { get; set; } = "Gem besøg";
private bool OrgWarning { get; set; } private bool OrgWarning { get; set; }
private const string PromptDemoForgotten = "Har du glemt demo?"; private const string PromptDemoForgotten = "Har du glemt demo?";
// ************************************************************* // *************************************************************
// Overlays // Overlays
private PriceCatalogOverlay CatalogOverlay { get; set; } = new(); private PriceCatalogOverlay CatalogOverlay { get; set; } = new();
@ -88,14 +94,16 @@ public partial class AdvisorActivityCreatePage : IDisposable
private ProductCheckConfirmationOverlay ConfirmationCheckOverlay { get; set; } = new(); private ProductCheckConfirmationOverlay ConfirmationCheckOverlay { get; set; } = new();
private CustomerInvoiceListOverlay InvoiceListOverlay { get; set; } = new(); private CustomerInvoiceListOverlay InvoiceListOverlay { get; set; } = new();
private CustomerInventoryListOverlay InventoryListOverlay { get; set; } = new(); private CustomerInventoryListOverlay InventoryListOverlay { get; set; } = new();
private CustomerActivityListOverlay ActivityListOverlay { get; set; } = new(); private CustomerActivityListOverlay ActivityListOverlay { get; set; } = new();
// ************************************************************* // *************************************************************
// Lists // Lists
private List<ProductInventoryView> Inventory { get; set; } = new(); private List<ProductInventoryView> Inventory { get; set; } = new();
private List<ProductInventoryView> CheckList { get; set; } = new(); private List<ProductInventoryView> CheckList { get; set; } = new();
private InvoiceListView CompanyInvoices { get; set; } = new(); private InvoiceListView CompanyInvoices { get; set; } = new();
private List<ReportItemView> Activities { get; set; } = new(); private List<ReportItemView> Activities { get; set; } = new();
/// <summary> /// <summary>
/// Page initialization /// Page initialization
/// </summary> /// </summary>
@ -110,13 +118,13 @@ public partial class AdvisorActivityCreatePage : IDisposable
// User Preferences // User Preferences
UserProfile = await ProfileService.GetProfile(); UserProfile = await ProfileService.GetProfile();
// User Info // User Info
SalesRep = await UserInfoService.GetUserInfo(); SalesRep = await UserService.GetUserInfo();
// Fetch Customer from http // Fetch Customer from http
Company = await CompanyRepo.GetCompanyById(CompanyId); Company = await CompanyRepo.GetCompanyById(CompanyId);
if (Company.HasFolded == 1) if (Company.HasFolded == 1)
// Company has shutdown activities // Company has shut down
Activity.OrderMessage = "BEMÆRK: CVR nummer er ophørt."; Activity.OrderMessage = "BEMÆRK: CVR nummer er ophørt.";
// variable to validate if customer needs phone number update // variable to validate if customer needs phone number update
OldPhone = Company.Phone; OldPhone = Company.Phone;
if (string.IsNullOrWhiteSpace(Company.Phone) if (string.IsNullOrWhiteSpace(Company.Phone)
@ -125,7 +133,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
{ {
Company.Phone = Company.Account[..8]; Company.Phone = Company.Account[..8];
} }
// Populate base activity information // Populate base activity information
Activity.BcId = Company.BcId; Activity.BcId = Company.BcId;
Activity.ActivityStatusEnum = "noSale"; Activity.ActivityStatusEnum = "noSale";
@ -141,7 +149,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
Activity.Mobile = Company.Mobile; Activity.Mobile = Company.Mobile;
Activity.Name = Company.Name; Activity.Name = Company.Name;
Activity.Address1 = Company.Address1; Activity.Address1 = Company.Address1;
Activity.Address2 = Company.Address2; Activity.Address2 = Company.Address2;
Activity.ZipCode = Company.ZipCode; Activity.ZipCode = Company.ZipCode;
Activity.City = Company.City; Activity.City = Company.City;
Activity.DlvName = Company.Name; Activity.DlvName = Company.Name;
@ -150,7 +158,10 @@ public partial class AdvisorActivityCreatePage : IDisposable
Activity.DlvZipCode = Company.ZipCode; Activity.DlvZipCode = Company.ZipCode;
Activity.DlvCity = Company.City; Activity.DlvCity = Company.City;
// Initialize date variable // Initialize date variable
SelectedDate = string.IsNullOrWhiteSpace(UserProfile.WorkDate) ? DateTime.Now : DateTime.Parse(UserProfile.WorkDate); Logger.LogDebug("AdvisorActivityCreatePage => DateTime parser => {}", UserProfile.WorkDate);
SelectedDate = string.IsNullOrWhiteSpace(UserProfile.WorkDate)
? DateTime.Now
: DateTime.Parse(UserProfile.WorkDate);
// raise flag if report is closed // raise flag if report is closed
ReportClosed = await ReportRepo.ReportExist($"{SelectedDate:yyyy-MM-dd}"); ReportClosed = await ReportRepo.ReportExist($"{SelectedDate:yyyy-MM-dd}");
// Ask for confirmation of date // Ask for confirmation of date
@ -160,6 +171,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
PromptDateConfirm = $"Aktiviteter oprettes med dato {SelectedDate.ToShortDateString()}. Er dette OK?"; PromptDateConfirm = $"Aktiviteter oprettes med dato {SelectedDate.ToShortDateString()}. Er dette OK?";
ConfirmWorkDate.Show(); ConfirmWorkDate.Show();
} }
// Lines may already have been added from the company inventory page // Lines may already have been added from the company inventory page
if (DraftProvider.Draft.DraftType == "order") if (DraftProvider.Draft.DraftType == "order")
{ {
@ -168,26 +180,27 @@ public partial class AdvisorActivityCreatePage : IDisposable
Activity.ActivityStatusEnum = "order"; Activity.ActivityStatusEnum = "order";
PoFormInvalid = false; PoFormInvalid = false;
} }
Working = false; Working = false;
} }
private async Task ShowVisitOverlay() private async Task ShowVisitOverlay()
{ {
Logger.LogDebug("ShowInventoryOverlay - wait for visits"); Logger.LogDebug("ShowInventoryOverlay - wait for visits");
ActivityListOverlay.Show(); ActivityListOverlay.Show();
Activities = await ActivityRepo.GetCustomerActivities(CompanyId); Activities = await ActivityRepo.GetCustomerActivities(CompanyId);
await Task.Delay(500); await Task.Delay(500);
} }
private async Task ShowInventoryOverlay() private async Task ShowInventoryOverlay()
{ {
Logger.LogDebug("ShowInventoryOverlay - wait for inventory"); Logger.LogDebug("ShowInventoryOverlay - wait for inventory");
InventoryListOverlay.Show(); InventoryListOverlay.Show();
Inventory = await HistoryRepo.FetchInventory(CompanyId); Inventory = await HistoryRepo.FetchInventory(CompanyId);
Inventory = Inventory.OrderBy(x => x.Description).ToList(); Inventory = Inventory.OrderBy(x => x.Description).ToList();
await Task.Delay(500); await Task.Delay(500);
@ -200,31 +213,32 @@ public partial class AdvisorActivityCreatePage : IDisposable
DraftProvider.Draft.Items.Add(item); DraftProvider.Draft.Items.Add(item);
StateHasChanged(); StateHasChanged();
} }
private async Task ShowInvoiceOverlay() private async Task ShowInvoiceOverlay()
{ {
Logger.LogDebug("ShowInvoiceOverlay - wait for invoices"); Logger.LogDebug("ShowInvoiceOverlay - wait for invoices");
InvoiceListOverlay.Show(); InvoiceListOverlay.Show();
CompanyInvoices = await FetchCompanyInvoices(); CompanyInvoices = await FetchCompanyInvoices();
await Task.Delay(500); await Task.Delay(500);
} }
private async Task<InvoiceListView> FetchCompanyInvoices() private async Task<InvoiceListView> FetchCompanyInvoices()
{ {
// fetch from storage // fetch from storage
var storage = await Storage.GetItemAsStringAsync($"{CompanyId}-invoices"); var storage = await Storage.GetItemAsStringAsync($"{CompanyId}-invoices");
await Task.Delay(500); await Task.Delay(500);
var iDate = await Storage.GetItemAsStringAsync($"{CompanyId}-iDate"); var iDate = await Storage.GetItemAsStringAsync($"{CompanyId}-iDate");
// if we have a list and iDate was today return the list // if we have a list and iDate was today return the list
if (!string.IsNullOrWhiteSpace(storage) && (!string.IsNullOrWhiteSpace(iDate) && DateTime.Parse(iDate.Replace("\"", "")) >= DateTime.Now)) if (!string.IsNullOrWhiteSpace(storage) && (!string.IsNullOrWhiteSpace(iDate) &&
DateTime.Parse(iDate.Replace("\"", "")) >= DateTime.Now))
{ {
Logger.LogDebug("fetching invoices from storage"); Logger.LogDebug("fetching invoices from storage");
Logger.LogDebug("storage contains <= {}", storage); Logger.LogDebug("storage contains <= {}", storage);
return JsonSerializer.Deserialize<InvoiceListView>(storage); return JsonSerializer.Deserialize<InvoiceListView>(storage);
} }
Logger.LogDebug("pulling invoices from backend"); Logger.LogDebug("pulling invoices from backend");
// pull invoices // pull invoices
var companyInvoices = await HistoryRepo.FetchInvoiceList(CompanyId); var companyInvoices = await HistoryRepo.FetchInvoiceList(CompanyId);
@ -236,30 +250,31 @@ public partial class AdvisorActivityCreatePage : IDisposable
Logger.LogDebug("backend contains <= {}", JsonSerializer.Serialize(companyInvoices)); Logger.LogDebug("backend contains <= {}", JsonSerializer.Serialize(companyInvoices));
return companyInvoices; return companyInvoices;
} }
private void ShowOrgWarning() private void ShowOrgWarning()
{ {
if (OrgWarning) if (OrgWarning)
return; return;
OrgWarning = true; OrgWarning = true;
if (Company.CountryCode.ToLower() == "se" && VatUtils.SanitizeVatNumber(Activity.VatNumber).Length < 10 && Activity.ActivityStatusEnum == "order") if (Company.CountryCode.ToLower() == "se" && VatUtils.SanitizeVatNumber(Activity.VatNumber).Length < 10 &&
Activity.ActivityStatusEnum == "order")
{ {
Toaster.ShowWarning("Org nummer er ufuldstændig. Skal opdateres før bestilling kan sendes. ", "ADVARSEL"); Toaster.ShowWarning("Org nummer er ufuldstændig. Skal opdateres før bestilling kan sendes. ", "ADVARSEL");
} }
} }
private async Task CallConfirmCheckOverlay() private async Task CallConfirmCheckOverlay()
{ {
// check if new account // check if new account
if (string.IsNullOrWhiteSpace(Company.Account) if (string.IsNullOrWhiteSpace(Company.Account)
|| Company.Account.ToLower() == "ny" || Company.Account.ToLower() == "ny"
|| Activity.ActivityStatusEnum.ToLower() == "quote") || Activity.ActivityStatusEnum.ToLower() == "quote")
{ {
// proceed to create activity - as there is no product check to be done // proceed to create activity - as there is no product check to be done
await CreateActivity(); await CreateActivity();
return; return;
} }
// check if product has been checked // check if product has been checked
// fetch products from storage // fetch products from storage
var pStorage = await Storage.GetItemAsStringAsync($"{CompanyId}-products"); var pStorage = await Storage.GetItemAsStringAsync($"{CompanyId}-products");
@ -269,13 +284,15 @@ public partial class AdvisorActivityCreatePage : IDisposable
if (string.IsNullOrWhiteSpace(pDate)) if (string.IsNullOrWhiteSpace(pDate))
pDate = $"{DateTime.Now.AddDays(-1):yyyy-MM-dd}"; pDate = $"{DateTime.Now.AddDays(-1):yyyy-MM-dd}";
Logger.LogDebug("pDate => {}", pDate); Logger.LogDebug("pDate => {}", pDate);
// check if product data is valid and updated today // check if product data is valid and updated today
if (string.IsNullOrWhiteSpace(pStorage) || pDate.Replace("\"", "") != $"{DateTime.Now:yyyy-MM-dd}") if (string.IsNullOrWhiteSpace(pStorage) || pDate.Replace("\"", "") != $"{DateTime.Now:yyyy-MM-dd}")
{ {
Working = true; Working = true;
// pop a message // pop a message
Toaster.ShowError("Produkt gennemgang mangler. Vent mens produkt oversigt indlæses. Gå ikke væk fra siden!", "Produkt check ..."); Toaster.ShowError(
"Produkt gennemgang mangler. Vent mens produkt oversigt indlæses. Gå ikke væk fra siden!",
"Produkt check ...");
// product inventory has not been updated // product inventory has not been updated
// send rpc call to sync ERP to CRM // send rpc call to sync ERP to CRM
Toaster.ShowInfo("Vent mens data synkroniseres ...", "ERP til CRM ..."); Toaster.ShowInfo("Vent mens data synkroniseres ...", "ERP til CRM ...");
@ -286,11 +303,11 @@ public partial class AdvisorActivityCreatePage : IDisposable
await Storage.SetItemAsync($"{CompanyId}-pDate", ts); await Storage.SetItemAsync($"{CompanyId}-pDate", ts);
// request products from backend // request products from backend
Toaster.ShowInfo("Vent mens produkt oversigt hentes", "CRM produkt liste"); Toaster.ShowInfo("Vent mens produkt oversigt hentes", "CRM produkt liste");
CheckList = await HistoryRepo.FetchInventory(CompanyId); CheckList = await HistoryRepo.FetchInventory(CompanyId);
if(CheckList.Any()) if (CheckList.Any())
CheckList = CheckList.OrderBy(x => x.Description).ToList(); CheckList = CheckList.OrderBy(x => x.Description).ToList();
await Storage.SetItemAsync($"{CompanyId}-products", CheckList); await Storage.SetItemAsync($"{CompanyId}-products", CheckList);
Working = false; Working = false;
} }
@ -298,14 +315,14 @@ public partial class AdvisorActivityCreatePage : IDisposable
{ {
// deserialize storage data // deserialize storage data
CheckList = JsonSerializer.Deserialize<List<ProductInventoryView>>(pStorage); CheckList = JsonSerializer.Deserialize<List<ProductInventoryView>>(pStorage);
if(CheckList.Any()) if (CheckList.Any())
CheckList = CheckList.OrderBy(x => x.Description).ToList(); CheckList = CheckList.OrderBy(x => x.Description).ToList();
} }
// Show CheckList modal // Show CheckList modal
ConfirmationCheckOverlay.Show(); ConfirmationCheckOverlay.Show();
} }
private async Task ConfirmProductCheckCallback() private async Task ConfirmProductCheckCallback()
{ {
ConfirmationCheckOverlay.Hide(); ConfirmationCheckOverlay.Hide();
@ -314,10 +331,10 @@ public partial class AdvisorActivityCreatePage : IDisposable
{ {
item.Check = false; item.Check = false;
} }
await Storage.SetItemAsync($"{CompanyId}-products", CheckList); await Storage.SetItemAsync($"{CompanyId}-products", CheckList);
} }
private async Task WorkDateConfirmCallback() private async Task WorkDateConfirmCallback()
{ {
await ProfileService.SetDateConfirmed(true); await ProfileService.SetDateConfirmed(true);
@ -332,12 +349,12 @@ public partial class AdvisorActivityCreatePage : IDisposable
SelectedDate = DateTime.Parse(workDate); SelectedDate = DateTime.Parse(workDate);
Activity.ActivityDate = workDate; Activity.ActivityDate = workDate;
} }
private void ShowPriceListOverlay() private void ShowPriceListOverlay()
{ {
CatalogOverlay.Show(); CatalogOverlay.Show();
} }
private async Task PriceListCallback(SelectedSku sku) private async Task PriceListCallback(SelectedSku sku)
{ {
// get selected item // get selected item
@ -349,13 +366,13 @@ public partial class AdvisorActivityCreatePage : IDisposable
Quantity = sku.Quantity; Quantity = sku.Quantity;
StateHasChanged(); StateHasChanged();
} }
private void ShowPriceHistoryOverlay() private void ShowPriceHistoryOverlay()
{ {
if(ShowItem) if (ShowItem)
PriceOverlay.Show(); PriceOverlay.Show();
} }
private void PriceHistoryCallback(decimal price) private void PriceHistoryCallback(decimal price)
{ {
if (price == 0) if (price == 0)
@ -376,6 +393,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
Toaster.ShowError("Kunde adresse er ufuldstændig."); Toaster.ShowError("Kunde adresse er ufuldstændig.");
return; return;
} }
// validate org number // validate org number
// - this is a required input // - this is a required input
// - must validate according to country rules. // - must validate according to country rules.
@ -384,6 +402,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
Toaster.ShowError("Firma registreringsnummer er ikke korrekt."); Toaster.ShowError("Firma registreringsnummer er ikke korrekt.");
return; return;
} }
// validate input according to status // validate input according to status
switch (Activity.ActivityStatusEnum) switch (Activity.ActivityStatusEnum)
{ {
@ -400,6 +419,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
Toaster.ShowError("Ved tilbud skal en gyldig email adresse angives."); Toaster.ShowError("Ved tilbud skal en gyldig email adresse angives.");
return; return;
} }
// raise working flag // raise working flag
Working = true; Working = true;
@ -412,11 +432,12 @@ public partial class AdvisorActivityCreatePage : IDisposable
Activity.OrderMessage = $"Telefonnr. opdateret.\n{Activity.OrderMessage}"; Activity.OrderMessage = $"Telefonnr. opdateret.\n{Activity.OrderMessage}";
await CompanyRepo.UpdateErpData(Company.CompanyId, Company); await CompanyRepo.UpdateErpData(Company.CompanyId, Company);
} }
// begin assembling activity // begin assembling activity
Activity.ActivityDate = $"{SelectedDate:yyyy-MM-dd}"; Activity.ActivityDate = $"{SelectedDate:yyyy-MM-dd}";
Activity.OurRef = Activity.ActivityTypeEnum switch Activity.OurRef = Activity.ActivityTypeEnum switch
{ {
"phone" => $"T:{SalesRep.FirstName}", "phone" => $"T:{SalesRep.FirstName}",
"onSite" => $"B:{SalesRep.FirstName}", "onSite" => $"B:{SalesRep.FirstName}",
_ => "" _ => ""
}; };
@ -443,6 +464,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
.ToList(); .ToList();
Activity.Lines = lines; Activity.Lines = lines;
} }
// debug logging // debug logging
Logger.LogDebug("CrmNewActivityPage => \n {}", JsonSerializer.Serialize(Activity)); Logger.LogDebug("CrmNewActivityPage => \n {}", JsonSerializer.Serialize(Activity));
// post to api // post to api
@ -458,18 +480,19 @@ public partial class AdvisorActivityCreatePage : IDisposable
Navigator.NavigateTo($"/advisor/customers"); Navigator.NavigateTo($"/advisor/customers");
return; return;
} }
// lower working flag // lower working flag
Working = false; Working = false;
// show error message // show error message
Toaster.ShowError(result.Message, "ORDRE FEJL"); Toaster.ShowError(result.Message, "ORDRE FEJL");
} }
private async Task DeleteDraft() private async Task DeleteDraft()
{ {
await DraftProvider.DeleteDraftAsync(); await DraftProvider.DeleteDraftAsync();
Activity.ActivityStatusEnum = "noSale"; Activity.ActivityStatusEnum = "noSale";
} }
private async Task AddItem(SalesItemView salesItem) private async Task AddItem(SalesItemView salesItem)
{ {
ShowItem = false; ShowItem = false;
@ -489,12 +512,12 @@ public partial class AdvisorActivityCreatePage : IDisposable
Discount = "0"; Discount = "0";
// add it to the cart // add it to the cart
DraftProvider.Draft.Items.Add(item); DraftProvider.Draft.Items.Add(item);
if(Activity.ActivityStatusEnum != "quote") if (Activity.ActivityStatusEnum != "quote")
Activity.ActivityStatusEnum = "order"; Activity.ActivityStatusEnum = "order";
// save the item using the CartStateProvider's save method // save the item using the CartStateProvider's save method
await DraftProvider.SaveChangesAsync(); await DraftProvider.SaveChangesAsync();
} }
private async Task RemoveItem(DraftItem item) private async Task RemoveItem(DraftItem item)
{ {
// remove item // remove item
@ -504,10 +527,11 @@ public partial class AdvisorActivityCreatePage : IDisposable
if (!DraftProvider.Draft.Items.Any()) if (!DraftProvider.Draft.Items.Any())
Activity.ActivityStatusEnum = "noSale"; Activity.ActivityStatusEnum = "noSale";
} }
private void HandleFieldChanged(object sender, FieldChangedEventArgs e) private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{ {
Logger.LogDebug("ActivityNewPage => HandleFieldChanged => ActivityStatusEnum <= '{}'", Activity.ActivityStatusEnum); Logger.LogDebug("ActivityNewPage => HandleFieldChanged => ActivityStatusEnum <= '{}'",
Activity.ActivityStatusEnum);
DraftProvider.Draft.DraftType = Activity.ActivityStatusEnum; DraftProvider.Draft.DraftType = Activity.ActivityStatusEnum;
if (Activity.ActivityStatusEnum == "noSale") if (Activity.ActivityStatusEnum == "noSale")
{ {
@ -523,19 +547,20 @@ public partial class AdvisorActivityCreatePage : IDisposable
}; };
// InvalidCanvas = InvalidActivityType; // InvalidCanvas = InvalidActivityType;
InvalidActivity = InvalidActivityType InvalidActivity = InvalidActivityType
|| PoFormInvalid || PoFormInvalid
|| DraftProvider.Draft.Items.Count == 0 || DraftProvider.Draft.Items.Count == 0
|| (Activity.ActivityStatusEnum == "offer" && string.IsNullOrWhiteSpace(Activity.Email)); || (Activity.ActivityStatusEnum == "offer" && string.IsNullOrWhiteSpace(Activity.Email));
if (Activity.YourRef.Length > 35 || Activity.ReferenceNumber.Length > 20 || InvalidActivity) if (Activity.YourRef.Length > 35 || Activity.ReferenceNumber.Length > 20 || InvalidActivity)
{ {
PoFormInvalid = true; PoFormInvalid = true;
return; return;
} }
PoFormInvalid = !ActivityContext.Validate(); PoFormInvalid = !ActivityContext.Validate();
StateHasChanged(); StateHasChanged();
} }
private void ValidationChanged(object sender, ValidationStateChangedEventArgs e) private void ValidationChanged(object sender, ValidationStateChangedEventArgs e)
{ {
if (string.IsNullOrEmpty(Activity.ActivityTypeEnum) && !ReportClosed) if (string.IsNullOrEmpty(Activity.ActivityTypeEnum) && !ReportClosed)
@ -544,16 +569,16 @@ public partial class AdvisorActivityCreatePage : IDisposable
PoFormInvalid = true; PoFormInvalid = true;
return; return;
} }
if (Activity.ActivityStatusEnum.ToLower() is "order" or "quote" if (Activity.ActivityStatusEnum.ToLower() is "order" or "quote"
&& Company.CountryCode.ToLower() == "se" && Company.CountryCode.ToLower() == "se"
&& VatUtils.SanitizeVatNumber(Activity.VatNumber).Length < 10) && VatUtils.SanitizeVatNumber(Activity.VatNumber).Length < 10)
{ {
ShowOrgWarning(); ShowOrgWarning();
PoFormInvalid = true; PoFormInvalid = true;
return; return;
} }
PoFormInvalid = false; PoFormInvalid = false;
ActivityContext.OnFieldChanged -= HandleFieldChanged; ActivityContext.OnFieldChanged -= HandleFieldChanged;
ActivityContext.OnValidationStateChanged -= ValidationChanged; ActivityContext.OnValidationStateChanged -= ValidationChanged;
@ -561,7 +586,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
ActivityContext.OnFieldChanged += HandleFieldChanged; ActivityContext.OnFieldChanged += HandleFieldChanged;
ActivityContext.OnValidationStateChanged += ValidationChanged; ActivityContext.OnValidationStateChanged += ValidationChanged;
} }
public void Dispose() public void Dispose()
{ {
Interceptor.DisposeEvent(); Interceptor.DisposeEvent();

View file

@ -45,4 +45,4 @@
@if (Working) @if (Working)
{ {
<WorkingThreeDots/> <WorkingThreeDots/>
} }

View file

@ -1,4 +1,3 @@
// 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 // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as // it under the terms of the GNU Affero General Public License as
@ -14,6 +13,7 @@
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html] // along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
// //
using System.Diagnostics.CodeAnalysis;
using System.Text.Json; using System.Text.Json;
using Blazored.Toast.Services; using Blazored.Toast.Services;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@ -21,6 +21,7 @@ using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository; using Wonky.Client.HttpRepository;
using Wonky.Client.Services; using Wonky.Client.Services;
using Wonky.Entity.Views; using Wonky.Entity.Views;
#pragma warning disable CS8618 #pragma warning disable CS8618
namespace Wonky.Client.Pages; namespace Wonky.Client.Pages;
@ -34,7 +35,7 @@ public partial class AdvisorActivityTodayListPage : IDisposable
[Inject] public IAdvisorActivityRepository ActivityRepo { get; set; } [Inject] public IAdvisorActivityRepository ActivityRepo { get; set; }
[Inject] public IAdvisorReportRepository ReportRepo { get; set; } [Inject] public IAdvisorReportRepository ReportRepo { get; set; }
[Inject] public IToastService Toaster { get; set; } [Inject] public IToastService Toaster { get; set; }
private ReportStatusView ReportStatusView { get; set; } = new(); private ReportStatusView ReportStatusView { get; set; } = new();
private UserProfile UserProfile { get; set; } = new(); private UserProfile UserProfile { get; set; } = new();
private DateTime SelectedDate { get; set; } private DateTime SelectedDate { get; set; }
private bool ReportExist { get; set; } private bool ReportExist { get; set; }
@ -45,12 +46,14 @@ public partial class AdvisorActivityTodayListPage : IDisposable
Interceptor.RegisterEvent(); Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent(); Interceptor.RegisterBeforeSendEvent();
UserProfile = await ProfileService.GetProfile(); UserProfile = await ProfileService.GetProfile();
SelectedDate = string.IsNullOrWhiteSpace(UserProfile.WorkDate) ? DateTime.Now : DateTime.Parse(UserProfile.WorkDate); SelectedDate = string.IsNullOrWhiteSpace(UserProfile.WorkDate)
? DateTime.Now
: DateTime.Parse(UserProfile.WorkDate);
ReportExist = await ReportRepo.ReportExist($"{SelectedDate:yyyy-MM-dd}"); ReportExist = await ReportRepo.ReportExist($"{SelectedDate:yyyy-MM-dd}");
await GetActivities($"{SelectedDate:yyyy-MM-dd}"); await GetActivities($"{SelectedDate:yyyy-MM-dd}");
Working = false; Working = false;
} }
private async Task GetActivities(string workDate) private async Task GetActivities(string workDate)
{ {
Working = true; Working = true;
@ -67,6 +70,5 @@ public partial class AdvisorActivityTodayListPage : IDisposable
public void Dispose() public void Dispose()
{ {
Interceptor.DisposeEvent(); Interceptor.DisposeEvent();
} }
} }

View file

@ -23,7 +23,6 @@
@page "/advisor/customers/{CompanyId}/quotes/{OrderId}" @page "/advisor/customers/{CompanyId}/quotes/{OrderId}"
<PageTitle>@ReportItem.Company.Name @ReportItem.OrderDate</PageTitle> <PageTitle>@ReportItem.Company.Name @ReportItem.OrderDate</PageTitle>
@* <ReportItemComponent ReportItem="@_item" /> *@
<table class="table table-sm table-striped d-print-table"> <table class="table table-sm table-striped d-print-table">
<thead> <thead>
@ -124,7 +123,9 @@
{ {
<tr> <tr>
<td colspan="4"></td> <td colspan="4"></td>
<td colspan="2"><h5 class="fw-bold text-center"><i class="bi-lightning-charge the-fast" style="font-size: 2rem;"></i> HASTER</h5></td> <td colspan="2">
<h5 class="fw-bold text-center"><i class="bi-lightning-charge the-fast" style="font-size: 2rem;"></i> HASTER</h5>
</td>
</tr> </tr>
} }
</tbody> </tbody>
@ -145,7 +146,7 @@
<button class="btn btn-primary btn-lg" type="button" @onclick="UpdateOfficeNote" disabled="@Disabled">Opdater Note /Kontor</button> <button class="btn btn-primary btn-lg" type="button" @onclick="UpdateOfficeNote" disabled="@Disabled">Opdater Note /Kontor</button>
</div> </div>
</EditForm> </EditForm>
</div> </div>
} }
else else
{ {
@ -159,4 +160,4 @@ else
@if (Working) @if (Working)
{ {
<WorkingThreeDots/> <WorkingThreeDots/>
} }

View file

@ -1,4 +1,3 @@
// 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 // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as // it under the terms of the GNU Affero General Public License as
@ -22,6 +21,7 @@ using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository; using Wonky.Client.HttpRepository;
using Wonky.Entity.DTO; using Wonky.Entity.DTO;
using Wonky.Entity.Views; using Wonky.Entity.Views;
#pragma warning disable CS8618 #pragma warning disable CS8618
namespace Wonky.Client.Pages; namespace Wonky.Client.Pages;
@ -41,6 +41,7 @@ public partial class AdvisorActivityViewEditPage : IDisposable
private bool Disabled { get; set; } private bool Disabled { get; set; }
private int GraceTime { get; set; } = 60; private int GraceTime { get; set; } = 60;
private bool Working { get; set; } = true; private bool Working { get; set; } = true;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -59,7 +60,7 @@ public partial class AdvisorActivityViewEditPage : IDisposable
{ {
StateHasChanged(); StateHasChanged();
} }
private async Task UpdateOfficeNote() private async Task UpdateOfficeNote()
{ {
Working = true; Working = true;
@ -76,7 +77,7 @@ public partial class AdvisorActivityViewEditPage : IDisposable
return false; return false;
return DateTime.UtcNow < createTs.AddMinutes(GraceTime); return DateTime.UtcNow < createTs.AddMinutes(GraceTime);
} }
public void Dispose() public void Dispose()
{ {
Interceptor.DisposeEvent(); Interceptor.DisposeEvent();

View file

@ -35,10 +35,10 @@
<a class="btn btn-primary d-block" href="/advisor/customers/@Company.CompanyId/activities/new"><i class="bi-arrow-right"></i> Besøg</a> <a class="btn btn-primary d-block" href="/advisor/customers/@Company.CompanyId/activities/new"><i class="bi-arrow-right"></i> Besøg</a>
</div> </div>
</div> </div>
<CustomerActivityListComponent Activities="ActivityList" /> <CustomerActivityListComponent Activities="ActivityList"/>
} }
@if (Working) @if (Working)
{ {
<WorkingThreeDots/> <WorkingThreeDots/>
} }

View file

@ -1,4 +1,3 @@
// 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 // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as // it under the terms of the GNU Affero General Public License as
@ -20,6 +19,7 @@ using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository; using Wonky.Client.HttpRepository;
using Wonky.Entity.DTO; using Wonky.Entity.DTO;
using Wonky.Entity.Views; using Wonky.Entity.Views;
#pragma warning disable CS8618 #pragma warning disable CS8618
namespace Wonky.Client.Pages; namespace Wonky.Client.Pages;
@ -33,12 +33,12 @@ public partial class AdvisorCustomerActivityListPage : IDisposable
private List<ReportItemView> ActivityList { get; set; } = new(); private List<ReportItemView> ActivityList { get; set; } = new();
private CompanyDto Company { get; set; } = new(); private CompanyDto Company { get; set; } = new();
private bool Working { get; set; } = true; private bool Working { get; set; } = true;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
Interceptor.RegisterEvent(); Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent(); Interceptor.RegisterBeforeSendEvent();
Company = await CompanyRepo.GetCompanyById(CompanyId); Company = await CompanyRepo.GetCompanyById(CompanyId);
await GetActivities(); await GetActivities();
Working = false; Working = false;
@ -48,11 +48,11 @@ public partial class AdvisorCustomerActivityListPage : IDisposable
{ {
Working = true; Working = true;
ActivityList = await AdvisorActivityRepo.GetCustomerActivities(CompanyId); ActivityList = await AdvisorActivityRepo.GetCustomerActivities(CompanyId);
if(ActivityList.Any()) if (ActivityList.Any())
ActivityList = ActivityList.OrderByDescending(x => x.OrderDate).ToList(); ActivityList = ActivityList.OrderByDescending(x => x.OrderDate).ToList();
Working = false; Working = false;
} }
public void Dispose() public void Dispose()
{ {
Interceptor.DisposeEvent(); Interceptor.DisposeEvent();

View file

@ -14,7 +14,6 @@
// //
using System.Text.Json; using System.Text.Json;
using System.Xml;
using Blazored.LocalStorage; using Blazored.LocalStorage;
using Blazored.Toast.Services; using Blazored.Toast.Services;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@ -27,162 +26,161 @@ using Wonky.Client.Services;
using Wonky.Client.Shared; using Wonky.Client.Shared;
using Wonky.Entity.DTO; using Wonky.Entity.DTO;
using Wonky.Entity.Models; using Wonky.Entity.Models;
using Wonky.Entity.Views;
#pragma warning disable CS8618 #pragma warning disable CS8618
namespace Wonky.Client.Pages namespace Wonky.Client.Pages;
public partial class AdvisorCustomerCreatePage : IDisposable
{ {
public partial class AdvisorCustomerCreatePage : IDisposable [Inject] public IToastService Toaster { get; set; }
[Inject] public ILogger<AdvisorCustomerCreatePage> Logger { get; set; }
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public VatInfoLookupService VatService { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
private EditContext CompanyContext { get; set; }
private CompanyDto Company { get; set; } = new();
private VatAddress CompanyVatAddress { get; set; } = new();
private VatLookupDkModal VatLookupPopup { get; set; } = new();
private bool FormInvalid { get; set; } = true;
private string RegState { get; set; } = "";
private DateTime LastVisit { get; set; }
private DateTime NextVisit { get; set; }
private bool Dk { get; set; } = true;
private bool Working { get; set; }
protected override async Task OnInitializedAsync()
{ {
[Inject] public IToastService Toaster { get; set; } CompanyContext = new EditContext(Company);
[Inject] public ILogger<AdvisorCustomerCreatePage> Logger { get; set; }
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public VatInfoLookupService VatService { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
private EditContext CompanyContext { get; set; }
private CompanyDto Company { get; set; } = new();
private VatAddress CompanyVatAddress { get; set; } = new();
private VatLookupDkModal VatLookupPopup { get; set; } = new();
private bool FormInvalid { get; set; } = true; CompanyContext.OnFieldChanged += HandleFieldChanged;
private string RegState { get; set; } = ""; CompanyContext.OnValidationStateChanged += ValidationChanged;
private DateTime LastVisit { get; set; }
private DateTime NextVisit { get; set; }
private bool Dk { get; set; } = true;
private bool Working { get; set; }
protected override async Task OnInitializedAsync() var xu = await UserInfoService.GetUserInfo();
Dk = xu.CountryCode.ToLower() == "dk";
Company.SalesRepId = xu.UserId;
Company.CountryCode = xu.CountryCode.ToLower();
LastVisit = DateTime.Now;
NextVisit = DateTime.Now.AddDays(Company.Interval * 7);
Company.LastVisit = $"{LastVisit:yyyy-MM-dd}";
Company.NextVisit = $"{NextVisit:yyyy-MM-dd}";
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
}
/// <summary>
/// Show Vat Lookup modal
/// </summary>
private void CallVatLookupModal()
{
VatLookupPopup.Show();
}
/// <summary>
/// Modal callback to update company properties
/// </summary>
/// <param name="regInfo"></param>
private void SelectCompanyCallback(VirkRegInfo regInfo)
{
Logger.LogDebug("CrmCompanyView => SelectCompanyCallback => {}", JsonSerializer.Serialize(regInfo));
// this can be removed in favor of the new data returned from updating the VatNumber
RegState = regInfo.States[0].State.ToLower() == "normal" ? "the-good" : "the-dead";
if (regInfo.SyncAll)
{ {
CompanyContext = new EditContext(Company); Company.Name = regInfo.Name;
Company.Address1 = regInfo.Address;
CompanyContext.OnFieldChanged += HandleFieldChanged; Company.Address2 = regInfo.CoName;
CompanyContext.OnValidationStateChanged += ValidationChanged; Company.ZipCode = regInfo.ZipCode;
Company.City = regInfo.City;
var xu = await UserInfoService.GetUserInfo();
Dk = xu.CountryCode.ToLower() == "dk";
Company.SalesRepId = xu.UserId;
Company.CountryCode = xu.CountryCode.ToLower();
LastVisit = DateTime.Now;
NextVisit = DateTime.Now.AddDays(Company.Interval * 7);
Company.LastVisit = $"{LastVisit:yyyy-MM-dd}";
Company.NextVisit = $"{NextVisit:yyyy-MM-dd}";
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
} }
/// <summary> Company.VatNumber = regInfo.VatNumber;
/// Show Vat Lookup modal Company.ValidVat = 1;
/// </summary> FormInvalid = false;
private void CallVatLookupModal() }
private async Task SubmitCompanyForm()
{
Working = true;
FormInvalid = true;
Company.LastVisit = $"{LastVisit:yyyy-MM-dd}";
Company.NextVisit = $"{NextVisit:yyyy-MM-dd}";
var newId = await CompanyRepo.CreateCompany(Company);
if (!string.IsNullOrWhiteSpace(newId))
{ {
VatLookupPopup.Show(); Toaster.ShowSuccess($"'{Company.Name}' er oprettet i CRM.");
Navigator.NavigateTo($"/advisor/customers/{newId}");
} }
else
/// <summary>
/// Modal callback to update company properties
/// </summary>
/// <param name="regInfo"></param>
private void SelectCompanyCallback(VirkRegInfo regInfo)
{ {
Logger.LogDebug("CrmCompanyView => SelectCompanyCallback => {}", JsonSerializer.Serialize(regInfo)); Toaster.ShowWarning($"'{Company.Name}' IKKE oprettet.");
// this can be removed in favor of the new data returned from updating the VatNumber
RegState = regInfo.States[0].State.ToLower() == "normal" ? "the-good" : "the-dead";
if (regInfo.SyncAll)
{
Company.Name = regInfo.Name;
Company.Address1 = regInfo.Address;
Company.Address2 = regInfo.CoName;
Company.ZipCode = regInfo.ZipCode;
Company.City = regInfo.City;
}
Company.VatNumber = regInfo.VatNumber;
Company.ValidVat = 1;
FormInvalid = false; FormInvalid = false;
} }
private async Task SubmitCompanyForm() Working = false;
}
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{
NextVisit = LastVisit.AddDays(7 * Company.Interval);
// invalid vat number id not accepted by the ERP system
// but is removed without warning
// it is necessary to validate if vat number has been added
// as the format should conform to country rule of generation
if (!string.IsNullOrWhiteSpace(Company.VatNumber))
{ {
Working = true; // validate vat number according to country
FormInvalid = true; if (!VatUtils.ValidateFormat(Company.CountryCode, Company.VatNumber))
Company.LastVisit = $"{LastVisit:yyyy-MM-dd}";
Company.NextVisit = $"{NextVisit:yyyy-MM-dd}";
var newId = await CompanyRepo.CreateCompany(Company);
if (!string.IsNullOrWhiteSpace(newId))
{ {
Toaster.ShowSuccess($"'{Company.Name}' er oprettet i CRM."); Toaster.ShowError("Momsnummber er ikke korrekt.");
Navigator.NavigateTo($"/advisor/customers/{newId}");
}
else
{
Toaster.ShowWarning($"'{Company.Name}' IKKE oprettet.");
FormInvalid = false;
}
Working = false;
}
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{
NextVisit = LastVisit.AddDays(7 * Company.Interval);
// invalid vat number id not accepted by the ERP system
// but is removed without warning
// it is necessary to validate if vat number has been added
// as the format should conform to country rule of generation
if (!string.IsNullOrWhiteSpace(Company.VatNumber))
{
// validate vat number according to country
if (!VatUtils.ValidateFormat(Company.CountryCode, Company.VatNumber))
{
Toaster.ShowError("Momsnummber er ikke korrekt.");
FormInvalid = true;
Company.ValidVat = 0;
RegState = "the-ugly";
}
}
if (!Company.ValidDateSpan())
{
Toaster.ShowError("Dato for næste besøg skal ligge efter sidste besøg.");
FormInvalid = true; FormInvalid = true;
Company.ValidVat = 0;
RegState = "the-ugly";
} }
else
{
FormInvalid = !CompanyContext.Validate();
}
StateHasChanged();
} }
private void ValidationChanged(object sender, ValidationStateChangedEventArgs e) if (!Company.ValidDateSpan())
{ {
Toaster.ShowError("Dato for næste besøg skal ligge efter sidste besøg.");
FormInvalid = true; FormInvalid = true;
CompanyContext.OnFieldChanged -= HandleFieldChanged;
CompanyContext = new EditContext(Company);
FormInvalid = !CompanyContext.Validate();
CompanyContext.OnFieldChanged += HandleFieldChanged;
CompanyContext.OnValidationStateChanged -= ValidationChanged;
} }
else
public void Dispose()
{ {
Interceptor.DisposeEvent(); FormInvalid = !CompanyContext.Validate();
CompanyContext.OnFieldChanged -= HandleFieldChanged;
CompanyContext.OnValidationStateChanged -= ValidationChanged;
} }
StateHasChanged();
}
private void ValidationChanged(object sender, ValidationStateChangedEventArgs e)
{
FormInvalid = true;
CompanyContext.OnFieldChanged -= HandleFieldChanged;
CompanyContext = new EditContext(Company);
FormInvalid = !CompanyContext.Validate();
CompanyContext.OnFieldChanged += HandleFieldChanged;
CompanyContext.OnValidationStateChanged -= ValidationChanged;
}
public void Dispose()
{
Interceptor.DisposeEvent();
CompanyContext.OnFieldChanged -= HandleFieldChanged;
CompanyContext.OnValidationStateChanged -= ValidationChanged;
} }
} }

View file

@ -23,40 +23,42 @@
<div class="sticky-top bg-dark text-light rounded-2 px-3"> <div class="sticky-top bg-dark text-light rounded-2 px-3">
<div class="row g-3"> <div class="row g-3">
<div class="col-sm-2"> <div class="col-sm-2">
<CustomerSearchColumnComponent OnChanged="SetSearchCol" /> <CustomerSearchColumnComponent OnChanged="SetSearchCol"/>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<CustomerSearchPhraseComponent OnChanged="SetSearchPhrase" /> <CustomerSearchPhraseComponent OnChanged="SetSearchPhrase"/>
</div> </div>
<div class="col-sm-2"> <div class="col-sm-2">
<CustomerSortComponent OnChanged="SetSortCol" /> <CustomerSortComponent OnChanged="SetSortCol"/>
</div> </div>
<div class="col-sm-2"> <div class="col-sm-2">
<PageSizeComponent OnChanged="SetPageSize" /> <PageSizeComponent OnChanged="SetPageSize"/>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-2 mx-auto">
<button type button class="btn btn-warning @(ShowFolded ? "active" : "")" <button type button class="btn btn-warning @(ShowFolded ? "active" : "")"
data-bs-toggle="button" aria-pressed="@ShowFolded" @onclick="OnFoldedClick"> data-bs-toggle="button" aria-pressed="@ShowFolded" @onclick="ToggleFolded">
@ButtonFoldedText @ToggleFoldedText
</button>
<button type button class="btn btn-warning @(@ShowHidden ? "active" : "")"
data-bs-toggle="button" aria-pressed="@ShowHidden" @onclick="OnHiddenClick">
@ButtonHiddenText
</button> </button>
</div> </div>
<div class="col-sm-7"> <div class="col-sm-2 mx-auto">
<button type button class="btn btn-warning @(@ShowHidden ? "active" : "")"
data-bs-toggle="button" aria-pressed="@ShowHidden" @onclick="ToggleHidden">
@ToggleHiddenText
</button>
</div>
<div class="col-sm-5">
<PaginationComponent MetaData="PageData" Spread="2" SelectedPage="SelectedPage"/> <PaginationComponent MetaData="PageData" Spread="2" SelectedPage="SelectedPage"/>
</div> </div>
<div class="col-sm-2 text-end"> <div class="col-sm-1">@* placeholder *@</div>
<div class="col-sm-2 mx-auto">
<a class="btn btn-success text-nowrap" href="/advisor/customers/new">Opret kunde <i class="bi-plus"></i></a> <a class="btn btn-success text-nowrap" href="/advisor/customers/new">Opret kunde <i class="bi-plus"></i></a>
</div> </div>
</div> </div>
</div> </div>
<AdvisorCustomerListComponent CompanyList="CompanyList" OnDelete="DeleteCompany" /> <AdvisorCustomerListComponent CompanyList="CompanyList" OnDelete="DeleteCompany"/>
@if (Working) @if (Working)
{ {
<WorkingThreeDots /> <WorkingThreeDots/>
} }

View file

@ -1,4 +1,3 @@
// 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 // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as // it under the terms of the GNU Affero General Public License as
@ -22,147 +21,147 @@ using Wonky.Client.Services;
using Wonky.Entity.DTO; using Wonky.Entity.DTO;
using Wonky.Entity.Requests; using Wonky.Entity.Requests;
using Wonky.Entity.Views; using Wonky.Entity.Views;
#pragma warning disable CS8618 #pragma warning disable CS8618
namespace Wonky.Client.Pages namespace Wonky.Client.Pages;
public partial class AdvisorCustomerListPage : IDisposable
{ {
public partial class AdvisorCustomerListPage : IDisposable [Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] public IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
private List<CompanyDto> CompanyList { get; set; } = new();
private UserProfile Profile { get; set; } = new();
private UserManagerEditView UserInfo { get; set; } = new();
private string SavedSearch { get; set; } = "";
private bool Working { get; set; } = true;
private MetaData PageData { get; set; } = new();
private CustomerPaging Paging { get; set; } = new();
private string ToggleFoldedText { get; set; } = "Vis Lukkede";
private bool ShowFolded { get; set; }
private string ToggleHiddenText { get; set; } = "Inkl. Skjulte";
private bool ShowHidden { get; set; }
protected override void OnParametersSet()
{ {
[Inject] public ILocalStorageService Storage { get; set; } Interceptor.RegisterEvent();
[Inject] public UserProfileService ProfileService { get; set; } Interceptor.RegisterBeforeSendEvent();
[Inject] public IAdvisorCustomerRepository CompanyRepo { get; set; } }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public NavigationManager Navigator { get; set; } protected override async Task OnInitializedAsync()
[Inject] public IUserInfoService UserInfoService { get; set; } {
// set preferences
Profile = await ProfileService.GetProfile();
UserInfo = await UserInfoService.GetUserInfo();
Paging.OrderBy = Profile.CompanySort;
Paging.SearchColumn = Profile.CompanySearch;
Paging.PageSize = Convert.ToInt32(Profile.PageSize);
Paging.HasFolded = ShowFolded ? 1 : 0;
// load saved search
SavedSearch = string.IsNullOrWhiteSpace(Profile.CompanyFilterPhrase) ? "" : Profile.CompanyFilterPhrase;
Paging.SearchTerm = SavedSearch;
private List<CompanyDto> CompanyList { get; set; } = new(); // get companies
private UserProfile Profiles { get; set; } = new(); await FetchCustomers();
private UserManagerEditView XUserInfo { get; set; } = new(); Working = false;
private string SavedSearch { get; set; } = ""; }
private bool Working { get; set; } = true;
private MetaData PageData { get; set; } = new(); private async Task ToggleFolded()
private CustomerPaging Paging { get; set; } = new(); {
private string ButtonFoldedText { get; set; } = "Vis Ophørte"; Working = true;
private bool ShowFolded { get; set; } ShowFolded = !ShowFolded;
private string ButtonHiddenText { get; set; } = "Vis Skjulte"; ToggleFoldedText = ShowFolded ? "Normal Visning" : "Vis Lukkede";
private bool ShowHidden { get; set; } CompanyList = new List<CompanyDto>();
protected override void OnParametersSet() Paging.PageNumber = 1;
Paging.HasFolded = ShowFolded ? 1 : 0;
await FetchCustomers();
}
private async Task ToggleHidden()
{
Working = true;
ShowHidden = !ShowHidden;
ToggleHiddenText = ShowHidden ? "Normal Visning" : "Inkl. Skjulte";
CompanyList = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.IsHidden = ShowHidden ? 1 : 0;
await FetchCustomers();
}
private async Task SelectedPage(int page)
{
CompanyList = new List<CompanyDto>();
Paging.PageNumber = page;
await FetchCustomers();
}
private async Task SetSearchCol(string searchColumn)
{
CompanyList = new List<CompanyDto>();
Paging.SearchColumn = searchColumn;
Paging.PageNumber = 1;
await FetchCustomers();
}
private async Task SetPageSize(string pageSize)
{
CompanyList = new List<CompanyDto>();
Paging.PageSize = Convert.ToInt32(pageSize);
Paging.PageNumber = 1;
await FetchCustomers();
}
private async Task SetSearchPhrase(string searchTerm)
{
CompanyList = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.SearchTerm = searchTerm;
await FetchCustomers();
}
private async Task SetSortCol(string orderBy)
{
CompanyList = new List<CompanyDto>();
Paging.OrderBy = orderBy;
await FetchCustomers();
}
/// <summary>
/// Removes a company from CRM
/// </summary>
/// <param name="companyId"></param>
private async Task DeleteCompany(string companyId)
{
CompanyList = new List<CompanyDto>();
await CompanyRepo.DeleteCompany(companyId);
if (Paging.PageNumber > 1 && CompanyList.Count == 1)
Paging.PageNumber--;
await FetchCustomers();
}
private async Task FetchCustomers()
{
Working = true;
var pageRes = await CompanyRepo.GetCompanies(Paging);
Working = false;
if (pageRes.Items.Any())
{ {
Interceptor.RegisterEvent(); CompanyList = pageRes.Items;
Interceptor.RegisterBeforeSendEvent(); PageData = pageRes.MetaData;
} }
else
protected override async Task OnInitializedAsync()
{
// set preferences
Profiles = await ProfileService.GetProfile();
XUserInfo = await UserInfoService.GetUserInfo();
Paging.OrderBy = Profiles.CompanySort;
Paging.SearchColumn = Profiles.CompanySearch;
Paging.PageSize = Convert.ToInt32(Profiles.PageSize);
Paging.HasFolded = ShowFolded ? 1 : 0;
// load saved search
SavedSearch = string.IsNullOrWhiteSpace(Profiles.CompanyFilterPhrase) ? "" : Profiles.CompanyFilterPhrase;
Paging.SearchTerm = SavedSearch;
// get companies
await FetchCustomers();
Working = false;
}
private async Task OnFoldedClick()
{
Working = true;
ShowFolded = !ShowFolded;
ButtonFoldedText = ShowFolded ? "Vis Aktive" : "Vis Ophørte";
CompanyList = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.HasFolded = ShowFolded ? 1 : 0;
await FetchCustomers();
}
private async Task OnHiddenClick()
{
Working = true;
ShowHidden = !ShowHidden;
ButtonHiddenText = ShowHidden ? "Vis Normale" : "Vis skjulte";
CompanyList = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.IsHidden = ShowHidden ? 1 : 0;
await FetchCustomers();
}
private async Task SelectedPage(int page)
{ {
CompanyList = new List<CompanyDto>(); CompanyList = new List<CompanyDto>();
Paging.PageNumber = page; PageData = new MetaData();
await FetchCustomers();
}
private async Task SetSearchCol(string searchColumn)
{
CompanyList = new List<CompanyDto>();
Paging.SearchColumn = searchColumn;
Paging.PageNumber = 1;
await FetchCustomers();
}
private async Task SetPageSize(string pageSize)
{
CompanyList = new List<CompanyDto>();
Paging.PageSize = Convert.ToInt32(pageSize);
Paging.PageNumber = 1;
await FetchCustomers();
} }
}
private async Task SetSearchPhrase(string searchTerm) public void Dispose() => Interceptor.DisposeEvent();
{ }
CompanyList = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.SearchTerm = searchTerm;
await FetchCustomers();
}
private async Task SetSortCol(string orderBy)
{
CompanyList = new List<CompanyDto>();
Paging.OrderBy = orderBy;
await FetchCustomers();
}
/// <summary>
/// Removes a company from CRM
/// </summary>
/// <param name="companyId"></param>
private async Task DeleteCompany(string companyId)
{
CompanyList = new List<CompanyDto>();
await CompanyRepo.DeleteCompany(companyId);
if (Paging.PageNumber > 1 && CompanyList.Count == 1)
Paging.PageNumber--;
await FetchCustomers();
}
private async Task FetchCustomers()
{
Working = true;
var pageRes = await CompanyRepo.GetCompanies(Paging);
Working = false;
if (pageRes.Items.Any())
{
CompanyList = pageRes.Items;
PageData = pageRes.MetaData;
}
else
{
CompanyList = new List<CompanyDto>();
PageData = new MetaData();
}
}
public void Dispose() => Interceptor.DisposeEvent();
}
}

View file

@ -36,85 +36,86 @@
// erp context // erp context
<EditForm EditContext="ErpContext"> <EditForm EditContext="ErpContext">
<DataAnnotationsValidator/> <DataAnnotationsValidator/>
<div class="row g-3"> <div class="row g-1">
@* Company Name *@ @* Company Name *@
<label for="name" class="col-sm-1 col-form-label-sm">Navn</label> <label for="name" class="col-sm-1 col-form-label-sm">Navn</label>
<div class="col-sm-5"> <div class="col-sm-5">
<InputText id="name" class="form-control" @bind-Value="Company.Name"/> <InputText id="name" class="form-control" @bind-Value="Company.Name" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Name)"></ValidationMessage> <ValidationMessage For="@(() => Company.Name)"></ValidationMessage>
</div> </div>
@* Company Attention *@ @* Company Attention *@
<label for="attention" class="col-sm-1 col-form-label-sm">Att.</label> <label for="attention" class="col-sm-1 col-form-label-sm">Att.</label>
<div class="col-sm-5"> <div class="col-sm-5">
<InputText id="attention" class="form-control" @bind-Value="Company.Attention"/> <InputText id="attention" class="form-control" @bind-Value="Company.Attention" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Attention)"></ValidationMessage> <ValidationMessage For="@(() => Company.Attention)"></ValidationMessage>
</div> </div>
@* Address 1 *@ @* Address 1 *@
<label for="address1" class="col-sm-1 col-form-label-sm">Adresse</label> <label for="address1" class="col-sm-1 col-form-label-sm">Adresse</label>
<div class="col-sm-5"> <div class="col-sm-5">
<InputText id="address1" class="form-control" @bind-Value="Company.Address1"/> <InputText id="address1" class="form-control" @bind-Value="Company.Address1" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Address1)"></ValidationMessage> <ValidationMessage For="@(() => Company.Address1)"></ValidationMessage>
</div> </div>
@* Address 2 *@ @* Address 2 *@
<label for="address2" class="col-sm-1 col-form-label-sm">Adresse</label> <label for="address2" class="col-sm-1 col-form-label-sm">Adresse</label>
<div class="col-sm-5"> <div class="col-sm-5">
<InputText id="address2" class="form-control" @bind-Value="Company.Address2"/> <InputText id="address2" class="form-control" @bind-Value="Company.Address2" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Address2)"></ValidationMessage> <ValidationMessage For="@(() => Company.Address2)"></ValidationMessage>
</div> </div>
@* Post Code *@ @* Post Code *@
<label for="zipCode" class="col-sm-1 col-form-label-sm">PostNr</label> <label for="zipCode" class="col-sm-1 col-form-label-sm">PostNr</label>
<div class="col-sm-2"> <div class="col-sm-2">
<InputText id="zipCode" class="form-control" @bind-Value="Company.ZipCode"/> <InputText id="zipCode" class="form-control" @bind-Value="Company.ZipCode" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.ZipCode)"></ValidationMessage> <ValidationMessage For="@(() => Company.ZipCode)"></ValidationMessage>
</div> </div>
@* City Name *@ @* City Name *@
<label for="city" class="col-sm-1 col-form-label-sm">Bynavn</label> <label for="city" class="col-sm-1 col-form-label-sm">Bynavn</label>
<div class="col-sm-8"> <div class="col-sm-8">
<InputText id="city" class="form-control" @bind-Value="Company.City"/> <InputText id="city" class="form-control" @bind-Value="Company.City" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.City)"></ValidationMessage> <ValidationMessage For="@(() => Company.City)"></ValidationMessage>
</div> </div>
@* Phone *@ @* Phone *@
<label for="phone" class="col-sm-1 col-form-label-sm">Telefon</label> <label for="phone" class="col-sm-1 col-form-label-sm">Telefon</label>
<div class="col-sm-3"> <div class="col-sm-2">
<InputText id="phone" class="form-control" @bind-Value="Company.Phone"/> <InputText id="phone" class="form-control" @bind-Value="Company.Phone" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Phone)"></ValidationMessage> <ValidationMessage For="@(() => Company.Phone)"></ValidationMessage>
</div> </div>
@* Mobile *@ @* Mobile *@
<label for="mobile" class="col-sm-1 col-form-label-sm">Mobil</label> <label for="mobile" class="col-sm-1 col-form-label-sm">Mobil</label>
<div class="col-sm-3"> <div class="col-sm-2">
<InputText id="mobile" class="form-control" @bind-Value="Company.Mobile"/> <InputText id="mobile" class="form-control" @bind-Value="Company.Mobile" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Mobile)"></ValidationMessage> <ValidationMessage For="@(() => Company.Mobile)"></ValidationMessage>
</div> </div>
<div class="col-sm-4 d-grid mx-auto">
<button type="button" class="btn btn-danger" @onclick="ToggleVisibility">@ToggleButtonText</button>
</div>
@* Email *@ @* Email *@
<label for="email" class="col-sm-1 col-form-label-sm">Epost</label> <label for="email" class="col-sm-1 col-form-label-sm">Epost</label>
<div class="col-sm-5"> <div class="col-sm-5">
<InputText id="email" class="form-control" @bind-Value="Company.Email"/> <InputText id="email" class="form-control" @bind-Value="Company.Email" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Email)"></ValidationMessage> <ValidationMessage For="@(() => Company.Email)"></ValidationMessage>
</div> </div>
<div class="col-sm-3 d-grid mx-auto"> <div class="col-sm-4">@* ---- placeholder --- *@</div>
<button type="button" class="btn btn-primary d-block" disabled="@(Company.HasFolded == 0 || Company.Name == "ERROR")" onclick="@ForceActivity">Aktiver besøg</button> <div class="col-sm-2 d-grid mx-auto">
<button type="button" class="btn btn-edit" @onclick="ToggleErpEdit"><i class="bi-pencil"></i> STAM data</button>
</div> </div>
<div class="col-sm-3 d-grid mx-auto"> <div class="col-sm-3 d-grid mx-auto">
<button type="button" class="btn btn-danger d-block" onclick="@UpdateErpData" disabled="@(Working || Company.Name == "ERROR")"><i class="bi-cloud-arrow-up"></i> STAM data </button> <button type="button" class="btn btn-primary d-block" disabled="@(Company.HasFolded == 0 || Company.Name == "ERROR")" @onclick="ForceActivity">Aktiver besøg</button>
</div>
<div class="col-sm-3 d-grid mx-auto">
<button type="button" class="btn btn-danger d-block" onclick="@UpdateErpData" disabled="@(Working || Company.Name == "ERROR" || ErpEditDisabled)"><i class="bi-cloud-arrow-up"></i> STAM data </button>
</div> </div>
</div>
<hr class="mb-3"/>
<div class="row">
@* vat number*@ @* vat number*@
<label for="vatNumber" class="col-sm-2 col-form-label-sm">CVR/Org nr.</label> <label for="vatNumber" class="col-sm-1 col-form-label-sm">CVR/Org nr.</label>
<div class="col-sm-4"> <div class="col-sm-3">
<div class="input-group"> <div class="input-group">
<span class="input-group-text"> <span class="input-group-text">
<DisplayStateComponent StateClass="@VatState"/> <DisplayStateComponent StateClass="@VatState"/>
</span> </span>
<InputText id="vatNumber" class="form-control" @bind-Value="Company.VatNumber"/> <InputText id="vatNumber" class="form-control" @bind-Value="Company.VatNumber" readonly="@(VatEditDisabled)"/>
<ValidationMessage For="@(() => Company.VatNumber)"></ValidationMessage> <ValidationMessage For="@(() => Company.VatNumber)"></ValidationMessage>
</div> </div>
</div> </div>
<div class="col-sm-2 d-grid mx-auto">
<button type="button" class="btn btn-edit" @onclick="ToggleVatEdit"><i class="bi-pencil"></i> Moms/Org Nr.</button>
</div>
@* vat lookup *@ @* vat lookup *@
<div class="col-sm-3 d-grid mx-auto"> <div class="col-sm-3 d-grid mx-auto">
@switch (CountryCode) @switch (CountryCode)
@ -132,7 +133,7 @@
</div> </div>
@* save vat number *@ @* save vat number *@
<div class="col-sm-3 d-grid mx-auto"> <div class="col-sm-3 d-grid mx-auto">
<button type="button" class="btn btn-warning d-block" @onclick="UpdateVatNumber"><i class="bi-cloud-arrow-up"></i> Moms/Org Nr.</button> <button type="button" class="btn btn-warning d-block" @onclick="UpdateVatNumber" disabled="@(VatEditDisabled)"><i class="bi-cloud-arrow-up"></i> Moms/Org Nr.</button>
</div> </div>
</div> </div>
@ -240,6 +241,11 @@
<InputTextArea id="crmNotes" class="form-control" @bind-Value="Company.CrmNotes"/> <InputTextArea id="crmNotes" class="form-control" @bind-Value="Company.CrmNotes"/>
</div> </div>
</div> </div>
<div class="row pt-3">
<div class="col-sm-3 d-grid">
<button type="button" class="btn btn-danger" @onclick="ToggleVisibility">@ToggleButtonText</button>
</div>
</div>
</EditForm> </EditForm>
} }

View file

@ -1,4 +1,3 @@
// 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 // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as // it under the terms of the GNU Affero General Public License as
@ -28,7 +27,7 @@ using Wonky.Client.Services;
using Wonky.Client.Shared; using Wonky.Client.Shared;
using Wonky.Entity.DTO; using Wonky.Entity.DTO;
using Wonky.Entity.Models; using Wonky.Entity.Models;
using Wonky.Entity.Views;
#pragma warning disable CS8618 #pragma warning disable CS8618
namespace Wonky.Client.Pages; namespace Wonky.Client.Pages;
@ -46,8 +45,8 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
[Inject] public VatInfoLookupService VatService { get; set; } [Inject] public VatInfoLookupService VatService { get; set; }
[Inject] public ILocalStorageService Storage { get; set; } [Inject] public ILocalStorageService Storage { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; } [Inject] public IUserInfoService UserInfoService { get; set; }
private readonly JsonSerializerOptions _options = new () { PropertyNameCaseInsensitive = true }; private readonly JsonSerializerOptions _options = new() { PropertyNameCaseInsensitive = true };
private CompanyDto Company { get; set; } = new(); private CompanyDto Company { get; set; } = new();
private EditContext ErpContext { get; set; } private EditContext ErpContext { get; set; }
private DateTime LastVisit { get; set; } private DateTime LastVisit { get; set; }
@ -63,6 +62,8 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
private string ActionLink { get; set; } = ""; private string ActionLink { get; set; } = "";
private bool Working { get; set; } = true; private bool Working { get; set; } = true;
private bool CountryIsDk { get; set; } = true; private bool CountryIsDk { get; set; } = true;
private bool ErpEditDisabled { get; set; } = true;
private bool VatEditDisabled { get; set; } = true;
private List<ContactDto> Contacts { get; set; } = new(); private List<ContactDto> Contacts { get; set; } = new();
private VatLookupDkModal VatLookupPopup { get; set; } = new(); private VatLookupDkModal VatLookupPopup { get; set; } = new();
private ContactDto SelectedContact { get; set; } = new(); private ContactDto SelectedContact { get; set; } = new();
@ -70,7 +71,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
private ContactModal ContactPopup { get; set; } = new(); private ContactModal ContactPopup { get; set; } = new();
private UserManagerEditView UserInfo { get; set; } = new(); private UserManagerEditView UserInfo { get; set; } = new();
private string ToggleButtonText { get; set; } = ""; private string ToggleButtonText { get; set; } = "";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
// setup interceptor // setup interceptor
@ -78,25 +79,26 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
Interceptor.RegisterBeforeSendEvent(); Interceptor.RegisterBeforeSendEvent();
// initialize default contact // initialize default contact
DefaultContact = new ContactDto { CompanyId = CompanyId, ContactId = "", FirstName = ""}; DefaultContact = new ContactDto { CompanyId = CompanyId, ContactId = "", FirstName = "" };
// setup form context // setup form context
ErpContext = new EditContext(Company); ErpContext = new EditContext(Company);
// assign event handlers to context // assign event handlers to context
ErpContext.OnFieldChanged += HandleFieldChanged; ErpContext.OnFieldChanged += HandleFieldChanged;
ErpContext.OnValidationStateChanged += ValidationChanged; ErpContext.OnValidationStateChanged += ValidationChanged;
// fetch user info from local storage // fetch user info from local storage
UserInfo = await UserInfoService.GetUserInfo(); UserInfo = await UserInfoService.GetUserInfo();
CountryCode = UserInfo.CountryCode.ToLower(); CountryCode = UserInfo.CountryCode.ToLower();
CountryIsDk = CountryCode == "dk"; CountryIsDk = CountryCode == "dk";
Logger.LogDebug("companyId => {}", CompanyId); Logger.LogDebug("companyId => {}", CompanyId);
Company = await CustomerRepo.GetCompanyById(CompanyId); Company = await CustomerRepo.GetCompanyById(CompanyId);
Logger.LogDebug("company => {}", JsonSerializer.Serialize(Company)); Logger.LogDebug("company => {}", JsonSerializer.Serialize(Company));
ToggleButtonText = Company.IsHidden == 0 ? "Skjul kunde" : "Vis kunde"; // toggle view button text
ToggleButtonText = Company.IsHidden == 0 ? "Udelad kunde i oversigt" : "Brug Normal Visning";
CurrentVat = Company.VatNumber; CurrentVat = Company.VatNumber;
Company.CountryCode = UserInfo.CountryCode.ToLower(); Company.CountryCode = UserInfo.CountryCode.ToLower();
// internal flag // internal flag
@ -121,7 +123,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
// action link passed to activity button component // action link passed to activity button component
ActionLink = $"/advisor/customers/{CompanyId}/activities/new"; // used when drawing visit button ActionLink = $"/advisor/customers/{CompanyId}/activities/new"; // used when drawing visit button
// handle company out of business case // handle company out of business case
if(Company.HasFolded == 1) if (Company.HasFolded == 1)
{ {
// this is only used if user has selected to show closed companies // this is only used if user has selected to show closed companies
HasFolded = true; HasFolded = true;
@ -135,36 +137,49 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
// valid vat flag // valid vat flag
ValidVat = Company.ValidVat == 1; // true/false flag set if company has a valid vatNumber ValidVat = Company.ValidVat == 1; // true/false flag set if company has a valid vatNumber
// vat state css class // vat state css class
VatState = Company.ValidVat == 1 ? "the-good" : "no-vat"; // assign css class VatState = Company.ValidVat == 1 ? "the-good" : "no-vat"; // assign css class
} }
// create search address from address // create search address from address
if (CountryIsDk) if (CountryIsDk)
CompanyVatAddress = PrepareVatAddress(Company); CompanyVatAddress = PrepareVatAddress(Company);
await FetchContacts(CompanyId); await FetchContacts(CompanyId);
// remove loading image // remove loading image
Working = false; Working = false;
await Task.Delay(100); await Task.Delay(100);
await RequestErpUpdate(); await RequestErpUpdate();
} }
private void ToggleErpEdit()
{
ErpEditDisabled = !ErpEditDisabled;
}
private void ToggleVatEdit()
{
VatEditDisabled = !VatEditDisabled;
}
private async Task ToggleVisibility() private async Task ToggleVisibility()
{ {
Company.IsHidden = Company.IsHidden == 0 ? 1 : 0; Company.IsHidden = Company.IsHidden == 0 ? 1 : 0;
ToggleButtonText = Company.IsHidden == 0 ? "Skjul kunde" : "Vis kunde"; // toggle view button text
ToggleButtonText = Company.IsHidden == 0 ? "Udelad kunde i oversigt" : "Brug Normal Visning";
Logger.LogDebug("ToggleVisibility => Company.IsHidden == {}", Company.IsHidden); Logger.LogDebug("ToggleVisibility => Company.IsHidden == {}", Company.IsHidden);
await CustomerRepo.UpdateCrmData(CompanyId, Company); await CustomerRepo.UpdateCrmData(CompanyId, Company);
} }
private async Task RequestErpUpdate() private async Task RequestErpUpdate()
{ {
if(Working) if (Working)
return; return;
Working = true; Working = true;
Company.HistorySync = await HistoryRepo.InvoiceErpToCrmRpc(CompanyId, Company.HistorySync); Company.HistorySync = await HistoryRepo.InvoiceErpToCrmRpc(CompanyId, Company.HistorySync);
Working = false; Working = false;
} }
/// <summary> /// <summary>
/// Fetch contacts from backend /// Fetch contacts from backend
/// </summary> /// </summary>
@ -173,7 +188,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
{ {
// load contacts // load contacts
Contacts = await AdvisorContactRepo.GetContacts(companyId); Contacts = await AdvisorContactRepo.GetContacts(companyId);
if(Contacts.Any() && Contacts.Count > 1) if (Contacts.Any() && Contacts.Count > 1)
Contacts = Contacts.OrderBy(x => x.FirstName).ToList(); Contacts = Contacts.OrderBy(x => x.FirstName).ToList();
} }
@ -183,8 +198,8 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
private void OpenVatLookupModal() private void OpenVatLookupModal()
{ {
VatLookupPopup.Show(); VatLookupPopup.Show();
} }
/// <summary> /// <summary>
/// Callback to update company properties /// Callback to update company properties
/// </summary> /// </summary>
@ -206,9 +221,10 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
Company.ZipCode = regInfo.ZipCode; Company.ZipCode = regInfo.ZipCode;
Company.City = regInfo.City; Company.City = regInfo.City;
} }
Company.VatNumber = regInfo.VatNumber; Company.VatNumber = regInfo.VatNumber;
} }
/// <summary> /// <summary>
/// Open contact edit popup /// Open contact edit popup
/// </summary> /// </summary>
@ -245,15 +261,16 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
// contact modified // contact modified
Logger.LogDebug("update => {}", jsonContact); Logger.LogDebug("update => {}", jsonContact);
// send put request to backend // send put request to backend
await AdvisorContactRepo.UpdateContact(contact); await AdvisorContactRepo.UpdateContact(contact);
} }
// reset selected contact // reset selected contact
SelectedContact = new ContactDto(); SelectedContact = new ContactDto();
// reload contacts from backend // reload contacts from backend
await FetchContacts(CompanyId); await FetchContacts(CompanyId);
Working = false; Working = false;
} }
/// <summary> /// <summary>
/// Delete contact callback /// Delete contact callback
/// </summary> /// </summary>
@ -270,7 +287,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
await FetchContacts(CompanyId); await FetchContacts(CompanyId);
Working = false; Working = false;
} }
/// <summary> /// <summary>
/// Update CRM data /// Update CRM data
/// </summary> /// </summary>
@ -290,6 +307,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
Company = result; Company = result;
StateHasChanged(); StateHasChanged();
} }
Working = false; Working = false;
Toaster.ClearAll(); Toaster.ClearAll();
} }
@ -302,6 +320,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
{ {
if (Working) if (Working)
return; return;
ErpEditDisabled = true;
Working = true; Working = true;
Toaster.ShowInfo("Vent venligst ...", "OPDATERER STAM DATA"); Toaster.ShowInfo("Vent venligst ...", "OPDATERER STAM DATA");
var result = await CustomerRepo.UpdateErpData(CompanyId, Company); var result = await CustomerRepo.UpdateErpData(CompanyId, Company);
@ -310,6 +329,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
Company = result; Company = result;
StateHasChanged(); StateHasChanged();
} }
Working = false; Working = false;
Toaster.ClearAll(); Toaster.ClearAll();
} }
@ -326,8 +346,10 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
Toaster.ShowError($"Moms Nummer ugyldigt"); Toaster.ShowError($"Moms Nummer ugyldigt");
return; return;
} }
if (Working) if (Working)
return; return;
VatEditDisabled = true;
Working = true; Working = true;
Toaster.ShowInfo("Vent venligst ...", "OPDATERER MOMS NUMMER"); Toaster.ShowInfo("Vent venligst ...", "OPDATERER MOMS NUMMER");
var result = await CustomerRepo.UpdateCompanyVat(CompanyId, Company.VatNumber); var result = await CustomerRepo.UpdateCompanyVat(CompanyId, Company.VatNumber);
@ -336,10 +358,11 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
Company = result; Company = result;
StateHasChanged(); StateHasChanged();
} }
Toaster.ClearAll(); Toaster.ClearAll();
Working = false; Working = false;
} }
/// <summary> /// <summary>
/// Prepare vat address from company model /// Prepare vat address from company model
/// </summary> /// </summary>
@ -359,6 +382,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
HouseNumber = Regex.Replace(model.Address1[pos1..], "[^0-9]", "").Trim() HouseNumber = Regex.Replace(model.Address1[pos1..], "[^0-9]", "").Trim()
}; };
} }
// process address2 // process address2
var pos2 = model.Address2.IndexOfAny(digits); var pos2 = model.Address2.IndexOfAny(digits);
if (pos2 > 0) if (pos2 > 0)
@ -370,10 +394,11 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
HouseNumber = Regex.Replace(model.Address2[pos2..], "[^0-9]", "").Trim() HouseNumber = Regex.Replace(model.Address2[pos2..], "[^0-9]", "").Trim()
}; };
} }
// return empty model // return empty model
return new VatAddress(); return new VatAddress();
} }
/// <summary> /// <summary>
/// Change activity enabled state /// Change activity enabled state
/// </summary> /// </summary>
@ -381,7 +406,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
{ {
EnableActivity = EnableActivity == 0 ? 1 : 0; EnableActivity = EnableActivity == 0 ? 1 : 0;
} }
/// <summary> /// <summary>
/// Context field change event callback /// Context field change event callback
/// </summary> /// </summary>
@ -398,9 +423,10 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
ValidVat = true; ValidVat = true;
EnableActivity = 1; EnableActivity = 1;
} }
StateHasChanged(); StateHasChanged();
} }
/// <summary> /// <summary>
/// Context validation change event callback /// Context validation change event callback
/// </summary> /// </summary>
@ -410,13 +436,13 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
{ {
ErpContext.OnFieldChanged -= HandleFieldChanged; ErpContext.OnFieldChanged -= HandleFieldChanged;
ErpContext.OnValidationStateChanged -= ValidationChanged!; ErpContext.OnValidationStateChanged -= ValidationChanged!;
ErpContext = new EditContext(Company); ErpContext = new EditContext(Company);
ErpContext.OnFieldChanged += HandleFieldChanged; ErpContext.OnFieldChanged += HandleFieldChanged;
ErpContext.OnValidationStateChanged += ValidationChanged; ErpContext.OnValidationStateChanged += ValidationChanged;
} }
/// <summary> /// <summary>
/// Dispose /// Dispose
/// </summary> /// </summary>

View file

@ -16,13 +16,12 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace Wonky.Client.Pages namespace Wonky.Client.Pages;
{
public partial class ErrorReportPage
{
[Parameter]
public int ErrorCode { get; set; }
[Parameter] public string ErrorDescription { get; set; } = ""; public partial class ErrorReportPage
} {
} [Parameter]
public int ErrorCode { get; set; }
[Parameter] public string ErrorDescription { get; set; } = "";
}

View file

@ -24,123 +24,120 @@ using Wonky.Entity.Requests;
using Wonky.Entity.Views; using Wonky.Entity.Views;
#pragma warning disable CS8618 #pragma warning disable CS8618
namespace Wonky.Client.Pages namespace Wonky.Client.Pages;
public partial class OfficeCountryCustomerListPage : IDisposable
{ {
public partial class OfficeCountryCustomerListPage : IDisposable [Parameter] public string CountryCode { get; set; } = "";
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] public ICountryCustomerRepository CustomerRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
private List<CompanyDto> Companies { get; set; } = new();
private UserProfile Profiles { get; set; } = new();
private UserManagerEditView XUserInfo { get; set; } = new();
private string SavedSearch { get; set; } = "";
private bool ShowFolded { get; set; }
private bool Working { get; set; } = true;
private MetaData PageData { get; set; } = new();
private CustomerPaging Paging { get; set; } = new();
private string ButtonFoldedText { get; set; } = "Vis Ophørte";
protected override async Task OnParametersSetAsync()
{ {
[Parameter] public string CountryCode { get; set; } = ""; Working = true;
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] public ICountryCustomerRepository CustomerRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
private List<CompanyDto> Companies { get; set; } = new();
private UserProfile Profiles { get; set; } = new();
private UserManagerEditView XUserInfo { get; set; } = new();
private string SavedSearch { get; set; } = "";
private bool ShowFolded { get; set; }
private bool Working { get; set; } = true;
private MetaData PageData { get; set; } = new();
private CustomerPaging Paging { get; set; } = new();
private string ButtonFoldedText { get; set; } = "Vis Ophørte";
protected override async Task OnParametersSetAsync()
{
Working = true;
Interceptor.RegisterEvent(); Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent(); Interceptor.RegisterBeforeSendEvent();
// set preferences // set preferences
Profiles = await ProfileService.GetProfile(); Profiles = await ProfileService.GetProfile();
XUserInfo = await UserInfoService.GetUserInfo(); XUserInfo = await UserInfoService.GetUserInfo();
Paging.OrderBy = Profiles.CompanySort; Paging.OrderBy = Profiles.CompanySort;
Paging.SearchColumn = Profiles.CompanySearch; Paging.SearchColumn = Profiles.CompanySearch;
Paging.PageSize = Convert.ToInt32(Profiles.PageSize); Paging.PageSize = Convert.ToInt32(Profiles.PageSize);
Paging.HasFolded = ShowFolded ? 1 : 0; Paging.HasFolded = ShowFolded ? 1 : 0;
// load saved search // load saved search
SavedSearch = string.IsNullOrWhiteSpace(Profiles.CompanyFilterPhrase) ? "" : Profiles.CompanyFilterPhrase; SavedSearch = string.IsNullOrWhiteSpace(Profiles.CompanyFilterPhrase) ? "" : Profiles.CompanyFilterPhrase;
Paging.SearchTerm = SavedSearch; Paging.SearchTerm = SavedSearch;
// get companies // get companies
await FetchCustomers(); await FetchCustomers();
} }
private async Task OnFoldedClick() private async Task OnFoldedClick()
{ {
Working = true; Working = true;
ShowFolded = !ShowFolded; ShowFolded = !ShowFolded;
ButtonFoldedText = ShowFolded ? "Vis Aktive" : "Vis Ophørte"; ButtonFoldedText = ShowFolded ? "Vis Aktive" : "Vis Ophørte";
Companies = new List<CompanyDto>(); Companies = new List<CompanyDto>();
Paging.PageNumber = 1; Paging.PageNumber = 1;
Paging.HasFolded = ShowFolded ? 1 : 0; Paging.HasFolded = ShowFolded ? 1 : 0;
await FetchCustomers(); await FetchCustomers();
} }
private async Task SelectedPage(int page) private async Task SelectedPage(int page)
{ {
Working = true; Working = true;
Companies = new List<CompanyDto>(); Companies = new List<CompanyDto>();
Paging.PageNumber = page; Paging.PageNumber = page;
await FetchCustomers(); await FetchCustomers();
} }
private async Task SetSearchCol(string searchColumn) private async Task SetSearchCol(string searchColumn)
{ {
Working = true; Working = true;
Companies = new List<CompanyDto>(); Companies = new List<CompanyDto>();
Paging.SearchColumn = searchColumn; Paging.SearchColumn = searchColumn;
Paging.PageNumber = 1; Paging.PageNumber = 1;
await FetchCustomers(); await FetchCustomers();
} }
private async Task SetPageSize(string pageSize) private async Task SetPageSize(string pageSize)
{ {
Working = true; Working = true;
Companies = new List<CompanyDto>(); Companies = new List<CompanyDto>();
Paging.PageSize = Convert.ToInt32(pageSize); Paging.PageSize = Convert.ToInt32(pageSize);
Paging.PageNumber = 1; Paging.PageNumber = 1;
await FetchCustomers(); await FetchCustomers();
} }
private async Task SetSearchPhrase(string searchTerm) private async Task SetSearchPhrase(string searchTerm)
{ {
Working = true; Working = true;
Companies = new List<CompanyDto>(); Companies = new List<CompanyDto>();
Paging.PageNumber = 1; Paging.PageNumber = 1;
Paging.SearchTerm = searchTerm; Paging.SearchTerm = searchTerm;
await FetchCustomers(); await FetchCustomers();
} }
private async Task SetSortCol(string orderBy) private async Task SetSortCol(string orderBy)
{
Working = true;
Companies = new List<CompanyDto>();
Paging.OrderBy = orderBy;
await FetchCustomers();
}
private async Task FetchCustomers()
{
Working = true;
var response = await CustomerRepo.GetCompaniesPaged(CountryCode, Paging);
Working = false;
if (response.Items.Any())
{
Companies = response.Items;
PageData = response.MetaData;
}
else
{ {
Working = true;
Companies = new List<CompanyDto>(); Companies = new List<CompanyDto>();
Paging.OrderBy = orderBy; PageData = new MetaData();
await FetchCustomers();
} }
}
private async Task FetchCustomers() public void Dispose() => Interceptor.DisposeEvent();
{ }
Working = true;
var response = await CustomerRepo.GetCompaniesPaged(CountryCode, Paging);
Working = false;
if (response.Items.Any())
{
Companies = response.Items;
PageData = response.MetaData;
}
else
{
Companies = new List<CompanyDto>();
PageData = new MetaData();
}
}
public void Dispose() => Interceptor.DisposeEvent();
}
}

View file

@ -24,123 +24,121 @@ using Wonky.Entity.Configuration;
using Wonky.Entity.DTO; using Wonky.Entity.DTO;
using Wonky.Entity.Views; using Wonky.Entity.Views;
namespace Wonky.Client.Services namespace Wonky.Client.Services;
public class AuthenticationService : IAuthenticationService
{ {
public class AuthenticationService : IAuthenticationService private readonly JsonSerializerOptions _options = new() { PropertyNameCaseInsensitive = true };
private readonly HttpClient _client;
private readonly AuthenticationStateProvider _authStateProvider;
private readonly IOptions<ApiConfig> _apiConfig;
private readonly ILogger<AuthenticationService> _logger;
private readonly IUserInfoService _infoService;
private readonly UserProfileService _profile;
private readonly ILocalStorageService _localStorage;
public AuthenticationService(
HttpClient client,
AuthenticationStateProvider authStateProvider,
IOptions<ApiConfig> apiConfig,
ILogger<AuthenticationService> logger,
IUserInfoService infoService,
UserProfileService profile,
ILocalStorageService localStorage
)
{ {
private readonly JsonSerializerOptions _options = new() { PropertyNameCaseInsensitive = true }; _client = client;
private readonly HttpClient _client; _authStateProvider = authStateProvider;
private readonly AuthenticationStateProvider _authStateProvider; _apiConfig = apiConfig;
private readonly IOptions<ApiConfig> _apiConfig; _logger = logger;
private readonly ILogger<AuthenticationService> _logger; _infoService = infoService;
private readonly IUserInfoService _infoService; _profile = profile;
private readonly UserProfileService _profile; _localStorage = localStorage;
private readonly ILocalStorageService _localStorage; }
public AuthenticationService(
HttpClient client,
AuthenticationStateProvider authStateProvider,
IOptions<ApiConfig> apiConfig,
ILogger<AuthenticationService> logger,
IUserInfoService infoService,
UserProfileService profile,
ILocalStorageService localStorage
)
{
_client = client;
_authStateProvider = authStateProvider;
_apiConfig = apiConfig;
_logger = logger;
_infoService = infoService;
_profile = profile;
_localStorage = localStorage;
}
public async Task<AuthResponseView> Login(CredentialDto credentials) public async Task<AuthResponseView> Login(CredentialDto credentials)
{
var credForm = new Dictionary<string, string>
{ {
var credForm = new Dictionary<string, string> ["grant_type"] = "password",
["username"] = credentials.Email,
["password"] = credentials.Password
};
var response = await _client
.PostAsync(_apiConfig.Value.ServicesAuth, new FormUrlEncodedContent(credForm));
var resContent = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
return new AuthResponseView
{ {
["grant_type"] = "password", IsSuccess = false, ErrorMessage = $"Kontroller indtastning"
["username"] = credentials.Email,
["password"] = credentials.Password
}; };
var response = await _client // process response content
.PostAsync(_apiConfig.Value.ServicesAuth, new FormUrlEncodedContent(credForm)); var data = JsonSerializer.Deserialize<AuthResponseView>(resContent, _options);
await _infoService.SetAccessToken(data.AccessToken);
await _infoService.SetRefreshToken(data.RefreshToken);
await _infoService.SetExpiration((int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds + data.ExpiresIn - 60);
var resContent = await response.Content.ReadAsStringAsync(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", data.AccessToken);
if (!response.IsSuccessStatusCode) var userInfo = await UserInfo();
return new AuthResponseView
{
IsSuccess = false, ErrorMessage = $"Kontroller indtastning"
};
// process response content await _infoService.SetUserInfo(userInfo);
var data = JsonSerializer.Deserialize<AuthResponseView>(resContent, _options);
await _infoService.SetAccessToken(data.AccessToken); // notify system on state change
await _infoService.SetRefreshToken(data.RefreshToken); ((AuthStateProvider)_authStateProvider).NotifyUserAuthenticationAsync(data.AccessToken);
await _infoService.SetExpiration((int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds + data.ExpiresIn - 60); data.IsSuccess = true;
return data;
}
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", data.AccessToken); public async Task<string> RefreshToken()
{
var userInfo = await UserInfo(); var refreshToken = await _infoService.GetRefreshToken();
var credentials = new Dictionary<string, string>
await _infoService.SetUserInfo(userInfo);
// notify system on state change
((AuthStateProvider)_authStateProvider).NotifyUserAuthenticationAsync(data.AccessToken);
data.IsSuccess = true;
return data;
}
public async Task<string> RefreshToken()
{ {
var refreshToken = await _infoService.GetRefreshToken(); ["grant_type"] = "refresh_token",
var credentials = new Dictionary<string, string> ["refresh_token"] = refreshToken
{ };
["grant_type"] = "refresh_token", var response = await _client.PostAsync(_apiConfig.Value.ServicesAuth, new FormUrlEncodedContent(credentials));
["refresh_token"] = refreshToken if (!response.IsSuccessStatusCode)
}; return string.Empty;
var response = await _client.PostAsync(_apiConfig.Value.ServicesAuth, new FormUrlEncodedContent(credentials));
if (!response.IsSuccessStatusCode)
return string.Empty;
var resContent = await response.Content.ReadAsStringAsync(); var resContent = await response.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<AuthResponseView>(resContent, _options); var data = JsonSerializer.Deserialize<AuthResponseView>(resContent, _options);
if (string.IsNullOrWhiteSpace(data.AccessToken)) if (string.IsNullOrWhiteSpace(data.AccessToken))
return string.Empty; return string.Empty;
// set default request headers using access_token // set default request headers using access_token
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", data.AccessToken); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", data.AccessToken);
await _infoService.SetAccessToken(data.AccessToken); await _infoService.SetAccessToken(data.AccessToken);
await _infoService.SetRefreshToken(data.RefreshToken); await _infoService.SetRefreshToken(data.RefreshToken);
await _infoService.SetExpiration((int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds + data.ExpiresIn - 60); await _infoService.SetExpiration((int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds + data.ExpiresIn - 60);
return data.AccessToken; return data.AccessToken;
} }
public async Task Logout() public async Task Logout()
{ {
var profileBackup = await _profile.GetProfile(); var profileBackup = await _profile.GetProfile();
Task.Delay(150); Task.Delay(150);
await _localStorage.ClearAsync(); await _localStorage.ClearAsync();
Task.Delay(150); Task.Delay(150);
await _profile.SetProfile(profileBackup); await _profile.SetProfile(profileBackup);
_client.DefaultRequestHeaders.Authorization = null; _client.DefaultRequestHeaders.Authorization = null;
((AuthStateProvider)_authStateProvider).NotifyUserLogout(); ((AuthStateProvider)_authStateProvider).NotifyUserLogout();
} }
public async Task<UserManagerEditView> UserInfo(bool write = false)
{
var response = await _client.GetAsync(_apiConfig.Value.UserInfo).ConfigureAwait(true);
var content = await response.Content.ReadAsStringAsync();
var userInfo = JsonSerializer.Deserialize<UserManagerEditView>(content, _options);
if(write)
await _infoService.SetUserInfo(userInfo);
return userInfo ?? new UserManagerEditView();
}
}
}
public async Task<UserManagerEditView> UserInfo(bool write = false)
{
var response = await _client.GetAsync(_apiConfig.Value.UserInfo).ConfigureAwait(true);
var content = await response.Content.ReadAsStringAsync();
var userInfo = JsonSerializer.Deserialize<UserManagerEditView>(content, _options);
if(write)
await _infoService.SetUserInfo(userInfo);
return userInfo ?? new UserManagerEditView();
}
}

View file

@ -17,14 +17,12 @@
using Wonky.Entity.DTO; using Wonky.Entity.DTO;
using Wonky.Entity.Views; using Wonky.Entity.Views;
namespace Wonky.Client.Services namespace Wonky.Client.Services;
{
public interface IAuthenticationService
{
Task<AuthResponseView> Login(CredentialDto credentials);
Task Logout();
Task<string> RefreshToken();
Task<UserManagerEditView> UserInfo(bool write = false);
}
}
public interface IAuthenticationService
{
Task<AuthResponseView> Login(CredentialDto credentials);
Task Logout();
Task<string> RefreshToken();
Task<UserManagerEditView> UserInfo(bool write = false);
}

View file

@ -22,86 +22,84 @@ using Wonky.Client.Services;
using Wonky.Entity.DTO; using Wonky.Entity.DTO;
using Wonky.Entity.Views; using Wonky.Entity.Views;
namespace Wonky.Client.Shared namespace Wonky.Client.Shared;
public class AuthStateProvider : AuthenticationStateProvider
{ {
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 HttpClient _client; _client = client;
// private readonly ILocalStorageService _storage; _infoService = infoService;
private readonly AuthenticationState _anonymous; _anonymous = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
private readonly IUserInfoService _infoService;
public AuthStateProvider(HttpClient client, IUserInfoService infoService)
{
_client = client;
_infoService = infoService;
_anonymous = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = await _infoService.GetAccessToken();
if (string.IsNullOrEmpty(token))
return _anonymous;
var userInfo = await _infoService.GetUserInfo();
if (userInfo == null)
return _anonymous;
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
var exp = await _infoService.GetExpiration();
var claims = new List<Claim>
{
new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"),
new(ClaimTypes.Email, userInfo.Email),
new(ClaimTypes.Country, userInfo.CountryCode),
new(ClaimTypes.MobilePhone, userInfo.PhoneNumber),
new(ClaimTypes.Expiration, exp.ToString())
};
claims.AddRange(
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")));
}
public async void NotifyUserAuthenticationAsync(string token)
{
if (string.IsNullOrEmpty(token))
return;
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
var userInfo = await _infoService.GetUserInfo();
var exp = await _infoService.GetExpiration();
var claims = new List<Claim>
{
new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"),
new(ClaimTypes.Email, userInfo.Email),
new(ClaimTypes.Country, userInfo.CountryCode),
new(ClaimTypes.MobilePhone, userInfo.PhoneNumber),
new(ClaimTypes.Expiration, exp.ToString())
};
claims.AddRange(
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"))));
NotifyAuthenticationStateChanged(authState);
}
public void NotifyUserLogout()
{
var authState = Task.FromResult(_anonymous);
NotifyAuthenticationStateChanged(authState);
}
} }
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = await _infoService.GetAccessToken();
if (string.IsNullOrEmpty(token))
return _anonymous;
var userInfo = await _infoService.GetUserInfo();
if (userInfo == null)
return _anonymous;
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
var exp = await _infoService.GetExpiration();
var claims = new List<Claim>
{
new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"),
new(ClaimTypes.Email, userInfo.Email),
new(ClaimTypes.Country, userInfo.CountryCode),
new(ClaimTypes.MobilePhone, userInfo.PhoneNumber),
new(ClaimTypes.Expiration, exp.ToString())
};
claims.AddRange(
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")));
}
public async void NotifyUserAuthenticationAsync(string token)
{
if (string.IsNullOrEmpty(token))
return;
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
var userInfo = await _infoService.GetUserInfo();
var exp = await _infoService.GetExpiration();
var claims = new List<Claim>
{
new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"),
new(ClaimTypes.Email, userInfo.Email),
new(ClaimTypes.Country, userInfo.CountryCode),
new(ClaimTypes.MobilePhone, userInfo.PhoneNumber),
new(ClaimTypes.Expiration, exp.ToString())
};
claims.AddRange(
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"))));
NotifyAuthenticationStateChanged(authState);
}
public void NotifyUserLogout()
{
var authState = Task.FromResult(_anonymous);
NotifyAuthenticationStateChanged(authState);
}
}

View file

@ -1,7 +1,7 @@
{ {
"appInfo": { "appInfo": {
"name": "Wonky Online", "name": "Wonky Online",
"version": "0.117.1", "version": "0.118.0",
"rc": true, "rc": true,
"sandBox": false, "sandBox": false,
"image": "grumpy-coder.png" "image": "grumpy-coder.png"
@ -19,7 +19,7 @@
} }
}, },
"apiConfig": { "apiConfig": {
"baseUrl": "https://dev.innotec.dk", "baseUrl": "https://zeta.innotec.dk",
"catalog": "api/v2/catalog/country", "catalog": "api/v2/catalog/country",
"crmCustomers": "api/v2/crm/companies", "crmCustomers": "api/v2/crm/companies",
"crmInventoryExt": "history/inventory", "crmInventoryExt": "history/inventory",