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)
{
<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
{

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
// 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>

View file

@ -16,6 +16,7 @@
// 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>}
@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">
<thead>
<tr>

View file

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

View file

@ -28,6 +28,5 @@
<WorkDateComponent OnChanged="GetCalender"></WorkDateComponent>
</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
<AuthorizeView>
<Authorized>
<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="logout">LOG AF</a>
<a class="btn btn-outline-light" href="info">HJÆLP</a>
</Authorized>
</AuthorizeView>

View file

@ -91,13 +91,17 @@ public class CompanyHttpRepository : ICompanyHttpRepository
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)
{
var response = await _client.PostAsJsonAsync($"{_apiConfig.CustomerEndpoint}", model);
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
var result = JsonSerializer.Deserialize<CompanyDto>(content);
return result == null ? "" : result.CompanyId;
var result = JsonSerializer.Deserialize<CompanyDto>(content, _options);
return result.CompanyId;
}
public async Task<bool> UpdateCompany(string companyId, CompanyDto model)

View file

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

View file

@ -30,6 +30,14 @@ public class ReportHttpRepository :IReportHttpRepository
_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)
{
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;
public partial class ActivityCreate : IDisposable
public partial class ActivityCompanyCreate : IDisposable
{
// 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 NavigationManager _navigator { get; set; }
[Inject] private ILocalStorageService _storage { get; set; }
@ -50,13 +50,13 @@ public partial class ActivityCreate : IDisposable
PropertyNameCaseInsensitive = true
};
private NgSalesItemView _selectedItem { get; set; } = new();
private List<NgSalesItemView> SalesItems { get; set; } = new();
private MetaData MetaData { get; set; } = new();
private List<NgSalesItemView> _caltalog { get; set; } = new();
private MetaData _metaData { get; set; } = new();
private Preferences _prefs { get; set; } = new();
private ActivityDto _poDraft { get; set; } = new();
private CompanyDto NgCompany = new();
private ActivityDto _draft { get; set; } = new();
private CompanyDto _company = new();
private CatalogPagingParams _paging = new();
private EditContext DraftContext { get; set; }
private EditContext _editContext { get; set; }
private bool _poFormInvalid { get; set; } = true;
private bool ShowItem { get; set; }
private bool HideButtons { get; set; }
@ -72,61 +72,67 @@ public partial class ActivityCreate : IDisposable
private UserInfoView Ux { get; set; } = new();
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();
if (!string.IsNullOrWhiteSpace(_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.PageSize = Convert.ToInt32(_prefs.PageSize);
await GetSalesItems();
Ux = await _storage.GetItemAsync<UserInfoView>("_xu");
NgCompany = await _companyRepo.GetCompanyById(CompanyId);
DraftContext = new EditContext(_poDraft);
DraftContext.OnFieldChanged += HandleFieldChanged;
DraftContext.OnValidationStateChanged += ValidationChanged;
_company = await _companyRepo.GetCompanyById(CompanyId);
// set up identification
_poDraft.CompanyId = NgCompany.CompanyId;
_poDraft.BcId = NgCompany.BcId;
_poDraft.SalesRepId = Ux.Id;
_draft.CompanyId = _company.CompanyId;
_draft.BcId = _company.BcId;
_draft.SalesRepId = Ux.Id;
_poDraft.ActivityStatusEnum = "noSale";
_poDraft.VisitTypeEnum = NgCompany.Account is "" or "NY" ? "new" : "recall";
_draft.ActivityStatusEnum = "noSale";
_draft.VisitTypeEnum = _company.Account is "" or "NY" ? "new" : "recall";
// permanent identifications
_poDraft.SalesRep = Ux.Adviser;
_poDraft.Account = NgCompany.Account;
_poDraft.VatNumber = NgCompany.VatNumber;
_poDraft.EMail = NgCompany.Email;
_poDraft.Phone = NgCompany.Phone;
_poDraft.Mobile = NgCompany.Mobile;
_draft.SalesRep = Ux.Adviser;
_draft.Account = _company.Account;
_draft.VatNumber = _company.VatNumber;
_draft.EMail = _company.Email;
_draft.Phone = _company.Phone;
_draft.Mobile = _company.Mobile;
_poDraft.Name = NgCompany.Name;
_poDraft.Address1 = NgCompany.Address1;
_poDraft.Address2 = NgCompany.Address2;
_poDraft.ZipCode = NgCompany.ZipCode;
_poDraft.City = NgCompany.City;
_draft.Name = _company.Name;
_draft.Address1 = _company.Address1;
_draft.Address2 = _company.Address2;
_draft.ZipCode = _company.ZipCode;
_draft.City = _company.City;
_poDraft.DlvName = NgCompany.Name;
_poDraft.DlvAddress1 = NgCompany.Address1;
_poDraft.DlvAddress2 = NgCompany.Address2;
_poDraft.DlvZipCode = NgCompany.ZipCode;
_poDraft.DlvCity = NgCompany.City;
_draft.DlvName = _company.Name;
_draft.DlvAddress1 = _company.Address1;
_draft.DlvAddress2 = _company.Address2;
_draft.DlvZipCode = _company.ZipCode;
_draft.DlvCity = _company.City;
}
@ -134,21 +140,21 @@ public partial class ActivityCreate : IDisposable
{
_logger.LogInformation("WorkDateComponent.OnChanged(SetWorkDate(workDate)) => {workDate}", workDate);
_workDate = DateTime.Parse(workDate);
_poDraft.ActivityDate = workDate;
_draft.ActivityDate = workDate;
}
private async Task CreateActivity()
{
HideButtons = true;
_poDraft.ActivityDate = _prefs.WorkDate;
_draft.ActivityDate = _prefs.WorkDate;
var activityType = _poDraft.ActivityTypeEnum switch
var activityType = _draft.ActivityTypeEnum switch
{
"phone" => "T",
"onSite" => "B",
_ => ""
};
_poDraft.OurRef = $"{activityType}:{Ux.FullName.Split(" ")[0]}";
_draft.OurRef = $"{activityType}:{Ux.FullName.Split(" ")[0]}";
var ln = 0;
// post to create activity endpoint
@ -168,18 +174,18 @@ public partial class ActivityCreate : IDisposable
};
lines.Add(line);
}
_poDraft.Lines = lines;
_draft.Lines = lines;
await _storage.SetItemAsync(CompanyId, _poDraft);
Console.WriteLine(JsonSerializer.Serialize(_poDraft));
var result = await _activityRepo.CreateActivity(_poDraft);
await _storage.SetItemAsync(CompanyId, _draft);
Console.WriteLine(JsonSerializer.Serialize(_draft));
var result = await _activityRepo.CreateActivity(_draft);
_toast.ShowSuccess($"{result.Message}.");
_navigator.NavigateTo($"/companies");
}
private void CheckActivity()
{
InvalidActivityType = string.IsNullOrWhiteSpace(_poDraft.ActivityTypeEnum);
InvalidActivityType = string.IsNullOrWhiteSpace(_draft.ActivityTypeEnum);
}
private async Task DeleteDraft()
{
@ -189,7 +195,7 @@ public partial class ActivityCreate : IDisposable
private void SelectItem(string itemId, string quantity, string price)
{
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;
Quantity = quantity;
}
@ -225,14 +231,14 @@ public partial class ActivityCreate : IDisposable
}
private async Task SetItemGroup(string groupFilter)
{
SalesItems = new List<NgSalesItemView>();
_caltalog = new List<NgSalesItemView>();
_paging.PageNumber = 1;
_paging.SelectGroup = groupFilter;
await GetSalesItems();
}
private async Task SetSearchCol(string columnName)
{
SalesItems = new List<NgSalesItemView>();
_caltalog = new List<NgSalesItemView>();
_paging.PageNumber = 1;
_paging.SearchTerm = "";
_paging.SearchColumn = columnName;
@ -240,27 +246,27 @@ public partial class ActivityCreate : IDisposable
}
private async Task SetSortCol(string orderBy)
{
SalesItems = new List<NgSalesItemView>();
_caltalog = new List<NgSalesItemView>();
_paging.OrderBy = orderBy;
await GetSalesItems();
}
private async Task SetSearchPhrase(string searchTerm)
{
SalesItems = new List<NgSalesItemView>();
_caltalog = new List<NgSalesItemView>();
_paging.PageNumber = 1;
_paging.SearchTerm = searchTerm;
await GetSalesItems();
}
private async Task SelectedPage(int page)
{
SalesItems = new List<NgSalesItemView>();
_caltalog = new List<NgSalesItemView>();
_paging.PageNumber = page;
await GetSalesItems();
}
private async Task SetPageSize(string pageSize)
{
SalesItems = new List<NgSalesItemView>();
_caltalog = new List<NgSalesItemView>();
_paging.PageSize = Convert.ToInt32(pageSize);
_paging.PageNumber = 1;
await GetSalesItems();
@ -269,8 +275,8 @@ public partial class ActivityCreate : IDisposable
private async Task GetSalesItems()
{
var response = await _itemRepo.GetSalesItemsPaged(_paging);
SalesItems = response.Items!;
MetaData = response.MetaData;
_caltalog = response.Items!;
_metaData = response.MetaData;
}
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
@ -287,27 +293,27 @@ public partial class ActivityCreate : IDisposable
// return;
// }
_poFormInvalid = !DraftContext.Validate();
_poFormInvalid = !_editContext.Validate();
StateHasChanged();
}
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");
_poFormInvalid = false;
DraftContext.OnFieldChanged -= HandleFieldChanged;
DraftContext = new EditContext(_poDraft);
DraftContext.OnFieldChanged += HandleFieldChanged;
DraftContext.OnValidationStateChanged -= ValidationChanged;
_editContext.OnFieldChanged -= HandleFieldChanged;
_editContext = new EditContext(_draft);
_editContext.OnFieldChanged += HandleFieldChanged;
_editContext.OnValidationStateChanged -= ValidationChanged;
}
public void Dispose()
{
_interceptor.DisposeEvent();
DraftContext.OnFieldChanged -= HandleFieldChanged;
DraftContext.OnValidationStateChanged -= ValidationChanged;
_editContext.OnFieldChanged -= HandleFieldChanged;
_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]
//
*@
@using Microsoft.AspNetCore.Authorization
@using Wonky.Client.Components
@attribute [Authorize(Roles = "Adviser")]
@page "/activity-today"
<div class="row mb-1 align-items-center">
<div class="col-md-4">
<span class="workDate">@(string.IsNullOrWhiteSpace(_workDate) ? "" : $"{DateTime.Parse(_workDate).ToLongDateString()}")</span>
</div>
<div class="col-md-4">
<WorkDateComponent OnChanged="GetActivities"></WorkDateComponent>
<div class="card">
<div class="card-header">
<div class="row mb-1 align-items-center">
<div class="col">
<h3 class="workDate">@(string.IsNullOrWhiteSpace(_workDate) ? "" : $"{DateTime.Parse(_workDate).ToLongDateString()}")</h3>
</div>
<div class="col">
<WorkDateComponent OnChanged="GetActivities"></WorkDateComponent>
</div>
</div>
</div>
</div>
<hr/>
<h5>Dagens aktivitet</h5>
<table class="table">
<thead>
<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>
<div class="card-body">
<ActivityTableComponent Activities="_view.Activities"></ActivityTableComponent>
</div>

View file

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

View file

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

View file

@ -44,7 +44,7 @@
<div class="col-md-3">
</div>
<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>

View file

@ -14,19 +14,22 @@
// 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
@using Wonky.Client.Helpers
@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-header">
<h5>@_companyView.Account - @_companyView.Name</h5>
<h3>@_company.Account - @_company.Name</h3>
</div>
@if (countryCode == "dk" && _company.ValidVat == 0)
{
<div class="card-body">
<VatAddressInputComponent Address="vatAddress" OnValidSubmit="GetInfoFromAddress"/>
</div>
@ -60,7 +63,8 @@
</tbody>
</table>
}
<EditForm EditContext="_updateCompany" OnValidSubmit="SubmitUpdate">
}
<EditForm EditContext="_editContext" OnValidSubmit="SubmitUpdate">
<DataAnnotationsValidator/>
<div class="card-body">
<table class="table">
@ -73,15 +77,15 @@
<DisplayStateComponent StateClass="@_vatState"></DisplayStateComponent>
</td>
<td>
<InputText id="vatNumber" class="form-control" @bind-Value="_companyView.VatNumber"/>
<ValidationMessage For="@(() => _companyView.VatNumber)"></ValidationMessage>
<InputText id="vatNumber" class="form-control" @bind-Value="_company.VatNumber"/>
<ValidationMessage For="@(() => _company.VatNumber)"></ValidationMessage>
</td>
<th>
Telefon
</th>
<td>
<InputText id="phone" class="form-control" @bind-Value="_companyView.Phone"/>
<ValidationMessage For="@(() => _companyView.Phone)"></ValidationMessage>
<InputText id="phone" class="form-control" @bind-Value="_company.Phone"/>
<ValidationMessage For="@(() => _company.Phone)"></ValidationMessage>
</td>
</tr>
<tr class="align-middle">
@ -90,15 +94,15 @@
</th>
<td></td>
<td>
<InputText id="name" class="form-control" @bind-Value="_companyView.Name"/>
<ValidationMessage For="@(() => _companyView.Name)"></ValidationMessage>
<InputText id="name" class="form-control" @bind-Value="_company.Name"/>
<ValidationMessage For="@(() => _company.Name)"></ValidationMessage>
</td>
<th>
Attention
</th>
<td>
<InputText id="attention" class="form-control" @bind-Value="_companyView.Attention"/>
<ValidationMessage For="@(() => _companyView.Attention)"></ValidationMessage>
<InputText id="attention" class="form-control" @bind-Value="_company.Attention"/>
<ValidationMessage For="@(() => _company.Attention)"></ValidationMessage>
</td>
</tr>
<tr class="align-middle">
@ -107,15 +111,15 @@
</th>
<td></td>
<td>
<InputText id="address1" class="form-control" @bind-Value="_companyView.Address1"/>
<ValidationMessage For="@(() => _companyView.Address1)"></ValidationMessage>
<InputText id="address1" class="form-control" @bind-Value="_company.Address1"/>
<ValidationMessage For="@(() => _company.Address1)"></ValidationMessage>
</td>
<th>
Adresse2
</th>
<td>
<InputText id="address2" class="form-control" @bind-Value="_companyView.Address2"/>
<ValidationMessage For="@(() => _companyView.Address2)"></ValidationMessage>
<InputText id="address2" class="form-control" @bind-Value="_company.Address2"/>
<ValidationMessage For="@(() => _company.Address2)"></ValidationMessage>
</td>
</tr>
<tr class="align-middle">
@ -124,15 +128,15 @@
</th>
<td></td>
<td>
<InputText id="zipCode" class="form-control" @bind-Value="_companyView.ZipCode"/>
<ValidationMessage For="@(() => _companyView.ZipCode)"></ValidationMessage>
<InputText id="zipCode" class="form-control" @bind-Value="_company.ZipCode"/>
<ValidationMessage For="@(() => _company.ZipCode)"></ValidationMessage>
</td>
<th>
Bynavn
</th>
<td>
<InputText id="city" class="form-control" @bind-Value="_companyView.City"/>
<ValidationMessage For="@(() => _companyView.City)"></ValidationMessage>
<InputText id="city" class="form-control" @bind-Value="_company.City"/>
<ValidationMessage For="@(() => _company.City)"></ValidationMessage>
</td>
</tr>
<tr class="align-middle">
@ -141,15 +145,15 @@
</th>
<td></td>
<td>
<InputText id="email" class="form-control" @bind-Value="_companyView.Email"/>
<ValidationMessage For="@(() => _companyView.Email)"></ValidationMessage>
<InputText id="email" class="form-control" @bind-Value="_company.Email"/>
<ValidationMessage For="@(() => _company.Email)"></ValidationMessage>
</td>
<th>
Mobil
</th>
<td>
<InputText id="mobile" class="form-control" @bind-Value="_companyView.Mobile"/>
<ValidationMessage For="@(() => _companyView.Mobile)"></ValidationMessage>
<InputText id="mobile" class="form-control" @bind-Value="_company.Mobile"/>
<ValidationMessage For="@(() => _company.Mobile)"></ValidationMessage>
</td>
</tr>
<tr class="align-middle">
@ -157,11 +161,12 @@
Næste besøg
</th>
<td class="state">
<DisplayStateComponent StateClass="@(_hasFolded ? "the-dead" : Utils.GetVisitState($"{_companyView.NextVisit}"))"> </DisplayStateComponent>
<DisplayStateComponent StateClass="@_visitState">
</DisplayStateComponent>
</td>
<td>
<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>
<th>
Besøgt
@ -176,8 +181,8 @@
</th>
<td></td>
<td>
<InputNumber id="interval" class="form-control" @bind-Value="_companyView.Interval"/>
<ValidationMessage For="@(() => _companyView.Interval)"></ValidationMessage>
<InputNumber id="interval" class="form-control" @bind-Value="_company.Interval"/>
<ValidationMessage For="@(() => _company.Interval)"></ValidationMessage>
</td>
<th scope="row">
@ -191,14 +196,6 @@
</div>
<div class="card-footer">
<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">
<button type="submit" class="btn btn-success">Gem</button>
</div>
@ -206,7 +203,7 @@
<a class="btn btn-primary" href="/companies">Til Oversigt</a>
</div>
<div class="col">
<a class="btn btn-primary" href="/company/@CompanyId">Tilbage</a>
<ActivityButton CompanyId="@_company.CompanyId"></ActivityButton>
</div>
</div>
</div>

View file

@ -32,19 +32,18 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Pages;
public partial class CompanyEdit : IDisposable
public partial class CompanyView : IDisposable
{
[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 ICompanyHttpRepository CompanyRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public VatInfoLookupService VatInfoLookupService { get; set; }
[Inject] public ILocalStorageService StorageService { get; set; }
[Parameter] public string Account { get; set; } = "";
[Parameter] public string CompanyId { get; set; } = "";
private CompanyDto _companyView { get; set; } = new();
private EditContext _updateCompany { get; set; }
private CompanyDto _company { get; set; } = new();
private EditContext _editContext { get; set; }
private List<VirkRegInfo> _vInfos { get; set; } = new();
private VirkRegInfo _virkRegInfo { get; set; } = new();
private DateTime _lastVisit { get; set; }
@ -55,6 +54,8 @@ public partial class CompanyEdit : IDisposable
private bool _hasFolded;
private bool _formInvalid = true;
private string _orgVat;
private string countryCode = "dk";
private string _visitState = "the-ugly";
private readonly JsonSerializerOptions _options = new ()
{
@ -64,93 +65,94 @@ public partial class CompanyEdit : IDisposable
protected override async Task OnInitializedAsync()
{
var ux = await StorageService.GetItemAsync<UserInfoView>("_xu");
countryCode = ux.CountryCode;
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
_companyView = await CompanyRepo.GetCompanyById(CompanyId);
_orgVat = _companyView.VatNumber;
_companyView.CountryCode = ux.CountryCode.ToLower();
_company = await CompanyRepo.GetCompanyById(CompanyId);
_orgVat = _company.VatNumber;
_company.CountryCode = ux.CountryCode.ToLower();
vatAddress = PrepareVatAddress(_companyView);
vatAddress = PrepareVatAddress(_company);
if (_companyView.Interval == 0)
_companyView.Interval = 8;
if (_company.Interval == 0)
_company.Interval = 8;
_lastVisit = DateTime.Parse(_companyView.LastVisit);
_nextVisit = DateTime.Parse(_companyView.NextVisit);
_lastVisit = DateTime.Parse(_company.LastVisit);
_nextVisit = DateTime.Parse(_company.NextVisit);
if (!_companyView.ValidDateSpan())
_nextVisit = _lastVisit.AddDays(_companyView.Interval * 7);
if(_companyView.HasFolded == 1)
if (!_company.ValidDateSpan())
_nextVisit = _lastVisit.AddDays(_company.Interval * 7);
if(_company.HasFolded == 1)
{
_hasFolded = true;
_vatState = "the-dead";
_visitState = "the-dead";
}
else
{
if (_companyView.ValidVat == 0 && !string.IsNullOrWhiteSpace(_companyView.VatNumber))
_companyView.ValidVat = VatUtils.ValidateFormat(_companyView.CountryCode, _companyView.VatNumber) ? 1 : 0;
_validVat = _companyView.ValidVat == 1;
_vatState = _companyView.ValidVat == 1 ? "the-good" : "the-draw";
if (_company.ValidVat == 0 && !string.IsNullOrWhiteSpace(_company.VatNumber))
_company.ValidVat = VatUtils.ValidateFormat(_company.CountryCode, _company.VatNumber) ? 1 : 0;
_validVat = _company.ValidVat == 1;
_vatState = _company.ValidVat == 1 ? "the-good" : "the-draw";
_visitState = Utils.GetVisitState($"{_nextVisit:yyyy-HH-mm}");
}
_updateCompany = new EditContext(_companyView);
_updateCompany.OnFieldChanged += HandleFieldChanged;
_updateCompany.OnValidationStateChanged += ValidationChanged;
_editContext = new EditContext(_company);
_editContext.OnFieldChanged += HandleFieldChanged;
_editContext.OnValidationStateChanged += ValidationChanged;
}
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}";
_companyView.NextVisit = $"{_nextVisit:yyyy-MM-dd}";
_company.LastVisit = $"{_lastVisit:yyyy-MM-dd}";
_company.NextVisit = $"{_nextVisit:yyyy-MM-dd}";
if (!VatUtils.ValidateFormat(_companyView.CountryCode, _companyView.VatNumber) ||
!_companyView.ValidDateSpan())
if (!VatUtils.ValidateFormat(_company.CountryCode, _company.VatNumber) ||
!_company.ValidDateSpan())
{
_formInvalid = true;
}
else
{
_formInvalid = !_updateCompany.Validate();
_formInvalid = !_editContext.Validate();
}
StateHasChanged();
}
private void ValidationChanged(object sender, ValidationStateChangedEventArgs e)
{
_formInvalid = true;
_updateCompany.OnFieldChanged -= HandleFieldChanged;
_editContext.OnFieldChanged -= HandleFieldChanged;
_updateCompany = new EditContext(_companyView);
_editContext = new EditContext(_company);
_updateCompany.OnFieldChanged += HandleFieldChanged;
_updateCompany.OnValidationStateChanged -= ValidationChanged;
_editContext.OnFieldChanged += HandleFieldChanged;
_editContext.OnValidationStateChanged -= ValidationChanged;
}
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.");
StateHasChanged();
return;
}
_companyView.LastVisit = $"{_lastVisit:yyyy-MM-dd}";
_companyView.NextVisit = $"{_nextVisit:yyyy-MM-dd}";
_companyView.IsHidden = 0;
if (_companyView.VatNumber != _orgVat)
_companyView.UpdateErpVat = 1;
_company.LastVisit = $"{_lastVisit:yyyy-MM-dd}";
_company.NextVisit = $"{_nextVisit:yyyy-MM-dd}";
_company.IsHidden = 0;
if (_company.VatNumber != _orgVat)
_company.UpdateErpVat = 1;
var x = JsonSerializer.Serialize(_companyView, _options);
var x = JsonSerializer.Serialize(_company, _options);
Logger.LogInformation(x);
var success = await CompanyRepo.UpdateCompany(CompanyId, _companyView );
var success = await CompanyRepo.UpdateCompany(CompanyId, _company );
if (success)
{
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();
if (syncAll)
{
_companyView.VatNumber = _virkRegInfo.VatNumber;
_companyView.Name = _virkRegInfo.Name;
_companyView.Address1 = _virkRegInfo.Address;
_companyView.Address2 = _virkRegInfo.CoName;
_companyView.ZipCode = _virkRegInfo.ZipCode;
_companyView.City = _virkRegInfo.City;
_company.VatNumber = _virkRegInfo.VatNumber;
_company.Name = _virkRegInfo.Name;
_company.Address1 = _virkRegInfo.Address;
_company.Address2 = _virkRegInfo.CoName;
_company.ZipCode = _virkRegInfo.ZipCode;
_company.City = _virkRegInfo.City;
}
else
{
_companyView.VatNumber = _virkRegInfo.VatNumber;
_company.VatNumber = _virkRegInfo.VatNumber;
}
_vInfos = new List<VirkRegInfo>();
@ -192,8 +194,8 @@ public partial class CompanyEdit : IDisposable
public void Dispose()
{
Interceptor.DisposeEvent();
_updateCompany.OnFieldChanged -= HandleFieldChanged;
_updateCompany.OnValidationStateChanged -= ValidationChanged;
_editContext.OnFieldChanged -= HandleFieldChanged;
_editContext.OnValidationStateChanged -= ValidationChanged;
}
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]
//
*@
@using Microsoft.AspNetCore.Authorization
@using Wonky.Client.Components
@attribute [Authorize(Roles = "Adviser")]
@page "/sales-report"
@page "/sales-reports/new"
<EditForm EditContext="_editContext">
<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 Microsoft.AspNetCore.Authorization
@page "/sales-report/view/{reportDate}"
@page "/sales-reports/view/{ReportDate}"
@attribute [Authorize(Roles = "Adviser,Admin,Supervisor")]
<WorkDateComponent OnChanged="GetReport"></WorkDateComponent>
<div>
@if (_report.Activities.Any())
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
@if (!string.IsNullOrWhiteSpace(ReportDate))
{
<table class="table">
<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>
<h3 class="workDate">Dagsrapport @DateTime.Parse(ReportDate).ToLongDateString()</h3>
}
<tr>
<td></td>
<td></td>
<td>Total</td>
<td class="align-content-end">@_report.Report.TotalTurnover</td>
</tr>
</tbody>
</table>
}
<table class="table">
<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 class="col">
<WorkDateComponent OnChanged="GetReport"></WorkDateComponent>
</div>
</div>
</div>
<div class="card-body">
<ReportSummaryComponent Report="_report.Report"></ReportSummaryComponent>
<ActivityTableComponent Activities="_report.Activities"></ActivityTableComponent>
</div>
</div>

View file

@ -21,6 +21,7 @@ public partial class ReportView
private async Task GetReport(string workDate)
{
_report = new NgSalesReportView();
_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
{
public partial class ErrorReport
public partial class SiteErrorReport
{
[Parameter]
public int ErrorCode { get; set; }

View file

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

View file

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

View file

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

View file

@ -21,34 +21,65 @@ namespace Wonky.Entity.DTO;
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; }
[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; }
[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; }
[Required(ErrorMessage = "ORG/VAT/CVR er ikke et gyldigt nummer")]
public string VatNumber { get; set; } = "";
public string CompanyId { get; set; } = "";
public string SalesRepId { get; set; } = "";
public string BcId { get; set; } = "";
[MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")] public string Address1 { get; set; } = "";
[MaxLength(50, ErrorMessage = "Du kan højst bruge 50 tegn")] public string Address2 { 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; } = "";
[MaxLength(100, ErrorMessage = "Du kan højst bruge 100 tegn")]
public string Address1 { get; set; } = "";
[MaxLength(50, ErrorMessage = "Du kan højst bruge 50 tegn")]
public string Address2 { 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 LastVisit { get; set; } = "";
public string NextVisit { get; set; } = "";
[Range(1, 52, ErrorMessage = "Angiv interval mellem 1 og 52 uger")]
public int Interval { get; set; } = 8;
public int HasFolded { get; set; }
public int IsHidden { get; set; }
public int ValidVat { 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"};
if (notAllowed.Contains(LastVisit) || notAllowed.Contains(NextVisit))

View file

@ -5,9 +5,16 @@ namespace Wonky.Entity.DTO;
public class ReportDto
{
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; } = "";
[Required(ErrorMessage = "Dagtype skal angives")] public string DayTypeEnum { 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; } = "";
[Required(ErrorMessage = "Dagtype skal angives")]
public string DayTypeEnum { get; set; } = "";
// Date interval (used for leave, sickLeave and work hours
public string FromDateTime { 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";
}