Browse Source

Analysis backend initial feature

pull/4/head
Benjamin Arhen 2 years ago
parent
commit
ee14a6cb16
  1. 5
      Server/BiskAcdbContext.cs
  2. 7
      Server/BiskilogContext.cs
  3. 96
      Server/Controllers/AnalyticsController.cs
  4. 3
      Server/Program.cs
  5. 165
      Server/Services/AnalyticalService.cs
  6. 3
      Server/Services/AuthenticationService.cs
  7. 2
      Server/Services/ConnectionService.cs
  8. 1
      Shared/ClientContractModels/Clientbusiness.cs
  9. 16
      Shared/CustomModels/CancelledSales.cs
  10. 15
      Shared/CustomModels/InDebtCustomers.cs
  11. 16
      Shared/CustomModels/ProductItem.cs
  12. 23
      Shared/CustomModels/ProductPriceChange.cs
  13. 53
      Shared/Interfaces/IAnalytics.cs
  14. 4
      Shared/Interfaces/ITokenService.cs
  15. 32
      Shared/ServiceRepo/TokenService.cs

5
Server/BiskAcdbContext.cs

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using Biskilog_Accounting.Shared.POSModels;
using Biskilog_Accounting.Shared.POSModels;
using Microsoft.EntityFrameworkCore;
namespace Biskilog_Accounting.Server.POSModels;
@ -15,7 +13,6 @@ public partial class BiskAcdbContext : DbContext
: base(options)
{
}
public virtual DbSet<Creditpurchase> Creditpurchases { get; set; }
public virtual DbSet<Customeraccount> Customeraccounts { get; set; }

7
Server/BiskilogContext.cs

@ -90,6 +90,13 @@ public partial class BiskilogContext : DbContext
.HasDefaultValueSql("current_timestamp()")
.HasColumnType("datetime")
.HasColumnName("date_joined");
entity.Property(e => e.BusinessExternalId)
.HasMaxLength(50)
.HasDefaultValueSql("''")
.HasColumnName("businessExternalId")
.UseCollation("utf8mb4_general_ci")
.HasCharSet("utf8mb4");
});
modelBuilder.Entity<Clientinfo>(entity =>

96
Server/Controllers/AnalyticsController.cs

@ -0,0 +1,96 @@
using Biskilog_Accounting.Server.POSModels;
using Biskilog_Accounting.Server.Services;
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;
namespace Biskilog_Accounting.Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AnalyticsController : ControllerBase
{
private readonly IConnectionService m_connection;
private readonly ITokenService m_tokenService;
public AnalyticsController(ITokenService tokenService, IConnectionService connection)
{
m_tokenService = tokenService;
m_connection = connection;
}
/// <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)
{
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!);
var result = analysis.GetCancelledSales(a_start, a_end);
return result;
}
/// <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)
{
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);
}
/// <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()
{
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.GetInDebtCustomers();
}
}
}

3
Server/Program.cs

@ -9,6 +9,7 @@ using Biskilog_Accounting.Server;
using Biskilog_Accounting.ServiceRepo;
using Biskilog_Accounting.Shared.Interfaces;
using Biskilog_Accounting.Server.Services;
using Biskilog_Accounting.Server.POSModels;
var builder = WebApplication.CreateBuilder(args);
@ -21,8 +22,10 @@ builder.Services.AddEntityFrameworkMySql().AddDbContext<BiskilogContext>(options
{
options.UseMySql(builder.Configuration.GetConnectionString("Connection"), new MariaDbServerVersion(new Version()));
});
builder.Services.AddScoped<BiskAcdbContext>();
builder.Services.AddScoped<IAuthService, AuthenticationService>();
builder.Services.AddScoped<ITokenService, TokenService>();
builder.Services.AddScoped<IConnectionService, ConnectionService>();
builder.Services.AddCors(options =>
{

165
Server/Services/AnalyticalService.cs

@ -0,0 +1,165 @@
using Biskilog_Accounting.Server.POSModels;
using Biskilog_Accounting.Shared.CustomModels;
using Biskilog_Accounting.Shared.Interfaces;
using Biskilog_Accounting.Shared.POSModels;
using Microsoft.EntityFrameworkCore;
using System.Data.Entity;
using System.Runtime.CompilerServices;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
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 bool m_comparisonMode;
private string m_activeBranch;
public AnalyticalService(BiskAcdbContext a_context, bool a_comparisonMode, string a_activeBranchId)
{
m_context = a_context;
m_comparisonMode = a_comparisonMode;
m_activeBranch = a_activeBranchId;
}
public IEnumerable<CancelledSales> GetCancelledSales(DateTime a_start, DateTime a_end)
{
//If in comparison mode, the list is fetched from all branchid of the business
if (m_comparisonMode)
{
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
select new CancelledSales
{
CancelledTransaction = cSale,
Value = activeSale.Sum(s => s.Total),
Customer = !String.IsNullOrEmpty(cc.CustomerId) ? $"{cc.Firstname} {cc.Surname}" : "Walk-IN Purchase"
};
}
else
{
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 && cSale.BranchId == m_activeBranch
select new CancelledSales
{
CancelledTransaction = cSale,
Value = (from a in activeSale where a.BranchId == m_activeBranch select a.Total).Sum(),
Customer = !String.IsNullOrEmpty(cc.CustomerId) ? $"{cc.Firstname} {cc.Surname}" : "Walk-IN Purchase"
};
}
}
public IEnumerable<Dictionary<string, List<Tblcart>>> GetEmployeeSales(DateTime a_start, DateTime a_end)
{
throw new NotImplementedException();
}
public IEnumerable<InDebtCustomers> 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
};
}
else
{
return null;
}
}
public IEnumerable<ProductItem> GetMostPurchasedItem(DateTime a_start, DateTime a_end)
{
throw new NotImplementedException();
}
public IEnumerable<ProductItem> 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;
}
public IEnumerable<ProductPriceChange> GetPriceChanges(DateTime a_start, DateTime a_end)
{
if (m_comparisonMode)
{
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
select new ProductPriceChange
{
BranchId = change.BranchId,
ChangeDate = change.ChangeDate,
Pcode = change.Pcode,
CountId = change.CountId,
CurrentPrice = change.CurrentPrice,
PreviousPrice = change.PreviousPrice,
ProductName = p.ProductName
};
}
else
{
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
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)
{
//If in comparison mode, the list is fetched from all branchid of the business
if (m_comparisonMode)
{
return m_context.Tblcarts.Where(t => t.Date >= a_start && t.Date <= a_end);
}
else
{
return m_context.Tblcarts.Where(t => t.Date >= a_start && t.Date <= a_end && t.BranchId == m_activeBranch);
}
}
}
}

3
Server/Services/AuthenticationService.cs

@ -55,11 +55,12 @@ namespace Biskilog_Accounting.Server.Services
List<int> businessIds = GetSiteaccesspermission(user.ClientId, user.UserId).Select(t => t.BusinessId).ToList();
Contract? contract = GetContract(user.ClientId, businessIds);
List<Clientbusiness> businesses = GetClientbusiness(user.ClientId);
if (contract == null)
return AuthEnums.Invalid.ToString();
return m_tokenService.GenerateToken(user, contract, databasemap);
return m_tokenService.GenerateToken(user, contract, databasemap, businesses[0], false);
}
/// <summary>

2
Server/Services/ConnectionService.cs

@ -46,7 +46,7 @@ namespace Biskilog_Accounting.Server.Services
DbContextOptionsBuilder<BiskAcdbContext> acdbContext = new DbContextOptionsBuilder<BiskAcdbContext>();
acdbContext.UseMySql(a_connectionString, new MariaDbServerVersion(new Version()));
return acdbContext;
return new BiskAcdbContext(acdbContext.Options);
}
}
}

1
Shared/ClientContractModels/Clientbusiness.cs

@ -17,4 +17,5 @@ public partial class Clientbusiness
public string BiskilogVersion { get; set; } = null!;
public DateTime DateJoined { get; set; }
public string BusinessExternalId { get; set; } = string.Empty!;
}

16
Shared/CustomModels/CancelledSales.cs

@ -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; }
}
}

15
Shared/CustomModels/InDebtCustomers.cs

@ -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;
}
}

16
Shared/CustomModels/ProductItem.cs

@ -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 List<Unitofmeasure> Unitofmeasure { get; set; } = new List<Unitofmeasure>();
}
}

23
Shared/CustomModels/ProductPriceChange.cs

@ -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!;
}
}

53
Shared/Interfaces/IAnalytics.cs

@ -0,0 +1,53 @@
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<ProductItem> 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>
IEnumerable<Dictionary<string, List<Tblcart>>> 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);
}
}

4
Shared/Interfaces/ITokenService.cs

@ -6,10 +6,12 @@ namespace Biskilog_Accounting.Shared.Interfaces
public interface ITokenService
{
AuthEnums ValidateToken(string a_token);
string GenerateToken(Userauth a_user, Contract a_clientContract, Databasemap a_database);
string GenerateToken(Userauth a_user, Contract a_clientContract, Databasemap a_database, Clientbusiness a_business, bool a_comparison);
int? GetDatabaseIdFromToken(string a_token);
int? GetUserIdFromToken(string a_token);
string? GetUserNameFromToken(string a_token);
string? GetBaseBranch(string a_token);
bool? GetComparison(string a_token);
}
}

32
Shared/ServiceRepo/TokenService.cs

@ -43,7 +43,7 @@ namespace Biskilog_Accounting.ServiceRepo
/// Generates an access token based on the user
/// </summary>
/// <returns>A tokenized string</returns>
public string GenerateToken(Userauth a_user, Contract a_clientContract, Databasemap a_database)
public string GenerateToken(Userauth a_user, Contract a_clientContract, Databasemap a_database, Clientbusiness a_business, bool a_comparison)
{
try
{
@ -56,6 +56,8 @@ namespace Biskilog_Accounting.ServiceRepo
new Claim("UserId", a_user.UserId.ToString()),
new Claim("Username", a_user.Username.ToString()),
new Claim("DbId",a_database.DbNo.ToString()),
new Claim("ComparisonMode",a_comparison.ToString()),
new Claim("BranchId",a_business.BusinessExternalId.ToString()),
new Claim("ClientId", a_user.ClientId.ToString()),
};
@ -120,5 +122,33 @@ namespace Biskilog_Accounting.ServiceRepo
}
return null;
}
/// <summary>
///Deserializes the token string if valid to return the specified branchId in the token string
/// </summary>
/// <param name="a_token"></param>
/// <returns>Username</returns>
public string? GetBaseBranch(string a_token)
{
if (ValidateToken(a_token) == AuthEnums.Valid)
{
string token = a_token.Substring(6).Trim();
var handler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtToken = (JwtSecurityToken)handler.ReadToken(token);
return jwtToken.Claims.First(claim => claim.Type == "BranchId").Value;
}
return null;
}
public bool? GetComparison(string a_token)
{
if (ValidateToken(a_token) == AuthEnums.Valid)
{
string token = a_token.Substring(6).Trim();
var handler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtToken = (JwtSecurityToken)handler.ReadToken(token);
return bool.Parse(jwtToken.Claims.First(claim => claim.Type == "ComparisonMode").Value);
}
return null;
}
}
}

Loading…
Cancel
Save