wip - refactor and adding functionality

This commit is contained in:
Frede Hundewadt 2022-06-19 13:55:29 +02:00
parent b0651d2ea6
commit f731bc4b58
38 changed files with 1076 additions and 783 deletions

View file

@ -1,6 +1,23 @@
@*
// 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]
//
*@
@if (Enabled == 1) @if (Enabled == 1)
{ {
<a type="button" class="btn btn-success" href="/company/@CompanyId/activity">Besøg</a> <a type="button" class="btn btn-success" href="/companies/@CompanyId/activities/new">Besøg</a>
} }
else else
{ {

View file

@ -0,0 +1,45 @@
@*
// 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]
//
*@
@if (Activities.Any())
{
<table class="table">
<thead>
<tr class="bg-dark text-white">
<th scope="col">Besøg</th>
<th scope="col">Demo</th>
<th scope="col">Salg</th>
<th class="text-end" scope="col">Beløb</th>
</tr>
</thead>
<tbody>
@foreach (var activity in Activities)
{
<tr>
<td>@activity.Company.Name, @activity.Company.ZipCity</td>
<td>@activity.Demo</td>
<td>@activity.SalesResume</td>
<td class="text-end">@activity.OrderAmount</td>
</tr>
}
</tbody>
</table>
}
else
{
<AppSpinner/>
}

View file

@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Components;
using Wonky.Entity.Views;
namespace Wonky.Client.Components;
public partial class ActivityTableComponent
{
[Parameter] public List<NgReportActivityView> Activities { get; set; }
}

View file

@ -13,6 +13,7 @@
// You should have received a copy of the GNU Affero General Public License // 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] // along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
// //
*@<div> *@
<img class="spinner pe-3" src="loader.gif" alt="Afventer svar fra netværk ..."/> Venter ... <div>
<img class="spinner pe-3" src="loader.gif" alt="Venter på svar ..."/> Afventer server ...
</div> </div>

View file

@ -16,6 +16,7 @@
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html] // along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
// //
*@ *@
<span class="version">@_app?.Version</span>@if(_app!.IsBeta){<span class="version">-beta</span>} <span class="version">@_app?.Version</span>@if(_app!.IsBeta){<span class="version">-beta</span>}
@code @code
{ {

View file

@ -1,3 +1,20 @@
@*
// 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 class="table">
<thead> <thead>
<tr> <tr>

View file

@ -20,42 +20,32 @@
@if (Companies.Any()) @if (Companies.Any())
{ {
<table class="table"> <div class="list-group list-group-flush">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">Navn</th>
<th scope="col">Konto</th>
<th scope="col">Bynavn</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@foreach (var company in Companies) @foreach (var company in Companies)
{ {
<tr> <a class="list-group-item list-group-item-action" href="/companies/@company.CompanyId">
<td class="align-middle"> <div class="row align-items-center">
<DisplayStateComponent StateClass="@(company.HasFolded == 1 <div class="col-sm-1">
? "the-dead" : Utils.GetVisitState(company.NextVisit))"> <DisplayStateComponent StateClass="@(company.HasFolded == 1 ? "the-dead" : Utils.GetVisitState(company.NextVisit))">
</DisplayStateComponent> </DisplayStateComponent>
</td> </div>
<td class="align-middle"> <div class="col">
@company.Name @company.Name
</td> </div>
<td class="align-middle"> <div class="col">
@company.Account @company.Account
</td> </div>
<td class="align-middle"> <div class="col">
@company.City @company.City
</td> </div>
<td class="align-middle"> <div class="col">
<a class="btn btn-edit" href="/company/@company.CompanyId/update">Rediger</a>
<ActivityButton CompanyId="@company.CompanyId" Enabled="@company.ValidVat"></ActivityButton> <ActivityButton CompanyId="@company.CompanyId" Enabled="@company.ValidVat"></ActivityButton>
</td> </div>
</tr>
</div>
</a>
} }
</tbody> </div>
</table>
} }
else else
{ {

View file

@ -28,6 +28,5 @@
<WorkDateComponent OnChanged="GetCalender"></WorkDateComponent> <WorkDateComponent OnChanged="GetCalender"></WorkDateComponent>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,135 @@
@*
// 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-striped">
<thead>
<tr class="bg-dark text-white">
<th></th>
<th class="text-center" colspan="2" scope="col">Dagens Demo @(Report.NewDemoCount + Report.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">
<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">@Report.NewVisitCount</td>
<td class="text-end">@Report.NewDemoCount</td>
<td class="text-end">@Report.NewSaleCount</td>
<td class="text-end border-end">@Report.NewTurnover</td>
<td class="text-end">@Report.NewVisitCountMonth</td>
<td class="text-end">@Report.NewDemoCountMonth</td>
<td class="text-end">@Report.NewSaleCountMonth</td>
<td class="text-end">@Report.NewTurnoverMonth</td>
</tr>
<tr>
<th scope="row">R</th>
<td class="text-end">@Report.RecallVisitCount</td>
<td class="text-end">@Report.RecallDemoCount</td>
<td class="text-end">@Report.RecallSaleCount</td>
<td class="text-end border-end">@Report.RecallTurnover</td>
<td class="text-end">@Report.RecallVisitCountMonth</td>
<td class="text-end">@Report.RecallDemoCountMonth</td>
<td class="text-end">@Report.RecallSaleCountMonth</td>
<td class="text-end">@Report.RecallTurnoverMonth</td>
</tr>
<tr>
<th scope="row">SAS</th>
<td class="bg-light"></td>
<td class="bg-light"></td>
<td class="text-end">@Report.SasCount</td>
<td class="text-end border-end">@Report.SasTurnover</td>
<td class="bg-light"></td>
<td class="bg-light"></td>
<td class="text-end">@Report.SasCountMonth</td>
<td class="text-end">@Report.SasTurnoverMonth</td>
</tr>
<tr>
<th scope="row">TOTAL</th>
<td class="text-end">@Report.TotalVisitCount</td>
<td class="text-end">@Report.TotalDemoCount</td>
<td class="text-end">@Report.TotalSaleCount</td>
<td class="text-end border-end">@Report.TotalTurnover</td>
<td class="text-end">@Report.TotalVisitCountMonth</td>
<td class="text-end">@Report.TotalDemoCountMonth</td>
<td class="text-end">@Report.TotalSaleCountMonth</td>
<td class="text-end">@Report.TotalTurnoverMonth</td>
</tr>
</tbody>
</table>
<table class="table">
<thead>
<tr class="bg-dark text-white">
<th scope="col">
Km Aften
</th>
<th scope="col">
Km Morgen
</th>
<th scope="col">
Km Kørt Dag
</th>
<th scope="col">
Km Kørt Md.
</th>
<th scope="col">
Km Privat
</th>
<th scope="col">
Km Privat Md.
</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Report.KmEvening</td>
<td>@Report.KmMorning</td>
<td>@Report.Distance</td>
<td>@Report.DistanceMonth</td>
<td>@Report.DistancePrivate</td>
<td>@Report.DistancePrivateMonth</td>
</tr>
</tbody>
</table>
@if (!string.IsNullOrWhiteSpace(Report.Description) || !string.IsNullOrWhiteSpace(Report.SupervisedBy))
{
<table class="table">
<thead>
<tr class="bg-dark text-white">
<th class="w-50" scope="col">Tekst</th>
<th class="w-50" scope="col">Medkørende Supervisor</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Report.Description</td>
<td>@Report.SupervisedBy</td>
</tr>
</tbody>
</table>
}

View file

@ -0,0 +1,11 @@
using Wonky.Entity.Views;
using Microsoft.AspNetCore.Components;
namespace Wonky.Client.Components;
public partial class ReportSummaryComponent
{
[Parameter]
public NgSalesReport Report { get; set; }
}

View file

@ -18,7 +18,7 @@
@using System.Security.Claims @using System.Security.Claims
<AuthorizeView> <AuthorizeView>
<Authorized> <Authorized>
<a class="btn btn-outline-light" href="logout">Log af</a> <a class="btn btn-outline-light" href="logout">LOG AF</a>
<a class="btn btn-outline-light" href="info">INFO</a> <a class="btn btn-outline-light" href="info">HJÆLP</a>
</Authorized> </Authorized>
</AuthorizeView> </AuthorizeView>

View file

@ -91,13 +91,17 @@ public class CompanyHttpRepository : ICompanyHttpRepository
return company ?? new CompanyDto(); return company ?? new CompanyDto();
} }
/// <summary>
/// Create company from model
/// </summary>
/// <param name="model"></param>
/// <returns>company id</returns>
public async Task<string> CreateCompany(CompanyDto model) public async Task<string> CreateCompany(CompanyDto model)
{ {
var response = await _client.PostAsJsonAsync($"{_apiConfig.CustomerEndpoint}", model); var response = await _client.PostAsJsonAsync($"{_apiConfig.CustomerEndpoint}", model);
var content = await response.Content.ReadAsStringAsync(); var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content); var result = JsonSerializer.Deserialize<CompanyDto>(content, _options);
var result = JsonSerializer.Deserialize<CompanyDto>(content); return result.CompanyId;
return result == null ? "" : result.CompanyId;
} }
public async Task<bool> UpdateCompany(string companyId, CompanyDto model) public async Task<bool> UpdateCompany(string companyId, CompanyDto model)

View file

@ -7,6 +7,7 @@ namespace Wonky.Client.HttpRepository;
public interface IReportHttpRepository public interface IReportHttpRepository
{ {
Task<bool> ReportExist(string workDate); Task<bool> ReportExist(string workDate);
Task<List<NgSalesReport>> GetReports();
Task<NgSalesReportView> GetReport(string workDate); Task<NgSalesReportView> GetReport(string workDate);
Task<ReportInitDto> InitializeReportData(string workDate); Task<ReportInitDto> InitializeReportData(string workDate);
Task<ApiResponse> PostReport(string workDate, ReportDto reportDto); Task<ApiResponse> PostReport(string workDate, ReportDto reportDto);

View file

@ -30,6 +30,14 @@ public class ReportHttpRepository :IReportHttpRepository
_apiConfig = configuration.Value; _apiConfig = configuration.Value;
} }
public async Task<List<NgSalesReport>> GetReports()
{
var response = await _client.GetStringAsync($"{_apiConfig.ReportEndpoint}");
Console.WriteLine(response);
return new List<NgSalesReport>();
}
public async Task<bool> ReportExist(string workDate) public async Task<bool> ReportExist(string workDate)
{ {
var result = var result =

View file

@ -0,0 +1,374 @@
@*
// 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]
//
*@
@page "/companies/{companyId}/activities/new"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Roles = "Adviser")]
@using Wonky.Client.Components
<div class="card">
<div class="card-header">
<div class="row align-items-md-center">
<div class="col">
<h3 class="workDate">@_workDate.ToLongDateString()</h3>
</div>
<div class="col">
<WorkDateComponent OnChanged="SetWorkDate"></WorkDateComponent>
</div>
</div>
</div>
<div class="card-body">
<EditForm EditContext="_editContext">
<DataAnnotationsValidator/>
<div class="accordion-flush" id="crmActivity">
<div class="accordion-item">
<h2 class="accordion-header" id="activityHeader">
<button class="accordion-button bg-light" type="button"
data-bs-toggle="collapse" data-bs-target="#activityBody"
aria-expanded="true" aria-controls="activityBody">
@_draft.Name - @_draft.Account
</button>
</h2>
<div id="activityBody" class="accordion-collapse collapse show"
aria-labelledby="activityHeader" data-bs-parent="#crmActivity">
<div class="accordion-body">
<div class="row mb-1">
<label for="activityType" class="col-md-2 col-form-label">Ordre Type</label>
<div class="col-md-4">
<InputSelect id="activityType" class="form-select" @bind-Value="@_draft.ActivityTypeEnum">
<option value="">ORDRE TYPE</option>
<option value="onSite">Besøg</option>
<option value="phone">Telefon</option>
</InputSelect>
<ValidationMessage For="@(() => _draft.ActivityTypeEnum)"></ValidationMessage>
</div>
<label for="statusType" class="col-md-2 col-form-label">Status</label>
<div class="col-md-4">
<InputSelect id="statusType" class="form-select" @bind-Value="@_draft.ActivityStatusEnum">
<option value="noSale" selected>Ingen salg</option>
<option value="order">Bestilling</option>
<option value="quote">Tilbud</option>
</InputSelect>
<ValidationMessage For="@(() => _draft.ActivityStatusEnum)"></ValidationMessage>
</div>
</div>
<div class="row mb-1">
<label for="demo" class="col-md-2 col-form-label">Demo</label>
<div class="col-md-4">
<InputText id="demo" class="form-control" @bind-Value="_draft.Demo"/>
<ValidationMessage For="@(() => _draft.Demo)"></ValidationMessage>
</div>
<label for="email" class="col-md-2 col-form-label">Epost</label>
<div class="col-md-4">
<InputText id="email" class="form-control" @bind-Value="_draft.EMail"/>
<ValidationMessage For="@(() => _draft.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">
<InputText id="referenceNumber" class="form-control"
@bind-Value="_draft.ReferenceNumber"v/>
<ValidationMessage For="@(() => _draft.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="_draft.YourRef"/>
<ValidationMessage For="@(() => _draft.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="_draft.OrderMessage"/>
<ValidationMessage For="@(() => _draft.OrderMessage)"></ValidationMessage>
</div>
<label for="crmNote" class="col-md-2 col-form-label">Note /Mig</label>
<div class="col-md-4">
<InputTextArea id="crmNote" class="form-control"
@bind-Value="_draft.CrmNote"/>
<ValidationMessage For="@(() => _draft.CrmNote)"></ValidationMessage>
</div>
</div>
<div class="row mb-1">
<label for="attention" class="col-md-2 col-form-label">Att.</label>
<div class="col-md-4">
<InputText id="attention" class="form-control"
@bind-Value="_draft.Attention"/>
<ValidationMessage For="@(() => _draft.Attention)"></ValidationMessage>
</div>
</div>
</div>
</div>
</div>
@* Order lines *@
<div class="accordion-item" style="@(_draft.ActivityStatusEnum is "order" or "quote" ? "display: block" : "display:none")">
<h2 class="accordion-header" id="catalogHeader">
<button class="accordion-button collapsed bg-light" type="button"
data-bs-toggle="collapse" data-bs-target="#catalogBody"
aria-expanded="false" aria-controls="catalogBody">
Varelinjer
</button>
</h2>
<div id="catalogBody" class="accordion-collapse collapse"
aria-labelledby="catalogHeader" data-bs-parent="#crmActivity">
<div class="accordion-body">
<div class="row mb-1">
<div class="col">
<ItemGroupComponent OnChanged="SetItemGroup"/>
</div>
<div class="col">
<ItemSearchComponent OnChanged="SetSearchCol"/>
</div>
<div class="col">
<SearchPhrase OnChanged="SetSearchPhrase"/>
</div>
<div class="col">
<ItemSortComponent OnChanged="SetSortCol"/>
</div>
<div class="col">
<PageSizeComponent OnChanged="SetPageSize"></PageSizeComponent>
</div>
</div>
</div>
<div class="row">
<div class="col">
<PaginationComponent MetaData="_metaData" Spread="2" SelectedPage="SelectedPage"></PaginationComponent>
</div>
</div>
@if (_caltalog.Any())
{
<table class="table table-hover table-striped justify-content-center">
<thead>
<tr>
<th scope="col">Navn</th>
<th scope="col" class="text-nowrap">Varenr</th>
<th scope="col" class="text-nowrap">Fork</th>
<th scope="col">Stk / Pris</th>
</tr>
</thead>
<tbody>
@foreach (var item in _caltalog)
{
<tr>
<td>@item.Name</td>
<td>@item.Sku</td>
<td>@item.ShortName</td>
<td>
<ul class="list-group">
@foreach (var rate in item.Rates)
{
<li class="list-group-item d-flex justify-content-between align-items-end">
<div class="text-sm-start px-2">@rate.Quantity</div>
<div class="text-sm-end">@rate.Rate</div>
<button type="button" class="btn btn-primary btn-sm"
@onclick="@(() => SelectItem(item.ItemId, rate.Quantity, rate.Rate))">
Vælg
</button>
</li>
}
</ul>
</td>
</tr>
}
</tbody>
</table>
}
else
{
<AppSpinner/>
}
@if (_selectedItem != null && ShowItem)
{
<div class="card mb-3 mt-3">
<div class="card-header bg-dark fw-bold text-white">Kladdelinje</div>
<div class="card-body">
<div class="row">
<div class="col col-md-4 fw-bold">
Varenavn
</div>
<div class="col fw-bold">
Varenr
</div>
<div class="col fw-bold">
Antal
</div>
<div class="col fw-bold">
Pris
</div>
<div class="col">
Rabat
</div>
<div class="col">
SAS
</div>
<div class="col">
</div>
</div>
<div class="row">
<div class="col col-md-4">
@_selectedItem.Name
</div>
<div class="col">
@_selectedItem.Sku
</div>
<div class="col">
<input type="number" class="form-control" @bind-value="@Quantity"/>
</div>
<div class="col">
<input type="number" class="form-control" @bind-value="@Price"/>
</div>
<div class="col">
<input type="number" class="form-control" @bind-value="@Discount"/>
</div>
<div class="col">
<input type="checkbox" class="form-check" @bind-value="@Sas"/>
</div>
<div class="col">
<button type="button" class="btn btn-info" @onclick="@(() => AddItem(_selectedItem))">Læg til</button>
</div>
</div>
</div>
</div>
}
@* Order draft lines *@
<div class="card">
<div class="card-header">
Kladdelinjer <span class="mx-2 draft-expires-msg">Global kladde (udløber efter @(DraftStateProvider.Draft.TimeToLiveInSeconds / 60)m inaktivitet)</span>
</div>
<div class="card-body">
<table class="table table-hover table-striped justify-content-center">
<thead>
<tr>
<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-end">Linjesum</th>
<th scope="col">&nbsp;</th>
</tr>
</thead>
<tbody>
@if (DraftStateProvider != null && DraftStateProvider.Draft.Items.Count > 0)
{
@foreach (var cItem in DraftStateProvider.Draft.Items)
{
<tr>
<td>@cItem.Item.Name</td>
<td>@cItem.Item.Sku</td>
<td class="text-end">@cItem.Quantity</td>
<td class="text-end">@cItem.Price</td>
<td class="text-end">@cItem.LineTotal</td>
<td>
<input type="checkbox" checked="@cItem.Sas" disabled/>
</td>
<td>
<button type="button" class="btn btn-warning" @onclick="@(() => RemoveItem(@cItem))">Slet</button>
</td>
</tr>
}
<tr>
<td></td>
<td></td>
<td></td>
<td class="text-black text-end fw-bold">Total</td>
<td class="text-black text-end fw-bold">@DraftStateProvider.Draft.Total</td>
<td></td>
</tr>
}
</tbody>
</table>
</div>
<div class="card-footer">
<div class="row">
<div class="col">
<button type="button" class="btn btn-danger"
@onclick="@DeleteDraft"
disabled="@(DraftStateProvider.Draft.Items.Count == 0)">
Slet kladde
</button>
</div>
</div>
</div>
</div>
</div>
</div>
@* Delivery address *@
<div class="accordion-item" style="@(_draft.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="_draft.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="_draft.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="_draft.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="_draft.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="_draft.DlvCity"/>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-2 mb-2">
<div class="col">
<a class="btn btn-primary" href="/companies">Til Oversigt</a>
</div>
<div class="col">
<a class="btn btn-primary" href="/company/@_company.CompanyId">Tilbage</a>
</div>
<div class="col">
@* <button type="submit" class="btn btn-success" disabled="@InvalidActivity">Gem</button> *@
<button type="button" class="btn btn-success" @onclick="CreateActivity">Opret besøg</button>
</div>
</div>
</EditForm>
</div>
</div>

View file

@ -30,10 +30,10 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Pages; namespace Wonky.Client.Pages;
public partial class ActivityCreate : IDisposable public partial class ActivityCompanyCreate : IDisposable
{ {
// todo: prevent creating activity for workDate with closed report // todo: prevent creating activity for workDate with closed report
[Inject] private ILogger<ActivityCreate> _logger { get; set; } [Inject] private ILogger<ActivityCompanyCreate> _logger { get; set; }
[Inject] private IToastService _toast { get; set; } [Inject] private IToastService _toast { get; set; }
[Inject] private NavigationManager _navigator { get; set; } [Inject] private NavigationManager _navigator { get; set; }
[Inject] private ILocalStorageService _storage { get; set; } [Inject] private ILocalStorageService _storage { get; set; }
@ -50,13 +50,13 @@ public partial class ActivityCreate : IDisposable
PropertyNameCaseInsensitive = true PropertyNameCaseInsensitive = true
}; };
private NgSalesItemView _selectedItem { get; set; } = new(); private NgSalesItemView _selectedItem { get; set; } = new();
private List<NgSalesItemView> SalesItems { get; set; } = new(); private List<NgSalesItemView> _caltalog { get; set; } = new();
private MetaData MetaData { get; set; } = new(); private MetaData _metaData { get; set; } = new();
private Preferences _prefs { get; set; } = new(); private Preferences _prefs { get; set; } = new();
private ActivityDto _poDraft { get; set; } = new(); private ActivityDto _draft { get; set; } = new();
private CompanyDto NgCompany = new(); private CompanyDto _company = new();
private CatalogPagingParams _paging = new(); private CatalogPagingParams _paging = new();
private EditContext DraftContext { get; set; } private EditContext _editContext { get; set; }
private bool _poFormInvalid { get; set; } = true; private bool _poFormInvalid { get; set; } = true;
private bool ShowItem { get; set; } private bool ShowItem { get; set; }
private bool HideButtons { get; set; } private bool HideButtons { get; set; }
@ -72,61 +72,67 @@ public partial class ActivityCreate : IDisposable
private UserInfoView Ux { get; set; } = new(); private UserInfoView Ux { get; set; } = new();
private DateTime _workDate { get; set; } = DateTime.Now; private DateTime _workDate { get; set; } = DateTime.Now;
protected override async Task OnInitializedAsync() protected override async Task OnParametersSetAsync()
{ {
_interceptor.RegisterEvent();
_interceptor.RegisterBeforeSendEvent();
_prefs = await _userPrefs.GetPreferences(); _prefs = await _userPrefs.GetPreferences();
if (!string.IsNullOrWhiteSpace(_prefs.WorkDate)) if (!string.IsNullOrWhiteSpace(_prefs.WorkDate))
_workDate = DateTime.Parse(_prefs.WorkDate); _workDate = DateTime.Parse(_prefs.WorkDate);
_poDraft.ActivityDate = $"{_workDate:yyyy-MM-dd}" ; // raise flag if report is closed
_reportClosdd = await _reportRepo.ReportExist($"{_workDate:yyyy-MM-dd}");
if(_reportClosdd)
_navigator.NavigateTo($"/sales-reports/view/{_workDate:yyyy-MM-dd}");
// check if report is closed }
_reportClosdd = await _reportRepo.ReportExist(_poDraft.ActivityDate); protected override async Task OnInitializedAsync()
{
_editContext = new EditContext(_draft);
_editContext.OnFieldChanged += HandleFieldChanged;
_editContext.OnValidationStateChanged += ValidationChanged;
_interceptor.RegisterEvent();
_interceptor.RegisterBeforeSendEvent();
_draft.ActivityDate = $"{_workDate:yyyy-MM-dd}" ;
// todo - does it make sense to continue if _reportClosed is true?
_paging.SearchColumn = _prefs.ItemSearch ?? "name"; _paging.SearchColumn = _prefs.ItemSearch ?? "name";
_paging.PageSize = Convert.ToInt32(_prefs.PageSize); _paging.PageSize = Convert.ToInt32(_prefs.PageSize);
await GetSalesItems(); await GetSalesItems();
Ux = await _storage.GetItemAsync<UserInfoView>("_xu"); Ux = await _storage.GetItemAsync<UserInfoView>("_xu");
NgCompany = await _companyRepo.GetCompanyById(CompanyId); _company = await _companyRepo.GetCompanyById(CompanyId);
DraftContext = new EditContext(_poDraft);
DraftContext.OnFieldChanged += HandleFieldChanged;
DraftContext.OnValidationStateChanged += ValidationChanged;
// set up identification // set up identification
_poDraft.CompanyId = NgCompany.CompanyId; _draft.CompanyId = _company.CompanyId;
_poDraft.BcId = NgCompany.BcId; _draft.BcId = _company.BcId;
_poDraft.SalesRepId = Ux.Id; _draft.SalesRepId = Ux.Id;
_poDraft.ActivityStatusEnum = "noSale"; _draft.ActivityStatusEnum = "noSale";
_poDraft.VisitTypeEnum = NgCompany.Account is "" or "NY" ? "new" : "recall"; _draft.VisitTypeEnum = _company.Account is "" or "NY" ? "new" : "recall";
// permanent identifications // permanent identifications
_poDraft.SalesRep = Ux.Adviser; _draft.SalesRep = Ux.Adviser;
_poDraft.Account = NgCompany.Account; _draft.Account = _company.Account;
_poDraft.VatNumber = NgCompany.VatNumber; _draft.VatNumber = _company.VatNumber;
_poDraft.EMail = NgCompany.Email; _draft.EMail = _company.Email;
_poDraft.Phone = NgCompany.Phone; _draft.Phone = _company.Phone;
_poDraft.Mobile = NgCompany.Mobile; _draft.Mobile = _company.Mobile;
_poDraft.Name = NgCompany.Name; _draft.Name = _company.Name;
_poDraft.Address1 = NgCompany.Address1; _draft.Address1 = _company.Address1;
_poDraft.Address2 = NgCompany.Address2; _draft.Address2 = _company.Address2;
_poDraft.ZipCode = NgCompany.ZipCode; _draft.ZipCode = _company.ZipCode;
_poDraft.City = NgCompany.City; _draft.City = _company.City;
_poDraft.DlvName = NgCompany.Name; _draft.DlvName = _company.Name;
_poDraft.DlvAddress1 = NgCompany.Address1; _draft.DlvAddress1 = _company.Address1;
_poDraft.DlvAddress2 = NgCompany.Address2; _draft.DlvAddress2 = _company.Address2;
_poDraft.DlvZipCode = NgCompany.ZipCode; _draft.DlvZipCode = _company.ZipCode;
_poDraft.DlvCity = NgCompany.City; _draft.DlvCity = _company.City;
} }
@ -134,21 +140,21 @@ public partial class ActivityCreate : IDisposable
{ {
_logger.LogInformation("WorkDateComponent.OnChanged(SetWorkDate(workDate)) => {workDate}", workDate); _logger.LogInformation("WorkDateComponent.OnChanged(SetWorkDate(workDate)) => {workDate}", workDate);
_workDate = DateTime.Parse(workDate); _workDate = DateTime.Parse(workDate);
_poDraft.ActivityDate = workDate; _draft.ActivityDate = workDate;
} }
private async Task CreateActivity() private async Task CreateActivity()
{ {
HideButtons = true; HideButtons = true;
_poDraft.ActivityDate = _prefs.WorkDate; _draft.ActivityDate = _prefs.WorkDate;
var activityType = _poDraft.ActivityTypeEnum switch var activityType = _draft.ActivityTypeEnum switch
{ {
"phone" => "T", "phone" => "T",
"onSite" => "B", "onSite" => "B",
_ => "" _ => ""
}; };
_poDraft.OurRef = $"{activityType}:{Ux.FullName.Split(" ")[0]}"; _draft.OurRef = $"{activityType}:{Ux.FullName.Split(" ")[0]}";
var ln = 0; var ln = 0;
// post to create activity endpoint // post to create activity endpoint
@ -168,18 +174,18 @@ public partial class ActivityCreate : IDisposable
}; };
lines.Add(line); lines.Add(line);
} }
_poDraft.Lines = lines; _draft.Lines = lines;
await _storage.SetItemAsync(CompanyId, _poDraft); await _storage.SetItemAsync(CompanyId, _draft);
Console.WriteLine(JsonSerializer.Serialize(_poDraft)); Console.WriteLine(JsonSerializer.Serialize(_draft));
var result = await _activityRepo.CreateActivity(_poDraft); var result = await _activityRepo.CreateActivity(_draft);
_toast.ShowSuccess($"{result.Message}."); _toast.ShowSuccess($"{result.Message}.");
_navigator.NavigateTo($"/companies"); _navigator.NavigateTo($"/companies");
} }
private void CheckActivity() private void CheckActivity()
{ {
InvalidActivityType = string.IsNullOrWhiteSpace(_poDraft.ActivityTypeEnum); InvalidActivityType = string.IsNullOrWhiteSpace(_draft.ActivityTypeEnum);
} }
private async Task DeleteDraft() private async Task DeleteDraft()
{ {
@ -189,7 +195,7 @@ public partial class ActivityCreate : IDisposable
private void SelectItem(string itemId, string quantity, string price) private void SelectItem(string itemId, string quantity, string price)
{ {
ShowItem = true; ShowItem = true;
_selectedItem = (from x in SalesItems where x.ItemId == itemId select x).First(); _selectedItem = (from x in _caltalog where x.ItemId == itemId select x).First();
Price = price; Price = price;
Quantity = quantity; Quantity = quantity;
} }
@ -225,14 +231,14 @@ public partial class ActivityCreate : IDisposable
} }
private async Task SetItemGroup(string groupFilter) private async Task SetItemGroup(string groupFilter)
{ {
SalesItems = new List<NgSalesItemView>(); _caltalog = new List<NgSalesItemView>();
_paging.PageNumber = 1; _paging.PageNumber = 1;
_paging.SelectGroup = groupFilter; _paging.SelectGroup = groupFilter;
await GetSalesItems(); await GetSalesItems();
} }
private async Task SetSearchCol(string columnName) private async Task SetSearchCol(string columnName)
{ {
SalesItems = new List<NgSalesItemView>(); _caltalog = new List<NgSalesItemView>();
_paging.PageNumber = 1; _paging.PageNumber = 1;
_paging.SearchTerm = ""; _paging.SearchTerm = "";
_paging.SearchColumn = columnName; _paging.SearchColumn = columnName;
@ -240,27 +246,27 @@ public partial class ActivityCreate : IDisposable
} }
private async Task SetSortCol(string orderBy) private async Task SetSortCol(string orderBy)
{ {
SalesItems = new List<NgSalesItemView>(); _caltalog = new List<NgSalesItemView>();
_paging.OrderBy = orderBy; _paging.OrderBy = orderBy;
await GetSalesItems(); await GetSalesItems();
} }
private async Task SetSearchPhrase(string searchTerm) private async Task SetSearchPhrase(string searchTerm)
{ {
SalesItems = new List<NgSalesItemView>(); _caltalog = new List<NgSalesItemView>();
_paging.PageNumber = 1; _paging.PageNumber = 1;
_paging.SearchTerm = searchTerm; _paging.SearchTerm = searchTerm;
await GetSalesItems(); await GetSalesItems();
} }
private async Task SelectedPage(int page) private async Task SelectedPage(int page)
{ {
SalesItems = new List<NgSalesItemView>(); _caltalog = new List<NgSalesItemView>();
_paging.PageNumber = page; _paging.PageNumber = page;
await GetSalesItems(); await GetSalesItems();
} }
private async Task SetPageSize(string pageSize) private async Task SetPageSize(string pageSize)
{ {
SalesItems = new List<NgSalesItemView>(); _caltalog = new List<NgSalesItemView>();
_paging.PageSize = Convert.ToInt32(pageSize); _paging.PageSize = Convert.ToInt32(pageSize);
_paging.PageNumber = 1; _paging.PageNumber = 1;
await GetSalesItems(); await GetSalesItems();
@ -269,8 +275,8 @@ public partial class ActivityCreate : IDisposable
private async Task GetSalesItems() private async Task GetSalesItems()
{ {
var response = await _itemRepo.GetSalesItemsPaged(_paging); var response = await _itemRepo.GetSalesItemsPaged(_paging);
SalesItems = response.Items!; _caltalog = response.Items!;
MetaData = response.MetaData; _metaData = response.MetaData;
} }
private void HandleFieldChanged(object sender, FieldChangedEventArgs e) private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
@ -287,27 +293,27 @@ public partial class ActivityCreate : IDisposable
// return; // return;
// } // }
_poFormInvalid = !DraftContext.Validate(); _poFormInvalid = !_editContext.Validate();
StateHasChanged(); StateHasChanged();
} }
private void ValidationChanged(object sender, ValidationStateChangedEventArgs e) private void ValidationChanged(object sender, ValidationStateChangedEventArgs e)
{ {
if (string.IsNullOrEmpty(_poDraft.ActivityTypeEnum)) if (string.IsNullOrEmpty(_draft.ActivityTypeEnum))
_toast.ShowWarning("Aktivitet type kan ikke være tom"); _toast.ShowWarning("Aktivitet type kan ikke være tom");
_poFormInvalid = false; _poFormInvalid = false;
DraftContext.OnFieldChanged -= HandleFieldChanged; _editContext.OnFieldChanged -= HandleFieldChanged;
DraftContext = new EditContext(_poDraft); _editContext = new EditContext(_draft);
DraftContext.OnFieldChanged += HandleFieldChanged; _editContext.OnFieldChanged += HandleFieldChanged;
DraftContext.OnValidationStateChanged -= ValidationChanged; _editContext.OnValidationStateChanged -= ValidationChanged;
} }
public void Dispose() public void Dispose()
{ {
_interceptor.DisposeEvent(); _interceptor.DisposeEvent();
DraftContext.OnFieldChanged -= HandleFieldChanged; _editContext.OnFieldChanged -= HandleFieldChanged;
DraftContext.OnValidationStateChanged -= ValidationChanged; _editContext.OnValidationStateChanged -= ValidationChanged;
} }
} }

View file

@ -1,386 +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]
//
*@
@page "/company/{companyId}/activity"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Roles = "Adviser")]
@using Wonky.Client.Components
<div class="row mb-2 align-items-center">
<div class="col">
<h5 style="font-variant: small-caps">@_workDate.ToLongDateString()</h5>
</div>
<div class="col">
<WorkDateComponent OnChanged="SetWorkDate"></WorkDateComponent>
</div>
</div>
@if (_reportClosdd)
{
<div class="row align-items-md-center">
<div class="col">
<h5>Rapport for @($"{_workDate:yyyy-MM-dd}") er fundet.</h5>
</div>
<div class="col">
<a class="btn btn-info" href="/sales-report/view/@($"{_workDate:yyyy-MM-dd}")">Vis rapport</a>
</div>
</div>
}
else
{
@if (DraftContext != null)
{
<EditForm EditContext="DraftContext">
<DataAnnotationsValidator/>
<div class="accordion-flush" id="crmActivity">
<div class="accordion-item">
<h2 class="accordion-header" id="activityHeader">
<button class="accordion-button bg-light" type="button"
data-bs-toggle="collapse" data-bs-target="#activityBody"
aria-expanded="true" aria-controls="activityBody">
@_poDraft.Name - @_poDraft.Account
</button>
</h2>
<div id="activityBody" class="accordion-collapse collapse show"
aria-labelledby="activityHeader" data-bs-parent="#crmActivity">
<div class="accordion-body">
<div class="row mb-1">
<label for="activityType" class="col-md-2 col-form-label">Ordre Type</label>
<div class="col-md-4">
<InputSelect id="activityType" class="form-select" @bind-Value="@_poDraft.ActivityTypeEnum">
<option value="">ORDRE TYPE</option>
<option value="onSite">Besøg</option>
<option value="phone">Telefon</option>
</InputSelect>
<ValidationMessage For="@(() => _poDraft.ActivityTypeEnum)"></ValidationMessage>
</div>
<label for="statusType" class="col-md-2 col-form-label">Status</label>
<div class="col-md-4">
<InputSelect id="statusType" class="form-select" @bind-Value="@_poDraft.ActivityStatusEnum">
<option value="noSale" selected>Ingen salg</option>
<option value="order">Bestilling</option>
<option value="quote">Tilbud</option>
</InputSelect>
<ValidationMessage For="@(() => _poDraft.ActivityStatusEnum)"></ValidationMessage>
</div>
</div>
<div class="row mb-1">
<label for="demo" class="col-md-2 col-form-label">Demo</label>
<div class="col-md-4">
<InputText id="demo" class="form-control" @bind-Value="_poDraft.Demo"/>
<ValidationMessage For="@(() => _poDraft.Demo)"></ValidationMessage>
</div>
<label for="email" class="col-md-2 col-form-label">Epost</label>
<div class="col-md-4">
<InputText id="email" class="form-control" @bind-Value="_poDraft.EMail"/>
<ValidationMessage For="@(() => _poDraft.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">
<InputText id="referenceNumber" class="form-control"
@bind-Value="_poDraft.ReferenceNumber"v/>
<ValidationMessage For="@(() => _poDraft.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="_poDraft.YourRef"/>
<ValidationMessage For="@(() => _poDraft.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="_poDraft.OrderMessage"/>
<ValidationMessage For="@(() => _poDraft.OrderMessage)"></ValidationMessage>
</div>
<label for="crmNote" class="col-md-2 col-form-label">Note /Mig</label>
<div class="col-md-4">
<InputTextArea id="crmNote" class="form-control"
@bind-Value="_poDraft.CrmNote"/>
<ValidationMessage For="@(() => _poDraft.CrmNote)"></ValidationMessage>
</div>
</div>
<div class="row mb-1">
<label for="attention" class="col-md-2 col-form-label">Att.</label>
<div class="col-md-4">
<InputText id="attention" class="form-control"
@bind-Value="_poDraft.Attention"/>
<ValidationMessage For="@(() => _poDraft.Attention)"></ValidationMessage>
</div>
</div>
</div>
</div>
</div>
@* Order lines *@
<div class="accordion-item" style="@(_poDraft.ActivityStatusEnum is "order" or "quote" ? "display: block" : "display:none")">
<h2 class="accordion-header" id="catalogHeader">
<button class="accordion-button collapsed bg-light" type="button"
data-bs-toggle="collapse" data-bs-target="#catalogBody"
aria-expanded="false" aria-controls="catalogBody">
Varelinjer
</button>
</h2>
<div id="catalogBody" class="accordion-collapse collapse"
aria-labelledby="catalogHeader" data-bs-parent="#crmActivity">
<div class="accordion-body">
<div class="row mb-1">
<div class="col">
<ItemGroupComponent OnChanged="SetItemGroup"/>
</div>
<div class="col">
<ItemSearchComponent OnChanged="SetSearchCol"/>
</div>
<div class="col">
<SearchPhrase OnChanged="SetSearchPhrase"/>
</div>
<div class="col">
<ItemSortComponent OnChanged="SetSortCol"/>
</div>
<div class="col">
<PageSizeComponent OnChanged="SetPageSize"></PageSizeComponent>
</div>
</div>
</div>
<div class="row">
<div class="col">
<PaginationComponent MetaData="MetaData" Spread="2" SelectedPage="SelectedPage"></PaginationComponent>
</div>
</div>
@if (SalesItems.Any())
{
<table class="table table-hover table-striped justify-content-center">
<thead>
<tr>
<th scope="col">Navn</th>
<th scope="col" class="text-nowrap">Varenr</th>
<th scope="col" class="text-nowrap">Fork</th>
<th scope="col">Stk / Pris</th>
</tr>
</thead>
<tbody>
@foreach (var item in SalesItems)
{
<tr>
<td>@item.Name</td>
<td>@item.Sku</td>
<td>@item.ShortName</td>
<td>
<ul class="list-group">
@foreach (var rate in item.Rates)
{
<li class="list-group-item d-flex justify-content-between align-items-end">
<div class="text-sm-start px-2">@rate.Quantity</div>
<div class="text-sm-end">@rate.Rate</div>
<button type="button" class="btn btn-primary btn-sm"
@onclick="@(() => SelectItem(item.ItemId, rate.Quantity, rate.Rate))">
Vælg
</button>
</li>
}
</ul>
</td>
</tr>
}
</tbody>
</table>
}
else
{
<AppSpinner/>
}
@if (_selectedItem != null && ShowItem)
{
<div class="card mb-3 mt-3">
<div class="card-header bg-dark fw-bold text-white">Kladdelinje</div>
<div class="card-body">
<div class="row">
<div class="col col-md-4 fw-bold">
Varenavn
</div>
<div class="col fw-bold">
Varenr
</div>
<div class="col fw-bold">
Antal
</div>
<div class="col fw-bold">
Pris
</div>
<div class="col">
Rabat
</div>
<div class="col">
SAS
</div>
<div class="col">
</div>
</div>
<div class="row">
<div class="col col-md-4">
@_selectedItem.Name
</div>
<div class="col">
@_selectedItem.Sku
</div>
<div class="col">
<input type="number" class="form-control" @bind-value="@Quantity"/>
</div>
<div class="col">
<input type="number" class="form-control" @bind-value="@Price"/>
</div>
<div class="col">
<input type="number" class="form-control" @bind-value="@Discount"/>
</div>
<div class="col">
<input type="checkbox" class="form-check" @bind-value="@Sas"/>
</div>
<div class="col">
<button type="button" class="btn btn-info" @onclick="@(() => AddItem(_selectedItem))">Læg til</button>
</div>
</div>
</div>
</div>
}
@* Order draft lines *@
<div class="card">
<div class="card-header">
Kladdelinjer <span class="mx-2 draft-expires-msg">Global kladde (udløber efter @(DraftStateProvider.Draft.TimeToLiveInSeconds / 60)m inaktivitet)</span>
</div>
<div class="card-body">
<table class="table table-hover table-striped justify-content-center">
<thead>
<tr>
<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-end">Linjesum</th>
<th scope="col">&nbsp;</th>
</tr>
</thead>
<tbody>
@if (DraftStateProvider != null && DraftStateProvider.Draft.Items.Count > 0)
{
@foreach (var cItem in DraftStateProvider.Draft.Items)
{
<tr>
<td>@cItem.Item.Name</td>
<td>@cItem.Item.Sku</td>
<td class="text-end">@cItem.Quantity</td>
<td class="text-end">@cItem.Price</td>
<td class="text-end">@cItem.LineTotal</td>
<td>
<input type="checkbox" checked="@cItem.Sas" disabled/>
</td>
<td>
<button type="button" class="btn btn-warning" @onclick="@(() => RemoveItem(@cItem))">Slet</button>
</td>
</tr>
}
<tr>
<td></td>
<td></td>
<td></td>
<td class="text-black text-end fw-bold">Total</td>
<td class="text-black text-end fw-bold">@DraftStateProvider.Draft.Total</td>
<td></td>
</tr>
}
</tbody>
</table>
</div>
<div class="card-footer">
<div class="row">
<div class="col">
<button type="button" class="btn btn-danger"
@onclick="@DeleteDraft"
disabled="@(DraftStateProvider.Draft.Items.Count == 0)">
Slet kladde
</button>
</div>
</div>
</div>
</div>
</div>
</div>
@* Delivery address *@
<div class="accordion-item" style="@(_poDraft.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="_poDraft.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="_poDraft.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="_poDraft.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="_poDraft.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="_poDraft.DlvCity"/>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-2 mb-2">
<div class="col">
<a class="btn btn-primary" href="/companies">Til Oversigt</a>
</div>
<div class="col">
<a class="btn btn-primary" href="/company/@NgCompany.CompanyId">Tilbage</a>
</div>
<div class="col">
@* <button type="submit" class="btn btn-success" disabled="@InvalidActivity">Gem</button> *@
<button type="button" class="btn btn-success" @onclick="CreateActivity">Opret besøg</button>
</div>
</div>
</EditForm>
}
}

View file

@ -0,0 +1,19 @@
@*
// 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]
//
*@
@page "/ActivityList"
<h3>ActivityList</h3>

View file

@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Components;
using Toolbelt.Blazor;
using Wonky.Client.HttpRepository;
using Wonky.Entity.Views;
namespace Wonky.Client.Pages;
public partial class ActivityList
{
[Inject] public IActivityHttpRepository ActivityRepo { get; set; }
[Inject] public IHttpClientInterceptor Interceptor { get; set; }
}

View file

@ -14,53 +14,24 @@
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html] // along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
// //
*@ *@
@using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Authorization
@using Wonky.Client.Components @using Wonky.Client.Components
@attribute [Authorize(Roles = "Adviser")] @attribute [Authorize(Roles = "Adviser")]
@page "/activity-today" @page "/activity-today"
<div class="card">
<div class="card-header">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<div class="col-md-4"> <div class="col">
<span class="workDate">@(string.IsNullOrWhiteSpace(_workDate) ? "" : $"{DateTime.Parse(_workDate).ToLongDateString()}")</span> <h3 class="workDate">@(string.IsNullOrWhiteSpace(_workDate) ? "" : $"{DateTime.Parse(_workDate).ToLongDateString()}")</h3>
</div>
<div class="col-md-4">
<WorkDateComponent OnChanged="GetActivities"></WorkDateComponent>
</div> </div>
<div class="col"> <div class="col">
<WorkDateComponent OnChanged="GetActivities"></WorkDateComponent>
</div> </div>
</div> </div>
</div>
<hr/> </div>
<h5>Dagens aktivitet</h5> <div class="card-body">
<table class="table"> <ActivityTableComponent Activities="_view.Activities"></ActivityTableComponent>
<thead> </div>
<tr class="align-items-center">
<th scope="col">Kunde</th>
<th scope="col">Demo</th>
<th scope="col">Salg</th>
<th scope="col">Sum</th>
</tr>
</thead>
<tbody>
@if (_view != null)
{
foreach (var activity in _view.Activities)
{
<tr class="align-items-center">
<td>
@activity.Company.Name
</td>
<td>
@activity.Demo
</td>
<td>
@activity.SalesResume
</td>
<td>
@activity.OrderAmount
</td>
</tr>
}
}
</tbody>
</table>

View file

@ -15,7 +15,7 @@
// //
*@ *@
@page "/company/create" @page "/companies/new"
@using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Authorization
@using Wonky.Client.Components @using Wonky.Client.Components
@using System.Xml @using System.Xml

View file

@ -124,10 +124,11 @@ namespace Wonky.Client.Pages
private async Task SubmitCompanyForm() private async Task SubmitCompanyForm()
{ {
var newId = await CompanyRepo.CreateCompany(_companyObject); var newId = await CompanyRepo.CreateCompany(_companyObject);
if (!string.IsNullOrWhiteSpace(newId)) if (!string.IsNullOrWhiteSpace(newId))
{ {
ToastService.ShowSuccess($"'{_companyObject.Name}' er oprettet i CRM."); ToastService.ShowSuccess($"'{_companyObject.Name}' er oprettet i CRM.");
Navigation.NavigateTo($"/company/id/{newId}"); Navigation.NavigateTo($"/companies/{newId}");
} }
else else
{ {

View file

@ -44,7 +44,7 @@
<div class="col-md-3"> <div class="col-md-3">
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<a class="btn btn-success" href="/company/create">Opret kunde</a> <a class="btn btn-success" href="/companies/new">Opret kunde</a>
</div> </div>
</div> </div>

View file

@ -14,19 +14,22 @@
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html] // along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
// //
*@ *@
@using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components @using Microsoft.AspNetCore.Components
@using Wonky.Client.Components @using Wonky.Client.Components
@using Wonky.Client.Helpers @using Wonky.Client.Helpers
@attribute [Authorize(Roles = "Adviser")] @attribute [Authorize(Roles = "Adviser")]
@page "/company/{CompanyId}/update" @page "/companies/{CompanyId}"
@if (!string.IsNullOrWhiteSpace(_companyView.Name)) @if (!string.IsNullOrWhiteSpace(_company.Name))
{ {
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h5>@_companyView.Account - @_companyView.Name</h5> <h3>@_company.Account - @_company.Name</h3>
</div> </div>
@if (countryCode == "dk" && _company.ValidVat == 0)
{
<div class="card-body"> <div class="card-body">
<VatAddressInputComponent Address="vatAddress" OnValidSubmit="GetInfoFromAddress"/> <VatAddressInputComponent Address="vatAddress" OnValidSubmit="GetInfoFromAddress"/>
</div> </div>
@ -60,7 +63,8 @@
</tbody> </tbody>
</table> </table>
} }
<EditForm EditContext="_updateCompany" OnValidSubmit="SubmitUpdate"> }
<EditForm EditContext="_editContext" OnValidSubmit="SubmitUpdate">
<DataAnnotationsValidator/> <DataAnnotationsValidator/>
<div class="card-body"> <div class="card-body">
<table class="table"> <table class="table">
@ -73,15 +77,15 @@
<DisplayStateComponent StateClass="@_vatState"></DisplayStateComponent> <DisplayStateComponent StateClass="@_vatState"></DisplayStateComponent>
</td> </td>
<td> <td>
<InputText id="vatNumber" class="form-control" @bind-Value="_companyView.VatNumber"/> <InputText id="vatNumber" class="form-control" @bind-Value="_company.VatNumber"/>
<ValidationMessage For="@(() => _companyView.VatNumber)"></ValidationMessage> <ValidationMessage For="@(() => _company.VatNumber)"></ValidationMessage>
</td> </td>
<th> <th>
Telefon Telefon
</th> </th>
<td> <td>
<InputText id="phone" class="form-control" @bind-Value="_companyView.Phone"/> <InputText id="phone" class="form-control" @bind-Value="_company.Phone"/>
<ValidationMessage For="@(() => _companyView.Phone)"></ValidationMessage> <ValidationMessage For="@(() => _company.Phone)"></ValidationMessage>
</td> </td>
</tr> </tr>
<tr class="align-middle"> <tr class="align-middle">
@ -90,15 +94,15 @@
</th> </th>
<td></td> <td></td>
<td> <td>
<InputText id="name" class="form-control" @bind-Value="_companyView.Name"/> <InputText id="name" class="form-control" @bind-Value="_company.Name"/>
<ValidationMessage For="@(() => _companyView.Name)"></ValidationMessage> <ValidationMessage For="@(() => _company.Name)"></ValidationMessage>
</td> </td>
<th> <th>
Attention Attention
</th> </th>
<td> <td>
<InputText id="attention" class="form-control" @bind-Value="_companyView.Attention"/> <InputText id="attention" class="form-control" @bind-Value="_company.Attention"/>
<ValidationMessage For="@(() => _companyView.Attention)"></ValidationMessage> <ValidationMessage For="@(() => _company.Attention)"></ValidationMessage>
</td> </td>
</tr> </tr>
<tr class="align-middle"> <tr class="align-middle">
@ -107,15 +111,15 @@
</th> </th>
<td></td> <td></td>
<td> <td>
<InputText id="address1" class="form-control" @bind-Value="_companyView.Address1"/> <InputText id="address1" class="form-control" @bind-Value="_company.Address1"/>
<ValidationMessage For="@(() => _companyView.Address1)"></ValidationMessage> <ValidationMessage For="@(() => _company.Address1)"></ValidationMessage>
</td> </td>
<th> <th>
Adresse2 Adresse2
</th> </th>
<td> <td>
<InputText id="address2" class="form-control" @bind-Value="_companyView.Address2"/> <InputText id="address2" class="form-control" @bind-Value="_company.Address2"/>
<ValidationMessage For="@(() => _companyView.Address2)"></ValidationMessage> <ValidationMessage For="@(() => _company.Address2)"></ValidationMessage>
</td> </td>
</tr> </tr>
<tr class="align-middle"> <tr class="align-middle">
@ -124,15 +128,15 @@
</th> </th>
<td></td> <td></td>
<td> <td>
<InputText id="zipCode" class="form-control" @bind-Value="_companyView.ZipCode"/> <InputText id="zipCode" class="form-control" @bind-Value="_company.ZipCode"/>
<ValidationMessage For="@(() => _companyView.ZipCode)"></ValidationMessage> <ValidationMessage For="@(() => _company.ZipCode)"></ValidationMessage>
</td> </td>
<th> <th>
Bynavn Bynavn
</th> </th>
<td> <td>
<InputText id="city" class="form-control" @bind-Value="_companyView.City"/> <InputText id="city" class="form-control" @bind-Value="_company.City"/>
<ValidationMessage For="@(() => _companyView.City)"></ValidationMessage> <ValidationMessage For="@(() => _company.City)"></ValidationMessage>
</td> </td>
</tr> </tr>
<tr class="align-middle"> <tr class="align-middle">
@ -141,15 +145,15 @@
</th> </th>
<td></td> <td></td>
<td> <td>
<InputText id="email" class="form-control" @bind-Value="_companyView.Email"/> <InputText id="email" class="form-control" @bind-Value="_company.Email"/>
<ValidationMessage For="@(() => _companyView.Email)"></ValidationMessage> <ValidationMessage For="@(() => _company.Email)"></ValidationMessage>
</td> </td>
<th> <th>
Mobil Mobil
</th> </th>
<td> <td>
<InputText id="mobile" class="form-control" @bind-Value="_companyView.Mobile"/> <InputText id="mobile" class="form-control" @bind-Value="_company.Mobile"/>
<ValidationMessage For="@(() => _companyView.Mobile)"></ValidationMessage> <ValidationMessage For="@(() => _company.Mobile)"></ValidationMessage>
</td> </td>
</tr> </tr>
<tr class="align-middle"> <tr class="align-middle">
@ -157,11 +161,12 @@
Næste besøg Næste besøg
</th> </th>
<td class="state"> <td class="state">
<DisplayStateComponent StateClass="@(_hasFolded ? "the-dead" : Utils.GetVisitState($"{_companyView.NextVisit}"))"> </DisplayStateComponent> <DisplayStateComponent StateClass="@_visitState">
</DisplayStateComponent>
</td> </td>
<td> <td>
<InputDate id="nextVisit" class="form-control" @bind-Value="@(_nextVisit)"/> <InputDate id="nextVisit" class="form-control" @bind-Value="@(_nextVisit)"/>
<ValidationMessage For="@(() => _companyView.NextVisit)">Dato kan ikke vær før sidste besøg</ValidationMessage> <ValidationMessage For="@(() => _company.NextVisit)">Dato kan ikke vær før sidste besøg</ValidationMessage>
</td> </td>
<th> <th>
Besøgt Besøgt
@ -176,8 +181,8 @@
</th> </th>
<td></td> <td></td>
<td> <td>
<InputNumber id="interval" class="form-control" @bind-Value="_companyView.Interval"/> <InputNumber id="interval" class="form-control" @bind-Value="_company.Interval"/>
<ValidationMessage For="@(() => _companyView.Interval)"></ValidationMessage> <ValidationMessage For="@(() => _company.Interval)"></ValidationMessage>
</td> </td>
<th scope="row"> <th scope="row">
@ -191,14 +196,6 @@
</div> </div>
<div class="card-footer"> <div class="card-footer">
<div class="row"> <div class="row">
@*
<div class="col">
<button type="button" class="btn btn-warning">Fjern</button>
</div>
<div class="col">
<button type="button" class="btn btn-danger">Slet</button>
</div>
*@
<div class="col"> <div class="col">
<button type="submit" class="btn btn-success">Gem</button> <button type="submit" class="btn btn-success">Gem</button>
</div> </div>
@ -206,7 +203,7 @@
<a class="btn btn-primary" href="/companies">Til Oversigt</a> <a class="btn btn-primary" href="/companies">Til Oversigt</a>
</div> </div>
<div class="col"> <div class="col">
<a class="btn btn-primary" href="/company/@CompanyId">Tilbage</a> <ActivityButton CompanyId="@_company.CompanyId"></ActivityButton>
</div> </div>
</div> </div>
</div> </div>

View file

@ -32,19 +32,18 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Pages; namespace Wonky.Client.Pages;
public partial class CompanyEdit : IDisposable public partial class CompanyView : IDisposable
{ {
[Inject] public IToastService ToastService { get; set; } [Inject] public IToastService ToastService { get; set; }
[Inject] public ILogger<CompanyEdit> Logger { get; set; } [Inject] public ILogger<CompanyView> Logger { get; set; }
[Inject] public NavigationManager Navigation { get; set; } [Inject] public NavigationManager Navigation { get; set; }
[Inject] public ICompanyHttpRepository CompanyRepo { get; set; } [Inject] public ICompanyHttpRepository CompanyRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; } [Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public VatInfoLookupService VatInfoLookupService { get; set; } [Inject] public VatInfoLookupService VatInfoLookupService { get; set; }
[Inject] public ILocalStorageService StorageService { get; set; } [Inject] public ILocalStorageService StorageService { get; set; }
[Parameter] public string Account { get; set; } = "";
[Parameter] public string CompanyId { get; set; } = ""; [Parameter] public string CompanyId { get; set; } = "";
private CompanyDto _companyView { get; set; } = new(); private CompanyDto _company { get; set; } = new();
private EditContext _updateCompany { get; set; } private EditContext _editContext { get; set; }
private List<VirkRegInfo> _vInfos { get; set; } = new(); private List<VirkRegInfo> _vInfos { get; set; } = new();
private VirkRegInfo _virkRegInfo { get; set; } = new(); private VirkRegInfo _virkRegInfo { get; set; } = new();
private DateTime _lastVisit { get; set; } private DateTime _lastVisit { get; set; }
@ -55,6 +54,8 @@ public partial class CompanyEdit : IDisposable
private bool _hasFolded; private bool _hasFolded;
private bool _formInvalid = true; private bool _formInvalid = true;
private string _orgVat; private string _orgVat;
private string countryCode = "dk";
private string _visitState = "the-ugly";
private readonly JsonSerializerOptions _options = new () private readonly JsonSerializerOptions _options = new ()
{ {
@ -64,93 +65,94 @@ public partial class CompanyEdit : IDisposable
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
var ux = await StorageService.GetItemAsync<UserInfoView>("_xu"); var ux = await StorageService.GetItemAsync<UserInfoView>("_xu");
countryCode = ux.CountryCode;
Interceptor.RegisterEvent(); Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent(); Interceptor.RegisterBeforeSendEvent();
_companyView = await CompanyRepo.GetCompanyById(CompanyId); _company = await CompanyRepo.GetCompanyById(CompanyId);
_orgVat = _companyView.VatNumber; _orgVat = _company.VatNumber;
_companyView.CountryCode = ux.CountryCode.ToLower(); _company.CountryCode = ux.CountryCode.ToLower();
vatAddress = PrepareVatAddress(_companyView); vatAddress = PrepareVatAddress(_company);
if (_companyView.Interval == 0) if (_company.Interval == 0)
_companyView.Interval = 8; _company.Interval = 8;
_lastVisit = DateTime.Parse(_companyView.LastVisit); _lastVisit = DateTime.Parse(_company.LastVisit);
_nextVisit = DateTime.Parse(_companyView.NextVisit); _nextVisit = DateTime.Parse(_company.NextVisit);
if (!_companyView.ValidDateSpan()) if (!_company.ValidDateSpan())
_nextVisit = _lastVisit.AddDays(_companyView.Interval * 7); _nextVisit = _lastVisit.AddDays(_company.Interval * 7);
if(_company.HasFolded == 1)
if(_companyView.HasFolded == 1)
{ {
_hasFolded = true; _hasFolded = true;
_vatState = "the-dead"; _vatState = "the-dead";
_visitState = "the-dead";
} }
else else
{ {
if (_companyView.ValidVat == 0 && !string.IsNullOrWhiteSpace(_companyView.VatNumber)) if (_company.ValidVat == 0 && !string.IsNullOrWhiteSpace(_company.VatNumber))
_companyView.ValidVat = VatUtils.ValidateFormat(_companyView.CountryCode, _companyView.VatNumber) ? 1 : 0; _company.ValidVat = VatUtils.ValidateFormat(_company.CountryCode, _company.VatNumber) ? 1 : 0;
_validVat = _companyView.ValidVat == 1; _validVat = _company.ValidVat == 1;
_vatState = _companyView.ValidVat == 1 ? "the-good" : "the-draw"; _vatState = _company.ValidVat == 1 ? "the-good" : "the-draw";
_visitState = Utils.GetVisitState($"{_nextVisit:yyyy-HH-mm}");
} }
_updateCompany = new EditContext(_companyView); _editContext = new EditContext(_company);
_updateCompany.OnFieldChanged += HandleFieldChanged; _editContext.OnFieldChanged += HandleFieldChanged;
_updateCompany.OnValidationStateChanged += ValidationChanged; _editContext.OnValidationStateChanged += ValidationChanged;
} }
private void HandleFieldChanged(object sender, FieldChangedEventArgs e) private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{ {
_nextVisit = _lastVisit.AddDays(_companyView.Interval * 7); _nextVisit = _lastVisit.AddDays(_company.Interval * 7);
_companyView.LastVisit = $"{_lastVisit:yyyy-MM-dd}"; _company.LastVisit = $"{_lastVisit:yyyy-MM-dd}";
_companyView.NextVisit = $"{_nextVisit:yyyy-MM-dd}"; _company.NextVisit = $"{_nextVisit:yyyy-MM-dd}";
if (!VatUtils.ValidateFormat(_companyView.CountryCode, _companyView.VatNumber) || if (!VatUtils.ValidateFormat(_company.CountryCode, _company.VatNumber) ||
!_companyView.ValidDateSpan()) !_company.ValidDateSpan())
{ {
_formInvalid = true; _formInvalid = true;
} }
else else
{ {
_formInvalid = !_updateCompany.Validate(); _formInvalid = !_editContext.Validate();
} }
StateHasChanged(); StateHasChanged();
} }
private void ValidationChanged(object sender, ValidationStateChangedEventArgs e) private void ValidationChanged(object sender, ValidationStateChangedEventArgs e)
{ {
_formInvalid = true; _formInvalid = true;
_updateCompany.OnFieldChanged -= HandleFieldChanged; _editContext.OnFieldChanged -= HandleFieldChanged;
_updateCompany = new EditContext(_companyView); _editContext = new EditContext(_company);
_updateCompany.OnFieldChanged += HandleFieldChanged; _editContext.OnFieldChanged += HandleFieldChanged;
_updateCompany.OnValidationStateChanged -= ValidationChanged; _editContext.OnValidationStateChanged -= ValidationChanged;
} }
private async Task SubmitUpdate() private async Task SubmitUpdate()
{ {
if (!VatUtils.ValidateFormat(_companyView.CountryCode, _companyView.VatNumber)) if (!VatUtils.ValidateFormat(_company.CountryCode, _company.VatNumber))
{ {
ToastService.ShowError($"CVR/VAT/ORG nummer er ugyldig."); ToastService.ShowError($"CVR/VAT/ORG nummer er ugyldig.");
StateHasChanged(); StateHasChanged();
return; return;
} }
_companyView.LastVisit = $"{_lastVisit:yyyy-MM-dd}"; _company.LastVisit = $"{_lastVisit:yyyy-MM-dd}";
_companyView.NextVisit = $"{_nextVisit:yyyy-MM-dd}"; _company.NextVisit = $"{_nextVisit:yyyy-MM-dd}";
_companyView.IsHidden = 0; _company.IsHidden = 0;
if (_companyView.VatNumber != _orgVat) if (_company.VatNumber != _orgVat)
_companyView.UpdateErpVat = 1; _company.UpdateErpVat = 1;
var x = JsonSerializer.Serialize(_companyView, _options); var x = JsonSerializer.Serialize(_company, _options);
Logger.LogInformation(x); Logger.LogInformation(x);
var success = await CompanyRepo.UpdateCompany(CompanyId, _companyView ); var success = await CompanyRepo.UpdateCompany(CompanyId, _company );
if (success) if (success)
{ {
ToastService.ShowSuccess("Check"); ToastService.ShowSuccess("Check");
Navigation.NavigateTo($"/company/{CompanyId}"); Navigation.NavigateTo($"/companies/{CompanyId}");
} }
} }
@ -173,16 +175,16 @@ public partial class CompanyEdit : IDisposable
_virkRegInfo = (from x in _vInfos where x.VatNumber == vatNumber select x).First(); _virkRegInfo = (from x in _vInfos where x.VatNumber == vatNumber select x).First();
if (syncAll) if (syncAll)
{ {
_companyView.VatNumber = _virkRegInfo.VatNumber; _company.VatNumber = _virkRegInfo.VatNumber;
_companyView.Name = _virkRegInfo.Name; _company.Name = _virkRegInfo.Name;
_companyView.Address1 = _virkRegInfo.Address; _company.Address1 = _virkRegInfo.Address;
_companyView.Address2 = _virkRegInfo.CoName; _company.Address2 = _virkRegInfo.CoName;
_companyView.ZipCode = _virkRegInfo.ZipCode; _company.ZipCode = _virkRegInfo.ZipCode;
_companyView.City = _virkRegInfo.City; _company.City = _virkRegInfo.City;
} }
else else
{ {
_companyView.VatNumber = _virkRegInfo.VatNumber; _company.VatNumber = _virkRegInfo.VatNumber;
} }
_vInfos = new List<VirkRegInfo>(); _vInfos = new List<VirkRegInfo>();
@ -192,8 +194,8 @@ public partial class CompanyEdit : IDisposable
public void Dispose() public void Dispose()
{ {
Interceptor.DisposeEvent(); Interceptor.DisposeEvent();
_updateCompany.OnFieldChanged -= HandleFieldChanged; _editContext.OnFieldChanged -= HandleFieldChanged;
_updateCompany.OnValidationStateChanged -= ValidationChanged; _editContext.OnValidationStateChanged -= ValidationChanged;
} }
private static VatAddress PrepareVatAddress(CompanyDto model) private static VatAddress PrepareVatAddress(CompanyDto model)
{ {

View file

@ -14,10 +14,11 @@
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html] // along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
// //
*@ *@
@using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Authorization
@using Wonky.Client.Components @using Wonky.Client.Components
@attribute [Authorize(Roles = "Adviser")] @attribute [Authorize(Roles = "Adviser")]
@page "/sales-report" @page "/sales-reports/new"
<EditForm EditContext="_editContext"> <EditForm EditContext="_editContext">
<div class="card"> <div class="card">

View file

@ -1,105 +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 Wonky.Client.Components @using Wonky.Client.Components
@using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Authorization
@page "/sales-report/view/{reportDate}" @page "/sales-reports/view/{ReportDate}"
@attribute [Authorize(Roles = "Adviser,Admin,Supervisor")] @attribute [Authorize(Roles = "Adviser,Admin,Supervisor")]
<WorkDateComponent OnChanged="GetReport"></WorkDateComponent> <div class="card">
<div> <div class="card-header">
@if (_report.Activities.Any()) <div class="row">
<div class="col">
@if (!string.IsNullOrWhiteSpace(ReportDate))
{ {
<table class="table"> <h3 class="workDate">Dagsrapport @DateTime.Parse(ReportDate).ToLongDateString()</h3>
<thead>
<tr>
<th scope="col">Besøg</th>
<th scope="col">Demo</th>
<th scope="col">Salg</th>
<th scope="col">Beløb</th>
</tr>
</thead>
<tbody>
@foreach (var activity in _report.Activities)
{
<tr>
<td>@activity.Company.Name - @activity.Company.ZipCity</td>
<td>@activity.Demo</td>
<td>@activity.SalesResume</td>
<td class="align-content-end">@activity.OrderAmount</td>
</tr>
} }
<tr> </div>
<td></td> <div class="col">
<td></td> <WorkDateComponent OnChanged="GetReport"></WorkDateComponent>
<td>Total</td> </div>
<td class="align-content-end">@_report.Report.TotalTurnover</td> </div>
</tr> </div>
</tbody> <div class="card-body">
</table> <ReportSummaryComponent Report="_report.Report"></ReportSummaryComponent>
} <ActivityTableComponent Activities="_report.Activities"></ActivityTableComponent>
<table class="table"> </div>
<thead>
<tr>
<th></th>
<th colspan="2" scope="col">Demo @(_report.Report.NewDemoCount + _report.Report.RecallDemoCount)</th>
<th colspan="2" scope="col">Resultat</th>
<th colspan="4" scope="col">Resultat Måned</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<th scope="col">Besøg</th>
<th scope="col">Demo</th>
<th scope="col">Salg</th>
<th scope="col">Beløb</th>
<th scope="col">Besøg</th>
<th scope="col">Demo</th>
<th scope="col">Salg</th>
<th scope="col">Beløb</th>
</tr>
<tr>
<th scope="row">N</th>
<td>@_report.Report.NewVisitCount</td>
<td>@_report.Report.NewDemoCount</td>
<td>@_report.Report.NewSaleCount</td>
<td>@_report.Report.NewTurnover</td>
<td>@_report.Report.NewVisitCountMonth</td>
<td>@_report.Report.NewDemoCountMonth</td>
<td>@_report.Report.NewSaleCountMonth</td>
<td>@_report.Report.NewTurnoverMonth</td>
</tr>
<tr>
<th scope="row">R</th>
<td>@_report.Report.RecallVisitCount</td>
<td>@_report.Report.RecallDemoCount</td>
<td>@_report.Report.RecallSaleCount</td>
<td>@_report.Report.RecallTurnover</td>
<td>@_report.Report.RecallVisitCountMonth</td>
<td>@_report.Report.RecallDemoCountMonth</td>
<td>@_report.Report.RecallSaleCountMonth</td>
<td>@_report.Report.RecallTurnoverMonth</td>
</tr>
<tr>
<th scope="row">SAS</th>
<td></td>
<td></td>
<td>@_report.Report.SasCount</td>
<td>@_report.Report.SasTurnover</td>
<td></td>
<td></td>
<td>@_report.Report.SasCountMonth</td>
<td>@_report.Report.SasTurnoverMonth</td>
</tr>
<tr>
<th scope="row">TOTAL</th>
<td>@(_report.Report.TotalVisitCount)</td>
<td>@(_report.Report.TotalDemoCount)</td>
<td>@(_report.Report.TotalSaleCount)</td>
<td>@(_report.Report.TotalTurnover)</td>
<td>@(_report.Report.TotalVisitCountMonth)</td>
<td>@(_report.Report.TotalDemoCountMonth)</td>
<td>@(_report.Report.TotalSaleCountMonth)</td>
<td>@(_report.Report.TotalTurnoverMonth)</td>
</tr>
</tbody>
</table>
</div> </div>

View file

@ -21,6 +21,7 @@ public partial class ReportView
private async Task GetReport(string workDate) private async Task GetReport(string workDate)
{ {
_report = new NgSalesReportView();
_report = await ReportRepo.GetReport(workDate); _report = await ReportRepo.GetReport(workDate);
} }
} }

View file

@ -0,0 +1,30 @@
@*
// 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]
//
*@
@page "/sales-reports"
<h3>SalesReportList</h3>
@if (_reports != null)
{
foreach (var report in _reports)
{
<span>@report.Name</span>
}
}
else
{
<AppSpinner />
}

View file

@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Components;
using Toolbelt.Blazor;
using Wonky.Client.HttpRepository;
using Wonky.Entity.Views;
namespace Wonky.Client.Pages;
public partial class SalesReportList
{
[Inject] public IReportHttpRepository ReportRepo { get; set; }
[Inject] public IHttpClientInterceptor Interceport { get; set; }
private List<NgSalesReport> _reports { get; set; }
protected override async Task OnInitializedAsync()
{
_reports = await ReportRepo.GetReports();
}
}

View file

@ -17,7 +17,7 @@ using Microsoft.AspNetCore.Components;
namespace Wonky.Client.Pages namespace Wonky.Client.Pages
{ {
public partial class ErrorReport public partial class SiteErrorReport
{ {
[Parameter] [Parameter]
public int ErrorCode { get; set; } public int ErrorCode { get; set; }

View file

@ -63,8 +63,8 @@
</NavLink> </NavLink>
</div> </div>
<div class="nav-item px-3"> <div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="sales-report"> <NavLink class="nav-link ps-2" href="sales-reports">
<span class="oi oi-document" aria-hidden="true"></span> Dagsrapport <span class="oi oi-document" aria-hidden="true"></span> Dagsrapporter
</NavLink> </NavLink>
</div> </div>

View file

@ -18,12 +18,12 @@
}, },
"appInfo": { "appInfo": {
"name": "Wonky Client", "name": "Wonky Client",
"version": "0.8.7", "version": "0.8.10",
"isBeta": true, "isBeta": true,
"image": "grumpy-coder.png" "image": "grumpy-coder.png"
}, },
"apiConfig": { "apiConfig": {
"baseAddress": "https://staging.innotec.dk", "baseAddress": "https://dev.innotec.dk",
"tokenPath": "token", "tokenPath": "token",
"userInfo": "api/auth/userinfo", "userInfo": "api/auth/userinfo",
"customerEndpoint": "api/v2/crm/companies", "customerEndpoint": "api/v2/crm/companies",

View file

@ -6,7 +6,9 @@
.spinner { .spinner {
height: 48px; height: 48px;
} }
.workDate {
font-variant: small-caps;
}
/* visit / vat state classes */ /* visit / vat state classes */
.state { .state {
width: 16px; width: 16px;

View file

@ -21,34 +21,65 @@ namespace Wonky.Entity.DTO;
public class CompanyDto public class CompanyDto
{ {
[Required(ErrorMessage = "Navn skal udfyldes")] [MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")] [Required(ErrorMessage = "Navn skal udfyldes")]
[MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")]
public string Name { get; set; } public string Name { get; set; }
[Required(ErrorMessage = "Postnummer skal udfyldes")] [MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")]
[Required(ErrorMessage = "Postnummer skal udfyldes")]
[MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")]
public string ZipCode { get; set; } public string ZipCode { get; set; }
[Required(ErrorMessage = "Bynavn skal udfyldes")] [MaxLength(30, ErrorMessage = "Du kan højst bruge 30 tegn")]
[Required(ErrorMessage = "Bynavn skal udfyldes")]
[MaxLength(30, ErrorMessage = "Du kan højst bruge 30 tegn")]
public string City { get; set; } public string City { get; set; }
[Required(ErrorMessage = "ORG/VAT/CVR er ikke et gyldigt nummer")] [Required(ErrorMessage = "ORG/VAT/CVR er ikke et gyldigt nummer")]
public string VatNumber { get; set; } = ""; public string VatNumber { get; set; } = "";
public string CompanyId { get; set; } = ""; public string CompanyId { get; set; } = "";
public string SalesRepId { get; set; } = ""; public string SalesRepId { get; set; } = "";
public string BcId { get; set; } = ""; public string BcId { get; set; } = "";
[MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")] public string Address1 { get; set; } = ""; [MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")]
[MaxLength(50, ErrorMessage = "Du kan højst bruge 50 tegn")] public string Address2 { get; set; } = ""; public string Address1 { get; set; } = "";
[MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")] public string Account { get; set; } = "";
[MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")] public string Phone { get; set; } = ""; [MaxLength(50, ErrorMessage = "Du kan højst bruge 50 tegn")]
[MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")] public string Mobile { get; set; } = ""; public string Address2 { get; set; } = "";
[MaxLength(80, ErrorMessage = "Du kan højst bruge 80 tegn")] public string Email { get; set; } = "";
[MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")] public string Attention { get; set; } = ""; [MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")]
public string Account { get; set; } = "";
[MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")]
public string Phone { get; set; } = "";
[MaxLength(20, ErrorMessage = "Du kan højst bruge 20 tegn")]
public string Mobile { get; set; } = "";
[MaxLength(80, ErrorMessage = "Du kan højst bruge 80 tegn")]
public string Email { get; set; } = "";
[MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")]
public string Attention { get; set; } = "";
public string CountryCode { get; set; } = ""; public string CountryCode { get; set; } = "";
public string LastVisit { get; set; } = ""; public string LastVisit { get; set; } = "";
public string NextVisit { get; set; } = ""; public string NextVisit { get; set; } = "";
[Range(1, 52, ErrorMessage = "Angiv interval mellem 1 og 52 uger")] [Range(1, 52, ErrorMessage = "Angiv interval mellem 1 og 52 uger")]
public int Interval { get; set; } = 8; public int Interval { get; set; } = 8;
public int HasFolded { get; set; } public int HasFolded { get; set; }
public int IsHidden { get; set; } public int IsHidden { get; set; }
public int ValidVat { get; set; } public int ValidVat { get; set; }
public int UpdateErpVat { get; set; } public int UpdateErpVat { get; set; }
public bool ValidDateSpan()
public virtual bool ValidDateSpan()
{ {
var notAllowed = new List<string> {"1970-01-01", "0001-01-01"}; var notAllowed = new List<string> {"1970-01-01", "0001-01-01"};
if (notAllowed.Contains(LastVisit) || notAllowed.Contains(NextVisit)) if (notAllowed.Contains(LastVisit) || notAllowed.Contains(NextVisit))

View file

@ -5,9 +5,16 @@ namespace Wonky.Entity.DTO;
public class ReportDto public class ReportDto
{ {
public string Name { get; set; } = ""; public string Name { get; set; } = "";
[MaxLength(1000, ErrorMessage = "Du kan højst bruge 1000 tegn")] public string Description { get; set; } = "";
[MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")] public string SupervisedBy { get; set; } = ""; [MaxLength(1000, ErrorMessage = "Du kan højst bruge 1000 tegn")]
[Required(ErrorMessage = "Dagtype skal angives")] public string DayTypeEnum { get; set; } = ""; public string Description { get; set; } = "";
[MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")]
public string SupervisedBy { get; set; } = "";
[Required(ErrorMessage = "Dagtype skal angives")]
public string DayTypeEnum { get; set; } = "";
// Date interval (used for leave, sickLeave and work hours // Date interval (used for leave, sickLeave and work hours
public string FromDateTime { get; set; } = ""; public string FromDateTime { get; set; } = "";
public string ToDateTime { get; set; } = ""; public string ToDateTime { get; set; } = "";

View file

@ -0,0 +1,30 @@
// 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]
//
namespace Wonky.Entity.Requests;
public class ReportPagingParams
{
private int _pageSize = 5;
private const int MaxPageSize = 50;
public int PageNumber { get; set; } = 1;
public int PageSize
{
get => _pageSize;
set => _pageSize = (value > MaxPageSize) ? MaxPageSize : value;
}
public string SearchTerm { get; set; } = "";
public string SearchColumn { get; set; } = "name";
public string OrderBy { get; set; } = "name";
}