BISK2023-2-performance-analysis-feature #4
Merged
barhen
merged 4 commits from BISK2023-2-performance-analysis-feature
into dev
2 years ago
19 changed files with 579 additions and 20 deletions
@ -0,0 +1,101 @@ |
|||||
|
using Biskilog_Accounting.Shared.CustomModels; |
||||
|
using Biskilog_Accounting.Shared.Interfaces; |
||||
|
using Biskilog_Accounting.Shared.POSModels; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.Net.Http.Headers; |
||||
|
using NuGet.Common; |
||||
|
|
||||
|
namespace Biskilog_Accounting.Server.Controllers |
||||
|
{ |
||||
|
[Route("api/[controller]")]
|
||||
|
[ApiController] |
||||
|
public class AnalyticsController : ControllerBase |
||||
|
{ |
||||
|
private readonly IAnalytics m_analyticService; |
||||
|
public AnalyticsController(IAnalytics a_analytics) |
||||
|
{ |
||||
|
m_analyticService = a_analytics; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Endpoint to return analysis on CancelledSales within a specified period
|
||||
|
/// </summary>
|
||||
|
/// <param name="a_start"></param>
|
||||
|
/// <param name="a_end"></param>
|
||||
|
[Authorize] |
||||
|
[HttpGet, Route("cancelledsales/{a_start}/{a_end}")] |
||||
|
public IEnumerable<CancelledSales> GetCancelledSalesAsync(DateTime a_start, DateTime a_end) |
||||
|
{ |
||||
|
return m_analyticService.GetCancelledSales(a_start, a_end); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// Endpoint to return analysis on Sales within a specified period
|
||||
|
/// </summary>
|
||||
|
/// <param name="a_start"></param>
|
||||
|
/// <param name="a_end"></param>
|
||||
|
[Authorize] |
||||
|
[HttpGet, Route("sales/{a_start}/{a_end}")] |
||||
|
public IEnumerable<Tblcart> GetSalesAsync(DateTime a_start, DateTime a_end) |
||||
|
{ |
||||
|
return m_analyticService.GetSalesTransaction(a_start, a_end); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// Endpoint to return analysis on in-debt customers
|
||||
|
/// </summary>
|
||||
|
/// <param name="a_start"></param>
|
||||
|
/// <param name="a_end"></param>
|
||||
|
[Authorize] |
||||
|
[HttpGet, Route("debtors")] |
||||
|
public IEnumerable<InDebtCustomers> GetInDebtCustomers() |
||||
|
{ |
||||
|
return m_analyticService.GetInDebtCustomers(); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// Endpoint to return analysis on product price changes
|
||||
|
/// </summary>
|
||||
|
/// <param name="a_start"></param>
|
||||
|
/// <param name="a_end"></param>
|
||||
|
[Authorize] |
||||
|
[HttpGet, Route("pricechanges/{a_start}/{a_end}")] |
||||
|
public IEnumerable<ProductPriceChange> GetPriceChanges(DateTime a_start, DateTime a_end) |
||||
|
{ |
||||
|
return m_analyticService.GetPriceChanges(a_start, a_end); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// Endpoint to return analysis on sales by employees
|
||||
|
/// </summary>
|
||||
|
/// <param name="a_start"></param>
|
||||
|
/// <param name="a_end"></param>
|
||||
|
[Authorize] |
||||
|
[HttpGet, Route("employeesales/{a_start}/{a_end}")] |
||||
|
public Dictionary<string, List<SaleItem>> GetEmployeeSales(DateTime a_start, DateTime a_end) |
||||
|
{ |
||||
|
return m_analyticService.GetEmployeeSales(a_start, a_end); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// Endpoint to return analysis on product items low on stock
|
||||
|
/// </summary>
|
||||
|
/// <param name="a_start"></param>
|
||||
|
/// <param name="a_end"></param>
|
||||
|
[Authorize] |
||||
|
[HttpGet, Route("lowonstock")] |
||||
|
public IEnumerable<ProductItem> GetLowOnStockItems() |
||||
|
{ |
||||
|
string token = Request.Headers[HeaderNames.Authorization]!; |
||||
|
|
||||
|
return m_analyticService.GetOutOfStockItems(); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// Endpoint to return analysis on the most purchased product item
|
||||
|
/// </summary>
|
||||
|
/// <param name="a_start"></param>
|
||||
|
/// <param name="a_end"></param>
|
||||
|
[Authorize] |
||||
|
[HttpGet, Route("mostpurchaseditem/{a_start}/{a_end}")] |
||||
|
public IEnumerable<MostPurchasedItem> GetMostPurchased(DateTime a_start, DateTime a_end) |
||||
|
{ |
||||
|
return m_analyticService.GetMostPurchasedItem(a_start, a_end); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,164 @@ |
|||||
|
using Biskilog_Accounting.Server.POSModels; |
||||
|
using Biskilog_Accounting.Shared.CustomModels; |
||||
|
using Biskilog_Accounting.Shared.Interfaces; |
||||
|
using Biskilog_Accounting.Shared.POSModels; |
||||
|
using Microsoft.Net.Http.Headers; |
||||
|
using System.Data.Entity; |
||||
|
|
||||
|
namespace Biskilog_Accounting.Server.Services |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the KPIs/ Analysis of operations made with the software
|
||||
|
/// </summary>
|
||||
|
public class AnalyticalService : IAnalytics |
||||
|
{ |
||||
|
private readonly BiskAcdbContext m_context; |
||||
|
private readonly ITokenService m_tokenService; |
||||
|
private readonly HttpContext m_httpContext; |
||||
|
|
||||
|
public AnalyticalService(BiskAcdbContext a_context, ITokenService a_tokenService, IHttpContextAccessor a_httpContextAccessor) |
||||
|
{ |
||||
|
m_context = a_context; |
||||
|
m_tokenService = a_tokenService; |
||||
|
m_httpContext = a_httpContextAccessor?.HttpContext; |
||||
|
} |
||||
|
public IEnumerable<CancelledSales> GetCancelledSales(DateTime a_start, DateTime a_end) |
||||
|
{ |
||||
|
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!; |
||||
|
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token); |
||||
|
|
||||
|
return from cSale in m_context.Tblcancelledtransactions |
||||
|
join aSale in m_context.Tblcarts on cSale.Transno equals aSale.Transno into activeSale |
||||
|
join cPurchase in m_context.Tblcustomerpurchases on cSale.Transno equals cPurchase.TransactionId into customerSales |
||||
|
from c in customerSales.DefaultIfEmpty() |
||||
|
join customer in m_context.Tblcustomers on c.CustomerId equals customer.CustomerId into Customers |
||||
|
from cc in Customers.DefaultIfEmpty() |
||||
|
where cSale.DateCancelled >= a_start && cSale.DateCancelled <= a_end && accessiblebranches.Contains(cSale.BranchId) |
||||
|
select new CancelledSales |
||||
|
{ |
||||
|
CancelledTransaction = cSale, |
||||
|
Value = (from a in activeSale where accessiblebranches.Contains(a.BranchId) select a.Total).Sum(), |
||||
|
Customer = !String.IsNullOrEmpty(cc.CustomerId) ? $"{cc.Firstname} {cc.Surname}" : "Walk-IN Purchase" |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public Dictionary<string, List<SaleItem>> GetEmployeeSales(DateTime a_start, DateTime a_end) |
||||
|
{ |
||||
|
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!; |
||||
|
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token); |
||||
|
|
||||
|
Dictionary<string, List<SaleItem>> sales = new Dictionary<string, List<SaleItem>>(); |
||||
|
var employeeSales = m_context.Tblcarts.Where(c => c.Date >= a_start && c.Date <= a_end |
||||
|
&& accessiblebranches.Contains(c.BranchId)).Select(e => e.Cashier).Distinct().ToList(); |
||||
|
|
||||
|
foreach (var employeeName in employeeSales) |
||||
|
{ |
||||
|
var list = (from a in employeeSales |
||||
|
join c in m_context.Tblcarts on a equals c.Cashier into Sales |
||||
|
from s in Sales |
||||
|
group s by s.Transno into saleItem |
||||
|
select new SaleItem |
||||
|
{ |
||||
|
Total = saleItem.Sum(c => c.Total), |
||||
|
Transno = saleItem.Key, |
||||
|
Cashier = employeeName, |
||||
|
Date = saleItem.First().Date, |
||||
|
Status = saleItem.First().Status, |
||||
|
BranchId = saleItem.First().BranchId |
||||
|
}).ToList(); |
||||
|
sales.Add(employeeName, list); |
||||
|
} |
||||
|
return sales; |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<InDebtCustomers> GetInDebtCustomers() |
||||
|
{ |
||||
|
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!; |
||||
|
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token); |
||||
|
|
||||
|
var listDebts = m_context.Customeraccounts.Where(t => t.Balance < 0 && accessiblebranches.Contains(t.BranchId)).OrderByDescending(d => d.Date).Select(t => t.CustomerId).Distinct().ToList(); |
||||
|
foreach (var customerId in listDebts) |
||||
|
{ |
||||
|
yield return new InDebtCustomers |
||||
|
{ |
||||
|
Customer = m_context.Tblcustomers.FirstOrDefault(i => i.CustomerId == customerId), |
||||
|
Debt = m_context.Customeraccounts.OrderByDescending(d => d.Date).FirstOrDefault(t => t.Balance < 0 && t.CustomerId == customerId).Balance, |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<MostPurchasedItem> GetMostPurchasedItem(DateTime a_start, DateTime a_end) |
||||
|
{ |
||||
|
//TODO either rewrite query or increase memory on server to deal with comparison mode performance issue
|
||||
|
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!; |
||||
|
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token); |
||||
|
|
||||
|
var items = (from s in m_context.Tblcarts |
||||
|
join p in m_context.Tblproducts on s.Id equals p.Pcode |
||||
|
where s.Date >= a_start && s.Date <= a_end && s.BranchId == accessiblebranches.First() |
||||
|
group s by p into g |
||||
|
orderby g.Count() descending |
||||
|
select new MostPurchasedItem |
||||
|
{ |
||||
|
ProductId = g.Key.Pcode, |
||||
|
ProductName = g.Key.ProductName, |
||||
|
NbrTimesSold = g.Count(), |
||||
|
Revenue = g.Sum(s => s.Price) |
||||
|
}).Take(50).ToList(); |
||||
|
return items; |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<ProductItem> GetOutOfStockItems() |
||||
|
{ |
||||
|
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!; |
||||
|
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token); |
||||
|
|
||||
|
return (from item in m_context.Tblinventories |
||||
|
join p in m_context.Tblproducts on item.Pcode equals p.Pcode |
||||
|
join pu in m_context.Productaltunits on item.Pcode equals pu.Pcode into AltUnit |
||||
|
from au in AltUnit.DefaultIfEmpty() |
||||
|
join rs in m_context.Restocklevels on item.Pcode equals rs.ProductId |
||||
|
join un in m_context.Unitofmeasures on p.BaseUnit equals un.UnitCode |
||||
|
where p.Status!.ToLower() != "inactive" && accessiblebranches.Contains(item.BranchId) && |
||||
|
((rs.WarnLevel >= item.Quantity && rs.Unit == p.BaseUnit) || (rs.WarnLevel >= (item.Quantity / au.QuantityUnit) && |
||||
|
rs.Unit == au.UnitCode) |
||||
|
) |
||||
|
select new ProductItem |
||||
|
{ |
||||
|
Product = p, |
||||
|
Stock = item, |
||||
|
BaseUnit = un.Unitshort! |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<ProductPriceChange> GetPriceChanges(DateTime a_start, DateTime a_end) |
||||
|
{ |
||||
|
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!; |
||||
|
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token); |
||||
|
|
||||
|
return from change in m_context.Tblpricechanges |
||||
|
join p in m_context.Tblproducts on change.Pcode equals p.Pcode |
||||
|
where change.ChangeDate >= a_start && change.ChangeDate <= a_end && accessiblebranches.Contains(change.BranchId) |
||||
|
select new ProductPriceChange |
||||
|
{ |
||||
|
BranchId = change.BranchId, |
||||
|
ChangeDate = change.ChangeDate, |
||||
|
Pcode = change.Pcode, |
||||
|
CountId = change.CountId, |
||||
|
CurrentPrice = change.CurrentPrice, |
||||
|
PreviousPrice = change.PreviousPrice, |
||||
|
ProductName = p.ProductName |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<Tblcart> GetSalesTransaction(DateTime a_start, DateTime a_end) |
||||
|
{ |
||||
|
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!; |
||||
|
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token); |
||||
|
|
||||
|
return m_context.Tblcarts.Where(t => t.Date >= a_start && t.Date <= a_end && accessiblebranches.Contains(t.BranchId)); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
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.CustomModels |
||||
|
{ |
||||
|
public class CancelledSales |
||||
|
{ |
||||
|
public Tblcancelledtransaction? CancelledTransaction { get; set; } |
||||
|
public string Customer { get; set; } = "WALK-IN Purchase"; |
||||
|
public decimal? Value { get; set; } |
||||
|
} |
||||
|
} |
@ -0,0 +1,15 @@ |
|||||
|
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.CustomModels |
||||
|
{ |
||||
|
public class InDebtCustomers |
||||
|
{ |
||||
|
public Tblcustomer Customer { get; set; } |
||||
|
public decimal Debt { get; set; } = 0; |
||||
|
} |
||||
|
} |
@ -0,0 +1,29 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Biskilog_Accounting.Shared.CustomModels |
||||
|
{ |
||||
|
public class MostPurchasedItem |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Specifies the id of the product
|
||||
|
/// </summary>
|
||||
|
public string ProductId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Specifies the name of the product
|
||||
|
/// </summary>
|
||||
|
public string ProductName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// The total revenue generated from the sale
|
||||
|
/// </summary>
|
||||
|
public decimal? Revenue { get; set; } |
||||
|
/// <summary>
|
||||
|
/// This is the number of times the item has been sold
|
||||
|
/// </summary>
|
||||
|
public int? NbrTimesSold { get; set; } |
||||
|
|
||||
|
} |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
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.CustomModels |
||||
|
{ |
||||
|
public class ProductItem |
||||
|
{ |
||||
|
public Tblproduct? Product { get; set; } |
||||
|
public Tblinventory? Stock { get; set; } |
||||
|
public string BaseUnit { get;set; } = string.Empty; |
||||
|
} |
||||
|
} |
@ -0,0 +1,23 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Biskilog_Accounting.Shared.CustomModels |
||||
|
{ |
||||
|
public class ProductPriceChange |
||||
|
{ |
||||
|
public string? Pcode { get; set; } |
||||
|
public string? ProductName { get; set; } |
||||
|
public decimal? PreviousPrice { get; set; } |
||||
|
|
||||
|
public decimal? CurrentPrice { get; set; } |
||||
|
|
||||
|
public DateTime? ChangeDate { get; set; } |
||||
|
|
||||
|
public string BranchId { get; set; } = null!; |
||||
|
|
||||
|
public string CountId { get; set; } = null!; |
||||
|
} |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Biskilog_Accounting.Shared.CustomModels |
||||
|
{ |
||||
|
public class SaleItem |
||||
|
{ |
||||
|
public string? Transno { get; set; } |
||||
|
|
||||
|
public DateTime? Date { get; set; } |
||||
|
|
||||
|
public string? Cashier { get; set; } |
||||
|
public string? Status { get; set; } |
||||
|
public decimal? Total { get; set; } |
||||
|
public string BranchId { get; set; } = null!; |
||||
|
|
||||
|
public string Customer { get; set; } = "Walk-In Purchase"!; |
||||
|
} |
||||
|
} |
@ -0,0 +1,54 @@ |
|||||
|
using Biskilog_Accounting.Shared.CustomModels; |
||||
|
using Biskilog_Accounting.Shared.POSModels; |
||||
|
|
||||
|
namespace Biskilog_Accounting.Shared.Interfaces |
||||
|
{ |
||||
|
public interface IAnalytics |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Fetches a collection of sales transaction made from the specified start date to the end date
|
||||
|
/// </summary>
|
||||
|
/// <param name="a_start">Specified Start Date</param>
|
||||
|
/// <param name="a_end">Specified end Date</param>
|
||||
|
/// <returns></returns>
|
||||
|
IEnumerable<Tblcart> GetSalesTransaction(DateTime a_start, DateTime a_end); |
||||
|
/// <summary>
|
||||
|
/// Fetches a collection of in-debt customers
|
||||
|
/// <returns></returns>
|
||||
|
IEnumerable<InDebtCustomers> GetInDebtCustomers(); |
||||
|
/// <summary>
|
||||
|
/// Fetches a collection of Product Items which are currently out of stock
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
IEnumerable<ProductItem> GetOutOfStockItems(); |
||||
|
/// <summary>
|
||||
|
/// Fetches a collection of the most purchased Product Items within a specified date range
|
||||
|
/// </summary>
|
||||
|
/// <param name="a_start"></param>
|
||||
|
/// <param name="a_end"></param>
|
||||
|
/// <returns></returns>
|
||||
|
IEnumerable<MostPurchasedItem> GetMostPurchasedItem(DateTime a_start, DateTime a_end); |
||||
|
/// <summary>
|
||||
|
/// Fetches a collection of cancelled transaction within a specified date range
|
||||
|
/// </summary>
|
||||
|
/// <param name="a_start"></param>
|
||||
|
/// <param name="a_end"></param>
|
||||
|
/// <returns></returns>
|
||||
|
IEnumerable<CancelledSales> GetCancelledSales(DateTime a_start, DateTime a_end); |
||||
|
/// <summary>
|
||||
|
/// Fetches a collection of transaction made by employees within a specified date range
|
||||
|
/// </summary>
|
||||
|
/// <param name="a_start"></param>
|
||||
|
/// <param name="a_end"></param>
|
||||
|
/// <returns>A dictionary of transactions made by employees with employee name as key</returns>
|
||||
|
Dictionary<string, List<SaleItem>> GetEmployeeSales(DateTime a_start, DateTime a_end); |
||||
|
/// <summary>
|
||||
|
/// Fetches a collection of product price changes with a specified date range
|
||||
|
/// </summary>
|
||||
|
/// <param name="a_start"></param>
|
||||
|
/// <param name="a_end"></param>
|
||||
|
/// <returns></returns>
|
||||
|
IEnumerable<ProductPriceChange> GetPriceChanges(DateTime a_start, DateTime a_end); |
||||
|
//void SetContraints(string a_token);
|
||||
|
} |
||||
|
} |
Loading…
Reference in new issue