Browse Source

Products and sales pages complete

BISK2023-4-item-search-feature
Benjamin Arhen 2 years ago
parent
commit
d473fe0be7
  1. 57
      Client/Biskilog Accounting.Client.csproj
  2. 46
      Client/Elements/Headbar.razor
  3. 26
      Client/Elements/Headbar.razor.cs
  4. 73
      Client/Layouts/MainLayout.razor
  5. 40
      Client/Layouts/MainLayout.razor.cs
  6. 59
      Client/Models/NavItem.cs
  7. 101
      Client/Pages/Dashboard/Dashboard.razor
  8. 44
      Client/Pages/Dashboard/Dashboard.razor.cs
  9. 37
      Client/Pages/Dashboard/Elements/ChartElement.razor
  10. 18
      Client/Pages/Dashboard/Elements/ChartElement.razor.cs
  11. 22
      Client/Pages/Dashboard/Elements/LowStockItems.razor
  12. 18
      Client/Pages/Dashboard/Elements/MostPurchasedElement.razor
  13. 4
      Client/Pages/Dashboard/Elements/ProductPriceHistory.razor
  14. 47
      Client/Pages/Dashboard/Elements/ProductPriceHistory.razor.cs
  15. 15
      Client/Pages/Dashboard/Elements/TransactionCard.razor
  16. 19
      Client/Pages/Product/Brands.razor
  17. 37
      Client/Pages/Product/Brands.razor.cs
  18. 19
      Client/Pages/Product/Categories.razor
  19. 37
      Client/Pages/Product/Categories.razor.cs
  20. 186
      Client/Pages/Product/Elements/ProductDialog.razor
  21. 25
      Client/Pages/Product/Elements/ProductDialog.razor.cs
  22. 29
      Client/Pages/Product/Products.razor
  23. 68
      Client/Pages/Product/Products.razor.cs
  24. 95
      Client/Pages/Transactions/Elements/ReceiptDialog.razor
  25. 67
      Client/Pages/Transactions/Elements/ReceiptDialog.razor.cs
  26. 25
      Client/Pages/Transactions/Elements/SaleSummary.razor
  27. 24
      Client/Pages/Transactions/Elements/SaleSummary.razor.cs
  28. 77
      Client/Pages/Transactions/Sales.razor
  29. 102
      Client/Pages/Transactions/Sales.razor.cs
  30. 19
      Client/Program.cs
  31. 98
      Client/Repos/MainInterfaceService.cs
  32. 143
      Client/Repos/ProductRepository.cs
  33. 98
      Client/Repos/SalesRepository.cs
  34. 2
      Client/_Imports.razor
  35. BIN
      Client/wwwroot/assets/img/box.png
  36. 5
      Client/wwwroot/assets/vendor/css/core.css
  37. 151
      Client/wwwroot/css/app.css
  38. BIN
      Client/wwwroot/fonts/CCode39.woff
  39. BIN
      Client/wwwroot/fonts/CCodeIND2of5.woff
  40. BIN
      Client/wwwroot/fonts/CCodePostnet.woff
  41. 32
      Client/wwwroot/index.html
  42. 9
      Server/BiskAcdbContext.cs
  43. 9
      Server/Controllers/AnalyticsController.cs
  44. 37
      Server/Controllers/CompanyInfoController.cs
  45. 55
      Server/Controllers/ProductsController.cs
  46. 49
      Server/Controllers/SalesController.cs
  47. 4
      Server/Program.cs
  48. 31
      Server/Services/AnalyticalService.cs
  49. 53
      Server/Services/CompanyService.cs
  50. 200
      Server/Services/ProductRepo.cs
  51. 169
      Server/Services/SalesService.cs
  52. 4
      Shared/CustomModels/ProductItem.cs
  53. 26
      Shared/CustomModels/ProductUnits.cs
  54. 6
      Shared/CustomModels/WeeklySaleItem.cs
  55. 5
      Shared/Interfaces/IAnalytics.cs
  56. 18
      Shared/Interfaces/ICompanyInfo.cs
  57. 15
      Shared/Interfaces/IMainInterface.cs
  58. 32
      Shared/Interfaces/IProducts.cs
  59. 24
      Shared/Interfaces/ISalesInterface.cs
  60. 16
      Shared/Interfaces/ISearchService.cs
  61. 4
      Shared/POSModels/Tblcart.cs
  62. 33
      Shared/ServiceRepo/SearchService.cs

57
Client/Biskilog Accounting.Client.csproj

@ -1,35 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
<AssemblyName>$(AssemblyName.Replace(' ', '_'))</AssemblyName>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
<AssemblyName>$(AssemblyName.Replace(' ', '_'))</AssemblyName>
</PropertyGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="Blazored.LocalStorage" Version="4.3.0" />
<PackageReference Include="Blazored.SessionStorage" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Blazor-ApexCharts" Version="0.9.21-beta" />
<PackageReference Include="Microsoft.Fast.Components.FluentUI" Version="2.3.6" />
<PackageReference Include="Radzen.Blazor" Version="4.12.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="BlazorDateRangePicker" Version="4.3.0" />
<PackageReference Include="Blazored.LocalStorage" Version="4.3.0" />
<PackageReference Include="Blazored.SessionStorage" Version="2.3.0" />
<PackageReference Include="Faso.Blazor.SpinKit" Version="1.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Blazor-ApexCharts" Version="0.9.21-beta" />
<PackageReference Include="Microsoft.Fast.Components.FluentUI" Version="2.3.6" />
<PackageReference Include="NETStandardBarcode" Version="1.0.2" />
<PackageReference Include="Radzen.Blazor" Version="4.12.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Biskilog Accounting.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Biskilog Accounting.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>
<ItemGroup>
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>
</Project>

46
Client/Elements/Headbar.razor

@ -1,5 +1,8 @@
<!-- Navbar -->
@using Biskilog_Accounting.Shared.Interfaces;
@inject ISearchService m_searchService
@inject IJSRuntime JSRuntime;
<!-- Navbar -->
<nav class="layout-navbar container-xxl navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme"
id="layout-navbar">
<div class="layout-menu-toggle navbar-nav align-items-xl-center me-3 me-xl-0 d-xl-none">
@ -10,19 +13,21 @@
<div class="navbar-nav-right d-flex align-items-center" id="navbar-collapse">
<!-- Search -->
<div class="navbar-nav align-items-center">
<div class="nav-item d-flex align-items-center">
<div class="navbar-nav align-items-center" style="width:90%;">
<div class="nav-item d-flex align-items-center" style="width:90%;">
<i class="bx bx-search fs-4 lh-0"></i>
<input type="text"
id="mainSearch"
class="form-control border-0 shadow-none"
placeholder="Search..."
aria-label="Search..." />
aria-label="Search..."
@oninput="@(args => SearchInput(args.Value.ToString()))"/>
</div>
</div>
<!-- /Search -->
<ul class="navbar-nav flex-row align-items-center ms-auto">
<!-- Place this tag where you want the button to render. -->
@*<!-- Place this tag where you want the button to render. -->
<li class="nav-item lh-1 me-3">
<a class="github-button"
href="https://github.com/themeselection/sneat-html-admin-template-free"
@ -30,9 +35,36 @@
data-size="large"
data-show-count="true"
aria-label="Star themeselection/sneat-html-admin-template-free on GitHub">Star</a>
</li>
</li>*@
<RadzenButton Variant="Variant.Outlined" Text="New" Icon="add_circle_outline" ButtonStyle="ButtonStyle.Success" />
<li class="nav-item dropdown notification-ui show">
<a class="nav-link dropdown-toggle notification-ui_icon" href="javascript:void()" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-bell"></i>
<span class="unread-notification"></span>
</a>
<div class="dropdown-menu notification-ui_dd show" aria-labelledby="navbarDropdown">
<div class="notification-ui_dd-header">
<small class="text-center">Notification</small>
</div>
<div class="notification-ui_dd-content">
<a href="#!" class="notification-list notification-list--unread text-dark">
<div class="notification-list_img">
<img src="images/users/user1.jpg" alt="user">
</div>
<div class="notification-list_detail">
<p><b>John Doe</b> <br><span class="text-muted">reacted to your post</span></p>
<p class="nt-link text-truncate">How to travel long way home from here.</p>
</div>
<p><small>10 mins ago</small></p>
</a>
</div>
<div class="notification-ui_dd-footer">
<a href="#!" class="btn btn-success btn-block">View All</a>
</div>
</div>
</li>
<!-- User -->
<!-- User -->
<li class="nav-item navbar-dropdown dropdown-user dropdown">
<a class="nav-link dropdown-toggle hide-arrow" href="javascript:void(0);" data-bs-toggle="dropdown">
<div class="avatar avatar-online">

26
Client/Elements/Headbar.razor.cs

@ -0,0 +1,26 @@
using Microsoft.JSInterop;
namespace Biskilog_Accounting.Client.Elements
{
public partial class Headbar
{
protected override void OnInitialized()
{
m_searchService.ClearTextBox += ClearTextBox;
base.OnInitialized();
}
private void ClearTextBox()
{
ClearInputField("mainSearch");
}
private async Task ClearInputField(string elementId)
{
await JSRuntime.InvokeVoidAsync("eval", $"document.getElementById('{elementId}').value = ''");
}
private void SearchInput(string a_key)
{
m_searchService.PerformSearch(a_key);
}
}
}

73
Client/Layouts/MainLayout.razor

@ -3,9 +3,15 @@
@inherits LayoutComponentBase
@inject NavigationManager m_navigationManager
@layout AuthLayout
@inject HttpClient m_http
@inject ITokenService m_tokenService
@inject IProduct m_productRepo
@inject DialogService m_dialogService
@inject ISalesInterface m_salesService
@inject ICompanyInfo m_companyInfo
<RadzenDialog />
<RadzenContextMenu />
<!-- Layout wrapper -->
<div class="layout-wrapper layout-content-navbar">
<div class="layout-container">
@ -18,17 +24,70 @@
<Headbar />
<!-- Content wrapper -->
<div class="content-wrapper">
<!-- Content -->
@Body
<!-- / Content -->
<!-- Footer -->
<div class="container-xxl flex-grow-1 container-p-y">
<!-- Content -->
@Body
<!-- / Content -->
</div>
</div>
<!-- Content wrapper -->
</div>
<!-- / Layout page -->
<Footer />
<Footer />
</div>
</div>
</div>
<!-- / Layout wrapper -->
<style>
.rz-datatable-thead th, .rz-grid-table thead th {
background-color: #e0e0e0;
}
.rz-data-row:hover {
cursor: pointer;
color: white !important;
}
.rz-steps .rz-state-highlight .rz-steps-title {
background-color: #0a0a38 !important;
color: white !important;
}
.rz-steps .rz-state-highlight .rz-steps-number {
background-color: #0a0a38 !important;
color: white !important;
}
.rz-selectable tbody tr.rz-data-row.rz-state-highlight .rz-cell-data {
color: #ffffff !important;
}
.rz-selectable tbody tr.rz-data-row.rz-state-highlight > td {
background-color: #0a0a38 !important;
}
.rz-dialog-titlebar {
padding: 10px !important;
background-image: linear-gradient(to bottom right,rgb(10,10,56,1.16),rgb(20, 158, 132,1.16)) !important;
color: white !important;
}
.rz-dialog-title {
font-weight: var(--rz-dialog-title-font-weight);
letter-spacing: var(--rz-dialog-title-letter-spacing);
color: #ffffff !important
}
.rz-dialog-titlebar-close .rzi-times {
font-size: var(--rz-dialog-close-font-size);
color: #ffffff !important;
vertical-align: var(--rz-dialog-close-vertical-align);
}
.rz-dialog-side-content {
flex: 0 1 auto;
padding: 1px !important;
overflow: auto;
}
</style>

40
Client/Layouts/MainLayout.razor.cs

@ -1,13 +1,38 @@
using System.Net.Http.Headers;
using Biskilog_Accounting.Client.Pages.Transactions.Elements;
using Radzen;
using System.Net.Http.Headers;
namespace Biskilog_Accounting.Client.Layouts
{
public partial class MainLayout
{
protected override Task OnInitializedAsync()
protected override async Task OnInitializedAsync()
{
CheckPermission();
return base.OnInitializedAsync();
await CheckPermission();
if (m_http.DefaultRequestHeaders.Authorization != null)
{
_ = Task.Run(async () =>
{
var fetchCompanyData = m_companyInfo.GetCompanyInfoAsync();
var fetchBranchData = m_companyInfo.GetBranches();
var fetchProduct = m_productRepo.FetchProducts();
var fetchUnits = m_productRepo.FetchUnits();
var fetchBrands = m_productRepo.FetchBrands();
var fetchCategories = m_productRepo.FetchCategories();
var fetchRecentSale = m_salesService.FetchRecentTransaction(250);
// Wait for all tasks to complete
await Task.WhenAll(
fetchCompanyData,
fetchBranchData,
fetchProduct,
fetchUnits,
fetchBrands,
fetchCategories,
fetchRecentSale
);
});
}
}
protected override void OnAfterRender(bool firstRender)
{
@ -17,7 +42,7 @@ namespace Biskilog_Accounting.Client.Layouts
}
base.OnAfterRender(firstRender);
}
private async void CheckPermission()
private async Task CheckPermission()
{
//Checks if user token is set else redirect user to login page
if (!await m_tokenService.IsTokenSet())
@ -31,5 +56,10 @@ namespace Biskilog_Accounting.Client.Layouts
m_http.DefaultRequestHeaders.Authorization = authHeader;
}
}
async Task OpenReceiptDialog()
{
await m_dialogService.OpenSideAsync<ReceiptDialog>("Receipt", options: new SideDialogOptions {
CloseDialogOnOverlayClick = true, Position = DialogPosition.Right,ShowMask = true });
}
}
}

59
Client/Models/NavItem.cs

@ -52,24 +52,21 @@
Description = "Transactions",
Title = "Transactions",
Icon = "bxs-cart",
Children = new List<NavItem>
{
new NavItem()
{
Id = 2.1,
Title = "Sales",
},
new NavItem()
{
Id = 2.2,
Title = "Cancelled Sales",
},
new NavItem()
{
Id = 2.3,
Title = "Statistics",
},
},
Link = "/transactions/sales",
//Children = new List<NavItem>
//{
// new NavItem()
// {
// Id = 2.1,
// Title = "Sales",
// Link = "/transactions/sales",
// },
// new NavItem()
// {
// Id = 2.2,
// Title = "Statistics",
// },
//},
},
new NavItem()
{
@ -84,29 +81,29 @@
Id = 3.1,
Description = "Brands",
Title = "Brands",
Link = "",
Link = "/products/brands",
},
new NavItem
{
Id = 3.2,
Description = "Category",
Title = "Category",
Link = "",
Link = "/products/categories",
},
new NavItem
{
Id = 3.3,
Description = "Inventory",
Title = "Inventory",
Link = "",
},
new NavItem
{
Id = 3.4,
Description = "Inventory",
Title = "Bulk Price Changes",
Link = "",
Link = "/products/inventory",
},
//new NavItem
//{
// Id = 3.4,
// Description = "Inventory",
// Title = "Bulk Price Changes",
// Link = "",
//},
new NavItem
{
Id = 3.5,
@ -129,10 +126,10 @@
new NavItem()
{
Id = 5,
Description = "Employees",
Description = "Users",
Icon = "bxs-user-account",
Link = "employees",
Title = "Employees"
Link = "users",
Title = "Users"
},
new NavItem()
{

101
Client/Pages/Dashboard/Dashboard.razor

@ -3,61 +3,62 @@
@inject HttpClient m_http
@inject ITokenService m_tokenService
@page "/"
<div class="container-xxl flex-grow-1 container-p-y">
<div class="row">
<div class="col-lg-8 mb-4 order-0">
<WelcomeCard TradeSummary="@m_tradeSummary" Username="@m_username" IsLoading="@m_loadingTradeSummary" />
</div>
<div class="col-lg-4 col-md-4 order-1">
<div class="row">
<div class="col-lg-6 col-md-12 col-6 mb-4">
<AnalyticsItemSmall IsLoading="@m_loadingTradeSummary" Icon="../assets/img/icons/unicons/chart-success.png" Title="Transactions" Value="@m_tradeSummary.CurrentTradeSales" Percentage="@(((m_tradeSummary.CurrentTradeSales-m_tradeSummary.LastTradeSales)/m_tradeSummary.LastTradeSales)*100)" />
</div>
<div class="col-lg-6 col-md-12 col-6 mb-4">
<AnalyticsItemSmall IsLoading="@m_loadingCancelledSummary" Icon="../assets/img/icons/unicons/wallet-info.png" Title="Cancelled Sales" Value="@(m_cancelledWeeklySale)" Percentage="@(m_cancelledPercentage)" />
</div>
<div class="row" style="margin-top:10px;">
<h4>Summary for the Week </h4>
</div>
<div class="row">
<div class="col-lg-8 mb-4 order-0">
<WelcomeCard TradeSummary="@m_tradeSummary" Username="@m_username" IsLoading="@m_loadingTradeSummary" />
</div>
<div class="col-lg-4 col-md-4 order-1">
<div class="row">
<div class="col-lg-6 col-md-12 col-6 mb-4">
<AnalyticsItemSmall IsLoading="@m_loadingTradeSummary" Icon="../assets/img/icons/unicons/chart-success.png" Title="Transactions" Value="@m_tradeSummary.CurrentTradeSales" Percentage="@(((m_tradeSummary.CurrentTradeSales-m_tradeSummary.LastTradeSales)/m_tradeSummary.LastTradeSales)*100)" />
</div>
</div>
<!-- Total Revenue -->
<div class="col-12 col-lg-8 order-2 order-md-3 order-lg-2 mb-4">
<ChartElement Series="@m_weeklySaleItems" Title="Weekly Revenue" SubTitle="Sale Trend" IsLoading="@m_loadingWeeklySales" />
</div>
<!--/ Total Revenue -->
<div class="col-12 col-md-8 col-lg-4 order-3 order-md-2">
<div class="row">
<div class="col-6 mb-4">
<AnalyticsItemSmall IsLoading="@(!m_loadingCreditSummary)" Icon="../assets/img/icons/unicons/paypal.png" Title="Credit Sales" Value="2456" Percentage="-22" />
</div>
<div class="col-6 mb-4">
<AnalyticsItemSmall IsLoading="@m_loadingDebtSummary" Icon="../assets/img/icons/unicons/cc-primary.png" Title="Debts To Collect" Value="@(m_totalDebt)" Percentage="-22" />
</div>
<!-- </div>
<div class="row"> -->
<div class="col-12 mb-4">
<ProductPriceHistory ProductHistory="@m_ProductPriceChanges" IsLoading="@m_loadingPriceHistory" />
</div>
<div class="col-lg-6 col-md-12 col-6 mb-4">
<AnalyticsItemSmall IsLoading="@m_loadingCancelledSummary" Icon="../assets/img/icons/unicons/wallet-info.png" Title="Cancelled Sales" Value="@(m_cancelledWeeklySale)" Percentage="@(m_cancelledPercentage)" />
</div>
</div>
</div>
<div class="row">
<!-- Low stock items -->
<div class="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
<LowStockItems LowStockProducts="@m_lowstock" IsLoading="@m_loadingLowStockSummary" />
</div>
<!--/ Order Statistics -->
<!-- Expense Overview -->
<div class="col-md-6 col-lg-4 order-1 mb-4">
<MostPurchasedElement MostPurchasedItems="@m_mostPurchased" IsLoading="@m_loadingMostPurchased" />
</div>
<!--/ Expense Overview -->
<!-- Transactions -->
<div class="col-md-6 col-lg-4 order-2 mb-4">
<!-- Recent Transactions -->
<TransactionCard Sales="@m_recentTransaction" IsLoading="@m_loadingRecentTransactionsSummary" />
<!--/ Recent Transactions -->
<!-- Total Revenue -->
<div class="col-12 col-lg-8 order-2 order-md-3 order-lg-2 mb-4">
<ChartElement Series="@m_weeklySaleItems" Title="Weekly Sales Trend" SubTitle="Top performing categories"
CategorySummary = "m_categorySummary" IsLoading="@m_loadingWeeklySales" />
</div>
<!--/ Total Revenue -->
<div class="col-12 col-md-8 col-lg-4 order-3 order-md-2">
<div class="row">
<div class="col-6 mb-4">
<AnalyticsItemSmall IsLoading="@(!m_loadingCreditSummary)" Icon="../assets/img/icons/unicons/paypal.png" Title="Credit Sales" Value="0" Percentage="0" />
</div>
<div class="col-6 mb-4">
<AnalyticsItemSmall IsLoading="@m_loadingDebtSummary" Icon="../assets/img/icons/unicons/cc-primary.png" Title="Debts To Collect" Value="@(m_totalDebt)" Percentage="-22" />
</div>
<!-- </div>
<div class="row"> -->
<div class="col-12 mb-4">
<ProductPriceHistory ProductHistory="@m_ProductPriceChanges" IsLoading="@m_loadingPriceHistory" />
</div>
</div>
<!--/ Transactions -->
</div>
</div>
<div class="row">
<!-- Low stock items -->
<div class="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
<LowStockItems LowStockProducts="@m_lowstock" IsLoading="@m_loadingLowStockSummary" />
</div>
<!--/ Order Statistics -->
<!-- Expense Overview -->
<div class="col-md-6 col-lg-4 order-1 mb-4">
<MostPurchasedElement MostPurchasedItems="@m_mostPurchased" IsLoading="@m_loadingMostPurchased" />
</div>
<!--/ Expense Overview -->
<!-- Transactions -->
<div class="col-md-6 col-lg-4 order-2 mb-4">
<!-- Recent Transactions -->
<TransactionCard Sales="@m_recentTransaction" IsLoading="@m_loadingRecentTransactionsSummary" />
<!--/ Recent Transactions -->
</div>
<!--/ Transactions -->
</div>

44
Client/Pages/Dashboard/Dashboard.razor.cs

@ -1,6 +1,7 @@
using Biskilog_Accounting.Shared.CustomModels;
using System.Net.Http.Json;
using System.Text.Json;
using static System.Net.WebRequestMethods;
namespace Biskilog_Accounting.Client.Pages.Dashboard
{
@ -14,6 +15,7 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard
private List<MostPurchasedItem> m_mostPurchased { get; set; } = new List<MostPurchasedItem> { };
private List<ProductItem> m_lowstock { get; set; } = new List<ProductItem> { };
private List<ProductPriceChange> m_ProductPriceChanges { get; set; } = new List<ProductPriceChange> { };
private IEnumerable<WeeklyCategorySummary> m_categorySummary { get; set; } = new List<WeeklyCategorySummary>();
private double m_cancelledWeeklySale { get; set; } = 0;
private double m_cancelledPercentage { get; set; } = 0;
private double m_totalDebt { get; set; } = 0;
@ -28,6 +30,7 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard
private bool m_loadingRecentTransactionsSummary { get; set; } = true;
private bool m_loadingCancelledSummary { get; set; } = true;
private bool m_loadingCreditSummary { get; set; } = true;
#endregion
#endregion
protected override async Task OnInitializedAsync()
@ -39,6 +42,7 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard
_ = Task.Run(async () =>
{
var weeklySalesTask = GetWeeklySales();
var categoryWeekly = GetCategoryTradeSummary();
var cancelledSalesTask = GetCancelledSales();
var debtSummaryTask = GetDebtSummary();
var recentTransactionsTask = GetRecentTransactions();
@ -49,6 +53,7 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard
// Wait for all tasks to complete
await Task.WhenAll(
weeklySalesTask,
categoryWeekly,
cancelledSalesTask,
debtSummaryTask,
recentTransactionsTask,
@ -59,7 +64,6 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard
});
return;
}
/// <summary>
/// Gets the trade summary
/// </summary>
@ -87,6 +91,32 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard
}
}
/// <summary>
/// Gets the trade summary
/// </summary>
/// <returns></returns>
async Task GetCategoryTradeSummary()
{
try
{
m_loadingWeeklySales = true;
StateHasChanged();
var response = await m_http.GetAsync("api/analytics/categorysummary");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var tradeCategorySummary = JsonSerializer.Deserialize<IEnumerable<WeeklyCategorySummary>>(jsonContent, options);
m_categorySummary = tradeCategorySummary;
m_loadingWeeklySales = false;
StateHasChanged();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
/// <summary>
/// Gets the debt summary
/// </summary>
/// <returns></returns>
@ -129,9 +159,17 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var sales = JsonSerializer.Deserialize<List<WeeklySaleItem>>(jsonContent, options);
m_weeklySaleItems = sales;
m_loadingWeeklySales = false;
StateHasChanged();
}
response = await m_http.GetAsync("api/analytics/categorysummary");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var tradeCategorySummary = JsonSerializer.Deserialize<IEnumerable<WeeklyCategorySummary>>(jsonContent, options);
m_categorySummary = tradeCategorySummary;
}
m_loadingWeeklySales = false;
StateHasChanged();
}
catch (Exception ex)
{

37
Client/Pages/Dashboard/Elements/ChartElement.razor

@ -16,37 +16,18 @@
Name="Sales"
YValue="@(e => e.Total)"
XValue="@(e => e.Date.ToString("dd MMM"))"
SeriesType="SeriesType.Bar" />
SeriesType="SeriesType.Area" />
</ApexChart>
</div>
<div class="col-md-5">
<div class="text-center fw-semibold pt-3 mb-2">@SubTitle</div>
<ApexChart TItem="WeeklySaleItem" Options="options">
<ApexPointSeries TItem="WeeklySaleItem" Items="@Series" Name="Sales" YValue="@(e => e.Total)" XValue="@(e => e.Date.ToString("dd MMM"))"
SeriesType="SeriesType.Area" />
</ApexChart>
<div class="d-flex px-xxl-4 px-lg-2 p-4 gap-xxl-3 gap-lg-1 gap-3 justify-content-between">
<div class="d-flex">
<div class="me-2">
<span class="badge bg-label-primary p-2"><i class="bx bx-dollar text-primary"></i></span>
</div>
<div class="d-flex flex-column">
<small>Best Day</small>
<small class="mb-0">@m_bestDay</small>
<small class="mb-0">@m_bestSales</small>
</div>
</div>
<div class="d-flex">
<div class="me-2">
<span class="badge bg-label-info p-2"><i class="bx bx-wallet text-info"></i></span>
</div>
<div class="d-flex flex-column">
<small>Worst Day</small>
<small class="mb-0">@m_worstDay</small>
<small class="mb-0">@m_worstSales</small>
</div>
</div>
</div>
@if (CategorySummary.Count() > 0)
{
<div class="text-center fw-semibold pt-3 mb-2">@SubTitle</div>
<ApexChart TItem="WeeklyCategorySummary" Options="options">
<ApexPointSeries TItem="WeeklyCategorySummary" Items="@CategorySummary" Name="Categories" YValue="@(e => e.Total)" XValue="@(e => e.Category)"
SeriesType="SeriesType.Pie" />
</ApexChart>
}
</div>
</div>
</div>

18
Client/Pages/Dashboard/Elements/ChartElement.razor.cs

@ -15,13 +15,11 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard.Elements
public string SubTitle { get; set; } = string.Empty;
[Parameter]
public List<WeeklySaleItem> Series { get; set; } = new List<WeeklySaleItem>();
[Parameter]
public IEnumerable<WeeklyCategorySummary> CategorySummary { get; set; } = Enumerable.Empty<WeeklyCategorySummary>();
private ApexChartOptions<WeeklySaleItem> m_options;
private ApexChartOptions<WeeklySaleItem> options = new ApexChartOptions<WeeklySaleItem>();
private ApexChartOptions<WeeklyCategorySummary> options = new ApexChartOptions<WeeklyCategorySummary>();
private string m_bestDay { get; set; } = string.Empty;
private string m_worstDay { get; set; } = string.Empty;
private string m_bestSales { get; set; } = string.Empty;
private string m_worstSales { get; set; } = string.Empty;
protected override void OnParametersSet()
{
m_options = new ApexChartOptions<WeeklySaleItem>
@ -47,7 +45,7 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard.Elements
}
},
};
options.Colors = new List<string> { "#11726d" };
options.Colors = new List<string> { "#11726d", "#096a71", "#003445", "#e69138" };
options.Fill = new Fill
{
@ -71,14 +69,6 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard.Elements
}
}
};
if (Series.Count > 1)
{
m_bestDay = Series.OrderByDescending(t => t.Total).First().Date.ToShortDateString();
m_bestSales = m_calculator.FormatMoneyWithCurrencyKilo((double)Series.OrderByDescending(t => t.Total).First().Total);
m_worstDay = Series.OrderByDescending(t => t.Total).Last().Date.ToShortDateString();
m_worstSales = m_calculator.FormatMoneyWithCurrencyKilo((double)Series.OrderByDescending(t => t.Total).Last().Total);
}
base.OnParametersSet();
}
}

22
Client/Pages/Dashboard/Elements/LowStockItems.razor

@ -3,15 +3,14 @@
@using Microsoft.Fast.Components.FluentUI
@inject ICalculator m_calculator
@if(!IsLoading){
<div class="card h-100">
<div class="card-header d-flex align-items-center justify-content-between pb-0">
<div class="card-title mb-0">
<h5 class="m-0 me-2">Low Stock Items</h5>
@if(!IsLoading)
{
<div class="card h-100">
<div class="card-header d-flex align-items-center justify-content-between">
<h5 class="card-title m-0 me-2">Low Stock Items</h5>
</div>
</div>
<div class="card-body" style="padding:5px !important;">
<RadzenDataGrid Data="@LowStockProducts" TItem="ProductItem" AllowPaging="true" AllowSorting="true" PageSize="5">
<div class="card-body" style="padding:5px !important;">
<RadzenDataGrid Data="@LowStockProducts" TItem="ProductItem" AllowPaging="true" AllowSorting="true" PageSize="5" GridLines="DataGridGridLines.Horizontal">
<Columns>
<RadzenDataGridColumn TItem="ProductItem" Property="Product" Title="Product">
<Template Context="detail">
@ -28,8 +27,8 @@
</RadzenDataGridColumn>
</Columns>
</RadzenDataGrid>
</div>
</div>
</div>
}else{
<FluentCard class="card-padding">
<FluentSkeleton style="border-radius: 4px; width: 50px; height: 50px;" Shape="SkeletonShape.Circle" Shimmer="true"></FluentSkeleton>
@ -47,8 +46,3 @@
Shimmer="true"></FluentSkeleton>
</FluentCard>
}
<style>
.rz-datatable {
border: none !important;
}
</style>

18
Client/Pages/Dashboard/Elements/MostPurchasedElement.razor

@ -6,13 +6,11 @@
@if (!IsLoading)
{
<div class="card h-100">
<div class="card-header d-flex align-items-center justify-content-between pb-0">
<div class="card-title mb-0">
<h5 class="text-nowrap mb-2">Top 50 most purchased items</h5>
</div>
<div class="card-header d-flex align-items-center justify-content-between">
<h5 class="card-title m-0 me-2">Top 50 most purchased items</h5>
</div>
<div class="card-body" style="padding:5px !important;">
<RadzenDataGrid Data="@MostPurchasedItems" TItem="MostPurchasedItem" AllowPaging="true" AllowSorting="true" PageSize="5">
<RadzenDataGrid Data="@MostPurchasedItems" TItem="MostPurchasedItem" AllowPaging="true" AllowSorting="true" PageSize="5" GridLines="DataGridGridLines.Horizontal">
<Columns>
<RadzenDataGridColumn TItem="MostPurchasedItem" Property="Revenue" Title="Product">
<Template Context="detail">
@ -52,20 +50,10 @@ else
</FluentCard>
}
<style>
.rz-datatable-thead th, .rz-grid-table thead th {
background-color: #a4d5d3;
padding: 0;
color: white;
}
.rz-paginator {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
}
.rz-datatable {
border: none !important;
}
</style>

4
Client/Pages/Dashboard/Elements/ProductPriceHistory.razor

@ -2,11 +2,11 @@
@using Biskilog_Accounting.Shared.Interfaces;
@using Microsoft.Fast.Components.FluentUI
@inject ICalculator m_calculator
@implements IDisposable
@if (!IsLoading)
{
<div class="card">
<ApexChart TItem="ProductPriceChange" Options="options" Title="@m_title">
<ApexChart TItem="ProductPriceChange" Options="options" Title="@m_title" @ref=chart>
<ApexPointSeries TItem="ProductPriceChange" Items="m_productHistory" Name="@m_subtitle"
SeriesType="SeriesType.Area" XValue="@(e => e.ChangeDate.Value.ToString("dd MMM,yy"))"
YValue="@(e => e.CurrentPrice)" />

47
Client/Pages/Dashboard/Elements/ProductPriceHistory.razor.cs

@ -1,6 +1,9 @@
using ApexCharts;
using Biskilog_Accounting.Shared.CustomModels;
using Microsoft.AspNetCore.Components;
using System;
using System.Timers;
using Timer = System.Timers.Timer;
namespace Biskilog_Accounting.Client.Pages.Dashboard.Elements
{
@ -19,10 +22,48 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard.Elements
private string m_productName { get; set; } = string.Empty;
private string m_subtitle { get; set; } = string.Empty;
private string m_title { get; set; } = string.Empty;
private Timer m_timer { get; set; }
private ApexChart<ProductPriceChange> chart;
private Random m_random = new Random();
protected override void OnInitialized()
{
base.OnInitialized();
// Set up the timer with a 10-second interval
m_timer = new System.Timers.Timer(10000); // 10 seconds
m_timer.Elapsed += TimerElapsed;
m_timer.AutoReset = true;
m_timer.Start();
}
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
// Code to execute every 10 seconds
InvokeAsync(async () =>
{
if (m_productHistory.Count > 0)
{
int randomNumber = m_random.Next(ProductHistory.Count);
m_currentProduct = ProductHistory[randomNumber].Pcode;
m_productHistory = ProductHistory.Where(t => t.Pcode == m_currentProduct).ToList();
ProductPriceChange change = m_productHistory.First();
m_percentage = (double)((change.CurrentPrice - change.PreviousPrice) / change.PreviousPrice > 0 ? change.PreviousPrice * 100 : 1);
m_increase = change.CurrentPrice > change.PreviousPrice;
m_currentPrice = (double)change.CurrentPrice;
m_productName = change.ProductName;
m_subtitle = $"Price :{m_calculator.GetCurrencyCode().CurrencySymbol}";
m_title = $"{m_productName} Price Changes";
StateHasChanged();
await chart.RenderAsync();
}
});
}
public void Dispose()
{
// Dispose of the timer when the component is disposed
m_timer?.Stop();
m_timer?.Dispose();
}
protected override void OnParametersSet()
{
@ -55,12 +96,12 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard.Elements
Random random = new Random();
int randomNumber = random.Next(ProductHistory.Count);
m_currentProduct = ProductHistory[randomNumber].Pcode;
m_productHistory = ValidateList(ProductHistory.Where(t => t.Pcode == m_currentProduct).ToList());
m_productHistory = ProductHistory.Where(t => t.Pcode == m_currentProduct).ToList();
if (m_productHistory.Count > 0)
{
ProductPriceChange change = m_productHistory.First();
m_percentage = (double)((change.CurrentPrice - change.PreviousPrice) / change.PreviousPrice * 100);
m_percentage = (double)((change.CurrentPrice - change.PreviousPrice) / change.PreviousPrice > 0 ? change.PreviousPrice * 100 : 1);
m_increase = change.CurrentPrice > change.PreviousPrice;
m_currentPrice = (double)change.CurrentPrice;
m_productName = change.ProductName;

15
Client/Pages/Dashboard/Elements/TransactionCard.razor

@ -2,6 +2,7 @@
@using Biskilog_Accounting.Shared.Interfaces;
@using Microsoft.Fast.Components.FluentUI
@inject ICalculator m_calculator
@inject IMainInterface m_mainInterface
@if (!IsLoading)
{
@ -10,7 +11,8 @@
<h5 class="card-title m-0 me-2">Last 50 Transactions</h5>
</div>
<div class="card-body" style="padding:5px !important;">
<RadzenDataGrid Data="@Sales" TItem="SaleItem" AllowPaging="true" AllowSorting="true">
<RadzenDataGrid Data="@Sales" TItem="SaleItem" AllowPaging="true" AllowSorting="true"
GridLines="DataGridGridLines.Horizontal" RowClick="@(args => m_mainInterface.ShowReceipt(args.Data.Transno))">
<Columns>
<RadzenDataGridColumn TItem="SaleItem" Property="Transno" Title="Receipt#" />
<RadzenDataGridColumn TItem="SaleItem" Property="Date" Title="Timestamp" />
@ -45,14 +47,3 @@ else
Shimmer="true"></FluentSkeleton>
</FluentCard>
}
<style>
.rz-datatable-thead th, .rz-grid-table thead th {
background-color: #a4d5d3;
padding: 0;
color: white;
}
.rz-datatable {
border: none !important;
}
</style>

19
Client/Pages/Product/Brands.razor

@ -0,0 +1,19 @@
@page "/products/brands"
@using Biskilog_Accounting.Shared.Interfaces;
@using Biskilog_Accounting.Shared.POSModels;
@inject IProduct m_productRepo
@inject ContextMenuService ContextMenuService
@inject DialogService m_dialogService
@inject ISearchService m_searchControl
@implements IDisposable
<div class="row" style="display:grid;justify-content:space-between;padding: 5px;margin-right:10px;">
<h3>Product Brands</h3>
</div>
<RadzenDataGrid AllowPaging="true" PageSize="50" AllowSorting="true" Data="@m_brands" TItem="Tblbrand" GridLines="DataGridGridLines.Horizontal">
<Columns>
<RadzenDataGridColumn TItem="Tblbrand" Property="Id" Title="Brand ID" Resizable Reorderable />
<RadzenDataGridColumn TItem="Tblbrand" Property="Brand" Title="Brand Name" Resizable Reorderable />
<RadzenDataGridColumn TItem="Tblbrand" Property="BranchId" Title="Branch" Resizable Reorderable />
</Columns>
</RadzenDataGrid>

37
Client/Pages/Product/Brands.razor.cs

@ -0,0 +1,37 @@
using Biskilog_Accounting.Shared.POSModels;
namespace Biskilog_Accounting.Client.Pages.Product
{
public partial class Brands
{
private IEnumerable<Tblbrand> m_brands { get; set; } = new List<Tblbrand>();
protected override void OnInitialized()
{
m_productRepo.BrandsChanged += BrandFetchComplete;
m_searchControl.SearchValueChanged += SearchValueChanged;
LoadBrands(string.Empty);
base.OnInitialized();
}
private void SearchValueChanged(string a_searchKey)
{
LoadBrands(a_searchKey);
}
public void Dispose()
{
m_productRepo.BrandsChanged -= BrandFetchComplete;
m_searchControl.SearchValueChanged -= SearchValueChanged;
}
private void BrandFetchComplete(object? sender, EventArgs e)
{
LoadBrands(string.Empty);
}
private void LoadBrands(string a_productKey)
{
m_brands = m_productRepo.GetBrands(a_productKey);
StateHasChanged();
}
}
}

19
Client/Pages/Product/Categories.razor

@ -0,0 +1,19 @@
@page "/products/categories"
@using Biskilog_Accounting.Shared.POSModels;
@using Biskilog_Accounting.Shared.Interfaces
@inject IProduct m_productRepo
@inject ContextMenuService ContextMenuService
@inject DialogService m_dialogService
@inject ISearchService m_searchControl
@implements IDisposable
<div class="row" style="display:flex;justify-content:space-between;padding: 5px;margin-right:10px;">
<h3>Categories</h3>
</div>
<RadzenDataGrid AllowPaging="true" PageSize="50" AllowSorting="true" Data="@m_categories" TItem="Tblcategory" GridLines="DataGridGridLines.Horizontal">
<Columns>
<RadzenDataGridColumn TItem="Tblcategory" Property="Id" Title="Category ID" Resizable Reorderable />
<RadzenDataGridColumn TItem="Tblcategory" Property="Category" Title="Category Name" Resizable Reorderable />
<RadzenDataGridColumn TItem="Tblcategory" Property="BranchId" Title="Branch" Resizable Reorderable />
</Columns>
</RadzenDataGrid>

37
Client/Pages/Product/Categories.razor.cs

@ -0,0 +1,37 @@
using Biskilog_Accounting.Shared.POSModels;
namespace Biskilog_Accounting.Client.Pages.Product
{
public partial class Categories
{
private IEnumerable<Tblcategory> m_categories { get; set; } = new List<Tblcategory>();
protected override void OnInitialized()
{
m_productRepo.CategoriesChanged += CategoryFetchComplete;
m_searchControl.SearchValueChanged += SearchValueChanged;
LoadCategories(string.Empty);
base.OnInitialized();
}
private void SearchValueChanged(string a_searchKey)
{
LoadCategories(a_searchKey);
}
public void Dispose()
{
m_productRepo.CategoriesChanged -= CategoryFetchComplete;
m_searchControl.SearchValueChanged -= SearchValueChanged;
}
private void CategoryFetchComplete(object? sender, EventArgs e)
{
LoadCategories(string.Empty);
}
private void LoadCategories(string a_productKey)
{
m_categories = m_productRepo.GetCategories(a_productKey);
StateHasChanged();
}
}
}

186
Client/Pages/Product/Elements/ProductDialog.razor

@ -0,0 +1,186 @@
@page "/products/{product}"
@using Biskilog_Accounting.Shared.CustomModels;
@using Biskilog_Accounting.Shared.Interfaces;
@inject IProduct m_productRepo
@inject ICalculator m_calculator
<RadzenCard Style="margin-bottom: 20px;">
<div class="row" style="justify-content: end;height: 20px;display: grid;">
<RadzenStack Orientation="Radzen.Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.5rem">
<RadzenSwitch @bind-Value="m_editMode" />
<RadzenLabel Text="Edit Mode" />
</RadzenStack>
</div>
<div class="row align-items-center" style="justify-content: center;">
<img src="assets/img/box.png" style="width:200px;" />
</div>
@if (m_editMode)
{
<div class="row">
<div class="col-md-6 align-items-center d-flex">
<RadzenFieldset Text="Product Info" Style="width:100%;">
<div class="row">
<div class="col-md-4 align-items-center d-flex">
<RadzenLabel Text="Product Id" />
</div>
<div class="col-md-8">
<RadzenTextBox Disabled="true" style="width: 100%;" Name="pcode" @bind-Value=@(Product.Product!.Pcode) />
</div>
</div>
<div class="row" style="margin-top:5px;">
<div class="col-md-4 align-items-center d-flex">
<RadzenLabel Text="Product Name" />
</div>
<div class="col-md-8">
<RadzenTextBox Disabled="true" style="width: 100%;" Name="productName" @bind-Value=@(Product.Product!.ProductName) />
</div>
</div>
<div class="row" style="margin-top:5px;">
<div class="col-md-4 align-items-center d-flex">
<RadzenLabel Text="Description" /> <b></b>
</div>
<div class="col-md-8">
<RadzenTextArea Disabled="true" @bind-Value=@(Product.Product!.Pdesc) Cols="25" Rows="4" />
</div>
</div>
</RadzenFieldset>
</div>
<div class="col-md-6">
<RadzenFieldset Text="Other Units Of Measure">
<div class="row" style="justify-content: end;height: 20px;display: grid;">
<RadzenButton Icon="add_circle_outline" ButtonStyle="ButtonStyle.Light" />
</div>
<div class="row" style="margin-top:5px;">
<div class="col-md-6 align-items-center d-flex">
<RadzenLabel Text="Unit Of Measure" /> <b></b>
</div>
<div class="col-md-6 align-items-center d-flex">
<RadzenLabel Text="Quantity" /> <b></b>
</div>
</div>
@foreach (ProductUnits unit in Product.Units)
{
<div class="row" style="margin-top:5px;">
<div class="col-md-6 align-items-center d-flex">
<RadzenDropDown @bind-Value=@(unit.UnitCode) Data=@(m_productRepo.GetUnitofmeasures()) TextProperty="UnitName" ValueProperty="UnitCode" />
</div>
<div class="col-md-6 align-items-center d-flex">
<RadzenNumeric Placeholder="10" Step="1" @bind-Value=@(unit.QuantityUnit) />
</div>
</div>
}
</RadzenFieldset>
</div>
</div>
<div class="row">
<div class="col-md-6 align-items-center d-flex">
<RadzenFieldset Text="Base Unit" Style="width:100%;">
<div class="row">
<div class="col-md-4 align-items-center d-flex">
<RadzenLabel Text="Base Unit" />
</div>
<div class="col-md-8">
<RadzenDropDown @bind-Value=@(Product.BaseUnit) Data=@(m_productRepo.GetUnitofmeasures()) TextProperty="UnitName" ValueProperty="UnitCode" />
</div>
</div>
</RadzenFieldset>
</div>
<div class="col-md-6">
<RadzenFieldset Text="Re-order Level">
<div class="row">
<div class="col-md-6 align-items-center d-flex">
<RadzenLabel Text="Quantity" />
</div>
<div class="col-md-6 align-items-center d-flex">
<RadzenNumeric Placeholder="10" Step="1" @bind-Value=@(Product.Restocklevel!.WarnLevel) />
</div>
</div>
<div class="row" style="margin-top:5px;">
<div class="col-md-6 align-items-center d-flex">
<RadzenLabel Text="Unit Of Measure" />
</div>
<div class="col-md-6 align-items-center d-flex">
<RadzenDropDown @bind-Value=@(Product.Restocklevel!.Unit) Data=@(m_reorderUnits) TextProperty="UnitName" ValueProperty="UnitCode" />
</div>
</div>
</RadzenFieldset>
</div>
</div>
<div class="row" style="display:flex;justify-content:safe center;margin-top:10px;margin-bottom:10px;">
<RadzenButton Text="Save" Style="width:fit-content;background-color:green;margin-right:10px;" />
<RadzenButton Text="Cancel" Style="width:fit-content;background-color:Darkred;" />
</div>
}
else
{
//Read Only View
<RadzenFieldset Text="Product Info" Style="width:100%;">
<div class="row">
<div class="col-md-4 align-items-center d-flex">
<RadzenLabel Text="Product Id" />
</div>
<div class="col-md-8">
<RadzenTextBox Disabled="true" style="width: 100%;" Name="pcode" @bind-Value=@(Product.Product!.Pcode) />
</div>
</div>
<div class="row" style="margin-top:5px;">
<div class="col-md-4 align-items-center d-flex">
<RadzenLabel Text="Product Name" />
</div>
<div class="col-md-8">
<RadzenTextBox Disabled="true" style="width: 100%;" Name="productName" @bind-Value=@(Product.Product!.ProductName) />
</div>
</div>
<div class="row" style="margin-top:5px;">
<div class="col-md-4 align-items-center d-flex">
<RadzenLabel Text="Description" /> <b></b>
</div>
<div class="col-md-8">
<RadzenTextArea Disabled="true" @bind-Value=@(Product.Product!.Pdesc) Cols="45" Rows="4" />
</div>
</div>
<div class="row" style="margin-top:5px;">
<div class="col-md-4 align-items-center d-flex">
<RadzenLabel Text="Base Unit" /> <b></b>
</div>
<div class="col-md-8">
<RadzenTextBox Disabled="true" Value=@(m_productRepo.GetUnitName(Product.Product!.BaseUnit)) />
</div>
</div>
<div class="row" style="margin-top:5px;">
<div class="col-md-4 align-items-center d-flex">
<RadzenLabel Text="Quantity" /> <b></b>
</div>
<div class="col-md-8">
<RadzenNumeric Disabled="true" @bind-Value=@(Product.Stock!.Quantity) />
</div>
</div>
<div class="row" style="margin-top:5px;">
<RadzenDataGrid Data="@(Product.Units.OrderBy(t => t.PriceUnit))" TItem="ProductUnits" GridLines="DataGridGridLines.Horizontal">
<Columns>
<RadzenDataGridColumn TItem="ProductUnits" Property="UnitCode" Title="Unit Of Measure">
<Template Context="detail">
@(m_productRepo.GetUnitName(detail.UnitCode))
</Template>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="ProductUnits" Property="QuantityUnit" Title="Quantity" />
<RadzenDataGridColumn TItem="ProductUnits" Property="PriceUnit" Title="Price Per Unit">
<Template Context="detail">
@(m_calculator.FormatMoneyWithCurrency((double)detail.PriceUnit))
</Template>
</RadzenDataGridColumn>
</Columns>
</RadzenDataGrid>
</div>
</RadzenFieldset>
<div class="row" style="display:flex;justify-content:safe center;margin-top:10px;margin-bottom:10px;">
<RadzenButton Text="Print Barcode" Style="width:fit-content;margin-right:10px;background-color:forestgreen;" />
<RadzenButton Text="Print Tags" Style="width:fit-content;margin-right:10px;background-color:cornflowerblue;" />
<RadzenButton Text="Add Stock" Style="width:fit-content;background-color:#435971;" />
</div>
}
</RadzenCard>

25
Client/Pages/Product/Elements/ProductDialog.razor.cs

@ -0,0 +1,25 @@
using Biskilog_Accounting.Shared.CustomModels;
using Microsoft.AspNetCore.Components;
namespace Biskilog_Accounting.Client.Pages.Product.Elements
{
public partial class ProductDialog
{
[Parameter]
public ProductItem Product { get; set; }
private bool m_editMode { get; set; } = false;
private List<ProductUnits> m_reorderUnits { get; set; } = new List<ProductUnits>();
protected override void OnParametersSet()
{
var list = Product.Units.Select(c => c.UnitCode);
m_reorderUnits = m_productRepo.GetUnitofmeasures().Where(t => t.UnitCode == Product.BaseUnit || list.Contains(t.UnitCode))
.Select(t => new ProductUnits
{
UnitCode = t.UnitCode,
UnitName = t.Unitname,
}).ToList();
base.OnParametersSet();
}
}
}

29
Client/Pages/Product/Products.razor

@ -0,0 +1,29 @@
@page "/products/inventory"
@using Biskilog_Accounting.Shared.CustomModels;
@using Biskilog_Accounting.Shared.Interfaces;
@inject IProduct m_productRepo
@inject ContextMenuService ContextMenuService
@inject DialogService m_dialogService
@inject ISearchService m_searchControl
@implements IDisposable
<div class="row" style="display:flex;justify-content:space-between;padding: 5px;margin-right:10px;">
<h3>Products</h3>
</div>
<RadzenDataGrid AllowPaging="true" PageSize="50" AllowSorting="true" Data="@m_products" TItem="ProductItem" GridLines="DataGridGridLines.Horizontal"
SelectionMode="DataGridSelectionMode.Single" @bind-Value=@m_selectedProducts CellContextMenu="@OnCellContextMenu">
<Columns>
<RadzenDataGridColumn TItem="ProductItem" Property="Product.Pcode" Title="Product ID" Width="15%" Resizable Reorderable />
<RadzenDataGridColumn TItem="ProductItem" Property="Product.ProductName" Title="Product Name" Width="20%" Resizable Reorderable />
<RadzenDataGridColumn TItem="ProductItem" Property="Product.Pdesc" Title="Description" Width="25%" Resizable Reorderable />
<RadzenDataGridColumn TItem="ProductItem" Property="Stock.Quantity" Title="Quantity" Width="10%" Resizable Reorderable />
<RadzenDataGridColumn TItem="ProductItem" Property="BaseUnit" Title="Unit" Width="10%" Reorderable Resizable>
<Template Context="detail">
@(m_productRepo.GetUnitName(detail.BaseUnit))
</Template>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="ProductItem" Property="Product.Status" Title="Status" Width="10%" Reorderable Resizable />
<RadzenDataGridColumn TItem="ProductItem" Property="Product.BranchId" Title="Branch" Width="10%" Resizable Reorderable />
</Columns>
</RadzenDataGrid>

68
Client/Pages/Product/Products.razor.cs

@ -0,0 +1,68 @@
using Biskilog_Accounting.Client.Pages.Product.Elements;
using Biskilog_Accounting.Shared.CustomModels;
using Radzen;
namespace Biskilog_Accounting.Client.Pages.Product
{
public partial class Products
{
private IEnumerable<ProductItem> m_products { get; set; } = Enumerable.Empty<ProductItem>();
private IList<ProductItem> m_selectedProducts { get; set; } = new List<ProductItem>();
protected override void OnInitialized()
{
m_productRepo.ProductsChanged += ProductsFetchComplete;
m_searchControl.SearchValueChanged += SearchValueChanged;
LoadProducts(string.Empty);
base.OnInitialized();
}
private void SearchValueChanged(string a_searchKey)
{
LoadProducts(a_searchKey);
}
public void Dispose()
{
m_productRepo.ProductsChanged -= ProductsFetchComplete;
m_searchControl.SearchValueChanged -= SearchValueChanged;
}
private void ProductsFetchComplete(object? sender, EventArgs e)
{
LoadProducts(string.Empty);
}
private void LoadProducts(string a_productKey)
{
m_products = m_productRepo.GetProducts(a_productKey);
StateHasChanged();
}
private void OnCellContextMenu(DataGridCellMouseEventArgs<ProductItem> args)
{
m_selectedProducts = new List<ProductItem>() { args.Data };
ContextMenuService.Open(args,
new List<ContextMenuItem> {
new ContextMenuItem(){ Text = "Open", Value = 1 },
new ContextMenuItem(){ Text = m_selectedProducts.First().Product!.Status == "INACTIVE" ?
"Activate" : "Deactivate", Value = 2},
new ContextMenuItem(){ Text = "Change Price", Value = 3},
new ContextMenuItem(){ Text = "Add Stock", Value = 4},
new ContextMenuItem(){ Text = "View Performance History", Value = 5},
},
(e) =>
{
OpenProductDialog(m_selectedProducts.First());
Console.WriteLine($"Menu item with Value={e.Value} clicked. Column: {args.Column.Property}, EmployeeID: {args.Data.Product.Pcode}");
}
);
}
async Task OpenProductDialog(ProductItem a_productItem)
{
await m_dialogService.OpenAsync<ProductDialog>($"{a_productItem.Product!.ProductName}",
new Dictionary<string, object>() { { "Product", a_productItem } },
new DialogOptions() { Width = "1020px", Height = "900px", Resizable = false, Draggable = true });
}
}
}

95
Client/Pages/Transactions/Elements/ReceiptDialog.razor

@ -0,0 +1,95 @@
@page "/transactions/receipt/{receiptID}"
@using Biskilog_Accounting.Shared.Interfaces;
@using Biskilog_Accounting.Shared.POSModels;
@inject ICalculator m_calculator
@inject IProduct m_productRepo
@inject ISalesInterface m_saleInterface
@inject ICompanyInfo m_companyInfo
<div class="container" style="padding-top:10px;">
<div class="row">
<div class="row" style="display:grid;justify-content:center;">
<small>@(m_companyName)</small>
</div>
<div class="row" style="display:grid;justify-content:center;">
<small>@m_branch?.BranchName</small>
</div>
<div class="row" style="display:grid;justify-content:center;">
<small>@m_branch?.City</small>
</div>
<div class="row" style="display:grid;justify-content:center;">
<small>@m_branch?.BranchTelephone</small>
</div>
<hr />
<div class="row" style="display:block;justify-content:space-between;">
<small> <em>Date</em> </small>
<small> @m_date </small>
</div>
<div class="row" style="display:block;justify-content:space-between;">
<small> <em>Cashier</em> </small>
<small> @m_cashier </small>
</div>
<div class="row" style="display:block;justify-content:space-between;">
<small> <em>Receipt Number</em> </small>
<small> <em>@ReceiptId</em> </small>
</div>
<hr />
<RadzenDataGrid TItem="Tblcart" Data="ReceiptDetail" GridLines="DataGridGridLines.Horizontal" Style="padding:0px;">
<Columns>
<RadzenDataGridColumn TItem="Tblcart" Property="Transno" Title="Item" Width="60%">
<Template Context="detail">
<small>@detail.Id <br />x@(detail.Quantity) @(detail.Unit)</small>
</Template>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="Tblcart" Property="Total" Title="Total" Width="40%">
<Template Context="detail">
<small>@(m_calculator.FormatMoneyWithCurrency((double)detail.Total))</small>
</Template>
</RadzenDataGridColumn>
</Columns>
</RadzenDataGrid>
<div class="row" style="display:grid;justify-content:end;">
<RadzenStack Orientation="Radzen.Orientation.Horizontal">
<small> <em>Sub Total</em> </small>
<small> <em>@m_subtotal</em> </small>
</RadzenStack>
</div>
<div class="row" style="display:grid;justify-content:end;">
<RadzenStack Orientation="Radzen.Orientation.Horizontal">
<small> <em>Discount</em> </small>
<small> <em>@m_discount</em> </small>
</RadzenStack>
</div>
<div class="row" style="display:grid;justify-content:end;">
<RadzenStack Orientation="Radzen.Orientation.Horizontal">
<small> <em>VAT</em> </small>
<small> <em>@m_vat</em> </small>
</RadzenStack>
</div>
<hr />
<div class="row" style="display:grid;justify-content:end;">
<RadzenStack Orientation="Radzen.Orientation.Horizontal">
<small> <em>Bill Total</em> </small>
<small> <em>@m_total</em> </small>
</RadzenStack>
</div>
<div class="row" style="display:grid;justify-content:end;">
<RadzenStack Orientation="Radzen.Orientation.Horizontal">
<small> <em>Balance</em> </small>
<small> <em>@m_balance</em> </small>
</RadzenStack>
</div>
<div class="row" style="display:grid;justify-content:end;">
<RadzenStack Orientation="Radzen.Orientation.Horizontal">
<small> <em>Tendered</em> </small>
<small> <em>@m_tendered</em> </small>
</RadzenStack>
</div>
<hr />
<div class="row" style="display:grid;justify-content:center;">
<small style="margin-bottom: 5px;margin-left: 20px"><em>COME BACK SOON !!! CHEERS</em> </small>
<div id="barcode">@barcode</div>
<div id="barcode_text">@barcode_text</div>
</div>
</div>
</div>

67
Client/Pages/Transactions/Elements/ReceiptDialog.razor.cs

@ -0,0 +1,67 @@
using Biskilog_Accounting.Shared.POSModels;
using Microsoft.AspNetCore.Components;
using Net.ConnectCode.Barcode;
namespace Biskilog_Accounting.Client.Pages.Transactions.Elements
{
public partial class ReceiptDialog
{
[Parameter]
public string ReceiptId { get; set; }
private IEnumerable<Tblcart> ReceiptDetail { get; set; } = Enumerable.Empty<Tblcart>();
string barcode = "";
string barcode_text = "";
private string m_balance { get; set; }
private string m_tendered { get; set; }
private string m_total { get; set; }
private string m_subtotal { get; set; }
private string m_discount { get; set; } = "0.00";
private string m_vat { get; set; }
private string m_cashier { get; set; }
private string m_date { get; set; }
private Tblbranch m_branch { get; set; }
private string m_companyName { get; set; } = string.Empty;
void GenerateBarcode()
{
BarcodeFonts bcf = new BarcodeFonts();
bcf.BarcodeType = BarcodeFonts.BarcodeEnum.Code39;
bcf.CheckDigit = BarcodeFonts.YesNoEnum.Yes;
bcf.Data = ReceiptId;
bcf.encode();
barcode = bcf.EncodedData;
barcode_text = ReceiptId;
}
protected override async Task OnParametersSetAsync()
{
ReceiptDetail = await m_saleInterface.GetReceiptDetail(ReceiptId);
SetParams();
GenerateBarcode();
StateHasChanged();
return;
}
private void SetParams()
{
if (ReceiptDetail.Count() > 0)
{
double total = (double)ReceiptDetail.Sum(t => t.Total).Value;
double discount = (double)ReceiptDetail.First().Discount.Value;
double vat = (double)ReceiptDetail.First().ValueAddTax.Value;
double billTotal = (vat + total);
m_cashier = ReceiptDetail.First().Cashier;
m_subtotal = m_calculator.FormatMoneyWithCurrency(total);
m_tendered = m_calculator.FormatMoneyWithCurrency((double)ReceiptDetail.First().Tendered.Value);
m_balance = m_calculator.FormatMoneyWithCurrency((double)ReceiptDetail.First().Balance.Value);
m_vat = (vat).ToString("0.00") + "%";
m_date = ReceiptDetail.First().Date.Value.ToString("dd MMMM, yyyy HH:mm:ss");
m_total = m_calculator.FormatMoneyWithCurrency(billTotal);
string branchId = ReceiptDetail.First().BranchId;
m_branch = m_companyInfo.FetchBranches().ToList().First(t => t.BranchId == branchId);
m_companyName = m_companyInfo.GetCompanyName();
}
}
}
}

25
Client/Pages/Transactions/Elements/SaleSummary.razor

@ -0,0 +1,25 @@
@using Biskilog_Accounting.Shared.Interfaces
@inject ICalculator m_calculator
<RadzenCard Style="margin-bottom:10px;">
<div class="row" style="margin:8px;display:grid;justify-content:center;">
@if (RecentTrans)
{
<h5>Transaction summary for the past @(TotalNbrTransactions) transactions</h5>
}else{
<h5>Transaction summary for the period between : @(StartRange.ToString("dd MMM, yyyy")) to @(EndRange.ToString("dd MMM, yyyy"))</h5>
}
</div>
<div class="row" style="margin:8px;">
<small>Total number of transactions made during the period : <b>@(TotalNbrTransactions)</b> </small>
</div>
<div class="row" style="margin:8px;">
<small>Total Revenue Generated during the period : <b>@(m_calculator.FormatMoneyWithCurrency(TotalRevenue))</b></small>
</div>
<div class="row" style="margin:8px;">
<small>Total number of cancelled transactions during the period : <b>@(TotalNbrCancelledRevenue)</b> </small>
</div>
<div class="row" style="margin:8px;">
<small>Total Revenue lost to cancelled receipts during the period : <b>@(m_calculator.FormatMoneyWithCurrency(TotalCancelledRevenue))</b></small>
</div>
</RadzenCard>

24
Client/Pages/Transactions/Elements/SaleSummary.razor.cs

@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Components;
namespace Biskilog_Accounting.Client.Pages.Transactions.Elements
{
public partial class SaleSummary
{
[Parameter]
public DateTime StartRange { get; set; }
[Parameter]
public DateTime EndRange { get; set; }
[Parameter]
public double TotalRevenue { get; set; }
[Parameter]
public int TotalNbrTransactions { get; set; }
[Parameter]
public int TotalNbrCancelledRevenue { get; set; }
[Parameter]
public string TopSalesMan { get; set; }
[Parameter]
public double TotalCancelledRevenue { get; set; }
[Parameter]
public bool RecentTrans { get; set; }
}
}

77
Client/Pages/Transactions/Sales.razor

@ -0,0 +1,77 @@
@page "/transactions/sales"
@using Biskilog_Accounting.Shared.CustomModels;
@using Biskilog_Accounting.Shared.Interfaces;
@using Biskilog_Accounting.Client.Pages.Transactions.Elements;
@using BlazorDateRangePicker
@using Faso.Blazor.SpinKit
@inject ICalculator m_calculator
@inject ISalesInterface m_salesRepo
@inject ContextMenuService ContextMenuService
@inject DialogService m_dialogService
@inject ISearchService m_searchControl
@implements IDisposable
@inject IMainInterface m_mainInterface
<h3>Sales</h3>
<div class="row" style="display:grid;justify-content:end;margin:5px;">
<DateRangePicker @bind-StartDate="m_startDate" @bind-EndDate="m_endDate" Ranges="m_ranges" ShowCustomRangeLabel="true" OnRangeSelect="@LoadTransactions">
<PickerTemplate>
<div id="@context.Id" @onclick="context.Toggle" style="background: #fff; cursor: pointer; padding: 5px 10px; width: 250px; border: 1px solid #ccc;">
<i class="tf-icons bx bx-calendar"></i>&nbsp;
<span>@context.FormattedRange @(string.IsNullOrEmpty(context.FormattedRange) ? "Choose dates..." : "")</span>
<i class="oi oi-chevron-bottom float-right"></i>
</div>
</PickerTemplate>
<ButtonsTemplate>
<button class="cancelBtn btn btn-sm btn-default"
@onclick="@context.ClickCancel" type="button">
Cancel
</button>
<button class="cancelBtn btn btn-sm btn-default"
@onclick="@(e => ResetClick(e, context))" type="button">
Reset
</button>
<button class="applyBtn btn btn-sm btn-primary" @onclick="@context.ClickApply"
disabled="@(context.TStartDate == null || context.TEndDate == null)"
type="button">
Apply
</button>
</ButtonsTemplate>
</DateRangePicker>
</div>
@if (!m_lookUpMode)
{
<SaleSummary StartRange="m_startDate.Value.LocalDateTime" EndRange="m_endDate.Value.LocalDateTime"
TotalRevenue="m_totalRevenue" TotalNbrTransactions="m_totalNbrRevenue" TotalCancelledRevenue="m_totalCancelledRevenue"
TotalNbrCancelledRevenue="m_totalNbrCancelled" RecentTrans="m_recent"/>
}
<RadzenDataGrid AllowPaging="true" PageSize="50" AllowSorting="true" Data="@m_salesItems" TItem="SaleItem"
GridLines="DataGridGridLines.Horizontal" RowClick="@(args => m_mainInterface.ShowReceipt(args.Data.Transno))">
<Columns>
<RadzenDataGridColumn TItem="SaleItem" Property="Transno" Title="Transaction ID" />
<RadzenDataGridColumn TItem="SaleItem" Property="Date" Title="Timestamp" />
<RadzenDataGridColumn TItem="SaleItem" Property="Cashier" Title="Cashier" />
<RadzenDataGridColumn TItem="SaleItem" Property="Customer" Title="Customer" />
<RadzenDataGridColumn TItem="SaleItem" Property="Status" Title="Status" Width="10%" />
<RadzenDataGridColumn TItem="SaleItem" Property="Total" Title="Total">
<Template Context="detail">
@(
m_calculator.FormatMoneyWithCurrency((double)detail.Total)
)
</Template>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="SaleItem" Property="BranchId" Title="Branch" Resizable Reorderable />
</Columns>
</RadzenDataGrid>
@if (m_fetching)
{
<div id="loading-div-background">
<div id="loading-div" class="ui-corner-all">
<div class="spinnermain-container">
<SpinKitChasingDots />
</div>
<h6 style="color:#fff;font-weight:normal;">Please wait fetching transaction details....</h6>
</div>
</div>
}

102
Client/Pages/Transactions/Sales.razor.cs

@ -0,0 +1,102 @@
using Biskilog_Accounting.Client.Pages.Product;
using Biskilog_Accounting.Shared.CustomModels;
using BlazorDateRangePicker;
using Microsoft.AspNetCore.Components.Web;
namespace Biskilog_Accounting.Client.Pages.Transactions
{
public partial class Sales
{
private IEnumerable<SaleItem> m_salesItems { get; set; } = Enumerable.Empty<SaleItem>();
private DateTimeOffset? m_startDate { get; set; } = DateTimeOffset.Now;
private DateTimeOffset? m_endDate { get; set; } = DateTimeOffset.Now;
private Dictionary<string, DateRange> m_ranges = new Dictionary<string, DateRange>();
private double m_totalRevenue { get; set; }
private double m_totalCancelledRevenue { get; set; }
private int m_totalNbrRevenue { get; set; }
private int m_totalNbrCancelled { get; set; }
private bool m_lookUpMode { get; set; }
private bool m_fetching { get; set; }
private bool m_recent { get; set; }
protected override void OnInitialized()
{
PopulateRange();
m_salesRepo.TransactionsChanged += TransactionsChanged;
m_searchControl.SearchValueChanged += SearchValueChanged;
m_salesRepo.FetchStart += FetchStart;
m_salesRepo.FetchComplete += FetchComplete;
m_salesItems = m_salesRepo.GetRecentTransaction();
SetParams();
m_recent = true;
base.OnInitialized();
}
private void FetchComplete(object? sender, EventArgs e)
{
m_fetching = false;
StateHasChanged();
}
private void FetchStart(object? sender, EventArgs e)
{
m_fetching = true;
StateHasChanged();
}
void ResetClick(MouseEventArgs e, DateRangePicker picker)
{
m_startDate = DateTimeOffset.UtcNow;
m_endDate = DateTimeOffset.UtcNow;
// Close the picker
picker.Close();
// Fire OnRangeSelectEvent
picker.OnRangeSelect.InvokeAsync(new DateRange());
}
private void PopulateRange()
{
m_ranges.Clear();
m_ranges.Add("Today", new DateRange() { Start = DateTime.Now, End = DateTime.Now.AddDays(1) });
m_ranges.Add("Yesterday", new DateRange() { Start = DateTime.Now.AddDays(-1), End = DateTime.Now });
m_ranges.Add("Last 7 Days", new DateRange() { Start = DateTime.Now.AddDays(-7), End = DateTime.Now });
m_ranges.Add("Last 30 Days", new DateRange() { Start = DateTime.Now.AddDays(-30), End = DateTime.Now });
}
private void TransactionsChanged(object? sender, EventArgs e)
{
m_salesItems = m_salesRepo.GetTransactions(m_startDate.Value.Date, m_endDate.Value.Date);
SetParams();
StateHasChanged();
}
private void SearchValueChanged(string a_searchKey)
{
m_salesRepo.FetchReceipt(a_searchKey);
m_lookUpMode = !string.IsNullOrEmpty(a_searchKey);
}
public void Dispose()
{
m_salesRepo.TransactionsChanged -= TransactionsChanged;
m_searchControl.SearchValueChanged -= SearchValueChanged;
m_salesRepo.FetchStart -= FetchStart;
m_salesRepo.FetchComplete -= FetchComplete;
}
private void SetParams()
{
m_totalRevenue = (double)m_salesItems.Where(c => c.Status == "SOLD").Sum(t => t.Total).Value;
m_totalNbrRevenue = m_salesItems.Where(c => c.Status == "SOLD").Count();
m_totalNbrCancelled = m_salesItems.Where(c => c.Status != "SOLD").Count();
m_totalCancelledRevenue = (double)m_salesItems.Where(c => c.Status != "SOLD").Sum(t => t.Total).Value;
m_recent = false;
}
private void LoadTransactions()
{
m_searchControl.SearchValueChanged -= SearchValueChanged;
m_searchControl.Clear();
m_lookUpMode = false;
m_salesRepo.FetchTransaction(m_startDate.Value.Date, m_endDate.Value.Date);
m_searchControl.SearchValueChanged += SearchValueChanged;
}
}
}

19
Client/Program.cs

@ -1,12 +1,16 @@
using Biskilog_Accounting.Client;
using Biskilog_Accounting.Client.Repos;
using Biskilog_Accounting.ServiceRepo;
using Biskilog_Accounting.Shared.CustomModels;
using Biskilog_Accounting.Shared.Interfaces;
using Biskilog_Accounting.Shared.ServiceRepo;
using BlazorDateRangePicker;
using Blazored.LocalStorage;
using Blazored.SessionStorage;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Fast.Components.FluentUI;
using Radzen;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
@ -18,14 +22,27 @@ builder.Services.AddHttpClient("Biskilog_Accounting.ServerAPI", client => client
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Biskilog_Accounting.ServerAPI"));
builder.Services.AddBlazoredLocalStorageAsSingleton();
builder.Services.AddBlazoredSessionStorageAsSingleton();
builder.Services.AddScoped<ISearchService,SearchService>();
builder.Services.AddScoped<DialogService>();
builder.Services.AddScoped<ContextMenuService>();
builder.Services.AddScoped<ICalculator, CalculatorService>();
builder.Services.AddScoped<ITokenService, TokenService>();
builder.Services.AddScoped<IProduct, ProductRepository>();
builder.Services.AddScoped<ISalesInterface, SalesRepository>();
builder.Services.AddScoped<IMainInterface, MainInterfaceService>();
builder.Services.AddScoped<ICompanyInfo, MainInterfaceService>();
builder.Services.AddFluentUIComponents(options =>
{
options.HostingModel = BlazorHostingModel.WebAssembly;
options.IconConfiguration = ConfigurationGenerator.GetIconConfiguration();
options.EmojiConfiguration = ConfigurationGenerator.GetEmojiConfiguration();
});
builder.Services.AddDateRangePicker(config =>
{
config.Attributes = new Dictionary<string, object>
{
{ "class", "form-control form-control-sm" }
};
});
await builder.Build().RunAsync();

98
Client/Repos/MainInterfaceService.cs

@ -0,0 +1,98 @@
using Biskilog_Accounting.Client.Pages.Transactions.Elements;
using Biskilog_Accounting.Shared.CustomModels;
using Biskilog_Accounting.Shared.Interfaces;
using Biskilog_Accounting.Shared.POSModels;
using Radzen;
using System.Text.Json;
namespace Biskilog_Accounting.Client.Repos
{
public class MainInterfaceService : IMainInterface, ICompanyInfo
{
private readonly HttpClient m_http;
private readonly DialogService m_dialogService;
private DateTime m_currentTradeDate;
private DateTime m_previousTradeDate;
private IEnumerable<Tblbranch> m_branches;
private Tblcompanydetail m_info;
public MainInterfaceService(DialogService a_dialogService, HttpClient http)
{
m_dialogService = a_dialogService;
m_currentTradeDate = new DateTime(2023, 05, 01);
m_previousTradeDate = new DateTime(2023, 04, 01);
m_http = http;
}
async Task OpenReceiptDialog(string a_receiptId)
{
await m_dialogService.OpenSideAsync<ReceiptDialog>("Receipt",
new Dictionary<string, object>() { { "ReceiptId", a_receiptId } },
options: new SideDialogOptions
{
CloseDialogOnOverlayClick = true,
Position = DialogPosition.Right,
ShowMask = true,
ShowTitle = false,
});
}
public void ShowReceipt(string a_receiptId)
{
OpenReceiptDialog(a_receiptId);
}
public DateOnly CurrentTradeDate()
{
return DateOnly.FromDateTime(m_currentTradeDate);
}
public DateOnly PreviousTradeDate()
{
return DateOnly.FromDateTime(m_previousTradeDate);
}
public IEnumerable<Tblbranch> FetchBranches()
{
return m_branches;
}
public async Task<Tblcompanydetail> GetCompanyInfoAsync()
{
var response = await m_http.GetAsync($"api/companyinfo/info");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var recent = JsonSerializer.Deserialize<Tblcompanydetail>(jsonContent, options);
m_info = recent;
return recent;
}
return null;
}
public async Task<IEnumerable<Tblbranch>> GetBranches()
{
var response = await m_http.GetAsync($"api/companyinfo/branches");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var recent = JsonSerializer.Deserialize<IEnumerable<Tblbranch>>(jsonContent, options);
m_branches = recent;
return recent;
}
return null;
}
public string GetCompanyName()
{
return m_info.CompanyName;
}
public string GetBranchName(string a_branchId)
{
return m_branches.First(b => b.BranchId == a_branchId).BranchName;
}
}
}

143
Client/Repos/ProductRepository.cs

@ -0,0 +1,143 @@
using Biskilog_Accounting.Shared.CustomModels;
using Biskilog_Accounting.Shared.Interfaces;
using System.Text.Json;
using Biskilog_Accounting.Shared.POSModels;
using System.Text.RegularExpressions;
namespace Biskilog_Accounting.Client.Repos
{
public class ProductRepository : IProduct
{
private IEnumerable<ProductItem> m_products;
private IEnumerable<Unitofmeasure> m_units;
private IEnumerable<Tblbrand> m_brands;
private IEnumerable<Tblcategory> m_categories;
private readonly HttpClient m_http;
public event EventHandler ProductsChanged;
public event EventHandler UnitsChanged;
public event EventHandler BrandsChanged;
public event EventHandler CategoriesChanged;
public ProductRepository(HttpClient a_http)
{
m_products = new List<ProductItem>();
m_units = new List<Unitofmeasure>();
m_brands = new List<Tblbrand>();
m_categories = new List<Tblcategory>();
m_http = a_http;
}
public async Task FetchProducts()
{
var response = await m_http.GetAsync($"api/products/fetch");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var products = JsonSerializer.Deserialize<List<ProductItem>>(jsonContent, options);
m_products = products;
ProductsChanged?.Invoke(this, EventArgs.Empty);
}
}
public ProductItem GetProductById(string a_id)
{
if (m_products.Count() > 0)
{
return m_products.First(i => i.Product.Pcode == a_id);
}
return null;
}
public ProductItem GetProductByName(string name)
{
throw new NotImplementedException();
}
public IEnumerable<ProductItem> GetProducts(string a_productKey)
{
a_productKey = Regex.Replace(a_productKey, @"\s", "").ToLower();
return m_products.Where(p => Regex.Replace(p.Product!.ProductName!, @"\s", "").ToLower().StartsWith(a_productKey) ||
Regex.Replace(p.Product!.Pcode!, @"\s", "").ToLower().StartsWith(a_productKey) ||
Regex.Replace(p.Product!.Pdesc!, @"\s", "").ToLower().StartsWith(a_productKey));
}
public void RefreshList()
{
throw new NotImplementedException();
}
public IEnumerable<Unitofmeasure> GetUnitofmeasures()
{
return m_units;
}
public async Task FetchUnits()
{
var response = await m_http.GetAsync($"api/products/units");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var units = JsonSerializer.Deserialize<List<Unitofmeasure>>(jsonContent, options);
m_units = units;
UnitsChanged?.Invoke(this, EventArgs.Empty);
}
}
public string GetUnitName(string a_unitCode)
{
return m_units?.FirstOrDefault(u => u.UnitCode == a_unitCode)?.Unitname;
}
public IEnumerable<Tblbrand> GetBrands(string a_brandKey = "")
{
a_brandKey = Regex.Replace(a_brandKey, @"\s", "").ToLower();
return m_brands.Where(b => Regex.Replace(b.Id, @"\s", "").ToLower().StartsWith(a_brandKey) ||
Regex.Replace(b.Brand!, @"\s", "").ToLower().StartsWith(a_brandKey));
}
public IEnumerable<Tblcategory> GetCategories(string a_categoryKey = "")
{
a_categoryKey = Regex.Replace(a_categoryKey, @"\s", "").ToLower();
return m_categories.Where(c => Regex.Replace(c.Id, @"\s", "").ToLower().StartsWith(a_categoryKey) ||
Regex.Replace(c.Category!, @"\s", "").ToLower().StartsWith(a_categoryKey));
}
public async Task FetchBrands()
{
var response = await m_http.GetAsync($"api/products/brands");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var brands = JsonSerializer.Deserialize<List<Tblbrand>>(jsonContent, options);
m_brands = brands;
BrandsChanged?.Invoke(this, EventArgs.Empty);
}
}
public async Task FetchCategories()
{
var response = await m_http.GetAsync($"api/products/categories");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var categories = JsonSerializer.Deserialize<List<Tblcategory>>(jsonContent, options);
m_categories = categories;
CategoriesChanged?.Invoke(this, EventArgs.Empty);
}
}
public IEnumerable<ProductItem> GetLowstockItems()
{
throw new NotImplementedException();
}
public Task FetchLowStockProducts()
{
throw new NotImplementedException();
}
}
}

98
Client/Repos/SalesRepository.cs

@ -0,0 +1,98 @@
using Biskilog_Accounting.Shared.CustomModels;
using Biskilog_Accounting.Shared.Interfaces;
using Biskilog_Accounting.Shared.POSModels;
using System.Text.Json;
namespace Biskilog_Accounting.Client.Repos
{
public class SalesRepository : ISalesInterface
{
private IEnumerable<SaleItem> m_transactions;
private IEnumerable<SaleItem> m_recentTransactions;
private readonly HttpClient m_http;
public event EventHandler TransactionsChanged;
public event EventHandler FetchComplete;
public event EventHandler FetchStart;
public SalesRepository(HttpClient a_http)
{
m_recentTransactions = m_transactions = new List<SaleItem>();
m_http = a_http;
}
public async Task FetchRecentTransaction(int a_limit)
{
FetchStart?.Invoke(this, EventArgs.Empty);
var response = await m_http.GetAsync($"api/analytics/sales/recent/{a_limit}");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var recent = JsonSerializer.Deserialize<List<SaleItem>>(jsonContent, options);
m_recentTransactions = recent;
}
FetchComplete?.Invoke(this, EventArgs.Empty);
}
public async Task FetchTransaction(DateTime a_start, DateTime a_end)
{
FetchStart?.Invoke(this, EventArgs.Empty);
string start = a_start.ToString("yyyy-MM-dd");
string end = a_end.ToString("yyyy-MM-dd");
var response = await m_http.GetAsync($"api/sales/transactions/{start}/{end}");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var recent = JsonSerializer.Deserialize<List<SaleItem>>(jsonContent, options);
m_transactions = recent;
TransactionsChanged?.Invoke(this, new EventArgs());
}
FetchComplete?.Invoke(this, EventArgs.Empty);
}
public IEnumerable<SaleItem> GetTransactions(DateTime a_start, DateTime a_end)
{
return m_transactions;
}
public IEnumerable<SaleItem> GetRecentTransaction()
{
return m_recentTransactions;
}
public async Task FetchReceipt(string a_receiptId)
{
FetchStart?.Invoke(this, EventArgs.Empty);
var response = await m_http.GetAsync($"api/sales/transactions/lookup/{a_receiptId}");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var recent = JsonSerializer.Deserialize<List<SaleItem>>(jsonContent, options);
m_transactions = recent;
TransactionsChanged?.Invoke(this, new EventArgs());
}
FetchComplete?.Invoke(this, EventArgs.Empty);
}
public IEnumerable<SaleItem> GetReceipt(string a_receiptId)
{
throw new NotImplementedException();
}
public async Task<IEnumerable<Tblcart>> GetReceiptDetail(string a_receiptId)
{
var response = await m_http.GetAsync($"api/sales/receipt/lookup/{a_receiptId}");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var recent = JsonSerializer.Deserialize<List<Tblcart>>(jsonContent, options);
return recent;
}
return null;
}
}
}

2
Client/_Imports.razor

@ -8,3 +8,5 @@
@using ApexCharts;
@using Radzen
@using Radzen.Blazor
@using BlazorDateRangePicker
@using Faso.Blazor.SpinKit

BIN
Client/wwwroot/assets/img/box.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

5
Client/wwwroot/assets/vendor/css/core.css

@ -17783,7 +17783,7 @@ html:not(.layout-footer-fixed) .content-wrapper {
width: 6px;
}
.layout-page::-webkit-scrollbar-thumb {
.layout-page::-webkit-scrollbar-thumb .rz-dialog-side-position-right::-webkit-scrollbar-thumb {
background-image: linear-gradient(to bottom right,rgb(10,10,56),rgb(20, 158, 132));
border-radius: 3px;
}
@ -17802,3 +17802,6 @@ html:not(.layout-footer-fixed) .content-wrapper {
overflow-y: auto;
height: 100vh;
}
.container-p-y:not([class^=pt-]):not([class*=" pt-"]) {
padding-top: 0.5rem !important;
}

151
Client/wwwroot/css/app.css

@ -141,3 +141,154 @@ h1:focus {
text-align: center;
margin-top: 1rem;
}
.rz-dialog-titlebar {
padding: 10px !important;
background-image: linear-gradient(to bottom right,rgb(10,10,56,1.16),rgb(20, 158, 132,1.16)) !important;
color: white !important;
}
#loading-div {
width: 300px;
height: 200px;
text-align: center;
position: absolute;
left: 50%;
top: 50%;
margin-left: -150px;
margin-top: -100px;
color: #ffff !important;
z-index: 9999;
}
#loading-div-background {
width: 100%;
height: 100%;
background-color: white;
text-align: center;
position: absolute;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5); /* Adjust the opacity as desired */
z-index: 9999;
}
.sk-chase-dot:before {
content: '';
display: block;
width: 25%;
height: 25%;
background-color: #fff;
border-radius: 100%;
animation: sk-chase-dot-before 2.0s infinite ease-in-out both;
}
.notification-ui a:after {
display: none;
}
.notification-ui_icon {
position: relative;
}
.notification-ui_icon .unread-notification {
display: inline-block;
height: 7px;
width: 7px;
border-radius: 7px;
background-color: #66bb6a;
position: absolute;
top: 7px;
left: 12px;
}
@media (min-width: 900px) {
.notification-ui_icon .unread-notification {
left: 20px;
}
}
.notification-ui_dd {
padding: 0;
border-radius: 10px;
box-shadow: 0 5px 20px -3px rgba(0, 0, 0, 0.16);
border: 0;
max-width: 400px;
}
@media (min-width: 900px) {
.notification-ui_dd {
min-width: 400px;
position: absolute;
left: -192px;
top: 70px;
}
}
.notification-ui_dd:after {
content: "";
position: absolute;
top: -30px;
left: calc(50% - 7px);
border-top: 15px solid transparent;
border-right: 15px solid transparent;
border-bottom: 15px solid #fff;
border-left: 15px solid transparent;
}
.notification-ui_dd .notification-ui_dd-header {
border-bottom: 1px solid #ddd;
padding: 15px;
}
.notification-ui_dd .notification-ui_dd-header h3 {
margin-bottom: 0;
}
.notification-ui_dd .notification-ui_dd-content {
max-height: 550px;
overflow: auto;
}
.notification-list {
display: flex;
justify-content: space-between;
padding: 20px 25px;
border-bottom: 1px solid #ddd;
}
.notification-list--unread {
position: relative;
background: #f4f8fa;
}
.notification-list--unread:before {
content: "";
position: absolute;
top: 0;
left: 0;
height: calc(100% + 1px);
border-left: 2px solid #007bff;
}
.notification-list .notification-list_img img {
height: 42px;
width: 42px;
border-radius: 50px;
margin-right: 20px;
}
.notification-list .notification-list_detail {
margin-right: auto;
max-width: 200px;
}
.notification-list .notification-list_detail p {
margin-bottom: 5px;
line-height: 1.2;
}
.notification-list .notification-list_detail .nt-link {
border-left: 3px solid #007bff;
line-height: 1.3;
padding-left: 10px;
}

BIN
Client/wwwroot/fonts/CCode39.woff

Binary file not shown.

BIN
Client/wwwroot/fonts/CCodeIND2of5.woff

Binary file not shown.

BIN
Client/wwwroot/fonts/CCodePostnet.woff

Binary file not shown.

32
Client/wwwroot/index.html

@ -34,19 +34,39 @@
<link rel="stylesheet" href="assets/vendor/libs/perfect-scrollbar/perfect-scrollbar.css" />
<link rel="stylesheet" href="assets/vendor/libs/apex-charts/apex-charts.css" />
<link rel="stylesheet" href="_content/BlazorDateRangePicker/daterangepicker.min.css" />
<link href="Biskilog_Accounting.Client.styles.css" rel="stylesheet" />
</head>
<body>
<div id="app">
<style TYPE="text/css" media="screen,print">
@font-face {
font-family: CCode39;
src: url("fonts/CCode39.woff") format('woff')
}
</div>
#barcode {
font-weight: normal;
font-style: normal;
line-height: normal;
font-family: 'CCode39', sans-serif;
text-align: center;
font-size: 11.5pt;
}
#barcode_text {
font-weight: normal;
font-style: normal;
line-height: normal;
font-family: sans-serif;
text-align: center;
font-size: 11.5pt;
}
</style>
<div id="app">
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_content/BlazorDateRangePicker/clickAndPositionHandler.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
<script src="_content/Blazor-ApexCharts/js/apex-charts.min.js"></script>
<script src="_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script>

9
Server/BiskAcdbContext.cs

@ -467,6 +467,15 @@ public partial class BiskAcdbContext : DbContext
.HasColumnName("unit")
.UseCollation("utf8mb4_general_ci")
.HasCharSet("utf8mb4");
entity.Property(e => e.Tendered)
.HasPrecision(19, 2)
.HasColumnName("tendered");
entity.Property(e => e.Balance)
.HasPrecision(19, 2)
.HasColumnName("balance");
entity.Property(e => e.ValueAddTax)
.HasPrecision(19, 2)
.HasColumnName("valueAddTax");
});
modelBuilder.Entity<Tblcategory>(entity =>

9
Server/Controllers/AnalyticsController.cs

@ -115,6 +115,15 @@ namespace Biskilog_Accounting.Server.Controllers
return m_analyticService.GetTradeSummary();
}
/// <summary>
/// Endpoint to return analysis on trade summary group by categories
/// </summary>
[Authorize]
[HttpGet, Route("categorysummary")]
public IEnumerable<WeeklyCategorySummary> GetCategoryTradeSummary()
{
return m_analyticService.GetWeeklySalesCategoryTransaction(10);
}
/// <summary>
/// Endpoint to return analysis on recent transactions
/// </summary>
[Authorize]

37
Server/Controllers/CompanyInfoController.cs

@ -0,0 +1,37 @@
using Biskilog_Accounting.Shared.Interfaces;
using Biskilog_Accounting.Shared.POSModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Biskilog_Accounting.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CompanyInfoController : ControllerBase
{
private readonly ICompanyInfo m_companyInfo;
public CompanyInfoController(ICompanyInfo a_companyInfo)
{
m_companyInfo = a_companyInfo;
}
/// <summary>
/// Endpoint to return company information
/// </summary>
[Authorize]
[HttpGet, Route("info")]
public Task<Tblcompanydetail> GetCompanyInfo()
{
return m_companyInfo.GetCompanyInfoAsync();
}
/// <summary>
/// Endpoint to return branch information in the company
/// </summary>
[Authorize]
[HttpGet, Route("branches")]
public Task<IEnumerable<Tblbranch>> Getbranches()
{
return m_companyInfo.GetBranches();
}
}
}

55
Server/Controllers/ProductsController.cs

@ -0,0 +1,55 @@
using Biskilog_Accounting.Shared.CustomModels;
using Biskilog_Accounting.Shared.Interfaces;
using Biskilog_Accounting.Shared.POSModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Biskilog_Accounting.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly IProduct m_productService;
public ProductsController(IProduct a_productService)
{
m_productService = a_productService;
}
/// <summary>
/// Endpoint to return all units of measure for products
/// </summary>
[Authorize]
[HttpGet, Route("units")]
public IEnumerable<Unitofmeasure> GetUnits()
{
return m_productService.GetUnitofmeasures();
}
/// <summary>
/// Endpoint to return all products
/// </summary>
[Authorize]
[HttpGet, Route("fetch")]
public IEnumerable<ProductItem> GetProducts()
{
return m_productService.GetProducts();
}
/// <summary>
/// Endpoint to return all product categories
/// </summary>
[Authorize]
[HttpGet, Route("categories")]
public IEnumerable<Tblcategory> GetCategories()
{
return m_productService.GetCategories();
}
/// <summary>
/// Endpoint to return all product brands
/// </summary>
[Authorize]
[HttpGet, Route("brands")]
public IEnumerable<Tblbrand> GetBrands()
{
return m_productService.GetBrands();
}
}
}

49
Server/Controllers/SalesController.cs

@ -0,0 +1,49 @@
using Biskilog_Accounting.Shared.CustomModels;
using Biskilog_Accounting.Shared.Interfaces;
using Biskilog_Accounting.Shared.POSModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Biskilog_Accounting.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class SalesController : ControllerBase
{
private readonly ISalesInterface m_salesService;
public SalesController(ISalesInterface a_salesService)
{
m_salesService = a_salesService;
}
/// <summary>
/// Endpoint to return Sales within a specified period
/// </summary>
/// <param name="a_start"></param>
/// <param name="a_end"></param>
[Authorize]
[HttpGet, Route("transactions/{a_start}/{a_end}")]
public IEnumerable<SaleItem> GetSalesAsync(DateTime a_start, DateTime a_end)
{
return m_salesService.GetTransactions(a_start, a_end);
}
/// <summary>
/// Endpoint to return Sales using the specified transaction id
/// </summary>
[Authorize]
[HttpGet, Route("transactions/lookup/{a_receipt}")]
public IEnumerable<SaleItem> GetSaleAsync(string a_receipt)
{
return m_salesService.GetReceipt(a_receipt);
}
/// <summary>
/// Endpoint to return receipt details
/// </summary>
[Authorize]
[HttpGet, Route("receipt/lookup/{a_receipt}")]
public IEnumerable<Tblcart> GetReceiptAsync(string a_receipt)
{
return m_salesService.GetReceiptDetail(a_receipt).Result.ToList();
}
}
}

4
Server/Program.cs

@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
@ -24,10 +23,13 @@ builder.Services.AddEntityFrameworkMySql().AddDbContext<BiskilogContext>(options
});
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddDbContext<BiskAcdbContext>();
builder.Services.AddScoped<ICompanyInfo, CompanyService>();
builder.Services.AddScoped<IAuthService, AuthenticationService>();
builder.Services.AddScoped<ITokenService, TokenService>();
builder.Services.AddScoped<IConnectionService, ConnectionService>();
builder.Services.AddScoped<IAnalytics, AnalyticalService>();
builder.Services.AddScoped<IProduct, ProductRepo>();
builder.Services.AddScoped<ISalesInterface, SalesService>();
builder.Services.AddCors(options =>
{

31
Server/Services/AnalyticalService.cs

@ -323,6 +323,37 @@ namespace Biskilog_Accounting.Server.Services
return new TradeSummary();
}
public IEnumerable<WeeklyCategorySummary> GetWeeklySalesCategoryTransaction(int a_limit)
{
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!;
if (AuthEnums.Valid == m_tokenService.ValidateToken(token))
{
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token);
using (var command = m_context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "CALL GetCategoryTradeSummary(@p0,@p1)";
command.Parameters.Add(new MySqlParameter("@p1", a_limit));
command.Parameters.Add(new MySqlParameter("@p0", string.Join(", ", accessiblebranches.ToArray())));
m_context.Database.OpenConnection();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
yield return new WeeklyCategorySummary
{
Category = reader.GetString(0),
Total = reader.GetDecimal(1),
};
}
}
}
}
}
public IEnumerable<WeeklySaleItem> GetWeeklySalesTransaction()
{
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!;

53
Server/Services/CompanyService.cs

@ -0,0 +1,53 @@
using Biskilog_Accounting.Server.POSModels;
using Biskilog_Accounting.Shared.Interfaces;
using Biskilog_Accounting.Shared.POSModels;
namespace Biskilog_Accounting.Server.Services
{
public class CompanyService : ICompanyInfo
{
private readonly BiskAcdbContext m_context;
private readonly ITokenService m_tokenService;
private readonly HttpContext m_httpContext;
private Tblcompanydetail m_companyInfo { get; set; }
private IEnumerable<Tblbranch> m_companyBranches { get; set; }
public CompanyService(BiskAcdbContext a_context, ITokenService a_tokenService, IHttpContextAccessor a_httpContextAccessor)
{
m_context = a_context;
m_tokenService = a_tokenService;
m_httpContext = a_httpContextAccessor?.HttpContext;
m_companyInfo = new Tblcompanydetail();
m_companyBranches = new List<Tblbranch>();
GetCompanyInfoAsync();
GetBranches();
}
public IEnumerable<Tblbranch> FetchBranches()
{
return m_companyBranches;
}
public async Task<IEnumerable<Tblbranch>> GetBranches()
{
m_companyBranches = m_context.Tblbranches;
return await Task.FromResult(m_companyBranches);
}
public string GetBranchName(string a_branchId)
{
return m_companyBranches.FirstOrDefault(b => b.BranchId == a_branchId).BranchName;
}
public Task<Tblcompanydetail> GetCompanyInfoAsync()
{
m_companyInfo = m_context.Tblcompanydetails.First();
return Task.FromResult(m_companyInfo);
}
public string GetCompanyName()
{
return m_companyInfo.CompanyName;
}
}
}

200
Server/Services/ProductRepo.cs

@ -0,0 +1,200 @@
using Biskilog_Accounting.Server.POSModels;
using Biskilog_Accounting.Shared.CustomModels;
using Biskilog_Accounting.Shared.Enums;
using Biskilog_Accounting.Shared.Interfaces;
using Biskilog_Accounting.Shared.POSModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.Net.Http.Headers;
using MySqlConnector;
using System.Data;
using System.Data.Common;
namespace Biskilog_Accounting.Server.Services
{
public class ProductRepo : IProduct
{
private readonly BiskAcdbContext m_context;
private readonly ITokenService m_tokenService;
private readonly HttpContext m_httpContext;
public event EventHandler ProductsChanged;
public event EventHandler UnitsChanged;
public event EventHandler BrandsChanged;
public event EventHandler CategoriesChanged;
public ProductRepo(BiskAcdbContext a_context, ITokenService a_tokenService, IHttpContextAccessor a_httpContextAccessor)
{
m_context = a_context;
m_tokenService = a_tokenService;
m_httpContext = a_httpContextAccessor?.HttpContext;
}
/// <summary>
/// Gets all products from the server
/// </summary>
/// <returns></returns>
public IEnumerable<ProductItem> GetProducts(string a_productKey = "")
{
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!;
if (AuthEnums.Valid == m_tokenService.ValidateToken(token))
{
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token);
using (var command = m_context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "CALL GetProducts(@p0)";
command.Parameters.Add(new MySqlParameter("@p0", string.Join(", ", accessiblebranches.ToArray())));
m_context.Database.OpenConnection();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
List<ProductUnits> pUnits = new List<ProductUnits>();
yield return new ProductItem
{
Product = new Tblproduct
{
Pcode = reader.GetString(0),
ProductName = reader.GetString(1),
Pdesc = reader.GetString(2),
BaseUnit = reader.GetString(3),
Costprice = reader.GetDecimal(4),
Status = reader.GetString(5),
Price = reader.GetDecimal(6),
BranchId = reader.GetString(7),
},
BaseUnit = reader.GetString(3),
Stock = new Tblinventory
{
Quantity = reader.GetInt32(8)
},
Restocklevel = new Restocklevel
{
WarnLevel = reader.GetInt32(9),
Unit = reader.GetString(10),
},
Units = GetAltUnits(reader)
};
}
}
}
}
}
private List<ProductUnits> GetAltUnits(DbDataReader a_reader)
{
List<ProductUnits> pUnits = new List<ProductUnits>();
for (int i = 1; i < 5; i++)
{
if (!a_reader.IsDBNull(a_reader.GetOrdinal($"AltUnit{i}")))
{
pUnits.Add(new ProductUnits
{
UnitCode = a_reader.GetFieldValue<string>($"AltUnit{i}"),
QuantityUnit = a_reader.GetFieldValue<int>($"AltUnit{i}QTY"),
PriceUnit = a_reader.GetFieldValue<decimal>($"AltUnit{i}Price"),
DistinctiveCode = a_reader.GetFieldValue<string>($"AltUnit{i}distinctiveCode")
});
}
else
{
return pUnits;
}
}
return pUnits;
}
public IEnumerable<Unitofmeasure> GetUnitofmeasures()
{
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!;
if (AuthEnums.Valid == m_tokenService.ValidateToken(token))
{
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token);
return m_context.Unitofmeasures.Where(b => accessiblebranches.Contains(b.BranchId));
}
return new List<Unitofmeasure>();
}
public IEnumerable<Tblbrand> GetBrands(string a_brandKey = "")
{
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!;
if (AuthEnums.Valid == m_tokenService.ValidateToken(token))
{
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token);
return m_context.Tblbrands.Where(b => accessiblebranches.Contains(b.BranchId));
}
return new List<Tblbrand>();
}
public IEnumerable<Tblcategory> GetCategories(string a_categoryKey = "")
{
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!;
if (AuthEnums.Valid == m_tokenService.ValidateToken(token))
{
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token);
return m_context.Tblcategories.Where(b => accessiblebranches.Contains(b.BranchId));
}
return new List<Tblcategory>();
}
#region Only Need to implement in the client Side
public Task FetchUnits()
{
throw new NotImplementedException();
}
public Task FetchProducts()
{
throw new NotImplementedException();
}
public ProductItem GetProductById(string a_id)
{
throw new NotImplementedException();
}
public ProductItem GetProductByName(string name)
{
throw new NotImplementedException();
}
public void RefreshList()
{
throw new NotImplementedException();
}
public string GetUnitName(string a_unitCode)
{
throw new NotImplementedException();
}
public Task FetchBrands()
{
throw new NotImplementedException();
}
public Task FetchCategories()
{
throw new NotImplementedException();
}
public IEnumerable<ProductItem> GetLowstockItems()
{
throw new NotImplementedException();
}
public Task FetchLowStockProducts()
{
throw new NotImplementedException();
}
}
#endregion
}

169
Server/Services/SalesService.cs

@ -0,0 +1,169 @@
using Biskilog_Accounting.Server.POSModels;
using Biskilog_Accounting.Shared.CustomModels;
using Biskilog_Accounting.Shared.Enums;
using Biskilog_Accounting.Shared.Interfaces;
using Biskilog_Accounting.Shared.POSModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.Net.Http.Headers;
using MySqlConnector;
using System.Drawing.Drawing2D;
namespace Biskilog_Accounting.Server.Services
{
public class SalesService : ISalesInterface
{
private readonly BiskAcdbContext m_context;
private readonly ITokenService m_tokenService;
private readonly HttpContext m_httpContext;
public event EventHandler TransactionsChanged;
public event EventHandler FetchComplete;
public event EventHandler FetchStart;
public SalesService(BiskAcdbContext a_context, ITokenService a_tokenService, IHttpContextAccessor a_httpContextAccessor)
{
m_context = a_context;
m_tokenService = a_tokenService;
m_httpContext = a_httpContextAccessor?.HttpContext;
}
public Task FetchRecentTransaction(int a_limit)
{
throw new NotImplementedException();
}
public IEnumerable<SaleItem> GetRecentTransaction()
{
throw new NotImplementedException();
}
public IEnumerable<SaleItem> GetTransactions(DateTime a_start, DateTime a_end)
{
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!;
if (AuthEnums.Valid == m_tokenService.ValidateToken(token))
{
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token);
using (var command = m_context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "CALL GetTransactionsByDate(@p0,@p1,@p2)";
command.Parameters.Add(new MySqlParameter("@p0", a_start.ToString("yyyy-MM-dd")));
command.Parameters.Add(new MySqlParameter("@p1", a_end.ToString("yyyy-MM-dd")));
command.Parameters.Add(new MySqlParameter("@p2", string.Join(", ", accessiblebranches.ToArray())));
m_context.Database.OpenConnection();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
yield return new SaleItem
{
Transno = reader.GetString(0),
Total = (decimal)reader.GetDouble(1),
Date = reader.GetDateTime(2),
Cashier = reader.GetString(3),
BranchId = reader.GetString(4),
Customer = reader.GetString(5),
Status = reader.GetString(6),
};
}
}
}
}
}
public Task FetchTransaction(DateTime a_start, DateTime a_end)
{
throw new NotImplementedException();
}
public Task FetchReceipt(string a_receiptId)
{
throw new NotImplementedException();
}
public IEnumerable<SaleItem> GetReceipt(string a_receiptId)
{
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!;
if (AuthEnums.Valid == m_tokenService.ValidateToken(token))
{
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token);
using (var command = m_context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "CALL GetTransactionsById(@p0,@p1)";
command.Parameters.Add(new MySqlParameter("@p0", a_receiptId));
command.Parameters.Add(new MySqlParameter("@p1", string.Join(", ", accessiblebranches.ToArray())));
m_context.Database.OpenConnection();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
yield return new SaleItem
{
Transno = reader.GetString(0),
Total = (decimal)reader.GetDouble(1),
Date = reader.GetDateTime(2),
Cashier = reader.GetString(3),
BranchId = reader.GetString(4),
Customer = reader.GetString(5),
Status = reader.GetString(6),
};
}
}
}
}
}
public Task<IEnumerable<Tblcart>> GetReceiptDetail(string a_receiptId)
{
List<Tblcart> details = new List<Tblcart>();
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!;
if (AuthEnums.Valid == m_tokenService.ValidateToken(token))
{
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token);
using (var command = m_context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "CALL GetReceiptDetails(@p0,@p1)";
command.Parameters.Add(new MySqlParameter("@p0", a_receiptId));
command.Parameters.Add(new MySqlParameter("@p1", string.Join(", ", accessiblebranches.ToArray())));
m_context.Database.OpenConnection();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
details.Add(new Tblcart
{
Transno = a_receiptId,
Id = reader.GetString(0),
Quantity = reader.GetInt32(1),
Date = reader.GetDateTime(2),
Price = reader.GetDecimal(3),
Cashier = reader.GetString(4),
Status = reader.GetString(5),
Total = (decimal)reader.GetDouble(6),
Unit = reader.GetString(7),
Costprice = reader.GetDecimal(8),
BranchId = reader.GetString(9),
CountId = reader.GetString(10),
Tendered = reader.GetDecimal(11),
Balance = reader.GetDecimal(12),
ValueAddTax = reader.GetDecimal(13)
});
}
}
}
}
return Task.FromResult(details.AsEnumerable());
}
}
}

4
Shared/CustomModels/ProductItem.cs

@ -11,6 +11,8 @@ namespace Biskilog_Accounting.Shared.CustomModels
{
public Tblproduct? Product { get; set; }
public Tblinventory? Stock { get; set; }
public string BaseUnit { get;set; } = string.Empty;
public Restocklevel? Restocklevel { get; set; }
public List<ProductUnits> Units { get; set; } = new List<ProductUnits>();
public string BaseUnit { get; set; } = string.Empty;
}
}

26
Shared/CustomModels/ProductUnits.cs

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Biskilog_Accounting.Shared.CustomModels
{
public class ProductUnits
{
public string? Pcode { get; set; }
public string? UnitCode { get; set; }
public string? UnitName { get; set; }
public string? UnitBarcode { get; set; }
public decimal? PriceUnit { get; set; }
public int? QuantityUnit { get; set; }
public string DistinctiveCode { get; set; } = null!;
public string BranchId { get; set; } = null!;
}
}

6
Shared/CustomModels/WeeklySaleItem.cs

@ -12,4 +12,10 @@ namespace Biskilog_Accounting.Shared.CustomModels
public decimal? Total { get; set; }
public string BranchId { get; set; } = null!;
}
public class WeeklyCategorySummary
{
public decimal? Total { get; set; }
public string Category { get; set; } = string.Empty!;
}
}

5
Shared/Interfaces/IAnalytics.cs

@ -77,5 +77,10 @@ namespace Biskilog_Accounting.Shared.Interfaces
/// <param name="a_limit">the number of products to fetch history</param>
/// <returns></returns>
IEnumerable<ProductPriceChange> GetProductPriceChangeHistory(int a_limit);
/// <summary>
/// Fetches a collection of sales transaction grouped by category made within a one week period
/// </summary>
/// <returns></returns>
IEnumerable<WeeklyCategorySummary> GetWeeklySalesCategoryTransaction(int a_limit);
}
}

18
Shared/Interfaces/ICompanyInfo.cs

@ -0,0 +1,18 @@
using Biskilog_Accounting.Shared.POSModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Biskilog_Accounting.Shared.Interfaces
{
public interface ICompanyInfo
{
IEnumerable<Tblbranch> FetchBranches();
Task<Tblcompanydetail> GetCompanyInfoAsync();
Task<IEnumerable<Tblbranch>> GetBranches();
string GetCompanyName();
string GetBranchName(string a_branchId);
}
}

15
Shared/Interfaces/IMainInterface.cs

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Biskilog_Accounting.Shared.Interfaces
{
public interface IMainInterface
{
void ShowReceipt(string a_receipt);
DateOnly CurrentTradeDate();
DateOnly PreviousTradeDate();
}
}

32
Shared/Interfaces/IProducts.cs

@ -0,0 +1,32 @@
using Biskilog_Accounting.Shared.CustomModels;
using Biskilog_Accounting.Shared.POSModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Biskilog_Accounting.Shared.Interfaces
{
public interface IProduct
{
IEnumerable<Unitofmeasure> GetUnitofmeasures();
IEnumerable<ProductItem> GetProducts(string a_productKey = "");
IEnumerable<Tblbrand> GetBrands(string a_brandKey = "");
IEnumerable<Tblcategory> GetCategories(string a_categoryKey = "");
IEnumerable<ProductItem> GetLowstockItems();
Task FetchProducts();
Task FetchLowStockProducts();
Task FetchUnits();
Task FetchBrands();
Task FetchCategories();
void RefreshList();
ProductItem GetProductById(string a_id);
ProductItem GetProductByName(string name);
string GetUnitName(string a_unitCode);
event EventHandler ProductsChanged;
event EventHandler UnitsChanged;
event EventHandler BrandsChanged;
event EventHandler CategoriesChanged;
}
}

24
Shared/Interfaces/ISalesInterface.cs

@ -0,0 +1,24 @@
using Biskilog_Accounting.Shared.CustomModels;
using Biskilog_Accounting.Shared.POSModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Biskilog_Accounting.Shared.Interfaces
{
public interface ISalesInterface
{
Task FetchRecentTransaction(int a_limit);
Task FetchTransaction(DateTime a_start, DateTime a_end);
IEnumerable<SaleItem> GetTransactions(DateTime a_start, DateTime a_end);
IEnumerable<SaleItem> GetRecentTransaction();
Task FetchReceipt(string a_receiptId);
IEnumerable<SaleItem> GetReceipt(string a_receiptId);
Task<IEnumerable<Tblcart>> GetReceiptDetail(string a_receiptId);
event EventHandler TransactionsChanged;
event EventHandler FetchComplete;
event EventHandler FetchStart;
}
}

16
Shared/Interfaces/ISearchService.cs

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Biskilog_Accounting.Shared.Interfaces
{
public interface ISearchService
{
public event Action<string> SearchValueChanged;
public event Action ClearTextBox;
void PerformSearch(string a_searchKey);
void Clear();
}
}

4
Shared/POSModels/Tblcart.cs

@ -22,6 +22,10 @@ public partial class Tblcart
public decimal? Total { get; set; }
public string? Unit { get; set; }
public decimal? Tendered { get; set; }
public decimal? Balance { get; set; }
public decimal? ValueAddTax { get; set; }
public decimal? Discount { get; set; } = 0;
public decimal? Costprice { get; set; }

33
Shared/ServiceRepo/SearchService.cs

@ -0,0 +1,33 @@
using Biskilog_Accounting.Shared.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Biskilog_Accounting.Shared.ServiceRepo
{
public class SearchService : ISearchService
{
public event Action<string> SearchValueChanged;
public event Action ClearTextBox;
// Method that raises the event
protected virtual void OnSearchValueChanged(string a_searchKey)
{
SearchValueChanged?.Invoke(a_searchKey);
}
// Method that triggers the event
public void PerformSearch(string a_searchKey)
{
OnSearchValueChanged(a_searchKey);
}
public void Clear()
{
ClearTextBox?.Invoke();
}
}
}
Loading…
Cancel
Save