Merge branch 'office-functionality---phone-order' into dev-v6

This commit is contained in:
Frede Hundewadt 2023-02-01 09:49:28 +01:00
commit 35cf79beba
207 changed files with 2608 additions and 1940 deletions

View file

@ -2,9 +2,12 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Affero/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Bestilling/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Bes_00F8g/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Danmark/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=fejl/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=kontrolleres/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Norge/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=opst_00E5et/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Sverige/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tilbud/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Venligst/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Virk/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -8,7 +8,7 @@
@code {
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] private UserProfileService ProfileService { get; set; }
private int KmMorning { get; set; }
private UserPref Prefs { get; set; } = new();

View file

@ -18,7 +18,7 @@
@if (ActivityList.Any())
{
<table class="table table-bordered d-print-table table-striped">
<table class="table table-sm table-bordered d-print-table table-striped">
<thead>
<tr class="bg-dark text-white opacity-75 border-bottom">
<th scope="col">Kunde</th>
@ -38,7 +38,7 @@
@foreach (var activity in ActivityList)
{
<tr>
<td class="align-middle"><a class="btn btn-outline-info text-black d-block" href="/customers/@activity.Company.CompanyId/orders/@activity.ActivityId">@activity.Company.Name</a></td>
<td class="align-middle"><a href="/advisor/customers/@activity.Company.CompanyId/orders/@activity.ActivityId">@activity.Company.Name</a></td>
<td class="align-middle">@activity.Company.City</td>
<td class="align-middle">@activity.Demo</td>
<td class="align-middle">@activity.Sales</td>

View file

@ -18,9 +18,9 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Components;
public partial class ActivityListComponent
public partial class AdvisorActivityListComponent
{
[Parameter] public List<ReportItemView> ActivityList { get; set; } = new();
[Inject] public NavigationManager Navigator { get; set; }
[Inject] private NavigationManager Navigator { get; set; }
}

View file

@ -58,7 +58,7 @@
@company.City
</td>
<td class="align-middle">
<ActivityButton CompanyId="@company.CompanyId" ActionLink="/customers/$ID$/activities/new"
<ActivityButton CompanyId="@company.CompanyId" ActionLink="/advisor/customers/$ID$/activities/new"
ButtonText="Besøg" ButtonType="primary" Enabled="@company.ValidVat"/>
</td>
</tr>

View file

@ -27,13 +27,13 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Components
{
public partial class AdvisorCompanyTableComponent
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 Js { get; set; }
[Inject] private NavigationManager Navigator { get; set; }
[Inject] private IJSRuntime Js { get; set; }
private Lazy<IJSObjectReference> BsTooltip = new();
@ -43,7 +43,7 @@ namespace Wonky.Client.Components
private void ViewCustomer(string companyId)
{
Navigator.NavigateTo($"/customers/{companyId}");
Navigator.NavigateTo($"/advisor/customers/{companyId}");
}
private void CallInformationModal(string info)

View file

@ -0,0 +1,93 @@
@using Wonky.Entity.Views
@using Wonky.Entity.DTO
@*
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
*@
<table class="table table-sm table-bordered table-striped">
<thead>
<tr class="bg-dark text-white opacity-75 border-bottom">
<th></th>
<th class="text-center" colspan="2" scope="col">Dagens Demo @(ReportData.NewDemoCount + ReportData.RecallDemoCount)</th>
<th class="text-center border-end" colspan="2" scope="col">Dagens Resultat</th>
<th class="text-center" colspan="4" scope="col">Måneds Resultat</th>
</tr>
</thead>
<tbody>
<tr class="bg-dark bg-opacity-50 border-bottom">
<td></td>
<th class="text-end text-white" scope="col">Besøg</th>
<th class="text-end text-white" scope="col">Demo</th>
<th class="text-end text-white" scope="col">Salg</th>
<th class="text-end text-white border-end" scope="col">Beløb</th>
<th class="text-end text-white" scope="col">Besøg</th>
<th class="text-end text-white" scope="col">Demo</th>
<th class="text-end text-white" scope="col">Salg</th>
<th class="text-end text-white" scope="col">Beløb</th>
</tr>
<tr>
<th scope="row">N</th>
<td class="text-end">@ReportData.NewVisitCount</td>
<td class="text-end">@ReportData.NewDemoCount</td>
<td class="text-end">@ReportData.NewSaleCount</td>
<td class="text-end border-end">@ReportData.NewTurnover</td>
<td class="text-end">@ReportData.NewVisitCountMonth</td>
<td class="text-end">@ReportData.NewDemoCountMonth</td>
<td class="text-end">@ReportData.NewSaleCountMonth</td>
<td class="text-end">@ReportData.NewTurnoverMonth</td>
</tr>
<tr>
<th scope="row">R</th>
<td class="text-end">@ReportData.RecallVisitCount</td>
<td class="text-end">@ReportData.RecallDemoCount</td>
<td class="text-end">@ReportData.RecallSaleCount</td>
<td class="text-end border-end">@ReportData.RecallTurnover</td>
<td class="text-end">@ReportData.RecallVisitCountMonth</td>
<td class="text-end">@ReportData.RecallDemoCountMonth</td>
<td class="text-end">@ReportData.RecallSaleCountMonth</td>
<td class="text-end">@ReportData.RecallTurnoverMonth</td>
</tr>
<tr>
<th scope="row">SAS</th>
<td class="bg-light"></td>
<td class="bg-light"></td>
<td class="text-end">@ReportData.SasCount</td>
<td class="text-end border-end">@ReportData.SasTurnover</td>
<td class="bg-light"></td>
<td class="bg-light"></td>
<td class="text-end">@ReportData.SasCountMonth</td>
<td class="text-end">@ReportData.SasTurnoverMonth</td>
</tr>
<tr>
<th scope="row">TOTAL</th>
<td class="text-end">@ReportData.TotalVisitCount</td>
<td class="text-end">@ReportData.TotalDemoCount</td>
<td class="text-end">@ReportData.TotalSaleCount</td>
<td class="text-end border-end">@ReportData.TotalTurnover</td>
<td class="text-end">@ReportData.TotalVisitCountMonth</td>
<td class="text-end">@ReportData.TotalDemoCountMonth</td>
<td class="text-end">@ReportData.TotalSaleCountMonth</td>
<td class="text-end">@ReportData.TotalTurnoverMonth</td>
</tr>
</tbody>
</table>
@code{
[Parameter]
public ReportFiguresDto ReportData { get; set; } = new();
}

View file

@ -22,8 +22,8 @@ namespace Wonky.Client.Components;
public partial class CatalogGroupComponent
{
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] private ILocalStorageService Storage { get; set; }
[Inject] private UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
private Dictionary<string, string> Items { get; set; } = new();
private UserPref Prefs = new();

View file

@ -24,5 +24,5 @@ namespace Wonky.Client.Components;
public partial class CatalogListComponent
{
[Parameter] public List<SalesItemView> ItemList { get; set; } = new();
[Inject] public IToastService ToastService { get; set; }
[Inject] private IToastService ToastService { get; set; }
}

View file

@ -17,7 +17,7 @@
<select class="form-select bg-warning text-bg-warning" @bind-value="@SearchCol" @bind-value:event="oninput" @onchange="OnSelectChanged">
<option value="-1" selected disabled>SØGNING</option>
<option value="name">Søg Navn</option>
<option value="sku">Søg Nummer</option>
<option value="shortName">Søg Forkort.</option>
<option value="name">Navn</option>
<option value="sku">Nummer</option>
<option value="shortName">Forkort.</option>
</select>

View file

@ -24,7 +24,7 @@ public partial class CatalogSearchComponent : IDisposable
/// <summary>
/// User preference service
/// </summary>
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] private UserProfileService ProfileService { get; set; }
/// <summary>
/// OnChanged event callback

View file

@ -17,6 +17,6 @@
<select class="form-select bg-success text-bg-success" @bind-value="@SortCol" @bind-value:event="oninput" @onchange="OnSelectChanged">
<option value="-1" selected disabled>SORTERING</option>
<option value="name">Navn sort</option>
<option value="sku">Varenr sort</option>
<option value="name">Navn</option>
<option value="sku">Varenr</option>
</select>

View file

@ -24,7 +24,7 @@ public partial class CatalogSortComponent : IDisposable
/// <summary>
/// User preference service
/// </summary>
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] private UserProfileService ProfileService { get; set; }
/// <summary>
/// OnChanged callback function

View file

@ -27,7 +27,7 @@ namespace Wonky.Client.Components;
public partial class CustomerInventoryListComponent
{
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] private ILocalStorageService Storage { get; set; }
// Parameters
[Parameter] public List<ProductInventoryView> Inventory { get; set; } = new();
[Parameter] public string CompanyId { get; set; } = "";

View file

@ -37,7 +37,7 @@
{
<div class="row">
<div class="col-md-1"></div>
<div class="col-md-1"><i class="bi-pencil"></i></div>
<div class="col-md-1"><i class="bi-card-text"></i></div>
<div class="col-md-10 fw-bold">@invoice.OrderNote</div>
</div>
}

View file

@ -28,7 +28,7 @@ public partial class CustomerProductCheckListComponent
{
[Parameter] public List<ProductInventoryView> ProductList { get; set; } = new();
[Parameter] public string CompanyId { get; set; } = "";
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] private ILocalStorageService Storage { get; set; }
// private variables
private bool Descending { get; set; }

View file

@ -17,9 +17,9 @@
<select class="form-select bg-warning text-bg-warning" @bind-value="@SearchCol" @bind-value:event="oninput" @onchange="OnSelectionChanged">
<option value="-1" disabled>SØGNING</option>
<option value="name">Søg Navn</option>
<option value="city">Søg By</option>
<option value="zip">Søg Post</option>
<option value="account">Søg Konto</option>
<option value="phone">Søg Tlf.</option>
<option value="name">Navn</option>
<option value="city">Bynavn</option>
<option value="zip">Postnr</option>
<option value="account">Konto</option>
<option value="phone">Telefon</option>
</select>

View file

@ -22,10 +22,10 @@ using Wonky.Client.Services;
namespace Wonky.Client.Components;
public partial class CompanySearchColumnComponent : IDisposable
public partial class CustomerSearchColumnComponent : IDisposable
{
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] private ILocalStorageService Storage { get; set; }
[Inject] private UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
private Dictionary<string, string> Items { get; set; } = new();
private UserPref Prefs { get; set; } = new();

View file

@ -20,12 +20,12 @@ using Timer = System.Timers.Timer;
namespace Wonky.Client.Components
{
public partial class CompanySearchPhraseComponent
public partial class CustomerSearchPhraseComponent
{
private Timer InputTimer { get; set; } = new();
private string SearchTerm { get; set; } = "";
private UserPref Prefs { get; set; } = new ();
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] private UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
protected override async Task OnInitializedAsync()

View file

@ -17,6 +17,6 @@
<select class="form-select bg-success text-bg-success" @bind-value="@SortCol" @bind-value:event="oninput" @onchange="OnSelectionChanged">
<option value="-1" selected disabled>SORTERING</option>
<option value="name">Navne sort.</option>
<option value="city">By sort.</option>
<option value="name">Firma</option>
<option value="city">Bynavn</option>
</select>

View file

@ -21,10 +21,10 @@ using Wonky.Client.Services;
namespace Wonky.Client.Components
{
public partial class CompanySortComponent : IDisposable
public partial class CustomerSortComponent : IDisposable
{
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] private ILocalStorageService Storage { get; set; }
[Inject] private UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
private Dictionary<string, string> Items { get; set; } = new();
private UserPref Prefs = new();

View file

@ -0,0 +1,75 @@
@*
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
*@
<div class="list-group">
<div class="list-group-item">
<div class="row">
<div class="col">
<h4>Dato</h4>
</div>
<div class="col">
<h4>Demo</h4>
</div>
<div class="col">
<h4>Salg</h4>
</div>
<div class="col">
<h4>Ordre Note</h4>
</div>
<div class="col">
<h4>Ordre Note</h4>
</div>
</div>
</div>
@if (Activities.Any())
{
@foreach (var activity in Activities)
{
<div class="list-group-item list-group-item-action" style="cursor: pointer" @onclick="() => ShowVisitOverlay(activity.ActivityId)">
<div class="row">
<div class="col">
@activity.OrderDate
</div>
<div class="col">
@activity.Demo
</div>
<div class="col">
@activity.Sales
</div>
<div class="col">
@activity.OfficeNote
</div>
<div class="col">
@activity.CrmNote
</div>
</div>
</div>
}
}
else
{
<div class="list-group-item">
<div class="row">
<div class="col">
Ingen data
</div>
</div>
</div>
}
</div>
<CustomerActivityViewModalOverlay ReportItem="Activity" @ref="ActivityViewOverlay"/>

View file

@ -0,0 +1,41 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using System.Globalization;
using System.Runtime.InteropServices;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components;
using Wonky.Client.HttpInterfaces;
using Wonky.Client.Models;
using Wonky.Client.Shared;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
namespace Wonky.Client.Components;
public partial class CustomerVisitListComponent
{
[Parameter] public List<ReportItemView> Activities { get; set; } = new();
private CustomerActivityViewModalOverlay ActivityViewOverlay { get; set; } = new();
private ReportItemView Activity { get; set; } = new();
private void ShowVisitOverlay(string activityId)
{
Activity = Activities.First(x => x.ActivityId == activityId);
ActivityViewOverlay.Show();
}
}

View file

@ -0,0 +1,47 @@
<h3>Sælger</h3>
<table class="table">
<thead>
<tr>
<th>Symbol</th>
<th>Betydning</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<i class="bi-calendar" style="font-size:1.3rem"></i>
</td>
<td>ToDo liste</td>
</tr>
<tr>
<td>
<i class="bi-file-spreadsheet" style="font-size:1.3rem"></i>
</td>
<td>Pris katalog</td>
</tr>
<tr>
<td>
<i class="bi-building" style="font-size:1.3rem"></i>
</td>
<td>Firmaer/Kunder</td>
</tr>
<tr>
<td>
<i class="bi-calculator" style="font-size:1.3rem"></i>
</td>
<td>Aftaler/Tilbud</td>
</tr>
<tr>
<td>
<i class="bi-activity" style="font-size:1.3rem"></i>
</td>
<td>Aktivitet</td>
</tr>
<tr>
<td>
<i class="bi-file-earmark-spreadsheet" style="font-size:1.3rem"></i>
</td>
<td>Dagsrapporter</td>
</tr>
</tbody>
</table>

View file

@ -1,4 +1,6 @@
.pictogram {
max-width: 30px;
}
.color-code {
max-width: 40px;
max-width: 30px;
}

View file

@ -1,4 +1,6 @@
.pictogram {
max-width: 30px;
}
.color-code {
max-width: 40px;
max-width: 30px;
}

View file

@ -0,0 +1,41 @@
<h3>Fælles</h3>
<table class="table">
<thead>
<tr>
<th>Symbol</th>
<th>Betydning</th>
</tr>
<tr>
<td><i class="bi-cloud-arrow-up" style="font-size:1.3rem"></i></td>
<td>Gem data</td>
</tr>
<tr>
<td><i class="bi-search" style="font-size:1.3rem"></i></td>
<td>Søg</td>
</tr>
<tr>
<td><i class="bi-plus" style="font-size:1.3rem"></i></td>
<td>Opret</td>
</tr>
<tr>
<td><i class="bi-card-text" style="font-size:1.3rem"></i></td>
<td>Notat</td>
</tr>
<tr>
<td><i class="bi-printer" style="font-size:1.3rem"></i></td>
<td>Udskrivning</td>
</tr>
<tr>
<td><i class="bi-sliders" style="font-size:1.3rem"></i></td>
<td>Indstillinger</td>
</tr>
<tr>
<td><i class="bi-lock" style="font-size:1.3rem"></i></td>
<td>Log af</td>
</tr>
<tr>
<td><i class="bi-question" style="font-size:1.3rem"></i></td>
<td>Hjælp/Info</td>
</tr>
</thead>
</table>

View file

@ -0,0 +1,17 @@
<h3>Kontor</h3>
<table class="table">
<thead>
<tr>
<th>Symbol</th>
<th>Betydning</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<i class="bi-people" style="font-size:1.3rem"></i>
</td>
<td>Brugere</td>
</tr>
</tbody>
</table>

View file

@ -1,5 +1,5 @@
<h3>Aktivitet Oversigt</h3>
<h3>Bestilling Status</h3>
<table class="table">
<thead>
<tr>
@ -18,6 +18,10 @@
<td><i class="bi-file-earmark" style="font-size:1.3rem"></i></td>
<td>Ubehandlet</td>
</tr>
<tr>
<td><i class="bi-hand-thumbs-up" style="font-size:1.3rem"></i></td>
<td>Accepteret</td>
</tr>
<tr>
<td><i class="bi-file-earmark-check" style="font-size:1.3rem"></i></td>
<td>Plukket</td>
@ -28,7 +32,7 @@
</tr>
<tr>
<td><i class="bi-truck" style="font-size:1.3rem"></i></td>
<td>Afhentet</td>
<td>Leveret</td>
</tr>
</thead>
</table>

View file

@ -0,0 +1,17 @@
<h3>Lager</h3>
<table class="table">
<thead>
<tr>
<th>Symbol</th>
<th>Betydning</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<i class="bi-box" style="font-size:1.3rem"></i>
</td>
<td>Pakning / Forsendelse</td>
</tr>
</tbody>
</table>

View file

@ -18,8 +18,6 @@
@using Microsoft.AspNetCore.Authorization
@using Wonky.Client.Components
<PageTitle>Innotec Rådgiver</PageTitle>
<AuthorizeView Roles="Advisor">
<div class="bg-dark text-white rounded-2 mb-2 py-2">
<WorkDateComponent />

View file

@ -32,7 +32,7 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Components;
public partial class LandingComponentAdvisor
{
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] private UserProfileService ProfileService { get; set; }
private readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
{

View file

@ -24,15 +24,17 @@
<div class="card-header">Danmark</div>
<div class="card-body">
<div class="list-group list-group-flush">
<AuthorizeView Roles="Admin">
<AuthorizeView Roles="Admin,Office">
<a class="list-group-item list-group-item-action list-group-item-warning" href="/office/users/advisors/dk">
<i class="bi-activity"></i> Sælgere
</a>
</AuthorizeView>
@*
<a class="list-group-item list-group-item-action list-group-item-success" href="/office/customers/dk">
<i class="bi-phone"></i> Tlf.Ordre
<i class="bi-building"></i> Tlf.Ordre
</a>
<a class="list-group-item list-group-item-action list-group-item-info" href="/office/catalog/dk">
*@
<a class="list-group-item list-group-item-action list-group-item-info" href="/catalog/dk">
<i class="bi-file-spreadsheet"></i> Priser
</a>
</div>
@ -45,15 +47,17 @@
<div class="card-header">Norge</div>
<div class="card-body">
<div class="list-group">
<AuthorizeView Roles="Admin">
<AuthorizeView Roles="Admin,Office">
<a class="list-group-item list-group-item-action list-group-item-warning" href="/office/users/advisors/no">
<i class="bi-activity"></i> Sælgere
</a>
</AuthorizeView>
@*
<a class="list-group-item list-group-item-action list-group-item-success" href="/office/customers/no">
<i class="bi-phone"></i> Tlf.Ordre
</a>
<a class="list-group-item list-group-item-action list-group-item-info" href="/office/catalog/no">
*@
<a class="list-group-item list-group-item-action list-group-item-info" href="/catalog/no">
<i class="bi-file-spreadsheet"></i> Priser
</a>
</div>
@ -66,15 +70,17 @@
<div class="card-header">Sverige</div>
<div class="card-body">
<div class="list-group">
<AuthorizeView Roles="Admin">
<AuthorizeView Roles="Admin,Office">
<a class="list-group-item list-group-item-action list-group-item-warning" href="/office/users/advisors/se">
<i class="bi-activity"></i> Sælgere
</a>
</AuthorizeView>
@*
<a class="list-group-item list-group-item-action list-group-item-success" href="/office/customers/se">
<i class="bi-phone"></i> Tlf.Ordre
</a>
<a class="list-group-item list-group-item-action list-group-item-info" href="/office/catalog/se">
*@
<a class="list-group-item list-group-item-action list-group-item-info" href="/catalog/se">
<i class="bi-file-spreadsheet"></i> Priser
</a>
</div>

View file

@ -30,9 +30,9 @@ using Wonky.Entity.Models;
using Wonky.Entity.Views;
namespace Wonky.Client.Components;
public partial class LandingComponentAdmin
public partial class LandingComponentOffice
{
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] private UserProfileService ProfileService { get; set; }
private readonly JsonSerializerOptions _options = new JsonSerializerOptions
{

View file

@ -0,0 +1,92 @@
@*
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
*@
@using Wonky.Entity.Views
@* Report activities *@
<table class="table table-striped">
<thead>
<tr class="bg-black opacity-75 text-white">
<th scope="col">Kunde</th>
<th scope="col">Bynavn</th>
<th scope="col">Demo</th>
<th scope="col">Salg</th>
<th scope="col">Note</th>
<th scope="col" class="text-end">sas</th>
<th scope="col" class="text-end">Beløb</th>
<th scope="col" class="text-center">
<i style="font-size:1.3em;" class="bi-phone"></i>
</th>
<th scope="col" class="text-center">
<i class="bi-lightning"></i>
</th>
<th scope="col" class="text-center">
<i class="bi-calculator"></i>
</th>
<th scope="col" class="text-center">
<i class="bi-truck"></i>
</th>
</tr>
</thead>
<tbody>
@foreach (var activity in Activities)
{
<tr>
<td class="text-sm-start">@activity.Company.Name</td>
<td class="text-sm-start">@activity.Company.City</td>
<td class="text-sm-start">@activity.Demo</td>
<td class="text-sm-start">@activity.Sales</td>
<td class="text-sm-start">@activity.OfficeNote</td>
<td class="text-end">@($"{activity.SasAmount:N2}")</td>
<td class="text-center">@(activity.StatusTypeEnum.Contains("Quote") ? $"{0:N2}" : $"{activity.OrderAmount:N2}")</td>
<td class="text-center">
@if (activity.OurRef.Contains("T:"))
{
<i style="font-size:1.5em;" class="bi-phone"></i>
}
</td>
<td class="text-center">
@if (activity.Express)
{
<i style="font-size:1.5em;" class="bi-lightning"></i>
}
</td>
<td class="text-center">
@if (activity.StatusTypeEnum == "Quote")
{
<i style="font-size:1.5em;" class="bi-calculator"></i>
}
</td>
<td class="text-center">
<ProcessStateComponent StateClass="@activity.ProcessStatusEnum"/>
</td>
</tr>
}
<tr>
<td class="bg-black opacity-75" colspan="5"></td>
<td class="text-end">Total</td>
<td class="text-end">@Activities.Where(x => x.StatusTypeEnum != "Quote").Sum(x => x.OrderAmount)</td>
<td class="bg-black opacity-75" colspan="4"></td>
</tr>
</tbody>
</table>
@code {
[Parameter]
public List<ReportItemView> Activities { get; set; } = new();
}

View file

@ -32,7 +32,11 @@
<a class="btn btn-success d-block" href="/office/users/advisors/@user.CountryCode.ToLower()/@user.UserId/customers">Kunder</a>
</div>
<div class="col-sm-2">
<a class="btn btn-info d-block" href="/office/users/advisors/@user.CountryCode.ToLower()/@user.UserId/view">Rediger</a>
<AuthorizeView Roles="Admin">
<Authorized>
<a class="btn btn-info d-block" href="/office/users/advisors/@user.CountryCode.ToLower()/@user.UserId/view">Rediger</a>
</Authorized>
</AuthorizeView>
</div>
</div>
</div>

View file

@ -19,7 +19,7 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Components;
public partial class CountrySalesRepListComponent
public partial class OfficeCountryAdvisorListComponent
{
[Parameter] public List<UserListAdminView> UserList { get; set; } = new();
}

View file

@ -20,6 +20,7 @@
@if (CompanyList.Any())
{
@*
<div class="list-group list-group-flush">
<div class="list-group-item px-3 bg-black text-white opacity-75">
<div class="row">
@ -40,9 +41,40 @@
</div>
</div>
</div>
*@
<div class="row d-flex g-3">
@foreach (var company in CompanyList)
{
<a class=" list-group-item list-group-item-action" href="/office/customers/@CountryCode/@company.CompanyId/view">
<div class="col-sm-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">
@company.Name
</h5>
<div class="row">
<div class="col-sm-3 col-md-3 fw-bold">Konto</div>
<div class="col-sm-3 col-dm-3">@company.Account</div>
<div class="col-sm-3 col-md-3 fw-bold">CVR / ORG</div>
<div class="col-sm-3 col-md-3">@company.VatNumber</div>
</div>
<div class="row">
<div class="col-sm-3 col-md-3 fw-bold">Telefon</div>
<div class="col-sm-3 col-md-3">@company.Phone</div>
</div>
<div class="row">
<div class="col-sm-3 col-md-3 fw-bold">Adresse</div>
<div class="col-sm-9 col-md-9">@company.Address1 @(string.IsNullOrWhiteSpace(company.Address2) ? "" : ",") @company.Address2</div>
</div>
<div class="row">
<div class="col-sm-3 col-md-3 fw-bold">Post By</div>
<div class="col-sm-9 col-md-9">@company.CountryCode.ToUpper()-@company.ZipCode @company.City</div>
</div>
</div>
</div>
</div>
@*
<a class=" list-group-item list-group-item-action" href="/office/customers/@CountryCode/@company.CompanyId/order">
<div class="row align-items-center">
<div class="col-sm-1">
@company.SalesRep
@ -61,6 +93,7 @@
</div>
</div>
</a>
*@
}
</div>
}

View file

@ -24,7 +24,7 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Components
{
public partial class CountryCustomerListComponent
public partial class OfficeCountryCustomerListComponent
{
[Parameter] public List<CompanyDto> CompanyList { get; set; } = new();
[Parameter] public string CountryCode { get; set; } = "";

View file

@ -18,7 +18,7 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Components;
public partial class CountryUserListComponent
public partial class OfficeCountryUserListComponent
{
[Parameter] public List<UserListAdminView> UserList { get; set; } = new();
}

View file

@ -1,3 +1,4 @@
@using Wonky.Client.Helpers
@*
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
@ -15,9 +16,10 @@
//
*@
@using Wonky.Client.Helpers
@if (ActivityList.Any())
{
<table class="table table-bordered d-print-table table-striped">
<table class="table table-sm table-bordered d-print-table table-striped">
<thead>
<tr class="bg-dark text-white opacity-75 border-bottom">
<th scope="col">Kunde</th>
@ -27,10 +29,10 @@
<th scope="col">Note</th>
<th class="text-end" scope="col">sas</th>
<th class="text-end" scope="col">Beløb</th>
<th class="text-center" scope="col"><i class="oi oi-phone"></i></th>
<th class="text-center" scope="col"><i class="oi oi-flash"></i></th>
<th class="text-center" scope="col"><i class="oi oi-calculator"></i></th>
<th class="text-center" scope="col"><i class="bi bi-truck"></i></th>
<th class="text-center" scope="col"><i class="bi-phone"></i></th>
<th class="text-center" scope="col"><i class="bi-lightning"></i></th>
<th class="text-center" scope="col"><i class="bi-calculator"></i></th>
<th class="text-center" scope="col"><i class="bi-truck"></i></th>
</tr>
</thead>
<tbody>
@ -59,7 +61,7 @@
<td class="align-middle state">
@if (activity.Lines.Any() && activity.StatusTypeEnum == "Order")
{
<ProcessStateComponent StateClass="@GetProcessStatus(activity.ProcessStatusEnum)"/>
<ProcessStateComponent StateClass="@Utils.GetProcessStatus(activity.ProcessStatusEnum)"/>
}
</td>
</tr>

View file

@ -13,12 +13,18 @@
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using Wonky.Entity.Views;
using Microsoft.AspNetCore.Components;
using Wonky.Entity.Views;
namespace Wonky.Client.Components;
public partial class ReportActivityLedgerComponent
public partial class OfficeReportActivityListComponent
{
[Parameter] public ReportData ReportData { get; set; } = new();
[Parameter] public List<ReportItemView> ActivityList { get; set; } = new();
[Inject] private NavigationManager Navigator { get; set; }
private void ShowOrder(string companyId, string orderId)
{
Navigator.NavigateTo($"/office/customers/{companyId}/orders/{orderId}");
}
}

View file

@ -18,7 +18,7 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Components;
public partial class ReportListOfficeComponent
public partial class OfficeReportListComponent
{
[Parameter] public List<SalesReportListView> ReportList { get; set; } = new();
[Parameter] public string UserId { get; set; } = "";

View file

@ -23,18 +23,20 @@ namespace Wonky.Client.Components
{
public partial class PageSizeComponent : IDisposable
{
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] private ILocalStorageService Storage { get; set; }
[Inject] private UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
private Dictionary<string, string> Items { get; set; } = new();
private UserPref Prefs = new();
private string PageSize { get; set; } = "";
protected override async Task OnInitializedAsync()
{
ProfileService.OnChange += ProfileServiceOnOnChange;
Prefs = await ProfileService.GetPreferences();
PageSize = Prefs.PageSize;
}
private async Task OnSelectChanged(ChangeEventArgs e)
{
var val = e.Value.ToString();
@ -42,11 +44,13 @@ namespace Wonky.Client.Components
await OnChanged.InvokeAsync(val);
await ProfileService.SetPageSize(val);
}
private void ProfileServiceOnOnChange(UserPref newUserPref)
{
Prefs = newUserPref;
StateHasChanged();
}
public void Dispose()
{
ProfileService.OnChange -= ProfileServiceOnOnChange;

View file

@ -30,6 +30,7 @@
"the-bad" => "file-earmark-check",
"the-ugly" => "box2-fill",
"the-dead" => "truck",
"accepted" => "hand-thumbs-up",
_ => "question-square"
};
}

View file

@ -64,20 +64,20 @@
</div>
<div class="col-sm-2 text-center">
<a class="btn btn-outline-dark d-block" style="font-family:monospace;font-size: 14px;"
href="/customers/@quote.Company.CompanyId/quotes/@quote.ActivityId">@quote.ESalesNumber</a>
href="/advisor/customers/@quote.Company.CompanyId/quotes/@quote.ActivityId">@quote.ESalesNumber</a>
</div>
@if (!string.IsNullOrWhiteSpace(quote.OfficeNote))
{
<div class="col-sm-2 text-end">Note</div>
<div class="col-sm-2 text-end">Kontor <i class="bi-card-text"></i></div>
<div class="col-sm-10">
<i class="bi-pencil"></i> @quote.OfficeNote
@quote.OfficeNote
</div>
}
@if (!string.IsNullOrWhiteSpace(quote.CrmNote))
{
<div class="col-sm-2 text-end">CRM note</div>
<div class="col-sm-2 text-end">CRM <i class="bi-card-text"></i></div>
<div class="col-sm-10">
<i class="bi-pencil"></i> @quote.CrmNote
@quote.CrmNote
</div>
}
</div>

View file

@ -8,11 +8,11 @@ public partial class QuoteListComponent
{
[Parameter]
public List<ReportItemView> Quotes { get; set; } = new();
[Parameter] public EventCallback<QuoteCallbackArgs> OnChangedCallback { get; set; }
[Parameter] public EventCallback<QCallbackArgs> OnChangedCallback { get; set; }
private async Task SetQuote(string eSalesNumber, QStatus status)
{
var args = new QuoteCallbackArgs()
var args = new QCallbackArgs()
{
ESalesNumber = eSalesNumber,
Status = status

View file

@ -1,3 +1,4 @@
@using Wonky.Entity.Views
@*
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
@ -84,4 +85,8 @@
</tbody>
</table>
</div>
</div>
@code{
[Parameter] public ReportData ReportData { get; set; } = new();
}

View file

@ -1,43 +0,0 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using Microsoft.AspNetCore.Components;
using Wonky.Entity.Views;
namespace Wonky.Client.Components;
public partial class ReportActivityTableOfficeComponent
{
[Parameter] public List<ReportItemView> ActivityList { get; set; } = new();
[Inject] public NavigationManager Navigator { get; set; }
private static string GetProcessStatus(string processStatus)
{
return processStatus.ToLower() switch
{
"express" => "the-fast",
"none" => "the-good",
"picked" => "the-bad",
"packed" => "the-ugly",
"shipped" => "the-dead",
_ => "the-draw"
};
}
private void ShowOrder(string companyId, string orderId)
{
Navigator.NavigateTo($"office/customers/{companyId}/orders/{orderId}");
}
}

View file

@ -22,7 +22,7 @@
<thead>
<tr>
<th class="p-0" colspan="4">
<div class="bg-light text-dark border border-1 rounded-3 pt-3 mb-2">
<div class="alert d-print-block border border-1 border-dark pt-3 mb-2">
<h2 class="fw-bold text-center">@ReportItem.Company.Name</h2>
@if (ReportItem.Express)
{
@ -134,10 +134,10 @@
</table>
@if (!string.IsNullOrWhiteSpace(@ReportItem.OfficeNote))
{
<div class="alert alert-dark d-print-block">
<h4 class="text-center">
<div class="alert d-print-block border border-1 border-dark">
<p class="text-center h4">
@ReportItem.OfficeNote
</h4>
</p>
</div>
}
</div>

View file

@ -47,7 +47,7 @@
</label>
</td>
<td class="align-middle">
<a class="btn btn-light border-dark" href="/tasks/@task.TaskItemId">
<a class="btn btn-light border-dark" href="/advisor/tasks/@task.TaskItemId">
<i class="oi oi-calendar"></i>
</a>
</td>
@ -64,7 +64,7 @@
<td class="align-middle">
@if (task.TaskTypeEnum is "Recall")
{
<a class="btn btn-light border-dark pe-3 me-2" href="/customers/@task.ReferenceId">
<a class="btn btn-light border-dark pe-3 me-2" href="/advisor/customers/@task.ReferenceId">
<i class="oi oi-pencil"></i>
</a>
}
@ -81,7 +81,7 @@
}
</tbody>
</table>
<ConfirmationModal BodyMessage="Handlingen kan ikke gøres om. Vil du slette opgaven?" OnOkClicked="DeleteTask" @ref="_confirmationModal"/>
<ConfirmationModal BodyMessage="Handlingen kan ikke gøres om. Vil du slette opgaven?" OnOkClicked="DeleteTask" OnCancelClicked="OnCancelCallback" @ref="_confirmationModal"/>
}
else
{

View file

@ -61,6 +61,10 @@ namespace Wonky.Client.Components
_confirmationModal.Show();
}
private void OnCancelCallback()
{
_confirmationModal.Hide();
}
/// <summary>
/// Delete task call back
/// </summary>

View file

@ -19,9 +19,9 @@
<AuthorizeView>
<Authorized>
<div class="d-print-none">
<a class="btn btn-outline-light" href="logout" title="Log af" ><i class="bi-lock"></i></a>
<a class="btn btn-outline-light" href="info" title="Information"><i class="bi-question"></i></a>
<a class="btn btn-outline-light" href="preferences" title="Indstillinger"><i class="bi-sliders"></i></a>
<a class="btn btn-outline-light" href="/logout" title="Log af" ><i class="bi-lock"></i></a>
<a class="btn btn-outline-light" href="/info" title="Information"><i class="bi-question"></i></a>
<a class="btn btn-outline-light" href="/preferences" title="Indstillinger"><i class="bi-sliders"></i></a>
</div>
</Authorized>
</AuthorizeView>

View file

@ -21,8 +21,8 @@
<EditForm EditContext="WorkDateContext">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-sm-7 work-date">
@SelectedDate.ToLongDateString()
<div class="col-sm-7 fw-bold">
d. @(SelectedDate.Day)/@(SelectedDate.Month)
</div>
<div class="col-sm-5">
<InputDate class="form-control calendar" @bind-Value="SelectedDate" @oninput="OnDateChanged"/>

View file

@ -14,7 +14,7 @@
</div>
@foreach (var workplace in Workplaces)
{
<a class="list-group-item list-group-item-action" href="/customers/@CompanyId/workplaces/@workplace.WorkplaceId">
<a class="list-group-item list-group-item-action" href="/advisor/customers/@CompanyId/workplaces/@workplace.WorkplaceId">
<div class="row">
<div class="col">
@workplace.Name

View file

@ -18,9 +18,27 @@ using System.Net.Mail;
using Wonky.Entity.DTO;
namespace Wonky.Client.Helpers;
/// <summary>
/// Utilities
/// </summary>
public static class Utils
{
/// <summary>
/// return Country Name from countryCode
/// </summary>
/// <param name="countryCode"></param>
/// <returns></returns>
public static string CountryName(string countryCode)
{
return countryCode.ToLower() switch
{
"dk" => "Danmark",
"no" => "Norge",
"se" => "Sverige",
_ => ""
};
}
/// <summary>
/// Helper to parse querystring
/// </summary>
@ -128,7 +146,8 @@ public static class Utils
"picked" => "the-bad",
"packed" => "the-ugly",
"shipped" => "the-dead",
_ => "question-square"
"accepted" => "accepted",
_ => "question"
};
}
}

View file

@ -14,6 +14,9 @@
//
using System.Globalization;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
namespace Wonky.Client.Helpers;
@ -29,11 +32,11 @@ public class VatUtils
/// <returns></returns>
public static bool ValidateFormat(string countryCode, string vatNumber)
{
if (string.IsNullOrWhiteSpace(vatNumber) || string.IsNullOrWhiteSpace(countryCode) || !IsDigitsOnly(vatNumber))
if (string.IsNullOrWhiteSpace(vatNumber) || string.IsNullOrWhiteSpace(countryCode))
return false;
var sanitisedVat = SanitizeVatNumber(vatNumber);
return countryCode.ToUpperInvariant() switch
{
"DK" => ValidateFormatDk(sanitisedVat),
@ -43,6 +46,19 @@ public class VatUtils
};
}
/// <summary>
/// Sanitize Vat remove everything but digits
/// </summary>
/// <param name="vatNumber"></param>
/// <returns></returns>
public static string SanitizeVatNumber(string vatNumber)
{
if (string.IsNullOrWhiteSpace(vatNumber))
return "";
var regexObj = new Regex(@"[^\d]");
return regexObj.Replace(vatNumber, "");
}
/// <summary>
/// Validate string is only numbers
/// </summary>
@ -96,6 +112,34 @@ public class VatUtils
/// <param name="vatNumber"></param>
/// <returns></returns>
private static bool ValidateFormatSe(string vatNumber)
{
var vatToCheck = vatNumber;
if (long.Parse(vatToCheck) == 0)
return false;
switch (vatToCheck.Length)
{
// if less than 10 chars validate as SSI
case 6:
return ValidateFormatSeExt(vatToCheck);
case < 10:
return false;
case > 10:
vatNumber = vatNumber[..10];
break;
}
// calculate check digit
var c10 = C10(vatToCheck);
// return comparison
return $"{vatToCheck[..9]}{c10}" == vatNumber;
}
/// <summary>
/// Calculate check digit for swedish org number
/// </summary>
/// <param name="orgNumber"></param>
/// <returns></returns>
private static int C10(string orgNumber)
{
// https://wiki.scn.sap.com/wiki/display/CRM/Sweden
// 12 digits 0 to 9
@ -104,23 +148,57 @@ public class VatUtils
// Si = int(Ci/5) + (Ci*2)MOD10)
// https://www.skatteverket.se/skatter/mervardesskattmoms/momsregistreringsnummer.4.18e1b10334ebe8bc80002649.html
// C11 C12 == 01 (De två sista siffrorna är alltid 01)
var vatToCheck = vatNumber;
if (vatToCheck.Length < 10 || long.Parse(vatToCheck) == 0)
return false;
var r = new[] { 0, 2, 4, 6, 8 }
.Sum(m => (int)char.GetNumericValue(vatToCheck[m]) / 5 +
(int)char.GetNumericValue(vatToCheck[m]) * 2 % 10);
var c1 = new[] { 1, 3, 5, 7 }.Sum(m => (int)char.GetNumericValue(vatToCheck[m]));
var c10 = (10 - (r + c1) % 10) % 10;
if (vatToCheck.Length == 10)
{
return $"{vatToCheck[..9]}{c10}" == vatNumber;
}
.Sum(m => (int)char.GetNumericValue(orgNumber[m]) / 5 +
(int)char.GetNumericValue(orgNumber[m]) * 2 % 10);
var c1 = new[] { 1, 3, 5, 7 }.Sum(m => (int)char.GetNumericValue(orgNumber[m]));
var c10 = (10 - (r + c1) % 10) % 10;
return c10;
// end check digit calculation
return $"{vatToCheck[..9]}{c10}01" == vatNumber;
}
/// <summary>
///
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
private static bool ValidateFormatSeExt(string data)
{
// Swedish personally held companies uses SSN number
// a relaxed validation is required as only first 6 digits is supplied
// birthday format e.g. 991231 yyMMdd
var y = int.Parse(data[..2]);
var m = int.Parse(data[2..4]);
var d = int.Parse(data[4..6]);
// this calculation is only valid within 21st century
var leap = y % 4 == 0; // 2000 was a leap year;
// day
if(d is < 1 or > 31)
return false;
// month
switch (m)
{
// feb
case 2:
{
if (leap)
return d <= 29;
return d <= 28;
}
// apr, jun, sep, nov
case 4 or 6 or 9 or 11:
return d <= 30;
// jan, mar, may, july, aug, oct, dec
case 1 or 3 or 5 or 7 or 8 or 10 or 12:
return true;
// does not exist
default:
return false;
}
}
/// <summary>
/// Modulus11 validator
/// </summary>
@ -139,19 +217,4 @@ public class VatUtils
return sum % 11 == 0;
}
/// <summary>
/// Sanitize Vat number to it's raw numbers
/// </summary>
/// <param name="vatNumber"></param>
/// <returns></returns>
private static string SanitizeVatNumber(string vatNumber)
{
return vatNumber.Replace(" ", "")
.Replace("-", "")
.Replace("DK", "")
.Replace("NO", "")
.Replace("SE", "")
.Replace("MVA", "");
}
}

View file

@ -15,6 +15,7 @@
using System.Net;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
using Blazored.LocalStorage;
using Blazored.Toast.Services;
@ -31,7 +32,7 @@ namespace Wonky.Client.HttpInterceptors
private readonly NavigationManager _navigation;
private readonly IToastService _toast;
private readonly RefreshTokenService _refreshTokenService;
private ILogger<HttpInterceptorService> _logger;
private readonly ILogger<HttpInterceptorService> _logger;
private readonly ILocalStorageService _storage;
private readonly IAuthenticationService _authenticationService;
@ -86,35 +87,37 @@ namespace Wonky.Client.HttpInterceptors
if (e.Response == null || e.Response.IsSuccessStatusCode)
return;
var message = "En fejl er opstået";
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)
{
case HttpStatusCode.NotFound:
case HttpStatusCode.NotFound:
_logger.LogDebug("NotFound <= {}", currDoc);
break;
case HttpStatusCode.BadRequest:
_logger.LogDebug("BadRequest <= {}", currDoc);
_logger.LogDebug("{}", message);
break;
case HttpStatusCode.Unauthorized:
_logger.LogDebug("Unauthorized <= {}", currDoc);
_logger.LogDebug("{}", message);
_authenticationService.Logout();
_navigation.NavigateTo($"/login/{currDoc}");
message = "Venligst login ...";
_toast.ShowInfo(message);
_toast.ShowInfo("Venligst Login. Tak.");
break;
case HttpStatusCode.Conflict:
_logger.LogDebug("Conflict <= {}", currDoc);
_logger.LogDebug("{}", message);
break;
case HttpStatusCode.InternalServerError:
// message = "Der er interne problemer på serveren ...";
// _toast.ShowError(message);
_logger.LogDebug("InternalServerError <= {}", currDoc);
_logger.LogDebug("{}", message);
break;
default:
_toast.ShowError(message);
_logger.LogDebug("{}", message);
break;
}
// throw new HttpResponseException(message);

View file

@ -1,68 +0,0 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using Wonky.Client.Features;
using Wonky.Entity.Requests;
using Wonky.Entity.Views;
namespace Wonky.Client.HttpInterfaces;
/// <summary>
/// Interface Catalog Http repository
/// </summary>
public interface IAdvisorCatalogRepository
{
/// <summary>
/// Get a paged sales item list
/// </summary>
/// <param name="pagingParameters"></param>
/// <returns></returns>
Task<PagingResponse<SalesItemView>> GetSalesItemsPaged(CatalogPaging pagingParameters);
/// <summary>
/// Get sales item by id
/// </summary>
/// <param name="salesItemId"></param>
/// <returns></returns>
Task<SalesItemView> GetSalesItemId(string salesItemId);
/// <summary>
/// Overload Get sales item by sku and country code
/// </summary>
/// <param name="sku"></param>
/// <param name="countryCode"></param>
/// <returns></returns>
Task<SalesItemView> GetSalesItemSku(string countryCode, string sku);
/// <summary>
/// Get sales item by variant id
/// </summary>
/// <param name="variantId"></param>
/// <returns></returns>
Task<SalesItemView> GetSalesVariantId(string variantId);
/// <summary>
/// Complete catalog for print
/// </summary>
/// <returns></returns>
Task<List<SalesItemView>> GetPriceList();
/// <summary>
/// Complete catalog for print country
/// </summary>
/// <param name="countryCode"></param>
/// <returns></returns>
Task<List<SalesItemView>> GetPriceList(string countryCode);
}

View file

@ -66,5 +66,5 @@ public interface IAdvisorCustomerHistoryRepository
/// <param name="companyId"></param>
/// <param name="syncDate"></param>
/// <returns></returns>
Task<string> ErpInvoiceToCrmRpc(string companyId, string syncDate);
Task<string> InvoiceErpToCrmRpc(string companyId, string syncDate);
}

View file

@ -21,7 +21,7 @@ namespace Wonky.Client.HttpInterfaces;
/// <summary>
/// Interface for handling Customer Workplaces (chemical document service)
/// </summary>
public interface IWorkplaceRepository
public interface IAdvisorWorkplaceRepository
{
/// <summary>
/// Get Workplaces for given customer id

View file

@ -28,9 +28,9 @@ public interface ICountryCatalogRepository
/// Get a paged sales item list
/// </summary>
/// <param name="countryCode"></param>
/// <param name="paging"></param>
/// <param name="pager"></param>
/// <returns></returns>
Task<PagingResponse<SalesItemView>> GetSalesItemsPaged(string countryCode, CatalogPaging paging);
Task<PagingResponse<SalesItemView>> GetSalesItemsPaged(string countryCode, CatalogPager pager);
/// <summary>
/// Get sales item by id

View file

@ -66,5 +66,5 @@ public interface ICountryCustomerHistoryRepository
/// <param name="companyId"></param>
/// <param name="syncDate"></param>
/// <returns></returns>
Task<string> ErpInvoiceToCrmRpc(string countryCode, string companyId, string syncDate);
Task<string> InvoiceErpToCrmRpc(string countryCode, string companyId, string syncDate);
}

View file

@ -13,6 +13,7 @@
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
namespace Wonky.Client.HttpInterfaces;
@ -20,7 +21,7 @@ namespace Wonky.Client.HttpInterfaces;
/// <summary>
/// Interface for processing orders in warehouse
/// </summary>
public interface IWarehouseRepository
public interface IOrderProcessRepository
{
/// <summary>
/// Get warehouse order list by date
@ -47,7 +48,7 @@ public interface IWarehouseRepository
/// <summary>
/// Update Order status setting new process status
/// </summary>
/// <param name="process"></param>
/// <param name="processState"></param>
/// <returns></returns>
Task UpdateWarehouseOrderStatus(WarehouseProcess process);
Task UpdateWarehouseOrderStatus(OrderProcessState processState);
}

View file

@ -36,7 +36,7 @@ public class AdvisorActivityRepository : IAdvisorActivityRepository
};
private readonly NavigationManager _navigation;
private ILogger<AdvisorActivityRepository> _logger;
private readonly ILogger<AdvisorActivityRepository> _logger;
private readonly HttpClient _client;
private readonly ApiConfig _api;
@ -166,7 +166,7 @@ public class AdvisorActivityRepository : IAdvisorActivityRepository
{
Code = 404,
IsSuccess = false,
Message = "Uventet svare fra server",
Message = "Uventet svar fra server",
Id = ""
};
}

View file

@ -1,187 +0,0 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Wonky.Client.Features;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Options;
using Wonky.Client.HttpInterfaces;
using Wonky.Entity.Configuration;
using Wonky.Entity.DTO;
using Wonky.Entity.Requests;
using Wonky.Entity.Views;
namespace Wonky.Client.HttpRepository;
public class AdvisorCatalogRepository : IAdvisorCatalogRepository
{
private readonly JsonSerializerOptions _options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
private readonly NavigationManager _navigation;
private ILogger<AdvisorCatalogRepository> _logger;
private readonly HttpClient _client;
private readonly ApiConfig _apiConfig;
public AdvisorCatalogRepository(HttpClient client,
ILogger<AdvisorCatalogRepository> logger,
NavigationManager navigation, IOptions<ApiConfig> configuration)
{
_client = client;
_logger = logger;
_navigation = navigation;
_apiConfig = configuration.Value;
}
/// <summary>
/// Get a paged sales item list
/// </summary>
/// <param name="pagingParameters"></param>
/// <returns></returns>
public async Task<PagingResponse<SalesItemView>> GetSalesItemsPaged(CatalogPaging pagingParameters)
{
var queryString = new Dictionary<string, string>
{
["pageNumber"] = pagingParameters.PageNumber.ToString(),
["pageSize"] = pagingParameters.PageSize.ToString(),
["orderBy"] = pagingParameters.OrderBy,
["searchColumn"] = pagingParameters.SearchColumn,
["searchTerm"] = pagingParameters.SearchTerm,
["selectGroup"] = pagingParameters.SelectGroup == "0" ? "" : pagingParameters.SelectGroup,
};
var response = await _client
.GetAsync(QueryHelpers.AddQueryString($"{_apiConfig.Catalog}/page", queryString));
if (!response.IsSuccessStatusCode)
{
return new PagingResponse<SalesItemView>
{
Items = new List<SalesItemView>(),
MetaData = new MetaData()
};
}
var content = await response.Content.ReadAsStringAsync();
var pagingResponse = new PagingResponse<SalesItemView>
{
Items = JsonSerializer.Deserialize<List<SalesItemView>>(content, _options),
MetaData = JsonSerializer.Deserialize<MetaData>(
response.Headers.GetValues("X-Pagination").First(), _options)
};
return pagingResponse;
}
/// <summary>
/// Get a paged sales item list for country
/// </summary>
/// <param name="pagingParameters"></param>
/// <param name="countryCode"></param>
/// <returns></returns>
public async Task<PagingResponse<SalesItemView>> GetSalesItemsPaged(CatalogPaging pagingParameters, string countryCode)
{
var queryString = new Dictionary<string, string>
{
["pageNumber"] = pagingParameters.PageNumber.ToString(),
["pageSize"] = pagingParameters.PageSize.ToString(),
["orderBy"] = pagingParameters.OrderBy,
["searchColumn"] = pagingParameters.SearchColumn,
["searchTerm"] = pagingParameters.SearchTerm,
["selectGroup"] = pagingParameters.SelectGroup == "0" ? "" : pagingParameters.SelectGroup,
};
var response = await _client
.GetAsync(QueryHelpers.AddQueryString($"{_apiConfig.Catalog}/page", queryString));
if (!response.IsSuccessStatusCode)
{
return new PagingResponse<SalesItemView>
{
Items = new List<SalesItemView>(),
MetaData = new MetaData()
};
}
var content = await response.Content.ReadAsStringAsync();
var pagingResponse = new PagingResponse<SalesItemView>
{
Items = JsonSerializer.Deserialize<List<SalesItemView>>(content, _options),
MetaData = JsonSerializer.Deserialize<MetaData>(
response.Headers.GetValues("X-Pagination").First(), _options)
};
return pagingResponse;
}
/// <summary>
/// Get sales item by id
/// </summary>
/// <param name="salesItemId"></param>
/// <returns></returns>
public async Task<SalesItemView> GetSalesItemId(string salesItemId)
{
var salesItem = await _client
.GetFromJsonAsync<SalesItemView>($"{_apiConfig.Catalog}/{salesItemId}");
return salesItem ?? new SalesItemView();
}
/// <summary>
/// Overload Get sales item by sku and country code
/// </summary>
/// <param name="sku"></param>
/// <param name="countryCode"></param>
/// <returns></returns>
public async Task<SalesItemView> GetSalesItemSku(string countryCode, string sku)
{
var salesItem = await _client.GetFromJsonAsync<SalesItemView>($"{_apiConfig.Catalog}/sku/{countryCode}/{sku}");
return salesItem ?? new SalesItemView();
}
/// <summary>
/// Get sales item by variant id
/// </summary>
/// <param name="variantId"></param>
/// <returns></returns>
public async Task<SalesItemView> GetSalesVariantId(string variantId)
{
var salesItem = await _client
.GetFromJsonAsync<SalesItemView>($"{_apiConfig.Catalog}/variant/{variantId}");
return salesItem ?? new SalesItemView();
}
/// <summary>
/// Complete catalog for print
/// </summary>
/// <returns></returns>
public async Task<List<SalesItemView>> GetPriceList()
{
return await _client.GetFromJsonAsync<List<SalesItemView>>($"{_apiConfig.Catalog}", _options);
}
/// <summary>
/// Complete catalog for print country
/// </summary>
/// <param name="countryCode"></param>
/// <returns></returns>
public async Task<List<SalesItemView>> GetPriceList(string countryCode)
{
return await _client.GetFromJsonAsync<List<SalesItemView>>($"{_apiConfig.Catalog}/{countryCode}", _options);
}
}

View file

@ -128,9 +128,9 @@ public class AdvisorCustomerHistoryRepository : IAdvisorCustomerHistoryRepositor
/// <param name="companyId"></param>
/// <param name="syncDate"></param>
/// <returns></returns>
public async Task<string> ErpInvoiceToCrmRpc(string companyId, string syncDate)
public async Task<string> InvoiceErpToCrmRpc(string companyId, string syncDate)
{
var x = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/{_api.CrmRpcSyncExt}/{syncDate}");
var x = await _client.GetAsync($"{_api.SyncRpc}/companies/{companyId}/{_api.SyncRpcInvoiceExt}/{syncDate}");
if (!x.IsSuccessStatusCode)
return string.Empty;
var content = await x.Content.ReadAsStringAsync();

View file

@ -189,6 +189,8 @@ public class AdvisorCustomerRepository : IAdvisorCustomerRepository
};
var response = await _client.PutAsJsonAsync($"{_conf.CrmCustomers}/{companyId}/vat", model, _options);
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<CompanyDto>(content);
return response.IsSuccessStatusCode
? JsonSerializer.Deserialize<CompanyDto>(content)
: new CompanyDto{Name = "ERROR", VatNumber = vatNumber, CrmNotes = $"FEJL: {content}"};
}
}

View file

@ -35,7 +35,7 @@ public class AdvisorReportRepository : IAdvisorReportRepository
private readonly NavigationManager _navigation;
private ILogger<AdvisorReportRepository> _logger;
private readonly HttpClient _client;
private readonly ApiConfig _apiConfig;
private readonly ApiConfig _api;
public AdvisorReportRepository(HttpClient client,
ILogger<AdvisorReportRepository> logger,
@ -44,7 +44,7 @@ public class AdvisorReportRepository : IAdvisorReportRepository
_client = client;
_logger = logger;
_navigation = navigation;
_apiConfig = configuration.Value;
_api = configuration.Value;
}
/// <summary>
@ -55,7 +55,7 @@ public class AdvisorReportRepository : IAdvisorReportRepository
public async Task<bool> ReportExist(string workDate)
{
var result = await _client.GetFromJsonAsync<SalesReportClosedView>(
$"{_apiConfig.CrmReports}/exist/{workDate}");
$"{_api.CrmReports}/exist/{workDate}");
return result.ReportClosed;
}
@ -65,7 +65,7 @@ public class AdvisorReportRepository : IAdvisorReportRepository
/// <returns></returns>
public async Task<List<SalesReportListView>> GetReports()
{
var result = await _client.GetAsync($"{_apiConfig.CrmReports}");
var result = await _client.GetAsync($"{_api.CrmReports}");
if (!result.IsSuccessStatusCode)
return new List<SalesReportListView>();
return await result.Content.ReadFromJsonAsync<List<SalesReportListView>>();
@ -78,7 +78,7 @@ public class AdvisorReportRepository : IAdvisorReportRepository
/// <returns></returns>
public async Task<ReportView> GetReport(string workDate)
{
var result = await _client.GetFromJsonAsync<ReportView>($"{_apiConfig.CrmReports}/{workDate}");
var result = await _client.GetFromJsonAsync<ReportView>($"{_api.CrmReports}/{workDate}");
return result ?? new ReportView();
}
@ -90,7 +90,7 @@ public class AdvisorReportRepository : IAdvisorReportRepository
public async Task<ReportInitDto> InitializeReportData(string workDate)
{
var initData = await _client
.GetFromJsonAsync<ReportInitDto>($"{_apiConfig.CrmReports}/init/{workDate}");
.GetFromJsonAsync<ReportInitDto>($"{_api.CrmReports}/init/{workDate}");
return initData ?? new ReportInitDto();
}
@ -103,7 +103,7 @@ public class AdvisorReportRepository : IAdvisorReportRepository
public async Task<ApiResponseView> CreateReport(string workDate, ReportDto reportDto)
{
var response = await _client
.PostAsJsonAsync($"{_apiConfig.CrmReports}/{workDate}", reportDto, _options);
.PostAsJsonAsync($"{_api.CrmReports}/{workDate}", reportDto, _options);
if (!response.IsSuccessStatusCode)
return new ApiResponseView
{

View file

@ -24,7 +24,7 @@ using Wonky.Entity.Views;
namespace Wonky.Client.HttpRepository;
public class WorkplaceRepository : IWorkplaceRepository
public class AdvisorWorkplaceRepository : IAdvisorWorkplaceRepository
{
private readonly JsonSerializerOptions? _options = new JsonSerializerOptions
{
@ -32,12 +32,12 @@ public class WorkplaceRepository : IWorkplaceRepository
};
private readonly NavigationManager _navigation;
private ILogger<WorkplaceRepository> _logger;
private ILogger<AdvisorWorkplaceRepository> _logger;
private readonly HttpClient _client;
private readonly ApiConfig _api;
public WorkplaceRepository(HttpClient client,
ILogger<WorkplaceRepository> logger,
public AdvisorWorkplaceRepository(HttpClient client,
ILogger<AdvisorWorkplaceRepository> logger,
NavigationManager navigation,
IOptions<ApiConfig> configuration)
{

View file

@ -41,7 +41,7 @@ public class CountryCatalogRepository : ICountryCatalogRepository
private readonly NavigationManager _navigation;
private ILogger<CountryCatalogRepository> _logger;
private readonly HttpClient _client;
private readonly ApiConfig _apiConfig;
private readonly ApiConfig _api;
public CountryCatalogRepository(HttpClient client,
ILogger<CountryCatalogRepository> logger,
@ -50,28 +50,28 @@ public class CountryCatalogRepository : ICountryCatalogRepository
_client = client;
_logger = logger;
_navigation = navigation;
_apiConfig = configuration.Value;
_api = configuration.Value;
}
/// <summary>
/// Get a paged sales item list
/// </summary>
/// <param name="countryCode"></param>
/// <param name="paging"></param>
/// <param name="pager"></param>
/// <returns></returns>
public async Task<PagingResponse<SalesItemView>> GetSalesItemsPaged(string countryCode, CatalogPaging paging)
public async Task<PagingResponse<SalesItemView>> GetSalesItemsPaged(string countryCode, CatalogPager pager)
{
var queryString = new Dictionary<string, string>
{
["pageNumber"] = paging.PageNumber.ToString(),
["pageSize"] = paging.PageSize.ToString(),
["orderBy"] = paging.OrderBy,
["searchColumn"] = paging.SearchColumn,
["searchTerm"] = paging.SearchTerm,
["selectGroup"] = paging.SelectGroup == "0" ? "" : paging.SelectGroup
["pageNumber"] = pager.PageNumber.ToString(),
["pageSize"] = pager.PageSize.ToString(),
["orderBy"] = pager.OrderBy,
["searchColumn"] = pager.SearchColumn,
["searchTerm"] = pager.SearchTerm,
["selectGroup"] = pager.SelectGroup == "0" ? "" : pager.SelectGroup
};
var response = await _client
.GetAsync(QueryHelpers.AddQueryString($"{_apiConfig.Catalog}/country/{countryCode}/page", queryString));
.GetAsync(QueryHelpers.AddQueryString($"{_api.Catalog}/{countryCode}/page", queryString));
if (!response.IsSuccessStatusCode)
{
@ -102,7 +102,7 @@ public class CountryCatalogRepository : ICountryCatalogRepository
public async Task<SalesItemView> GetSalesItemId(string countryCode, string salesItemId)
{
var salesItem = await _client
.GetFromJsonAsync<SalesItemView>($"{_apiConfig.Catalog}/country/{countryCode}/{salesItemId}");
.GetFromJsonAsync<SalesItemView>($"{_api.Catalog}/{countryCode}/{salesItemId}");
return salesItem ?? new SalesItemView();
}
@ -114,7 +114,7 @@ public class CountryCatalogRepository : ICountryCatalogRepository
/// <returns></returns>
public async Task<SalesItemView> GetSalesItemSku(string countryCode, string sku)
{
var salesItem = await _client.GetFromJsonAsync<SalesItemView>($"{_apiConfig.Catalog}/country/{countryCode}/sku/{sku}");
var salesItem = await _client.GetFromJsonAsync<SalesItemView>($"{_api.Catalog}/{countryCode}/sku/{sku}");
return salesItem ?? new SalesItemView();
}
@ -127,7 +127,7 @@ public class CountryCatalogRepository : ICountryCatalogRepository
public async Task<SalesItemView> GetSalesVariantId(string countryCode, string variantId)
{
var salesItem = await _client
.GetFromJsonAsync<SalesItemView>($"{_apiConfig.Catalog}/country/{countryCode}/variant/{variantId}");
.GetFromJsonAsync<SalesItemView>($"{_api.Catalog}/{countryCode}/variant/{variantId}");
return salesItem ?? new SalesItemView();
}
@ -138,6 +138,6 @@ public class CountryCatalogRepository : ICountryCatalogRepository
/// <returns></returns>
public async Task<List<SalesItemView>> GetPriceList(string countryCode)
{
return await _client.GetFromJsonAsync<List<SalesItemView>>($"{_apiConfig.Catalog}/country/{countryCode}", _options);
return await _client.GetFromJsonAsync<List<SalesItemView>>($"{_api.Catalog}/{countryCode}", _options);
}
}

View file

@ -134,9 +134,9 @@ public class CountryCustomerHistoryRepository : ICountryCustomerHistoryRepositor
/// <param name="companyId"></param>
/// <param name="syncDate"></param>
/// <returns></returns>
public async Task<string> ErpInvoiceToCrmRpc(string countryCode, string companyId, string syncDate)
public async Task<string> InvoiceErpToCrmRpc(string countryCode, string companyId, string syncDate)
{
var x = await _client.GetAsync($"{_api.OfficeCustomers}/{countryCode}/{companyId}/{_api.CrmRpcSyncExt}/{syncDate}");
var x = await _client.GetAsync($"{_api.OfficeCustomers}/{countryCode}/{companyId}/{_api.SyncRpcInvoiceExt}/{syncDate}");
if (!x.IsSuccessStatusCode)
return string.Empty;
var content = await x.Content.ReadAsStringAsync();

View file

@ -145,5 +145,4 @@ public class CountryCustomerRepository : ICountryCustomerRepository
Console.WriteLine(content);
return response.IsSuccessStatusCode;
}
}

View file

@ -55,7 +55,7 @@ public class CountryReportRepository : ICountryReportRepository
public async Task<bool> ReportExist(string salesRepId, string workDate)
{
var result = await _client.GetFromJsonAsync<SalesReportClosedView>(
$"{_apiConfig.OfficeReports}/exist/{workDate}");
$"{_apiConfig.OfficeReports}/{salesRepId}/{workDate}/exist");
return result.ReportClosed;
}
/// <summary>

View file

@ -19,11 +19,12 @@ using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Options;
using Wonky.Client.HttpInterfaces;
using Wonky.Entity.Configuration;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
namespace Wonky.Client.HttpRepository;
public class WarehouseRepository : IWarehouseRepository
public class OrderProcessRepository : IOrderProcessRepository
{
private readonly JsonSerializerOptions? _options = new JsonSerializerOptions
{
@ -31,12 +32,12 @@ public class WarehouseRepository : IWarehouseRepository
};
private readonly NavigationManager _navigation;
private ILogger<WarehouseRepository> _logger;
private ILogger<OrderProcessRepository> _logger;
private readonly HttpClient _client;
private readonly ApiConfig _api;
public WarehouseRepository(HttpClient client,
ILogger<WarehouseRepository> logger,
public OrderProcessRepository(HttpClient client,
ILogger<OrderProcessRepository> logger,
NavigationManager navigation,
IOptions<ApiConfig> configuration)
{
@ -81,11 +82,11 @@ public class WarehouseRepository : IWarehouseRepository
/// <summary>
/// Update Order status setting new process status
/// </summary>
/// <param name="process"></param>
/// <param name="processState"></param>
/// <returns></returns>
public async Task UpdateWarehouseOrderStatus(WarehouseProcess process)
public async Task UpdateWarehouseOrderStatus(OrderProcessState processState)
{
_logger.LogDebug("process => {}", JsonSerializer.Serialize(process, _options));
await _client.PutAsJsonAsync($"{_api.Warehouse}/{process.OrderId}", process, _options);
_logger.LogDebug("process => {}", JsonSerializer.Serialize(processState, _options));
await _client.PutAsJsonAsync($"{_api.Warehouse}/{processState.OrderId}", processState, _options);
}
}

View file

@ -7,5 +7,6 @@ public enum PStatus
Packed,
Shipped,
All,
Express
Express,
Accepted
}

View file

@ -1,6 +1,6 @@
namespace Wonky.Client.Models;
public class QuoteCallbackArgs
public class QCallbackArgs
{
/// <summary>
/// ESalesNumber

View file

@ -18,21 +18,23 @@
@using Microsoft.AspNetCore.Authorization
@using Wonky.Client.Components
@attribute [Authorize(Roles = "Advisor")]
@page "/customers/{CompanyId}/activities/new"
@page "/advisor/customers/{CompanyId}/activities/new"
<PageTitle>Ny aktivitet - @Company.Name</PageTitle>
<div class="row bg-dark text-white rounded-2 mb-2 py-2 align-items-center">
<div class="col">
<WorkDateComponent OnChangedCallback="WorkDateComponentCallback"/>
<WorkDateComponent OnChangedCallback="WorkDateComponentCallback" />
</div>
</div>
@if (!string.IsNullOrWhiteSpace(_company.Blocked))
@if (!string.IsNullOrWhiteSpace(Company.Blocked))
{
<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 class="row bg-light border-1 border-dark rounded-3 p-3">
<div class="row mb-2 bg-dark text-white rounded-3 p-3">
<div class="col">
<h3>@Activity.Name - @Activity.Account</h3>
</div>
@ -52,9 +54,9 @@ else
<EditForm EditContext="ActivityContext">
<DataAnnotationsValidator/>
<div class="row mb-1">
<label for="activityType" class="col-md-2 col-form-label">Ordre Type</label>
<div class="col-md-4">
<div class="row mb-3 g-2">
<label for="activityType" class="col-sm-2 col-form-label-sm">Ordre Type</label>
<div class="col-sm-4">
<InputSelect id="activityType" class="form-select bg-primary text-bg-primary" @bind-Value="@Activity.ActivityTypeEnum">
<option value="">&rarr; TAG MIG &larr;</option>
<option value="onSite">Besøg</option>
@ -63,11 +65,11 @@ else
<ValidationMessage For="@(() => Activity.ActivityTypeEnum)"></ValidationMessage>
</div>
<label for="statusType" class="col-md-2 col-form-label">Status</label>
<div class="col-md-4">
<label for="statusType" class="col-sm-2 col-form-label-sm">Status</label>
<div class="col-sm-4">
<InputSelect id="statusType" class="form-select bg-primary text-bg-primary" @bind-Value="@Activity.ActivityStatusEnum">
<option value="noSale">Ingen salg</option>
@if (!string.IsNullOrEmpty(Activity.VatNumber) && !string.IsNullOrWhiteSpace(Activity.Address1) && _company.HasFolded == 0)
@if (!string.IsNullOrEmpty(Activity.VatNumber) && !string.IsNullOrWhiteSpace(Activity.Address1) && Company.HasFolded == 0)
{
@if (DraftProvider.Draft.DraftType == "order")
{
@ -97,66 +99,78 @@ else
</div>
}
</div>
</div>
<div class="row mb-1">
<label for="demo" class="col-md-2 col-form-label">Demo</label>
<div class="col-md-4">
<label for="demo" class="col-sm-2 col-form-label-sm">Demo</label>
<div class="col-sm-4">
<InputText id="demo" class="form-control" @bind-Value="Activity.Demo"/>
<ValidationMessage For="@(() => Activity.Demo)"></ValidationMessage>
</div>
<label for="email" class="col-md-2 col-form-label">Epost</label>
<div class="col-md-4">
<label for="email" class="col-sm-2 col-form-label-sm">Epost</label>
<div class="col-sm-4">
<InputText id="email" class="form-control" @bind-Value="Activity.Email"/>
<ValidationMessage For="@(() => Activity.Email)"></ValidationMessage>
</div>
</div>
<div class="row mb-1">
<label for="referenceNumber" class="col-md-2 col-form-label">Rekvisition</label>
<div class="col-md-4">
<label for="referenceNumber" class="col-sm-2 col-form-label-sm">Rekvisition</label>
<div class="col-sm-4">
<InputText id="referenceNumber" class="form-control" @bind-Value="Activity.ReferenceNumber"/>
<ValidationMessage For="@(() => Activity.ReferenceNumber)"></ValidationMessage>
</div>
<label for="yourRef" class="col-md-2 col-form-label">Indkøber</label>
<div class="col-md-4">
<label for="yourRef" class="col-sm-2 col-form-label-sm">Indkøber</label>
<div class="col-sm-4">
<InputText id="yourRef" class="form-control" @bind-Value="Activity.YourRef"/>
<ValidationMessage For="@(() => Activity.YourRef)"></ValidationMessage>
</div>
</div>
<div class="row mb-1">
<label for="orderMessage" class="col-md-2 col-form-label">Note /Kontor</label>
<div class="col-md-4">
<InputTextArea id="orderMessage" class="form-control" @bind-Value="Activity.OrderMessage"/>
<ValidationMessage For="@(() => Activity.OrderMessage)"></ValidationMessage>
</div>
<label for="crmNote" class="col-md-2 col-form-label">Note /Selv</label>
<div class="col-md-4">
<InputTextArea id="crmNote" class="form-control" @bind-Value="Activity.CrmNote"/>
<ValidationMessage For="@(() => Activity.CrmNote)"></ValidationMessage>
</div>
</div>
<div class="row mb-2">
<label for="attention" class="col-md-2 col-form-label">Att.</label>
<div class="col-md-4">
<label for="attention" class="col-sm-2 col-form-label-sm">Att.</label>
<div class="col-sm-4">
<InputText id="attention" class="form-control" @bind-Value="Activity.Attention"/>
<ValidationMessage For="@(() => Activity.Attention)"></ValidationMessage>
</div>
<label for="phone" class="col-md-2 col-form-label">Tlf.</label>
<div class="col-md-4">
<label for="phone" class="col-sm-2 col-form-label-sm">Tlf.</label>
<div class="col-sm-4">
<InputText id="phone" class="form-control" @bind-Value="Activity.Phone"/>
<ValidationMessage For="@(() => Activity.Phone)"></ValidationMessage>
</div>
</div>
<label for="orderMessage" class="col-sm-2 col-form-label-sm">Note /Kontor</label>
<div class="col-sm-4">
<InputTextArea id="orderMessage" class="form-control" @bind-Value="Activity.OrderMessage"/>
<ValidationMessage For="@(() => Activity.OrderMessage)"></ValidationMessage>
</div>
<label for="crmNote" class="col-sm-2 col-form-label-sm">Note /Selv</label>
<div class="col-sm-4">
<InputTextArea id="crmNote" class="form-control" @bind-Value="Activity.CrmNote"/>
<ValidationMessage For="@(() => Activity.CrmNote)"></ValidationMessage>
</div>
<div class="col-sm-6"></div>
<label for="vatNumber" class="col-sm-2 col-form-label-sm">Cvr/Org nr.</label>
<div class="col-sm-4">
<InputText id="vatNumber" class="form-control" @bind-Value="Activity.VatNumber" />
<ValidationMessage For="@(() => Activity.VatNumber)" />
</div>
</div>
<div class="row g-2 mb-3">
<div class="col-sm-3 d-grid mx-auto">
<button class="btn btn-danger" disabled="@string.IsNullOrWhiteSpace(Activity.ActivityTypeEnum)" @onclick="ShowInvoiceOverlay">Faktura</button>
</div>
<div class="col-sm-3 d-grid mx-auto">
<button class="btn btn-warning" disabled="@string.IsNullOrWhiteSpace(Activity.ActivityTypeEnum)" @onclick="ShowVisitOverlay">Tidl. besøg</button>
@* <button class="btn btn-warning" disabled @onclick="ShowVisitOverlay">Tidl. besøg</button> *@
</div>
<div class="col-sm-3 d-grid mx-auto">
<button class="btn btn-success" disabled="@string.IsNullOrWhiteSpace(Activity.ActivityTypeEnum)" @onclick="ShowInventoryOverlay">Produkter</button>
</div>
</div>
<div id="this-draft" style="@(Activity.ActivityStatusEnum is "order" or "quote" ? "display: block" : "display:none")">
@* Order lines -----------------------------------------------------*@
@* Draft lines in draft -----------------------------------------------------*@
<div class="row">
<div class="col">
<table class="sticky-top table table-hover table-striped table-bordered">
@ -207,7 +221,7 @@ else
<td class="align-middle text-black text-end fw-bold">@($"{DraftProvider.Draft.Total:N2}")</td>
<td></td>
<td class="align-middle text-end">
<button class="btn btn-primary" type="button" @onclick="CallPriceListModal">
<button class="btn btn-primary" type="button" @onclick="ShowPriceListOverlay">
<i class="bi-plus"></i> Ny linje
</button>
</td>
@ -216,7 +230,7 @@ else
</table>
</div>
</div>
@* draft line ----------------------------------------------------- *@
@* Create Draft line ----------------------------------------------------- *@
<div class="row">
<div class="col">
@if (!string.IsNullOrWhiteSpace(SelectedItem.Name) && ShowItem)
@ -227,9 +241,9 @@ else
<th scope="col" colspan="6">Kladdelinje</th>
</tr>
<tr>
<th scope="col">Antal</th>
<th scope="col">Pris</th>
<th scope="col">Rabat</th>
<th style="min-width:100px;" scope="col">Antal</th>
<th style="min-width:200px;" scope="col">Pris</th>
<th style="min-width:100px;" scope="col">Rabat</th>
<th class="align-content-center justify-content-center" scope="col">SAS</th>
<th scope="col">Varenr.</th>
<th scope="col"></th>
@ -237,18 +251,18 @@ else
</thead>
<tbody>
<tr>
<td class="align-middle">
<td class="align-middle" style="min-width:100px;">
<input type="number" class="form-control" @bind-value="@Quantity"/>
</td>
<td class="align-middle">
<td class="align-middle" style="min-width:200px;">
<div class="input-group">
<input type="number" class="form-control" @bind-value="@Price"/>
<button class="btn btn-warning" type="button" @onclick="CallPriceHistoryModal">
<button class="btn btn-warning" type="button" @onclick="ShowPriceHistoryOverlay">
<i class="bi-list-ul"></i>
</button>
</div>
</td>
<td class="align-middle">
<td class="align-middle" style="min-width:100px;">
<input type="number" class="form-control" @bind-value="@Discount"/>
</td>
<td class="align-middle align-content-center justify-content-center">
@ -256,7 +270,7 @@ else
</td>
<td class="align-middle">@SelectedItem.Sku</td>
<td class="align-middle">
<button type="button" class="btn btn-primary text-nowrap d-block" @onclick="@(() => AddItem(SelectedItem))">BESTIL @SelectedItem.Name</button>
<button type="button" class="btn btn-primary d-block text-sm-center" @onclick="@(() => AddItem(SelectedItem))">@Quantity stk. @SelectedItem.Name</button>
</td>
</tr>
</tbody>
@ -278,31 +292,31 @@ else
aria-labelledby="deliveryHeader" data-bs-parent="#crmActivity">
<div class="accordion-body">
<div class="row mb-1">
<label for="dlvName" class="col-md-2 col-form-label">Lev. Navn</label>
<label for="dlvName" class="col-sm-2 col-form-label-sm">Lev. Navn</label>
<div class="col-md-10">
<InputText id="dlvName" class="form-control" @bind-Value="Activity.DlvName"/>
</div>
</div>
<div class="row mb-1">
<label for="dlvAddress1" class="col-md-2 col-form-label">Lev. Adresse</label>
<label for="dlvAddress1" class="col-sm-2 col-form-label-sm">Lev. Adresse</label>
<div class="col-md-10">
<InputText id="dlvAddress1" class="form-control" @bind-Value="Activity.DlvAddress1"/>
</div>
</div>
<div class="row mb-1">
<label for="dlvAddress2" class="col-md-2 col-form-label">Lev. Adresse</label>
<label for="dlvAddress2" class="col-sm-2 col-form-label-sm">Lev. Adresse</label>
<div class="col-md-10">
<InputText id="dlvAddress2" class="form-control" @bind-Value="Activity.DlvAddress2"/>
</div>
</div>
<div class="row mb-1">
<label for="dlvZipCode" class="col-md-2 col-form-label">Lev. Postnr</label>
<label for="dlvZipCode" class="col-sm-2 col-form-label-sm">Lev. Postnr</label>
<div class="col-md-10">
<InputText id="dlvZipCode" class="form-control" @bind-Value="Activity.DlvZipCode"/>
</div>
</div>
<div class="row mb-1">
<label for="dlvCity" class="col-md-2 col-form-label">Lev. Bynavn</label>
<label for="dlvCity" class="col-sm-2 col-form-label-sm">Lev. Bynavn</label>
<div class="col-md-10">
<InputText id="dlvCity" class="form-control" @bind-Value="Activity.DlvCity"/>
</div>
@ -314,16 +328,31 @@ else
</EditForm>
<div class="row mt-5 mb-2">
<div class="col-sm-6">
<a class="btn btn-warning" href="/customers/@_company.CompanyId">Kundekort <i class="bi-arrow-left"></i></a>
<a class="btn btn-warning" href="/advisor/customers/@Company.CompanyId">Kundekort <i class="bi-arrow-left"></i></a>
</div>
<div class="col-sm-4 text-end">
<button type="button" class="btn btn-warning" @onclick="CallConfirmProductCheckModel" disabled="@(PoFormInvalid || Working)"><i class="bi-save-fill"></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>
}
<ConfirmWorkDateModal BodyMessage="@PromptDateConfirm" OnOkClicked="WorkDateConfirmCallback" @ref="ConfirmWorkDateModal"/>
<PriceListModal OnSelected="PriceListCallback" @ref="PriceListModal"/>
<ProductHistoryModal CompanyId="@CompanyId" ItemSku="@SelectedItem.Sku" @ref="HistoryModal"/>
<ProductPriceHistoryModal OnSelected="PriceHistoryCallback" CompanyId="@CompanyId" Sku="@SelectedItem.Sku" @ref="PriceHistoryModal"/>
<ConfirmProductCheckModal BodyMessage="" CompanyId="@CompanyId" Products="CheckList" OnOkClicked="ConfirmProductCheckCallback" @ref="ConfirmProductCheckModal" />
<ConfirmWorkDateModalOverlay BodyMessage="@PromptDateConfirm"
OnOkClicked="WorkDateConfirmCallback" @ref="WorkDateOverlay"/>
<PriceCatalogModalOverlay CountryCode="@Company.CountryCode.ToLower()"
OnSelected="PriceListCallback" @ref="CatalogOverlay"/>
<ProductHistoryModalOverlay CompanyId="@CompanyId" ItemSku="@SelectedItem.Sku" @ref="ProductOverlay"/>
<ProductPriceHistoryModal CompanyId="@CompanyId" Sku="@SelectedItem.Sku"
OnSelected="PriceHistoryCallback" @ref="PriceOverlay"/>
<ConfirmProductCheckModalOverlay BodyMessage="" CompanyId="@CompanyId" Products="CheckList"
OnOkClicked="ConfirmProductCheckCallback" @ref="ProductCheckOverlay" />
<CustomerInvoiceListModalOverlay CustomerInvoices="CompanyInvoices" @ref="InvoiceListOverlay" />
<CustomerInventoryListModalOverlay CompanyName="@Company.Name" CompanyId="@CompanyId" CountryCode="@Company.CountryCode"
OnInventorySelected="OnInventoryCallback" Inventory="Inventory" @ref="InventoryListOverlay" />
<CustomerActivityListModalOverlay Activities="Activities" CompanyName="@Company.Name" @ref="ActivityListOverlay" />

View file

@ -30,29 +30,30 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Pages;
public partial class AdvisorCreateActivityPage : IDisposable
public partial class AdvisorActivityCreatePage : IDisposable
{
// Parameters
[CascadingParameter] DraftStateProvider DraftProvider { get; set; }
[Parameter] public string CompanyId { get; set; }
// Services
[Inject] public ILogger<AdvisorCreateActivityPage> Logger { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public UserProfileService Profiles { get; set; }
[Inject] public IToastService Toast { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public IAdvisorCatalogRepository AdvisorCatalogCrm { get; set; }
[Inject] public IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] public IAdvisorActivityRepository AdvisorActivityRepo { get; set; }
[Inject] public IAdvisorReportRepository AdvisorReportRepo { get; set; }
[Inject] public IAdvisorCustomerHistoryRepository HistoryRepo { get; set; }
[Inject] private ILogger<AdvisorActivityCreatePage> Logger { get; set; }
[Inject] private HttpInterceptorService Interceptor { get; set; }
[Inject] private UserProfileService Profiles { get; set; }
[Inject] private IToastService Toaster { get; set; }
[Inject] private NavigationManager Navigator { get; set; }
[Inject] private ILocalStorageService Storage { get; set; }
[Inject] private ICountryCatalogRepository CatalogRepo { get; set; }
[Inject] private IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] private IAdvisorActivityRepository ActivityRepo { get; set; }
[Inject] private IAdvisorReportRepository ReportRepo { get; set; }
[Inject] private IAdvisorCustomerHistoryRepository HistoryRepo { get; set; }
[CascadingParameter] private DraftStateProvider DraftProvider { get; set; } = new();
[Parameter] public string CompanyId { get; set; }
// variables
private readonly JsonSerializerOptions _options = new() {PropertyNameCaseInsensitive = true};
private SalesItemView SelectedItem { get; set; } = new();
private UserPref UserPrefs { get; set; } = new();
private ActivityDto Activity { get; set; } = new();
private CompanyDto _company = new();
private CompanyDto Company = new();
private EditContext? ActivityContext { get; set; }
private bool PoFormInvalid { get; set; } = true;
private bool ShowItem { get; set; }
@ -64,21 +65,30 @@ public partial class AdvisorCreateActivityPage : IDisposable
private bool InvalidActivity { get; set; } = true;
private bool ReportClosed { get; set; }
private bool Working { get; set; } = true;
private UserInfoView ThisUserInfo { get; set; } = new();
private UserInfoView SalesRep { get; set; } = new();
private DateTime SelectedDate { get; set; }
private string OldPhone { get; set; } = "";
private string PromptDateConfirm { get; set; } = "";
// MODAL DIALOGS
private PriceListModal PriceListModal { get; set; } = new();
private ProductHistoryModal HistoryModal { get; set; } = new();
private ProductPriceHistoryModal PriceHistoryModal { get; set; } = new();
private ConfirmWorkDateModal ConfirmWorkDateModal { get; set; } = new();
private ConfirmProductCheckModal ConfirmProductCheckModal { get; set; } = new();
// OVERLAY PAGES
private PriceCatalogModalOverlay CatalogOverlay { get; set; } = new();
private ProductHistoryModalOverlay ProductOverlay { get; set; } = new();
private ProductPriceHistoryModal PriceOverlay { get; set; } = new();
private ConfirmWorkDateModalOverlay WorkDateOverlay { get; set; } = new();
private ConfirmProductCheckModalOverlay ProductCheckOverlay { get; set; } = new();
private CustomerInvoiceListModalOverlay InvoiceListOverlay { get; set; } = new();
private CustomerInventoryListModalOverlay InventoryListOverlay { get; set; } = new();
private CustomerActivityListModalOverlay ActivityListOverlay { get; set; } = new();
private List<ProductInventoryView> Inventory { get; set; } = new();
private List<ProductInventoryView> CheckList { get; set; } = new();
private InvoiceListView CompanyInvoices { get; set; } = new();
private List<ReportItemView> Activities { get; set; } = new();
private string ButtonText { get; set; } = "Gem besøg";
private bool OrgWarning { get; set; }
/// <summary>
/// Page initialization
/// </summary>
@ -93,54 +103,55 @@ public partial class AdvisorCreateActivityPage : IDisposable
// User Preferences
UserPrefs = await Profiles.GetPreferences();
// User Info
ThisUserInfo = await Storage.GetItemAsync<UserInfoView>("_xu");
SalesRep = await Storage.GetItemAsync<UserInfoView>("_xu");
// Fetch Customer from http
_company = await CompanyRepo.GetCompanyById(CompanyId);
if (_company.HasFolded == 1)
Company = await CompanyRepo.GetCompanyById(CompanyId);
if (Company.HasFolded == 1)
// Company has shutdown activities
Activity.OrderMessage = "BEMÆRK: CVR nummer er ophørt.";
// variable to validate if customer needs phone number update
OldPhone = _company.Phone;
if (string.IsNullOrWhiteSpace(_company.Phone)
&& !string.IsNullOrWhiteSpace(_company.Account)
&& _company.Account != "NY" && _company.Account.Length > 7)
OldPhone = Company.Phone;
if (string.IsNullOrWhiteSpace(Company.Phone)
&& !string.IsNullOrWhiteSpace(Company.Account)
&& Company.Account != "NY" && Company.Account.Length > 7)
{
_company.Phone = _company.Account[..8];
Company.Phone = Company.Account[..8];
}
// Populate base activity information
Activity.BcId = _company.BcId;
Activity.BcId = Company.BcId;
Activity.ActivityStatusEnum = "noSale";
Activity.VisitTypeEnum = _company.Account is "" or "NY" ? "new" : "recall";
Activity.CompanyId = _company.CompanyId;
Activity.SalesRepId = ThisUserInfo.Id;
Activity.SalesRep = ThisUserInfo.Advisor;
Activity.CountryCode = ThisUserInfo.CountryCode;
Activity.Account = _company.Account;
Activity.VatNumber = _company.VatNumber;
Activity.Email = _company.Email;
Activity.Phone = _company.Phone;
Activity.Mobile = _company.Mobile;
Activity.Name = _company.Name;
Activity.Address1 = _company.Address1;
Activity.Address2 = _company.Address2;
Activity.ZipCode = _company.ZipCode;
Activity.City = _company.City;
Activity.DlvName = _company.Name;
Activity.DlvAddress1 = _company.Address1;
Activity.DlvAddress2 = _company.Address2;
Activity.DlvZipCode = _company.ZipCode;
Activity.DlvCity = _company.City;
Activity.VisitTypeEnum = Company.Account is "" or "NY" ? "new" : "recall";
Activity.CompanyId = Company.CompanyId;
Activity.SalesRepId = SalesRep.Id;
Activity.SalesRep = SalesRep.Advisor;
Activity.CountryCode = SalesRep.CountryCode;
Activity.Account = Company.Account;
Activity.VatNumber = Company.VatNumber;
Activity.Email = Company.Email;
Activity.Phone = Company.Phone;
Activity.Mobile = Company.Mobile;
Activity.Name = Company.Name;
Activity.Address1 = Company.Address1;
Activity.Address2 = Company.Address2;
Activity.ZipCode = Company.ZipCode;
Activity.City = Company.City;
Activity.DlvName = Company.Name;
Activity.DlvAddress1 = Company.Address1;
Activity.DlvAddress2 = Company.Address2;
Activity.DlvZipCode = Company.ZipCode;
Activity.DlvCity = Company.City;
// Initialize date variable
SelectedDate = string.IsNullOrWhiteSpace(UserPrefs.WorkDate) ? DateTime.Now : DateTime.Parse(UserPrefs.WorkDate);
// raise flag if report is closed
ReportClosed = await AdvisorReportRepo.ReportExist($"{SelectedDate:yyyy-MM-dd}");
ReportClosed = await ReportRepo.ReportExist($"{SelectedDate:yyyy-MM-dd}");
// Ask for confirmation of date
Logger.LogDebug("Preferences.DateConfirmed => {}", UserPrefs.DateConfirmed);
if (!UserPrefs.DateConfirmed)
{
PromptDateConfirm = $"Aktiviteter oprettes med dato {SelectedDate.ToShortDateString()}. Er dette OK?";
ConfirmWorkDateModal.Show();
WorkDateOverlay.Show();
}
// Lines may already have been added from the company inventory page
if (DraftProvider.Draft.DraftType == "order")
@ -151,14 +162,90 @@ public partial class AdvisorCreateActivityPage : IDisposable
PoFormInvalid = false;
}
Working = false;
//StateHasChanged();
}
private async Task CallConfirmProductCheckModel()
private async Task ShowVisitOverlay()
{
Logger.LogDebug("ShowInventoryOverlay - wait for visits");
ActivityListOverlay.Show();
Activities = await ActivityRepo.GetCustomerActivities(CompanyId);
await Task.Delay(500);
}
private async Task ShowInventoryOverlay()
{
Logger.LogDebug("ShowInventoryOverlay - wait for inventory");
InventoryListOverlay.Show();
Inventory = await HistoryRepo.FetchInventory(CompanyId);
await Task.Delay(500);
}
private async Task OnInventoryCallback(DraftItem item)
{
Activity.ActivityStatusEnum = "order";
DraftProvider.Draft.DraftType = "order";
DraftProvider.Draft.Items.Add(item);
StateHasChanged();
}
private async Task ShowInvoiceOverlay()
{
Logger.LogDebug("ShowInvoiceOverlay - wait for invoices");
InvoiceListOverlay.Show();
CompanyInvoices = await FetchCompanyInvoices();
await Task.Delay(500);
}
private async Task<InvoiceListView> FetchCompanyInvoices()
{
// fetch from storage
var storage = await Storage.GetItemAsStringAsync($"{CompanyId}-invoices");
await Task.Delay(500);
var iDate = await Storage.GetItemAsStringAsync($"{CompanyId}-iDate");
// 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))
{
Logger.LogDebug("fetching invoices from storage");
Logger.LogDebug("storage contains <= {}", storage);
return JsonSerializer.Deserialize<InvoiceListView>(storage);
}
Logger.LogDebug("pulling invoices from backend");
// pull invoices
var companyInvoices = await HistoryRepo.FetchInvoiceList(CompanyId);
// send invoices to storage
await Storage.SetItemAsync($"{CompanyId}-invoices", companyInvoices);
await Storage.SetItemAsync($"{CompanyId}-iDate", $"{DateTime.Now:yyyy-MM-dd}");
Logger.LogDebug(" --> return invoices from backend");
Working = false;
Logger.LogDebug("backend contains <= {}", JsonSerializer.Serialize(companyInvoices));
return companyInvoices;
}
private void ShowOrgWarning()
{
if (OrgWarning)
return;
OrgWarning = true;
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");
}
}
private async Task CallConfirmCheckOverlay()
{
// check if new account
if (string.IsNullOrWhiteSpace(_company.Account)
|| _company.Account.ToLower() == "ny"
if (string.IsNullOrWhiteSpace(Company.Account)
|| Company.Account.ToLower() == "ny"
|| Activity.ActivityStatusEnum.ToLower() == "quote")
{
// proceed to create activity - as there is no product check to be done
@ -172,23 +259,26 @@ public partial class AdvisorCreateActivityPage : IDisposable
Logger.LogDebug("pStorage => {}", pStorage);
// fetch pDate from storage
var pDate = await Storage.GetItemAsync<string>($"{CompanyId}-pDate");
if (string.IsNullOrWhiteSpace(pDate))
pDate = $"{DateTime.Now.AddDays(-1):yyyy-MM-dd}";
Logger.LogDebug("pDate => {}", pDate);
// check if product data is valid and updated today
if (string.IsNullOrWhiteSpace(pStorage) || pDate.Replace("\"", "") != $"{DateTime.Now:yyyy-MM-dd}")
{
Working = true;
// pop a message
Toast.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
// send rpc call to sync ERP to CRM
Toast.ShowInfo("Vent mens data synkroniseres ...", "ERP til CRM ...");
var ts = await HistoryRepo.ErpInvoiceToCrmRpc(CompanyId, _company.HistorySync);
Toaster.ShowInfo("Vent mens data synkroniseres ...", "ERP til CRM ...");
var ts = await HistoryRepo.InvoiceErpToCrmRpc(CompanyId, Company.HistorySync);
while (string.IsNullOrWhiteSpace(ts))
await Task.Delay(500);
// save pDate
await Storage.SetItemAsync($"{CompanyId}-pDate", ts);
// request products from backend
Toast.ShowInfo("Vent mens produkt oversigt hentes", "CRM produkt liste");
Toaster.ShowInfo("Vent mens produkt oversigt hentes", "CRM produkt liste");
CheckList = await HistoryRepo.FetchInventory(CompanyId);
if(CheckList.Any())
@ -206,11 +296,12 @@ public partial class AdvisorCreateActivityPage : IDisposable
}
// Show CheckList modal
ConfirmProductCheckModal.Show();
ProductCheckOverlay.Show();
}
private async Task ConfirmProductCheckCallback()
{
ConfirmProductCheckModal.Hide();
ProductCheckOverlay.Hide();
await CreateActivity();
foreach (var item in CheckList)
{
@ -226,7 +317,7 @@ public partial class AdvisorCreateActivityPage : IDisposable
private async Task WorkDateConfirmCallback()
{
await Profiles.SetDateConfirmed(true);
ConfirmWorkDateModal.Hide();
WorkDateOverlay.Hide();
StateHasChanged();
}
@ -236,7 +327,7 @@ public partial class AdvisorCreateActivityPage : IDisposable
/// <param name="workDate"></param>
private async Task WorkDateComponentCallback(string workDate)
{
ReportClosed = await AdvisorReportRepo.ReportExist(workDate);
ReportClosed = await ReportRepo.ReportExist(workDate);
SelectedDate = DateTime.Parse(workDate);
Activity.ActivityDate = workDate;
}
@ -244,9 +335,9 @@ public partial class AdvisorCreateActivityPage : IDisposable
/// <summary>
/// Show Price list modal
/// </summary>
private void CallPriceListModal()
private void ShowPriceListOverlay()
{
PriceListModal.Show();
CatalogOverlay.Show();
}
/// <summary>
@ -258,7 +349,7 @@ public partial class AdvisorCreateActivityPage : IDisposable
// get selected item
if (string.IsNullOrWhiteSpace(sku.ItemId))
return;
SelectedItem = await AdvisorCatalogCrm.GetSalesItemId(sku.ItemId);
SelectedItem = await CatalogRepo.GetSalesItemId(SalesRep.CountryCode.ToLower(), sku.ItemId);
ShowItem = true;
Price = sku.Rate;
Quantity = sku.Quantity;
@ -268,10 +359,10 @@ public partial class AdvisorCreateActivityPage : IDisposable
/// <summary>
/// Show Price History modal
/// </summary>
private void CallPriceHistoryModal()
private void ShowPriceHistoryOverlay()
{
if(ShowItem)
PriceHistoryModal.Show();
PriceOverlay.Show();
}
/// <summary>
@ -294,26 +385,35 @@ public partial class AdvisorCreateActivityPage : IDisposable
// avoid duplication
if (Working)
return;
// validate customer address1 - this is a required input
// validate customer address1
// - this is a required input
if (string.IsNullOrWhiteSpace(Activity.Address1))
{
Toast.ShowError("Kunde adresse er ufuldstændig.");
Toaster.ShowError("Kunde adresse er ufuldstændig.");
return;
}
//
// validate org number
// - this is a required input
// - must validate according to country rules.
if (!VatUtils.ValidateFormat(Company.CountryCode, Activity.VatNumber))
{
Toaster.ShowError("Firma registreringsnummer er ikke korrekt.");
return;
}
// validate input according to status
switch (Activity.ActivityStatusEnum)
{
// don't accept order with no lines
case "order" when DraftProvider.Draft.Items.Count == 0:
Toast.ShowError("Ved bestilling skal der være en eller flere linjer i kladden.");
Toaster.ShowError("Ved bestilling skal der være en eller flere linjer i kladden.");
return;
// phone number is required if first time customer
case "order" when _company.Account is "NY" or "" && string.IsNullOrWhiteSpace(Activity.Phone):
Toast.ShowError("Ved bestilling til ny kunde skal telefon nummer angives.");
case "order" when Company.Account is "NY" or "" && string.IsNullOrWhiteSpace(Activity.Phone):
Toaster.ShowError("Ved bestilling til ny kunde skal telefon nummer angives.");
return;
// verify email address is a valid address
case "quote" when !Utils.IsValidEmail(Activity.Email):
Toast.ShowError("Ved tilbud skal en gyldig email adresse angives.");
Toaster.ShowError("Ved tilbud skal en gyldig email adresse angives.");
return;
}
// raise working flag
@ -324,15 +424,16 @@ public partial class AdvisorCreateActivityPage : IDisposable
// check if phone number need to be updated
if (OldPhone != Activity.Phone)
{
_company.Phone = Activity.Phone;
await CompanyRepo.UpdateErpData(_company.CompanyId, _company);
Company.Phone = Activity.Phone;
Activity.OrderMessage = $"Telefonnr. opdateret.\n{Activity.OrderMessage}";
await CompanyRepo.UpdateErpData(Company.CompanyId, Company);
}
// begin assembling activity
Activity.ActivityDate = $"{SelectedDate:yyyy-MM-dd}";
Activity.OurRef = Activity.ActivityTypeEnum switch
{
"phone" => $"T:{ThisUserInfo.FullName.Split(" ")[0]}",
"onSite" => $"B:{ThisUserInfo.FullName.Split(" ")[0]}",
"phone" => $"T:{SalesRep.FullName.Split(" ")[0]}",
"onSite" => $"B:{SalesRep.FullName.Split(" ")[0]}",
_ => ""
};
if (Activity.Express)
@ -361,22 +462,22 @@ public partial class AdvisorCreateActivityPage : IDisposable
// debug logging
Logger.LogDebug("CrmNewActivityPage => \n {}", JsonSerializer.Serialize(Activity));
// post to api
var result = await AdvisorActivityRepo.CreateActivity(Activity);
var result = await ActivityRepo.CreateActivity(Activity);
// debug logging
Logger.LogDebug("ApiResponseView => \n {}", JsonSerializer.Serialize(result));
// show result message
if (result.IsSuccess)
{
Toast.ShowSuccess($"{result.Message}",
Toaster.ShowSuccess($"{result.Message}",
DraftProvider.Draft.Items.Count == 0 ? "Besøg er oprettet" : "Bestilling/Tilbud er oprettet");
await DeleteDraft();
Navigator.NavigateTo($"/customers");
Navigator.NavigateTo($"/advisor/customers");
return;
}
// lower working flag
Working = false;
// show error message
Toast.ShowError(result.Message, "ORDRE FEJL");
Toaster.ShowError(result.Message, "ORDRE FEJL");
}
/// <summary>
@ -446,12 +547,11 @@ public partial class AdvisorCreateActivityPage : IDisposable
DraftProvider.Draft.Items = new List<DraftItem>();
}
ButtonText = Activity.ActivityStatusEnum switch
ButtonText = Activity.ActivityStatusEnum.ToLower() switch
{
"noSale" => "Gem Besøg",
"order" => "Gem Bestilling",
"quote" => "Gem Tilbud",
_ => ButtonText
"nosale" => "Gem Besøg",
"order" => "Send Bestilling",
"quote" => "Send Tilbud"
};
// InvalidCanvas = InvalidActivityType;
@ -477,10 +577,20 @@ public partial class AdvisorCreateActivityPage : IDisposable
{
if (string.IsNullOrEmpty(Activity.ActivityTypeEnum) && !ReportClosed)
{
Toast.ShowWarning("Aktivitet type kan ikke være tom");
Toaster.ShowWarning("Aktivitet type kan ikke være tom");
PoFormInvalid = true;
return;
}
if (Activity.ActivityStatusEnum.ToLower() is "order" or "quote"
&& Company.CountryCode.ToLower() == "se"
&& VatUtils.SanitizeVatNumber(Activity.VatNumber).Length < 10)
{
ShowOrgWarning();
PoFormInvalid = true;
return;
}
PoFormInvalid = false;
ActivityContext.OnFieldChanged -= HandleFieldChanged;
ActivityContext.OnValidationStateChanged -= ValidationChanged;

View file

@ -18,8 +18,8 @@
@using Microsoft.AspNetCore.Authorization
@using Wonky.Client.Components
@attribute [Authorize(Roles = "Advisor")]
@page "/activity-today"
@page "/advisor/activity-today"
<PageTitle>Aktiviteter for @($"{SelectedDate:yyyy-MM-dd}")</PageTitle>
<div class="row bg-dark text-white rounded-2 mb-2 py-2 align-items-center">
<div class="col-sm-7">
<WorkDateComponent OnChangedCallback="GetActivities"/>
@ -30,17 +30,17 @@
<div class="col-sm-2 text-end">
@if (ReportExist)
{
<a class="btn btn-info" href="/sales-reports/view/@($"{SelectedDate:yyyy-MM-dd}")">Rapport <i style="font-size: 1.2em;" class="bi-info-lg"></i></a>
<a class="btn btn-info" href="/advisor/reports/view/@($"{SelectedDate:yyyy-MM-dd}")">Rapport <i style="font-size: 1.2em;" class="bi-info-lg"></i></a>
}
else
{
<a class="btn btn-primary" href="/sales-reports/new">Rapport <i style="font-size:1.2em;" class="bi-plus-lg"></i></a>
<a class="btn btn-primary" href="/advisor/reports/new">Rapport <i style="font-size:1.2em;" class="bi-plus-lg"></i></a>
}
</div>
</div>
@if (ReportStatusView.ReportItems.Any())
{
<ActivityListComponent ActivityList="ReportStatusView.ReportItems"/>
<AdvisorActivityListComponent ActivityList="ReportStatusView.ReportItems"/>
}
@if (Working)
{

View file

@ -25,15 +25,15 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Pages;
public partial class AdvisorTodayActivityListPage : IDisposable
public partial class AdvisorActivityTodayListPage : IDisposable
{
[Inject] public UserProfileService UserProfileService { get; set; }
[Inject] public ILogger<AdvisorTodayActivityListPage> Logger { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IAdvisorActivityRepository AdvisorActivityRepo { get; set; }
[Inject] public IAdvisorReportRepository AdvisorReportRepo { get; set; }
[Inject] public IToastService Toaster { get; set; }
[Inject] private UserProfileService UserProfileService { get; set; }
[Inject] private ILogger<AdvisorActivityTodayListPage> Logger { get; set; }
[Inject] private HttpInterceptorService Interceptor { get; set; }
[Inject] private NavigationManager Navigator { get; set; }
[Inject] private IAdvisorActivityRepository AdvisorActivityRepo { get; set; }
[Inject] private IAdvisorReportRepository AdvisorReportRepo { get; set; }
[Inject] private IToastService Toaster { get; set; }
private ReportStatusView? ReportStatusView { get; set; } = new();
private UserPref UserPref { get; set; } = new();
private DateTime SelectedDate { get; set; }

View file

@ -19,9 +19,10 @@
@using Wonky.Client.Components
@attribute [Authorize(Roles = "Admin,Advisor,Warehouse")]
@page "/customers/{CompanyId}/orders/{OrderId}"
@page "/customers/{CompanyId}/quotes/{OrderId}"
@page "/advisor/customers/{CompanyId}/orders/{OrderId}"
@page "/advisor/customers/{CompanyId}/quotes/{OrderId}"
<PageTitle>@ReportItem.Company.Name @ReportItem.OrderDate</PageTitle>
@* <ReportItemComponent ReportItem="@_item" /> *@
<table class="table table-sm table-striped d-print-table">
@ -129,7 +130,7 @@
</tbody>
</table>
@* Office Note *@
@if (ReportItem.ProcessStatusEnum == "None" && !ReportItem.Express && AllowOfficeNoteUpdate())
@if (ReportItem is { ProcessStatusEnum: "None",Express: false } && AllowOfficeNoteUpdate())
{
<div class="alert border border-1 border-primary">
<EditForm EditContext="NoteContext">

View file

@ -29,15 +29,15 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Pages;
public partial class AdvisorViewActivityPage : IDisposable
public partial class AdvisorActivityViewPage : IDisposable
{
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string OrderId { get; set; } = "";
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public IAdvisorActivityRepository AdvisorActivityRepo { get; set; }
[Inject] public ILogger<AdvisorViewActivityPage> Logger { get; set; }
[Inject] public IToastService Toaster { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] private HttpInterceptorService Interceptor { get; set; }
[Inject] private IAdvisorActivityRepository AdvisorActivityRepo { get; set; }
[Inject] private ILogger<AdvisorActivityViewPage> Logger { get; set; }
[Inject] private IToastService Toaster { get; set; }
[Inject] private NavigationManager Navigator { get; set; }
private ReportItemView ReportItem { get; set; } = new();
private ActivityOfficeNote Note { get; set; } = new();
private EditContext NoteContext { get; set; }
@ -70,7 +70,7 @@ public partial class AdvisorViewActivityPage : IDisposable
Logger.LogDebug("OfficeNote => \n {}", JsonSerializer.Serialize(Note));
await AdvisorActivityRepo.UpdateOfficeNote(Note);
Toaster.ShowInfo($"{ReportItem.ESalesNumber} - notat opdateret");
Navigator.NavigateTo("/activity-today");
Navigator.NavigateTo("/advisor/activity-today");
}
private bool AllowOfficeNoteUpdate()

View file

@ -1,52 +0,0 @@
@*
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
*@
@using Wonky.Client.Components
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Roles = "Advisor")]
@page "/price-catalog"
<div class="sticky-top bg-dark rounded-2 px-3">
<div class="row g-3 mb-3">
<div class="col-sm-2">
<CatalogGroupComponent OnChanged="SetGroupCol"/>
</div>
<div class="col-sm-2">
<CatalogSearchComponent OnChanged="SetSearchCol"/>
</div>
<div class="col-sm-4">
<CatalogSearchPhraseComponent OnChanged="SetSearchPhrase"/>
</div>
<div class="col-sm-2">
<CatalogSortComponent OnChanged="SetSortCol"/>
</div>
<div class="col-sm-2">
<PageSizeComponent OnChanged="SetPageSize"/>
</div>
<div class="col-sm-10">
<PaginationComponent MetaData="MetaInfo" Spread="2" SelectedPage="SetSelectedPage"/>
</div>
<div class="col-sm-2 text-end">
<a class="btn btn-secondary" href="/print/catalog"><i class="bi-printer"></i> Udskriv</a>
</div>
</div>
</div>
<CatalogListComponent ItemList="Items"/>
@if (Working)
{
<WorkingThreeDots />
}

View file

@ -1,119 +0,0 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Blazored.LocalStorage;
using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Microsoft.AspNetCore.Components;
using Wonky.Client.Components;
using Wonky.Client.HttpInterfaces;
using Wonky.Client.Services;
using Wonky.Entity.DTO;
using Wonky.Entity.Requests;
using Wonky.Entity.Views;
namespace Wonky.Client.Pages;
public partial class AdvisorCatalogPage : IDisposable
{
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public IAdvisorCatalogRepository ItemRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
private List<SalesItemView> Items { get; set; } = new();
private MetaData MetaInfo { get; set; } = new();
private CatalogPaging _page = new();
private UserPref Prefs = new();
private UserInfoView UserInfo { get; set; } = new();
private bool Working { get; set; } = true;
protected override async Task OnInitializedAsync()
{
Prefs = await ProfileService.GetPreferences();
UserInfo = await Storage.GetItemAsync<UserInfoView>("_xu");
_page.OrderBy = Prefs.ItemSort;
_page.SearchColumn = Prefs.ItemSearch;
_page.PageSize = Convert.ToInt32(Prefs.PageSize);
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
await FetchSalesItems();
}
private async Task SetSearchPhrase(string searchTerm)
{
Items = new List<SalesItemView>();
_page.PageNumber = 1;
_page.SearchTerm = searchTerm;
await FetchSalesItems();
}
private async Task SetPageSize(string pageSize)
{
Items = new List<SalesItemView>();
_page.PageSize = Convert.ToInt32(pageSize);
_page.PageNumber = 1;
await FetchSalesItems();
}
private async Task SetSearchCol(string columnName)
{
Items = new List<SalesItemView>();
_page.PageNumber = 1;
_page.SearchColumn = columnName;
await FetchSalesItems();
}
private async Task SetSortCol(string orderBy)
{
Items = new List<SalesItemView>();
_page.OrderBy = orderBy;
await FetchSalesItems();
}
private async Task SetSelectedPage(int page)
{
Items = new List<SalesItemView>();
_page.PageNumber = page;
await FetchSalesItems();
}
private async Task SetGroupCol(string groupFilter)
{
Items = new List<SalesItemView>();
_page.PageNumber = 1;
_page.SelectGroup = groupFilter;
await FetchSalesItems();
}
private async Task FetchSalesItems()
{
Working = true;
var pagingResponse = await ItemRepo.GetSalesItemsPaged(_page);
Working = false;
Items = pagingResponse.Items!;
MetaInfo = pagingResponse.MetaData;
}
public void Dispose() => Interceptor.DisposeEvent();
}

View file

@ -18,7 +18,9 @@
@using Wonky.Client.Components
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Roles = "Advisor")]
@page "/customers/{CompanyId}/activities"
@page "/advisor/customers/{CompanyId}/activities"
<PageTitle>Rådgiver Aktiviteter for @Company.Name</PageTitle>
@if (!string.IsNullOrWhiteSpace(Company.Name))
{
@ -27,70 +29,14 @@
<h4 class="pt-1">@Company.Name</h4>
</div>
<div class="col-sm-3 align-content-end">
<a class="btn btn-primary d-block" href="/customers/@Company.CompanyId"><i class="bi-arrow-right"></i> Kundekort</a>
<a class="btn btn-primary d-block" href="/advisor/customers/@Company.CompanyId"><i class="bi-arrow-right"></i> Kundekort</a>
</div>
<div class="col-sm-3 align-content-end">
<a class="btn btn-primary d-block" href="/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 class="list-group">
<div class="list-group-item">
<div class="row">
<div class="col">
<h4>Dato</h4>
</div>
<div class="col">
<h4>Demo</h4>
</div>
<div class="col">
<h4>Salg</h4>
</div>
<div class="col">
<h4>Note /Kontor</h4>
</div>
<div class="col">
<h4>Note /Selv</h4>
</div>
</div>
</div>
@if (Activities.Any())
{
@foreach (var activity in Activities)
{
<div class="list-group-item">
<div class="row">
<div class="col">
@activity.OrderDate
</div>
<div class="col">
@activity.Demo
</div>
<div class="col">
@activity.Sales
</div>
<div class="col">
@activity.OfficeNote
</div>
<div class="col">
@activity.CrmNote
</div>
</div>
</div>
}
}
else
{
<div class="list-group-item">
<div class="row">
<div class="col">
Ingen data
</div>
</div>
</div>
}
</div>
<CustomerVisitListComponent Activities="Activities" />
}
@if (Working)
{

View file

@ -28,9 +28,9 @@ namespace Wonky.Client.Pages;
public partial class AdvisorCustomerActivityListPage : IDisposable
{
[Parameter] public string CompanyId { get; set; } = "";
[Inject] public HttpInterceptorService _interceptor { get; set; }
[Inject] public IAdvisorActivityRepository AdvisorActivityRepo { get; set; }
[Inject] public IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] private HttpInterceptorService _interceptor { get; set; }
[Inject] private IAdvisorActivityRepository AdvisorActivityRepo { get; set; }
[Inject] private IAdvisorCustomerRepository CompanyRepo { get; set; }
private List<ReportItemView> Activities { get; set; } = new();
private CompanyDto Company { get; set; } = new();
private bool Working { get; set; } = true;

View file

@ -15,12 +15,13 @@
//
*@
@page "/customers/new"
@page "/advisor/customers/new"
@using Microsoft.AspNetCore.Authorization
@using Wonky.Client.Components
@using System.Xml
@attribute [Authorize(Roles = "Advisor")]
<PageTitle>Rådgiver Opret Kunde</PageTitle>
<h2>Opret kunde</h2>
<EditForm EditContext="CompanyContext" OnValidSubmit="SubmitCompanyForm">
@ -30,7 +31,20 @@
<div class="row g-2">
@* vat lookup *@
<div class="col-sm-12 text-end">
<button type="button" class="btn btn-primary" @onclick="CallVatLookupModal">CVR opslag</button>
<div class="d-grid mx-auto">
@switch (Company.CountryCode)
{
case "dk":
<button type="button" class="btn btn-info" @onclick="CallVatLookupModal"><i class="bi-search"></i> Firma søgning</button>
break;
case "no":
<a class="btn btn-info" href="https://brreg.no/" target="_blank"><i class="bi-search"></i> Firma søgning</a>
break;
case "se":
<a class="btn btn-info" href="https://www.allabolag.se/@(string.IsNullOrWhiteSpace(Company.Name) ? "" : "what/@Company.Name")" target="_blank"><i class="bi-search"></i> Firma søgning</a>
break;
}
</div>
</div>
@* entity name *@
<label for="name" class="col-sm-1 col-form-label-sm">Navn</label>
@ -69,7 +83,7 @@
<ValidationMessage For="@(() => Company.City)"></ValidationMessage>
</div>
@* entity vat number *@
<label for="vatNumber" class="col-sm-1 col-form-label-sm">Moms Nr</label>
<label for="vatNumber" class="col-sm-1 col-form-label-sm">Cvr/Org Nr.</label>
<div class="col-sm-3">
<div class="input-group">
<span class="input-group-text">
@ -99,7 +113,7 @@
</div>
</div>
<hr class="mb-3"/>
<div class="row g-2">
<div class="row g-2 mb-3">
<label for="note" class="col-sm-1 col-form-label-sm">OBS</label>
<div class="col-sm-5">
<InputText id="note" class="form-control" @bind-Value="Company.Note"/>
@ -124,7 +138,7 @@
</div>
</div>
<div class="row mb-3">
<div class="col-sm-12 text-end">
<div class="col-sm-12 d-grid mx-auto">
<button type="submit" class="btn btn-success" disabled="@FormInvalid">Opret</button>
</div>
</div>

View file

@ -37,15 +37,15 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Pages
{
public partial class AdvisorCreateCustomerPage : IDisposable
public partial class AdvisorCustomerCreatePage : IDisposable
{
[Inject] public IToastService Toaster { get; set; }
[Inject] public ILogger<AdvisorCreateCustomerPage> 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] private IToastService Toaster { get; set; }
[Inject] private ILogger<AdvisorCustomerCreatePage> Logger { get; set; }
[Inject] private ILocalStorageService Storage { get; set; }
[Inject] private NavigationManager Navigator { get; set; }
[Inject] private IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] private HttpInterceptorService Interceptor { get; set; }
[Inject] private VatInfoLookupService VatService { get; set; }
private EditContext CompanyContext { get; set; }
private CompanyDto Company { get; set; } = new();
private VirkRegInfo CompanyRegInfo { get; set; } = new();
@ -136,7 +136,7 @@ namespace Wonky.Client.Pages
/// <param name="regInfo"></param>
private void SelectCompanyCallback(VirkRegInfo regInfo)
{
Logger.LogDebug($"CrmCompanyView => SelectCompanyCallback => {JsonSerializer.Serialize(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";
@ -177,7 +177,7 @@ namespace Wonky.Client.Pages
if (!string.IsNullOrWhiteSpace(newId))
{
Toaster.ShowSuccess($"'{Company.Name}' er oprettet i CRM.");
Navigator.NavigateTo($"/customers/{newId}");
Navigator.NavigateTo($"/advisor/customers/{newId}");
}
else
{

View file

@ -17,24 +17,24 @@
@using Wonky.Client.Components
@using Microsoft.AspNetCore.Authorization
@page "/customers/{CompanyId}/h/i"
@page "/advisor/customers/{CompanyId}/h/i"
@attribute [Authorize(Roles = "Advisor")]
<PageTitle>Produkt oversigt for @Company.Name</PageTitle>
<div class="row pt-2 pb-1 rounded-2 bg-dark text-white">
<div class="col-sm-6">
<h4 class="pt-1">@Company.Name</h4>
</div>
<div class="col-sm-3 align-content-end">
<a class="btn btn-primary d-block" href="/customers/@CompanyId"><i class="bi-arrow-left"></i> Kundekort</a>
<a class="btn btn-primary d-block" href="/advisor/customers/@CompanyId"><i class="bi-arrow-right"></i> Kundekort</a>
</div>
<div class="col-sm-3 align-content-end">
<a class="btn btn-primary d-block" href="/customers/@CompanyId/activities/new"><i class="bi-arrow-right"></i> Nyt Besøg</a>
<a class="btn btn-primary d-block" href="/advisor/customers/@CompanyId/activities/new"><i class="bi-arrow-right"></i> Nyt Besøg</a>
</div>
</div>
<CustomerInventoryListComponent OnReorderSelected="OnReorderCallback" CompanyId="@CompanyId" Inventory="@Inventory"/>
<InventoryReorderModal OnSelected="@OnSelectedItem" CompanyId="@CompanyId" SalesItem="@SalesItem" @ref="ReorderModal"/>
<CustomerInventoryReorderModalOverlay OnSelected="@OnSelectedItem" CompanyId="@CompanyId" SalesItem="@SalesItem" @ref="CustomerInventoryReorderOverlay"/>
@if (Working)
{

View file

@ -28,13 +28,13 @@ namespace Wonky.Client.Pages;
public partial class AdvisorCustomerInventoryListPage : IDisposable
{
[Inject] public IAdvisorCustomerHistoryRepository HistoryRepo { get; set; }
[Inject] public IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public IToastService Toaster { get; set; }
[Inject] public ILogger<AdvisorCustomerInventoryListPage> Logger { get; set; }
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public IAdvisorCatalogRepository AdvisorCatalogRepo { get; set; }
[Inject] private IAdvisorCustomerHistoryRepository CustomerHistory { get; set; }
[Inject] private IAdvisorCustomerRepository Customers { get; set; }
[Inject] private HttpInterceptorService Interceptor { get; set; }
[Inject] private IToastService Toaster { get; set; }
[Inject] private ILogger<AdvisorCustomerInventoryListPage> Logger { get; set; }
[Inject] private ILocalStorageService Storage { get; set; }
[Inject] private ICountryCatalogRepository Catalog { get; set; }
[CascadingParameter] public DraftStateProvider DraftStateProvider { get; set; } = new();
[Parameter] public string CompanyId { get; set; } = "";
@ -44,7 +44,7 @@ public partial class AdvisorCustomerInventoryListPage : IDisposable
private CompanyDto Company { get; set; } = new();
private bool Working { get; set; } = true;
private SalesItemView SalesItem { get; set; } = new();
private InventoryReorderModal ReorderModal { get; set; } = new();
private CustomerInventoryReorderModalOverlay CustomerInventoryReorderOverlay { get; set; } = new();
private List<ProductInventoryView> Inventory { get; set; } = new();
@ -53,7 +53,7 @@ public partial class AdvisorCustomerInventoryListPage : IDisposable
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
Company = await CompanyRepo.GetCompanyById(CompanyId);
Company = await Customers.GetCompanyById(CompanyId);
// fetch product inventory
await FetchProductInventory();
@ -64,8 +64,8 @@ public partial class AdvisorCustomerInventoryListPage : IDisposable
private async Task OnReorderCallback(string sku)
{
// fetch item from http repo
SalesItem = await AdvisorCatalogRepo.GetSalesItemSku(Company.CountryCode.ToLower(), sku);
ReorderModal.Show();
SalesItem = await Catalog.GetSalesItemSku(Company.CountryCode.ToLower(), sku);
CustomerInventoryReorderOverlay.Show();
}
private async Task OnSelectedItem(DraftItem draftItem)
@ -92,7 +92,7 @@ public partial class AdvisorCustomerInventoryListPage : IDisposable
}
Logger.LogDebug("pulling products from backend");
// fetch product history
Inventory = await HistoryRepo.FetchInventory(CompanyId);
Inventory = await CustomerHistory.FetchInventory(CompanyId);
// default sort order by description
if (Inventory.Any())
Inventory = Inventory.OrderBy(x => x.Description).ToList();

View file

@ -16,9 +16,9 @@
*@
@using Wonky.Client.Components
@using Microsoft.AspNetCore.Authorization
@page "/customers/{CompanyId}/invoices"
@page "/advisor/customers/{CompanyId}/invoices"
@attribute [Authorize(Roles = "Advisor")]
<PageTitle>Faktura Oversigt for @Company.Name</PageTitle>
@if (!string.IsNullOrWhiteSpace(Company.Name))
{
<div class="row pt-2 pb-1 rounded-2 bg-dark text-white">
@ -26,14 +26,14 @@
<h4 class="pt-1">@Company.Name</h4>
</div>
<div class="col-sm-3 align-content-end">
<a class="btn btn-primary d-block" href="/customers/@Company.CompanyId"><i class="bi-arrow-right"></i> Kundekort</a>
<a class="btn btn-primary d-block" href="/advisor/customers/@Company.CompanyId"><i class="bi-arrow-right"></i> Kundekort</a>
</div>
<div class="col-sm-3 align-content-end">
<a class="btn btn-primary d-block" href="/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>
<CustomerInvoiceListComponent OnShowInvoice="CallInvoiceModal" CompanyId="@_companyId" InvoiceList="@CompanyInvoices.Invoices"/>
<InvoiceViewModal CompanyId="@_companyId" InvoiceId="@InvoiceId" @ref="InvoiceView" />
<CustomerInvoiceViewModalOverlay CompanyId="@_companyId" InvoiceId="@InvoiceId" @ref="CustomerInvoiceView" />
}
@if (Working)

View file

@ -15,17 +15,17 @@ namespace Wonky.Client.Pages;
public partial class AdvisorCustomerInvoiceListPage : IDisposable
{
[Parameter] public string CompanyId { get; set; } = "";
[Inject] public IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public IAdvisorCustomerHistoryRepository HistoryRepo { get; set; }
[Inject] public IToastService Toaster { get; set; }
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public ILogger<AdvisorCustomerInvoiceListPage> Logger { get; set; }
[Inject] private IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] private HttpInterceptorService Interceptor { get; set; }
[Inject] private IAdvisorCustomerHistoryRepository HistoryRepo { get; set; }
[Inject] private IToastService Toaster { get; set; }
[Inject] private ILocalStorageService Storage { get; set; }
[Inject] private ILogger<AdvisorCustomerInvoiceListPage> Logger { get; set; }
private InvoiceListView CompanyInvoices { get; set; } = new();
private CompanyDto Company { get; set; } = new();
private bool Working { get; set; }
private InvoiceViewModal InvoiceView { get; set; } = new();
private CustomerInvoiceViewModalOverlay CustomerInvoiceView { get; set; } = new();
private string InvoiceId { get; set; } = "";
private bool Working { get; set; }
private bool AllSet { get; set; }
private string _companyId = "";
@ -63,7 +63,7 @@ public partial class AdvisorCustomerInvoiceListPage : IDisposable
private void CallInvoiceModal(string invoiceId)
{
InvoiceId = invoiceId;
InvoiceView.Show();
CustomerInvoiceView.Show();
}
private async Task<InvoiceListView> FetchCompanyInvoices()
@ -80,13 +80,18 @@ public partial class AdvisorCustomerInvoiceListPage : IDisposable
}
Logger.LogDebug("pulling invoices from backend");
// pull invoices
var invoices = await HistoryRepo.FetchInvoiceList(_companyId);
var companyInvoices = await HistoryRepo.FetchInvoiceList(_companyId);
if (companyInvoices.Invoices.Any())
companyInvoices.Invoices = companyInvoices.Invoices
.OrderByDescending(x => x.DocumentDate)
.ToList();
// send invoices to storage
await Storage.SetItemAsync($"{_companyId}-invoices", invoices);
await Storage.SetItemAsync($"{_companyId}-invoices", companyInvoices);
await Storage.SetItemAsync($"{_companyId}-iDate", $"{DateTime.Now:yyyy-MM-dd}");
Logger.LogDebug("return invoices from backend");
Working = false;
return invoices;
return companyInvoices;
}
public void Dispose()

View file

@ -17,39 +17,46 @@
@using Wonky.Client.Components
@using Microsoft.AspNetCore.Authorization
@page "/customers"
@page "/advisor/customers"
@attribute [Authorize(Roles = "Advisor")]
<PageTitle>Kunde oversigt</PageTitle>
<div class="sticky-top bg-dark text-light rounded-2 px-3">
<div class="row g-3">
<div class="col-sm-2">
<CompanySearchColumnComponent OnChanged="SetSearchCol" />
<CustomerSearchColumnComponent OnChanged="SetSearchCol" />
</div>
<div class="col-sm-6">
<CompanySearchPhraseComponent OnChanged="SetSearchPhrase" />
<CustomerSearchPhraseComponent OnChanged="SetSearchPhrase" />
</div>
<div class="col-sm-2">
<CompanySortComponent OnChanged="SetSortCol" />
<CustomerSortComponent OnChanged="SetSortCol" />
</div>
<div class="col-sm-2">
<PageSizeComponent OnChanged="SetPageSize" />
</div>
<div class="col-sm-2">
<button type button class="btn btn-warning @(@IncludeFolded ? "active" : "")"
data-bs-toggle="button" aria-pressed="@IncludeFolded" @onclick="OnFoldedClick">
@ButtonFoldedText
</button>
@*
<div class="form-check">
<input type="checkbox" id="folded" class="form-check-input" checked="@IncludeFolded" @onclick="OnFoldedClick" >
<label for="folded" class="form-check-label">Ophørte</label>
</div>
*@
</div>
<div class="col-sm-8">
<PaginationComponent MetaData="PageData" Spread="2" SelectedPage="SelectedPage"/>
</div>
<div class="col-sm-2 text-end">
<a class="btn btn-success text-nowrap" href="/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>
<AdvisorCompanyTableComponent CompanyList="Companies" OnDelete="DeleteCompany" />
<AdvisorCustomerListComponent CompanyList="Companies" OnDelete="DeleteCompany" />
@if (Working)
{

View file

@ -30,11 +30,11 @@ namespace Wonky.Client.Pages
{
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] private ILocalStorageService Storage { get; set; }
[Inject] private UserProfileService ProfileService { get; set; }
[Inject] private IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] private HttpInterceptorService Interceptor { get; set; }
[Inject] private NavigationManager Navigator { get; set; }
private List<CompanyDto> Companies { get; set; } = new();
private UserPref Prefs { get; set; } = new();
private UserInfoView UserInfo { get; set; } = new();
@ -43,6 +43,7 @@ namespace Wonky.Client.Pages
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 void OnParametersSet()
{
@ -66,24 +67,26 @@ namespace Wonky.Client.Pages
Paging.SearchTerm = SavedSearch;
// get companies
await FetchCompanies();
await FetchCustomers();
Working = false;
}
private async Task OnFoldedClick()
{
Working = true;
IncludeFolded = !IncludeFolded;
ButtonFoldedText = IncludeFolded ? "Vis Aktive" : "Vis Ophørte";
Companies = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.HasFolded = IncludeFolded ? 1 : 0;
await FetchCompanies();
await FetchCustomers();
}
private async Task SelectedPage(int page)
{
Companies = new List<CompanyDto>();
Paging.PageNumber = page;
await FetchCompanies();
await FetchCustomers();
}
private async Task SetSearchCol(string searchColumn)
@ -91,14 +94,14 @@ namespace Wonky.Client.Pages
Companies = new List<CompanyDto>();
Paging.SearchColumn = searchColumn;
Paging.PageNumber = 1;
await FetchCompanies();
await FetchCustomers();
}
private async Task SetPageSize(string pageSize)
{
Companies = new List<CompanyDto>();
Paging.PageSize = Convert.ToInt32(pageSize);
Paging.PageNumber = 1;
await FetchCompanies();
await FetchCustomers();
}
private async Task SetSearchPhrase(string searchTerm)
@ -106,14 +109,14 @@ namespace Wonky.Client.Pages
Companies = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.SearchTerm = searchTerm;
await FetchCompanies();
await FetchCustomers();
}
private async Task SetSortCol(string orderBy)
{
Companies = new List<CompanyDto>();
Paging.OrderBy = orderBy;
await FetchCompanies();
await FetchCustomers();
}
/// <summary>
@ -126,10 +129,10 @@ namespace Wonky.Client.Pages
await CompanyRepo.DeleteCompany(companyId);
if (Paging.PageNumber > 1 && Companies.Count == 1)
Paging.PageNumber--;
await FetchCompanies();
await FetchCustomers();
}
private async Task FetchCompanies()
private async Task FetchCustomers()
{
Working = true;
var pageRes = await CompanyRepo.GetCompanies(Paging);

Some files were not shown because too many files have changed in this diff Show more