diff --git a/Server/Controllers/AnalyticsController.cs b/Server/Controllers/AnalyticsController.cs index 73156e7..b09e292 100644 --- a/Server/Controllers/AnalyticsController.cs +++ b/Server/Controllers/AnalyticsController.cs @@ -1,16 +1,10 @@ -using Biskilog_Accounting.Server.POSModels; -using Biskilog_Accounting.Server.Services; -using Biskilog_Accounting.Shared.CustomModels; +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; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Net.Http.Headers; -using System.ComponentModel.DataAnnotations; -using System.Data.Entity; +using NuGet.Common; namespace Biskilog_Accounting.Server.Controllers { @@ -18,12 +12,12 @@ namespace Biskilog_Accounting.Server.Controllers [ApiController] public class AnalyticsController : ControllerBase { - private readonly IConnectionService m_connection; private readonly ITokenService m_tokenService; - public AnalyticsController(ITokenService tokenService, IConnectionService connection) + private readonly IAnalytics m_analyticService; + public AnalyticsController(ITokenService tokenService, IAnalytics a_analytics) { m_tokenService = tokenService; - m_connection = connection; + m_analyticService = a_analytics; } /// @@ -36,18 +30,9 @@ namespace Biskilog_Accounting.Server.Controllers public IEnumerable GetCancelledSalesAsync(DateTime a_start, DateTime a_end) { string token = Request.Headers[HeaderNames.Authorization]!; - int? databaseId = m_tokenService.GetDatabaseIdFromToken(token); - string connectionString = m_connection.GetClientConnectionString(databaseId!.Value); - bool? comparisonMode = m_tokenService.GetComparison(token); - string? currentBranch = m_tokenService.GetBaseBranch(token); + m_analyticService.SetContraints(token); - //Creates a new db context - BiskAcdbContext newContext = (BiskAcdbContext)m_connection.PrepareDBContext(connectionString); - - AnalyticalService analysis = new(newContext, comparisonMode!.Value, currentBranch!); - var result = analysis.GetCancelledSales(a_start, a_end); - - return result; + return m_analyticService.GetCancelledSales(a_start, a_end); } /// /// Endpoint to return analysis on Sales within a specified period @@ -59,16 +44,9 @@ namespace Biskilog_Accounting.Server.Controllers public IEnumerable GetSalesAsync(DateTime a_start, DateTime a_end) { string token = Request.Headers[HeaderNames.Authorization]!; - int? databaseId = m_tokenService.GetDatabaseIdFromToken(token); - string connectionString = m_connection.GetClientConnectionString(databaseId!.Value); - bool? comparisonMode = m_tokenService.GetComparison(token); - string? currentBranch = m_tokenService.GetBaseBranch(token); - - //Creates a new db context - BiskAcdbContext newContext = (BiskAcdbContext)m_connection.PrepareDBContext(connectionString); - AnalyticalService analysis = new(newContext, comparisonMode!.Value, currentBranch!); - return analysis.GetSalesTransaction(a_start, a_end); + m_analyticService.SetContraints(token); + return m_analyticService.GetSalesTransaction(a_start, a_end); } /// /// Endpoint to return analysis on in-debt customers @@ -80,17 +58,65 @@ namespace Biskilog_Accounting.Server.Controllers public IEnumerable GetInDebtCustomers() { string token = Request.Headers[HeaderNames.Authorization]!; - int? databaseId = m_tokenService.GetDatabaseIdFromToken(token); - string connectionString = m_connection.GetClientConnectionString(databaseId!.Value); - bool? comparisonMode = m_tokenService.GetComparison(token); - string? currentBranch = m_tokenService.GetBaseBranch(token); + + m_analyticService.SetContraints(token); + return m_analyticService.GetInDebtCustomers(); + } + /// + /// Endpoint to return analysis on product price changes + /// + /// + /// + [Authorize] + [HttpGet, Route("pricechanges/{a_start}/{a_end}")] + public IEnumerable GetPriceChanges(DateTime a_start, DateTime a_end) + { + string token = Request.Headers[HeaderNames.Authorization]!; + + m_analyticService.SetContraints(token); + return m_analyticService.GetPriceChanges(a_start, a_end); + } + /// + /// Endpoint to return analysis on sales by employees + /// + /// + /// + [Authorize] + [HttpGet, Route("employeesales/{a_start}/{a_end}")] + public Dictionary> GetEmployeeSales(DateTime a_start, DateTime a_end) + { + string token = Request.Headers[HeaderNames.Authorization]!; - //Creates a new db context - BiskAcdbContext newContext = (BiskAcdbContext)m_connection.PrepareDBContext(connectionString); + m_analyticService.SetContraints(token); + return m_analyticService.GetEmployeeSales(a_start, a_end); + } + /// + /// Endpoint to return analysis on product items low on stock + /// + /// + /// + [Authorize] + [HttpGet, Route("lowonstock")] + public IEnumerable GetLowOnStockItems() + { + string token = Request.Headers[HeaderNames.Authorization]!; - AnalyticalService analysis = new(newContext, comparisonMode!.Value, currentBranch!); - return analysis.GetInDebtCustomers(); + m_analyticService.SetContraints(token); + return m_analyticService.GetOutOfStockItems(); } + /// + /// Endpoint to return analysis on the most purchased product item + /// + /// + /// + [Authorize] + [HttpGet, Route("mostpurchaseditem/{a_start}/{a_end}")] + public IEnumerable GetMostPurchased(DateTime a_start, DateTime a_end) + { + string token = Request.Headers[HeaderNames.Authorization]!; + m_analyticService.SetContraints(token); + return m_analyticService.GetMostPurchasedItem(a_start, a_end); + } } } diff --git a/Server/Services/AnalyticalService.cs b/Server/Services/AnalyticalService.cs index 6c2fc08..84918d8 100644 --- a/Server/Services/AnalyticalService.cs +++ b/Server/Services/AnalyticalService.cs @@ -1,8 +1,11 @@ -using Biskilog_Accounting.Server.POSModels; +using Azure.Core; +using Biskilog_Accounting.Server.POSModels; +using Biskilog_Accounting.ServiceRepo; using Biskilog_Accounting.Shared.CustomModels; using Biskilog_Accounting.Shared.Interfaces; using Biskilog_Accounting.Shared.POSModels; using Microsoft.EntityFrameworkCore; +using Microsoft.Net.Http.Headers; using System.Data.Entity; using System.Runtime.CompilerServices; using static Microsoft.EntityFrameworkCore.DbLoggerCategory; @@ -15,13 +18,13 @@ namespace Biskilog_Accounting.Server.Services public class AnalyticalService : IAnalytics { private readonly BiskAcdbContext m_context; + private readonly ITokenService m_tokenService; private bool m_comparisonMode; private string m_activeBranch; - public AnalyticalService(BiskAcdbContext a_context, bool a_comparisonMode, string a_activeBranchId) + public AnalyticalService(BiskAcdbContext a_context, ITokenService a_tokenService) { m_context = a_context; - m_comparisonMode = a_comparisonMode; - m_activeBranch = a_activeBranchId; + m_tokenService = a_tokenService; } public IEnumerable GetCancelledSales(DateTime a_start, DateTime a_end) { @@ -61,56 +64,143 @@ namespace Biskilog_Accounting.Server.Services } } - public IEnumerable>> GetEmployeeSales(DateTime a_start, DateTime a_end) + public Dictionary> GetEmployeeSales(DateTime a_start, DateTime a_end) { - throw new NotImplementedException(); + Dictionary> sales = new Dictionary>(); + if (m_comparisonMode) + { + var employeeSales = m_context.Tblcarts.Where(c => c.Date >= a_start && c.Date <= a_end).Select(e => e.Cashier).Distinct(); + + foreach (string 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); + + } + } + else + { + var employeeSales = m_context.Tblcarts.Where(c => c.Date >= a_start && c.Date <= a_end && c.BranchId == m_activeBranch).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 GetInDebtCustomers() { if (m_comparisonMode) { - return from c in m_context.Tblcustomers - join a in m_context.Customeraccounts on c.CustomerId equals a.CustomerId into CustomerAccounts - from ac in CustomerAccounts.OrderByDescending(ac => ac.Date).Take(1).DefaultIfEmpty() - where ac.Balance < 0 - orderby ac.Date descending - select new InDebtCustomers - { - Customer = c, - Debt = ac != null ? ac.Balance : 0 - }; - + var listDebts = m_context.Customeraccounts.Where(t => t.Balance < 0).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, + }; + } } else { - return null; + var listDebts = m_context.Customeraccounts.Where(t => t.Balance < 0 && t.BranchId == m_activeBranch).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 GetMostPurchasedItem(DateTime a_start, DateTime a_end) + public IEnumerable GetMostPurchasedItem(DateTime a_start, DateTime a_end) { - throw new NotImplementedException(); + 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 + 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 GetOutOfStockItems() { - //if (m_comparisonMode) - //{ - // return from r in m_context.Restocklevels - // join i in m_context.Tblinventories on r.ProductId equals i.Pcode - // join p in m_context.Tblproducts on i.Pcode equals p.Pcode - // where i.Quantity == r.WarnLevel - // select new ProductItem - // { - // Product = p, - // Stock = i, - // Unitofmeasure = - // } - //} - //else { - //} - return null; + if (m_comparisonMode) + { + 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" && + ((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! + }); + } + else + { + 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" && item.BranchId == m_activeBranch && + ((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! + }); + } + //return null; } public IEnumerable GetPriceChanges(DateTime a_start, DateTime a_end) @@ -119,7 +209,7 @@ namespace Biskilog_Accounting.Server.Services { 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 + where change.ChangeDate >= a_start && change.ChangeDate <= a_end select new ProductPriceChange { BranchId = change.BranchId, @@ -135,7 +225,7 @@ namespace Biskilog_Accounting.Server.Services { 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 && change.BranchId == m_activeBranch + where change.ChangeDate >= a_start && change.ChangeDate <= a_end && change.BranchId == m_activeBranch select new ProductPriceChange { BranchId = change.BranchId, @@ -161,5 +251,11 @@ namespace Biskilog_Accounting.Server.Services return m_context.Tblcarts.Where(t => t.Date >= a_start && t.Date <= a_end && t.BranchId == m_activeBranch); } } + + public void SetContraints(string a_token) + { + m_comparisonMode = m_tokenService.GetComparison(a_token)!.Value; + m_activeBranch = m_tokenService.GetBaseBranch(a_token)!; + } } } diff --git a/Shared/CustomModels/MostPurchaseItem.cs b/Shared/CustomModels/MostPurchaseItem.cs new file mode 100644 index 0000000..bbe8b16 --- /dev/null +++ b/Shared/CustomModels/MostPurchaseItem.cs @@ -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 + { + /// + /// Specifies the id of the product + /// + public string ProductId { get; set; } + /// + /// Specifies the name of the product + /// + public string ProductName { get; set; } + /// + /// The total revenue generated from the sale + /// + public decimal? Revenue { get; set; } + /// + /// This is the number of times the item has been sold + /// + public int? NbrTimesSold { get; set; } + + } +} diff --git a/Shared/CustomModels/ProductItem.cs b/Shared/CustomModels/ProductItem.cs index 6db6f2b..26dc93a 100644 --- a/Shared/CustomModels/ProductItem.cs +++ b/Shared/CustomModels/ProductItem.cs @@ -11,6 +11,6 @@ namespace Biskilog_Accounting.Shared.CustomModels { public Tblproduct? Product { get; set; } public Tblinventory? Stock { get; set; } - public List Unitofmeasure { get; set; } = new List(); + public string BaseUnit { get;set; } = string.Empty; } } diff --git a/Shared/CustomModels/SaleItem.cs b/Shared/CustomModels/SaleItem.cs new file mode 100644 index 0000000..82ad876 --- /dev/null +++ b/Shared/CustomModels/SaleItem.cs @@ -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"!; + } +} diff --git a/Shared/Interfaces/IAnalytics.cs b/Shared/Interfaces/IAnalytics.cs index b22b92f..c1f0102 100644 --- a/Shared/Interfaces/IAnalytics.cs +++ b/Shared/Interfaces/IAnalytics.cs @@ -27,7 +27,7 @@ namespace Biskilog_Accounting.Shared.Interfaces /// /// /// - IEnumerable GetMostPurchasedItem(DateTime a_start, DateTime a_end); + IEnumerable GetMostPurchasedItem(DateTime a_start, DateTime a_end); /// /// Fetches a collection of cancelled transaction within a specified date range /// @@ -41,7 +41,7 @@ namespace Biskilog_Accounting.Shared.Interfaces /// /// /// A dictionary of transactions made by employees with employee name as key - IEnumerable>> GetEmployeeSales(DateTime a_start, DateTime a_end); + Dictionary> GetEmployeeSales(DateTime a_start, DateTime a_end); /// /// Fetches a collection of product price changes with a specified date range /// @@ -49,5 +49,6 @@ namespace Biskilog_Accounting.Shared.Interfaces /// /// IEnumerable GetPriceChanges(DateTime a_start, DateTime a_end); + void SetContraints(string a_token); } }