wip - creating phone order page

This commit is contained in:
Frede Hundewadt 2023-01-05 17:56:42 +01:00
parent 2f900393e9
commit e1b9004629
8 changed files with 464 additions and 69 deletions

View file

@ -24,6 +24,14 @@ namespace Wonky.Client.HttpInterfaces;
/// <remarks>Possibly duplicated in IReportHttpRepository</remarks>
public interface ICountryReportRepository
{
/// <summary>
/// Report Exist for given data (SalesRep)
/// </summary>
/// <param name="salesRepId"></param>
/// <param name="workDate"></param>
/// <returns></returns>
Task<bool> ReportExist(string salesRepId, string workDate);
/// <summary>
/// Get Reports for given userId (Office)
/// </summary>

View file

@ -47,6 +47,17 @@ public class CountryReportRepository : ICountryReportRepository
_apiConfig = configuration.Value;
}
/// <summary>
/// Report Exist for given data (SalesRep)
/// </summary>
/// <param name="workDate"></param>
/// <returns></returns>
public async Task<bool> ReportExist(string salesRepId, string workDate)
{
var result = await _client.GetFromJsonAsync<SalesReportClosedView>(
$"{_apiConfig.OfficeReports}/exist/{workDate}");
return result.ReportClosed;
}
/// <summary>
/// Get Reports for given userId (Office)
/// </summary>

View file

@ -18,7 +18,6 @@ using System.Text.Json;
using Blazored.LocalStorage;
using Blazored.Toast.Services;
using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Wonky.Client.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;

View file

@ -0,0 +1,238 @@
@*
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
*@
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Roles = "Admin,Warehouse")]
@page "/office/customers/{CountryCode}/{CompanyId}/view"
<div class="row bg-dark text-white rounded-2 mb-2 py-2 align-items-center">
<div class="col">
<WorkDateComponent OnChangedCallback="@WorkDateComponentCallback"/>
</div>
</div>
<div class="row bg-light border-1 border-dark rounded-3 p-3">
<div class="col">
<h3>@Activity.Name - @Activity.Account</h3>
</div>
</div>
@if (ReportClosed)
{
<div class="row">
<div class="col">
<h3>Der kan ikke oprettes bestilling når der findes rapport for @SelectedDate.ToShortDateString()</h3>
</div>
</div>
}
else
{
<EditForm EditContext="ActivityContext">
<DataAnnotationsValidator/>
<div class="row mb-1">
<div class="form-check">
<InputCheckbox id="express" class="form-check-input" @bind-Value="@Activity.Express"/>
<label class="form-check-label" for="express">Express</label>
</div>
</div>
<div class="row mb-1">
<label for="referenceNumber" class="col-md-2 col-form-label">Rekvisition</label>
<div class="col-md-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">
<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>
</div>
<div class="row mb-2">
<label for="phone" class="col-md-2 col-form-label">Tlf.</label>
<div class="col-md-4">
<InputText id="phone" class="form-control" @bind-Value="Activity.Phone"/>
<ValidationMessage For="@(() => Activity.Phone)"></ValidationMessage>
</div>
</div>
<div id="this-draft" style="@(Activity.ActivityStatusEnum is "order" or "quote" ? "display: block" : "display:none")">
@* Order lines -----------------------------------------------------*@
<div class="row">
<div class="col">
<table class="sticky-top table table-hover table-striped table-bordered">
<thead>
<tr class="bg-dark text-white">
<th scope="col" colspan="7">
Ordrekladde <span class="mx-2 draft-expires-msg">Global kladde (udløber efter @(DraftProvider.Draft.TimeToLiveInSeconds / 60)m inaktivitet)</span>
</th>
<th scope="col" class="text-end">
<button type="button" class="btn btn-danger btn-sm" @onclick="@DeleteDraft" disabled="@(DraftProvider.Draft.Items.Count == 0)"><i class="bi-trash"></i> Slet kladde</button>
</th>
</tr>
<tr class="bg-dark opacity-75 text-white">
<th scope="col">Navn</th>
<th scope="col" class="text-nowrap">Varenr</th>
<th scope="col" class="text-end">Antal</th>
<th scope="col" class="text-end">Enhedspris</th>
<th scope="col" class="text-center">%</th>
<th scope="col" class="text-end">Linjesum</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@if (DraftProvider.Draft.Items.Count > 0)
{
@foreach (var cartItem in DraftProvider.Draft.Items)
{
<tr>
<td class="align-middle">@cartItem.Item.Name</td>
<td class="align-middle">@cartItem.Item.Sku</td>
<td class="align-middle text-end">@cartItem.Quantity</td>
<td class="align-middle text-end">@($"{cartItem.Price:N2}")</td>
<td class="align-middle text-end">@($"{cartItem.Discount:N2}")</td>
<td class="align-middle text-end">@($"{cartItem.LineTotal:N2}")</td>
<td class="align-middle text-end">
<button type="button" class="btn btn-danger" @onclick="@(() => RemoveItem(cartItem))"><i class="bi-trash2"></i> Slet Linje</button>
</td>
</tr>
}
}
<tr>
<td colspan="4"></td>
<td class="align-middle text-black text-end fw-bold">Total</td>
<td class="align-middle text-black text-end fw-bold">@($"{DraftProvider.Draft.Total:N2}")</td>
<td class="align-middle text-end">
<button class="btn btn-primary" type="button" @onclick="CallPriceListModal">
<i class="bi-plus"></i> Ny linje
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
@* draft line ----------------------------------------------------- *@
<div class="row">
<div class="col">
@if (!string.IsNullOrWhiteSpace(SelectedItem.Name) && ShowItem)
{
<table id="draft-line" class="table table-bordered">
<thead>
<tr class="bg-dark text-white">
<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 scope="col">Varenr.</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr>
<td class="align-middle">
<input type="number" class="form-control" @bind-value="@Quantity"/>
</td>
<td class="align-middle">
<div class="input-group">
<input type="number" class="form-control" @bind-value="@Price"/>
<button class="btn btn-warning" type="button" @onclick="CallPriceHistoryModal">
<i class="bi-list-ul"></i>
</button>
</div>
</td>
<td class="align-middle">
<input type="number" class="form-control" @bind-value="@Discount"/>
</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>
</td>
</tr>
</tbody>
</table>
}
</div>
</div>
@* end draft line ------------------------------------------------- *@
</div>
<div class="accordion" id="crmActivity">
@* Delivery address *@
<div class="accordion-item" style="@(Activity.ActivityStatusEnum == "order" ? "display: block" : "display:none")">
<h2 class="accordion-header" id="deliveryHeader">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#deliveryBody" aria-expanded="false" aria-controls="deliveryBody">
Leveringsadresse
</button>
</h2>
<div id="deliveryBody" class="accordion-collapse collapse"
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>
<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>
<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>
<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>
<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>
<div class="col-md-10">
<InputText id="dlvCity" class="form-control" @bind-Value="Activity.DlvCity"/>
</div>
</div>
</div>
</div>
</div>
</div>
</EditForm>
<div class="row mt-5 mb-2">
<div class="col-sm-10 text-end">
<button type="button" class="btn btn-warning" @onclick="CreateActivity" disabled="@(PoFormInvalid || Working)"><i class="bi-plus"></i> Opret ordre</button>
</div>
</div>
}

View file

@ -0,0 +1,204 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using System.Text.Json;
using Wonky.Client.HttpInterceptors;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Wonky.Client.HttpInterfaces;
using Wonky.Client.Models;
using Wonky.Client.Services;
using Wonky.Client.Shared;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
namespace Wonky.Client.Pages;
public partial class CountryCreateNewOrderPage : IDisposable
{
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string CountryCode { get; set; } = "";
[CascadingParameter] public DraftStateProvider DraftProvider { get; set; }
[Inject] public ILogger<CountryCreateNewOrderPage> Logger { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public ICountryCustomerRepository CustomerRepo { get; set; }
[Inject] public ICountryCustomerHistoryRepository InventoryRepo { get; set; }
[Inject] public ICountryReportRepository ReportRepo { get; set; }
[Inject] public ISystemUserRepository UserRepo { get; set; }
private DateTime SelectedDate { get; set; }
private CompanyDto _company { get; set; } = new();
private List<ProductInventoryView> CustomerInventory { get; set; } = new();
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 EditContext ActivityContext { get; set; }
private bool ShowItem { get; set; }
private string Quantity { get; set; } = "1";
private string Price { get; set; } = "0";
private string Discount { get; set; } = "0";
private bool ReportClosed { get; set; }
private bool PoFormInvalid { get; set; } = true;
private bool Working { get; set; }
private WebUserInfoView ThisUserInfo { get; set; } = new();
protected override async Task OnInitializedAsync()
{
ActivityContext = new EditContext(Activity);
ActivityContext.OnFieldChanged += HandleFieldChanged;
ActivityContext.OnValidationStateChanged += ValidationChanged;
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
_company = await CustomerRepo.GetByCustomerId(CountryCode, CompanyId);
var today = $"{DateTime.Now:yyyy-MM-dd}";
var ts = _company.HistorySync;
if (ts != today)
ts = await InventoryRepo.ErpInvoiceToCrmRpc(CountryCode, CompanyId, ts);
while (ts != today)
await Task.Delay(500);
// raise flag if report is closed
ReportClosed = await ReportRepo.ReportExist(_company.SalesRepId, ts);
// customer inventory
CustomerInventory = await InventoryRepo.FetchInventory(CountryCode, CompanyId);
// get sales rep info
ThisUserInfo = await UserRepo.GetAdvisorInfo(_company.SalesRepId);
// setting up base data
Activity.BcId = _company.BcId;
Activity.ActivityStatusEnum = "noSale";
Activity.VisitTypeEnum = _company.Account is "" or "NY" ? "new" : "recall";
Activity.ActivityTypeEnum = "phone";
Activity.ActivityStatusEnum = "order";
Activity.CompanyId = _company.CompanyId;
Activity.SalesRepId = _company.SalesRepId;
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;
// Initialize date variable
SelectedDate = DateTime.Now;
}
private void CallPriceListModal()
{
}
private void CallPriceHistoryModal()
{
}
private void DeleteDraft()
{
}
private void AddItem(SalesItemView item)
{
}
private void RemoveItem(DraftItem item)
{
}
private void CreateActivity()
{
}
/// <summary>
/// Work Date component callback
/// </summary>
/// <param name="workDate"></param>
private async Task WorkDateComponentCallback(string workDate)
{
ReportClosed = await ReportRepo.ReportExist(_company.SalesRepId, workDate);
SelectedDate = DateTime.Parse(workDate);
Activity.ActivityDate = workDate;
}
/// <summary>
/// Edit Context handle field change
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{
Logger.LogDebug("ActivityNewPage => HandleFieldChanged => ActivityStatusEnum <= '{}'", Activity.ActivityStatusEnum);
DraftProvider.Draft.DraftType = Activity.ActivityStatusEnum;
if (Activity.ActivityStatusEnum == "noSale")
{
Logger.LogDebug("ActivityNewPage => ActivityStatusEnum == 'noSale' <= remove items");
DraftProvider.Draft.Items = new List<DraftItem>();
}
// InvalidCanvas = InvalidActivityType;
if (Activity.YourRef.Length > 35 || Activity.ReferenceNumber.Length > 20 || DraftProvider.Draft.Items.Count == 0)
{
PoFormInvalid = true;
return;
}
PoFormInvalid = !ActivityContext.Validate();
StateHasChanged();
}
/// <summary>
/// Edit Context handle validation change
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ValidationChanged(object sender, ValidationStateChangedEventArgs e)
{
if (string.IsNullOrEmpty(Activity.ActivityTypeEnum) && !ReportClosed)
{
PoFormInvalid = true;
return;
}
PoFormInvalid = false;
ActivityContext.OnFieldChanged -= HandleFieldChanged;
ActivityContext.OnValidationStateChanged -= ValidationChanged;
ActivityContext = new EditContext(Activity);
ActivityContext.OnFieldChanged += HandleFieldChanged;
ActivityContext.OnValidationStateChanged += ValidationChanged;
}
/// <summary>
/// Implement Dispose from IDisposable
/// </summary>
public void Dispose()
{
Interceptor.DisposeEvent();
ActivityContext.OnFieldChanged -= HandleFieldChanged;
ActivityContext.OnValidationStateChanged -= ValidationChanged;
}
}

View file

@ -1,28 +0,0 @@
@*
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
*@
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components
@using Wonky.Client.Components
@attribute [Authorize(Roles = "Admin,Warehouse")]
@page "/office/customers/{CountryCode}/{CompanyId}/view"
<div class="row">
<div class="col">
<h3>Data for @CountryCode.ToUpper() Id '@CompanyId' er ikke klar</h3>
</div>
</div>

View file

@ -1,38 +0,0 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using System.Text.Json;
using System.Text.RegularExpressions;
using Blazored.LocalStorage;
using Blazored.Toast.Services;
using Wonky.Client.HttpInterceptors;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Wonky.Client.Helpers;
using Wonky.Client.HttpInterfaces;
using Wonky.Client.Models;
using Wonky.Client.Services;
using Wonky.Client.Shared;
using Wonky.Entity.DTO;
using Wonky.Entity.Models;
using Wonky.Entity.Views;
namespace Wonky.Client.Pages;
public partial class CountryCustomerViewPage
{
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string CountryCode { get; set; } = "";
}

View file

@ -59,14 +59,15 @@ builder.Services.AddScoped<IAdvisorCustomerRepository, AdvisorCustomerRepository
builder.Services.AddScoped<IAdvisorContactRepository, AdvisorContactRepository>();
builder.Services.AddScoped<IAdvisorCustomerHistoryRepository, AdvisorCustomerHistoryRepository>();
builder.Services.AddScoped<IAdvisorTaskItemRepository, AdvisorTaskItemRepository>();
builder.Services.AddScoped<IAdvisorReportRepository, AdvisorReportRepository>();
builder.Services.AddScoped<IWorkplaceRepository, WorkplaceRepository>();
builder.Services.AddScoped<IAdvisorCatalogRepository, AdvisorCatalogRepository>();
// administrative repositories
builder.Services.AddScoped<IAdvisorReportRepository, AdvisorReportRepository>();
builder.Services.AddScoped<ISystemUserRepository, SystemUserRepository>();
builder.Services.AddScoped<ICountryCustomerHistoryRepository, CountryCustomerHistoryRepository>();
builder.Services.AddScoped<ICountryCatalogRepository, CountryCatalogRepository>();
builder.Services.AddScoped<ICountryCustomerRepository, CountryCustomerRepository>();
builder.Services.AddScoped<ICountryReportRepository, CountryReportRepository>();
builder.Services.AddScoped<ISystemUserRepository, SystemUserRepository>();
// warehouse repository
builder.Services.AddScoped<IWarehouseRepository, WarehouseRepository>();
// mail service