Newer
Older
Warehouse / src / Infrastructure / ApplicationDependencies / Services / StockStatisticsService.cs
@Derek Comartin Derek Comartin on 22 Aug 2023 2 KB Init
using Microsoft.EntityFrameworkCore;
using MyWarehouse.Application.Dependencies.Services;
using MyWarehouse.Domain.Common.ValueObjects.Mass;
using MyWarehouse.Domain.Common.ValueObjects.Money;
using MyWarehouse.Infrastructure.Persistence.Context;

namespace MyWarehouse.Infrastructure.ApplicationDependencies.Services;

// It's rather questionable that this service is implemented here in the Infrastructure layer.
// One could argue that it should be in Application, but, on the other hand,
// it does depend on EF Core, and probably even specifically on SQL.
class StockStatisticsService : IStockStatisticsService
{
    private readonly ApplicationDbContext _dbContext;

    public StockStatisticsService(ApplicationDbContext dbContext)
        => _dbContext = dbContext;

    public async Task<(int ProductCount, int TotalStock)> GetProductStockCounts()
    {
        var res = await _dbContext.Products
            .GroupBy(x => 1)
            .Select(g => new {
                productCount = g.Count(),
                totalStock = g.Sum(p => p.NumberInStock)
            }).SingleAsync();

        return (res.productCount, res.totalStock);
    }

    public async Task<Mass> GetProductStockTotalMass(MassUnit unit)
    {
        var totalMassPerUnit = await _dbContext.Products
            .GroupBy(x => x.Mass.Unit, p => new
            {
                p.Mass,
                p.NumberInStock
            })
            .Select(g => new
            {
                MassUnit = g.Key,
                TotalMass = g.Sum(x => x.Mass.Value * x.NumberInStock)
            }).ToListAsync();

        var totalMass = totalMassPerUnit
            .Select(x => new Mass(x.TotalMass, x.MassUnit))
            .Sum(mass => mass.ConvertTo(unit).Value);

        return new Mass(totalMass, unit);
    }

    public async Task<Money> GetProductStockTotalValue()
    {
        var stockValuesPerCurrency = await _dbContext.Products
            .GroupBy(x => x.Price.Currency, p => new
            {
                UnitPrice = p.Price.Amount,
                Currency = p.Price.Currency,
                NumberInStock = p.NumberInStock
            })
            .Select(g => new
            {
                Currency = g.Key,
                TotalValue = g.Sum(x => x.UnitPrice * x.NumberInStock)
            }).ToListAsync();

        if (stockValuesPerCurrency.Count > 1)
            throw new InvalidOperationException(
                $"Operation cannot be completed, because not all product prices use the same currency. Distinct currencies detected: " +
                $"{string.Join(", ", stockValuesPerCurrency.Select(x => x.Currency.Code))}.");

        var stockValue = stockValuesPerCurrency.First();
        return new Money(stockValue.TotalValue, Currency.FromCode(stockValue.Currency.Code));
    }
}