using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using gehGassi.Core.Interfaces; using gehGassi.Domain.Advertisements; using gehGassi.Domain.Common; using gehGassi.Domain.Customers; using gehGassi.Domain.Dogs; using gehGassi.Domain.Shop; using Microsoft.EntityFrameworkCore; namespace gehGassi.Core.Services { /// /// Service der die Verwaltung von Bestellungen realisiert /// public class OrderService : ServiceBase, IOrderService { private readonly IRepository _itemRepository; private readonly IRepository _customerRepository; private readonly IRepository _appUserRepository; private readonly IRepository _productRepository; private readonly IRepository _productNamesRepository; private readonly IRepository _listingRepository; private readonly IRepository _bannerRepository; private readonly IRepository _advertisementRepository; private readonly IRepository _pinRepository; private readonly IRepository _physicalRepository; /// /// Erstellt eine Instanz /// /// Instanz eines IUnitOfWork public OrderService(IUnitOfWork unitOfWork) : base(unitOfWork) { _itemRepository = unitOfWork.GetRepository(); _customerRepository = unitOfWork.GetRepository(); _appUserRepository = unitOfWork.GetRepository(); _productRepository = unitOfWork.GetRepository(); _productNamesRepository = unitOfWork.GetRepository(); _listingRepository = unitOfWork.GetRepository(); _bannerRepository = unitOfWork.GetRepository(); _advertisementRepository = unitOfWork.GetRepository(); _pinRepository = unitOfWork.GetRepository(); _physicalRepository = unitOfWork.GetRepository(); } /// /// Gibt eine gefilterte Liste von Bestellungen zurück /// /// Filterbegriff der in bestimmten Feldern gesucht wird /// Gelöschte Daten inkludieren /// List betroffener Bestellungen public IQueryable Filter(string filter, bool includeDeleted) { if (includeDeleted) return Repository.Query(c => c.CustomerName.Contains(filter) || c.AppUserName.Contains(filter)); else return Repository.Query(c => c.Deleted == false && (c.CustomerName.Contains(filter) || c.AppUserName.Contains(filter))); } /// /// Gibt eine gefilterte Liste von Bestellungen für einen Kunden zurück /// /// Filterbegriff der in bestimmten Feldern gesucht wird /// Id des Kunden /// Gelöschte Daten inkludieren /// List betroffener Bestellungen public IQueryable FilterByCustomer(string filter, long customerId, bool includeDeleted) { if (includeDeleted) return Repository.Query(c => c.CustomerId == customerId && (c.CustomerName.Contains(filter))); else return Repository.Query(c => c.CustomerId == customerId && c.Deleted == false && (c.CustomerName.Contains(filter))); } /// /// Gibt eine gefilterte Liste von Bestellungen für einen App-User zurück /// /// Filterbegriff der in bestimmten Feldern gesucht wird /// Id des Hundebesitzers /// Gelöschte Daten inkludieren /// List betroffener Bestellungen public IQueryable FilterByAppUser(string filter, string appUserId, bool includeDeleted) { if (includeDeleted) return Repository.Query(c => c.AppUserId == appUserId && (c.AppUserName.Contains(filter) || c.AppUserCompany.Contains(filter))); else return Repository.Query(c => c.AppUserId == appUserId && c.Deleted == false && (c.AppUserName.Contains(filter) || c.AppUserCompany.Contains(filter))); } /// /// Erstellen einer Bestellung /// /// Bestellung public Order Create() { var item = new Order { UniqueId = Guid.NewGuid(), Total = 0, TotalGross = 0, Shipment = 0, ShipmentGross = 0, Created = DateTime.UtcNow, LastUpdate = DateTime.UtcNow }; return item; } /// /// Erstellen einer Bestellung /// /// Warenkorb /// Artikel im Warenkorb /// Zahlungsart /// Status der Zahlung /// Art der Zustellung /// Versandstatus /// Lieferadresse /// Rechnungsadresse /// Lieferadresse ist auch Rechnungsadresse /// Gewünschte Sprache /// Fallback Sprache /// Benutzer der die Bestellung anlegt /// Bestellung public async Task CreateAsync(Cart cart, List cartItems, PaymentType paymentType, PaymentStatus paymentStatus, ShipmentType shipmentType, ShipmentStatus shipmentStatus, OrderAddress deliveryAddress, OrderAddress billingAddress, bool deliveryAddressIsBilling, string language, string fallback, string userName) { var order = Create(); order.OrderSource = cart.OrderSource; order.Number = await GetNextNumberAsync(); order.Counter = await GetNextCounterAsync(); order.Subtotal = cart.Total; order.SubtotalGross = cart.TotalGross; order.Shipment = cart.Shipment; order.ShipmentGross = cart.ShipmentGross; order.Total = order.Subtotal + order.Shipment; order.TotalGross = order.SubtotalGross + order.ShipmentGross; order.OrderStatus = OrderStatus.Pending; order.PaymentType = paymentType; order.PaymentStatus = paymentStatus; order.ShipmentType = shipmentType; order.ShipmentStatus = shipmentStatus; order.DeliveryAddress = deliveryAddress; order.BillingAddress = billingAddress; order.Comment = string.Empty; order.TaxRatesJson = cart.TaxRatesJson; order.DeliveryAddressIsBilling = deliveryAddressIsBilling; order.Created = DateTime.UtcNow; order.LastUpdate = DateTime.UtcNow; order.AcceptTermsAndConditionsDate = DateTime.UtcNow; order.AcceptGdprDate = DateTime.UtcNow; order.CustomerId = null; order.CustomerName = string.Empty; order.AppUserId = string.Empty; order.AppUserName = string.Empty; order.AppUserCompany = string.Empty; switch (order.OrderSource) { case OrderSource.Customer: var customer = await _customerRepository.GetAsync(cart.CustomerId); order.CustomerId = customer.Id; order.CustomerName = customer.Name; break; case OrderSource.AppUserDogOwner: case OrderSource.AppUserDogWalker: case OrderSource.AppUserBoth: var dogOwner = await _appUserRepository.GetAsync(cart.AppUserId); order.AppUserId = dogOwner.Id; order.AppUserName = $"{dogOwner.FirstName} {dogOwner.LastName}"; order.AppUserCompany = string.Empty; break; default: throw new ArgumentOutOfRangeException(); } Repository.Add(order); await CommitAsync(userName); //Items foreach (var cartItem in cartItems) { var product = await _productRepository.FirstOrDefaultAsync(c => c.Id == cartItem.ProductId); var orderItem = CreateItem(); orderItem.OrderId = order.Id; orderItem.ProductId = cartItem.ProductId; orderItem.ProductCategoryId = product.ProductCategoryId; orderItem.Name = product.Name; orderItem.NameDefault = product.Get("Name", fallback); orderItem.Sku = product.Sku; orderItem.ProductType = cartItem.ProductType; orderItem.ItemId = cartItem.ItemId; orderItem.TaxRateId = product.TaxRateId; orderItem.IsTaxExempt = product.IsTaxExempt; orderItem.IsShipEnabled = product.IsShipEnabled; orderItem.IsFreeShipping = product.IsFreeShipping; orderItem.AdditionalShippingCharge = product.AdditionalShippingCharge; orderItem.Quantity = cartItem.Quantity; orderItem.Price = cartItem.Price; orderItem.PriceGross = cartItem.PriceGross; orderItem.Price2 = cartItem.Price2; orderItem.Price2Gross = cartItem.Price2Gross; orderItem.Total = cartItem.Total; orderItem.TotalGross = cartItem.TotalGross; orderItem.Weight = product.Weight; orderItem.Length = product.Length; orderItem.Width = product.Width; orderItem.Height = product.Height; orderItem.Created = DateTimeOffset.UtcNow; orderItem.LastUpdate = DateTimeOffset.UtcNow; _itemRepository.Add(orderItem); } await CommitAsync(userName); await SetOrderAndPaymentAsync(paymentType, paymentStatus, null, userName, order); return order; } /// /// Aktualisieren einer Bestellung /// /// Id der Bestellung die aktualisiert werden soll /// Warenkorb /// Artikel im Warenkorb /// Zahlungsart /// Status der Zahlung /// Art der Zustellung /// Versandstatus /// Lieferadresse /// Rechnungsadresse /// Lieferadresse ist auch Rechnungsadresse /// Gewünschte Sprache /// Fallback Sprache /// Benutzer der die Bestellung anlegt /// Bestellung public async Task UpdateAsync(long orderId, Cart cart, List cartItems, PaymentType paymentType, PaymentStatus paymentStatus, ShipmentType shipmentType, ShipmentStatus shipmentStatus, OrderAddress deliveryAddress, OrderAddress billingAddress, bool deliveryAddressIsBilling, string language, string fallback, string userName) { var order = await Repository.GetAsync(orderId); order.Subtotal = cart.Total; order.SubtotalGross = cart.TotalGross; order.Shipment = cart.Shipment; order.ShipmentGross = cart.ShipmentGross; order.Total = order.Subtotal + order.Shipment; order.TotalGross = order.SubtotalGross + order.ShipmentGross; order.OrderStatus = OrderStatus.Pending; order.PaymentType = paymentType; order.PaymentStatus = paymentStatus; order.ShipmentType = shipmentType; order.ShipmentStatus = shipmentStatus; order.BillingAddress.Title = billingAddress.Title; order.BillingAddress.FirstName = billingAddress.FirstName; order.BillingAddress.LastName = billingAddress.LastName; order.BillingAddress.Company = billingAddress.Company; order.BillingAddress.Vat = billingAddress.Vat; order.BillingAddress.AddressLine1 = billingAddress.AddressLine1; order.BillingAddress.AddressLine2 = billingAddress.AddressLine2; order.BillingAddress.Zip = billingAddress.Zip; order.BillingAddress.City = billingAddress.City; order.BillingAddress.State = billingAddress.State; order.BillingAddress.CountryCode = billingAddress.CountryCode; order.DeliveryAddress.Title = deliveryAddress.Title; order.DeliveryAddress.FirstName = deliveryAddress.FirstName; order.DeliveryAddress.LastName = deliveryAddress.LastName; order.DeliveryAddress.Company = deliveryAddress.Company; order.DeliveryAddress.Vat = deliveryAddress.Vat; order.DeliveryAddress.AddressLine1 = deliveryAddress.AddressLine1; order.DeliveryAddress.AddressLine2 = deliveryAddress.AddressLine2; order.DeliveryAddress.Zip = deliveryAddress.Zip; order.DeliveryAddress.City = deliveryAddress.City; order.DeliveryAddress.State = deliveryAddress.State; order.DeliveryAddress.CountryCode = deliveryAddress.CountryCode; order.Comment = string.Empty; order.TaxRatesJson = cart.TaxRatesJson; order.DeliveryAddressIsBilling = deliveryAddressIsBilling; order.LastUpdate = DateTime.UtcNow; order.AcceptTermsAndConditionsDate = DateTime.UtcNow; order.AcceptGdprDate = DateTime.UtcNow; await CommitAsync(userName); //Zurücksetzen der Relationen weil neue OrderItems angelegt werden await ResetOrderAsync(userName, order); //Items var oldItems = await _itemRepository.FindAsync(c => c.OrderId == orderId); foreach (var orderItem in oldItems) { _itemRepository.Remove(orderItem); } await CommitAsync(userName); //Items foreach (var cartItem in cartItems) { var product = await _productRepository.FirstOrDefaultAsync(c => c.Id == cartItem.ProductId); var orderItem = CreateItem(); orderItem.OrderId = order.Id; orderItem.ProductId = cartItem.ProductId; orderItem.ProductCategoryId = product.ProductCategoryId; orderItem.Name = product.Name; orderItem.NameDefault = product.Get("Name", fallback); orderItem.Sku = product.Sku; orderItem.ProductType = cartItem.ProductType; orderItem.ItemId = cartItem.ItemId; orderItem.TaxRateId = product.TaxRateId; orderItem.IsTaxExempt = product.IsTaxExempt; orderItem.IsShipEnabled = product.IsShipEnabled; orderItem.IsFreeShipping = product.IsFreeShipping; orderItem.AdditionalShippingCharge = product.AdditionalShippingCharge; orderItem.Quantity = cartItem.Quantity; orderItem.Price = cartItem.Price; orderItem.PriceGross = cartItem.PriceGross; orderItem.Price2 = cartItem.Price2; orderItem.Price2Gross = cartItem.Price2Gross; orderItem.Total = cartItem.Total; orderItem.TotalGross = cartItem.TotalGross; orderItem.Weight = product.Weight; orderItem.Length = product.Length; orderItem.Width = product.Width; orderItem.Height = product.Height; orderItem.Created = DateTimeOffset.UtcNow; orderItem.LastUpdate = DateTimeOffset.UtcNow; _itemRepository.Add(orderItem); } await CommitAsync(userName); await SetOrderAndPaymentAsync(paymentType, paymentStatus, null, userName, order); return order; } /// /// Anlegen eines Artikels für eine Bestellung /// /// Bestellung Artikel public OrderItem CreateItem() { var item = new OrderItem { Quantity = 0, Total = 0, TotalGross = 0, Created = DateTime.UtcNow, LastUpdate = DateTime.UtcNow }; return item; } /// /// Gibt einen Artikel in einer Bestellung zurück /// /// Id der Bestellung /// Id des Artikels /// Artikel in der Bestellung oder null, wenn nicht gefunden public async Task GetItemAsync(long orderId, int productId) { return await _itemRepository.FirstOrDefaultAsync(c => c.OrderId == orderId && c.ProductId == productId).ConfigureAwait(false); } /// /// Gibt eine Liste von Artikel in der Bestellung zurück /// /// Id der Bestellung /// Liste der Artikel in der Bestellung public async Task> GetItemsAsync(long orderId) { return (await _itemRepository.FindAsync(c => c.OrderId == orderId).ConfigureAwait(false)).ToList(); } /// /// Gibt den nächsten freien Bestell-spezifischen Zähler zurück /// /// Nächste freie Nummer public async Task GetNextCounterAsync() { long number = 1; var last = await Repository.Query(c => c.Id > 0).OrderByDescending(c => c.Counter).FirstOrDefaultAsync(); if (last != null) number = last.Counter + 1; return number; } /// /// Gibt die nächste freie Nummer für eine Bestellung als Bestellnummer zurück /// /// EndKunden-Nummer public async Task GetNextNumberAsync() { long number = 1; var last = await Repository.Query(c => c.Id > 0).OrderByDescending(c => c.Id).FirstOrDefaultAsync(); if (last != null) number = last.Counter + 1; return $"{DateTime.UtcNow.Year}{DateTime.UtcNow.Month}-{number:000000}"; } /// /// Löschen einer Bestellung /// /// Id der Bestellung /// Softdelete anwenden - Daten werden auf gelöscht gesetzt /// Task public async Task DeleteAsync(long id, bool softDelete) { var order = await GetAsync(id); if (order != null) { if (softDelete) order.Deleted = true; else Repository.Remove(order); } } /// /// Löschen aller Bestellungen die einem Kunden zugeordnet sind /// /// Id des Kunden /// Softdelete anwenden - Daten werden auf gelöscht gesetzt /// Task public async Task DeleteByCustomerAsync(long customerId, bool softDelete) { var orders = await Repository.FindAsync(c => c.CustomerId == customerId && c.Deleted == false).ConfigureAwait(false); if (orders.Any()) { if (softDelete) { foreach (var order in orders) { order.Deleted = true; } } else Repository.RemoveRange(orders); } } /// /// Löschen aller Bestellungen die einem App-User zugeordnet sind /// /// Id des AppUsers /// Softdelete anwenden - Daten werden auf gelöscht gesetzt /// Task public async Task DeleteByAppUserAsync(string appUserId, bool softDelete) { var orders = await Repository.FindAsync(c => c.AppUserId == appUserId && c.Deleted == false).ConfigureAwait(false); if (orders.Any()) { if (softDelete) { foreach (var order in orders) { order.Deleted = true; } } else Repository.RemoveRange(orders); } } /// /// Gibt die Anzahl aller Bestellungen im System zurück /// /// Gelöschte Daten inkludieren /// Anzahl Bestellungen public async Task CountOrdersAsync(bool includeDeleted) { if (includeDeleted) return await Repository.CountAsync().ConfigureAwait(false); else return await Repository.CountAsync(c => c.Deleted == false).ConfigureAwait(false); } /// /// Gibt die Anzahl der Bestellungen in einem bestimmten Status zurück /// /// Gesuchter Status /// Gelöschte Daten inkludieren /// Anzahl Bestellungen public async Task CountOrdersAsync(OrderStatus status, bool includeDeleted) { if (includeDeleted) return await Repository.CountAsync(c => c.OrderStatus == status).ConfigureAwait(false); else return await Repository.CountAsync(c => c.OrderStatus == status && c.Deleted == false).ConfigureAwait(false); } /// /// Gibt die Anzahl der Bestellungen in einem bestimmten Status für Kunden zurück /// /// Id des Kunden /// Gesuchter Status /// Gelöschte Daten inkludieren /// Anzahl Bestellungen public async Task CountOrdersByCustomerAsync(long customerId, OrderStatus status, bool includeDeleted) { if (includeDeleted) return await Repository.CountAsync(c => c.CustomerId == customerId && c.OrderStatus == status).ConfigureAwait(false); else return await Repository.CountAsync(c => c.CustomerId == customerId && c.OrderStatus == status && c.Deleted == false).ConfigureAwait(false); } /// /// Gibt die Anzahl der Bestellungen in einem bestimmten Status für App-User zurück /// /// Id des App-Users /// Gesuchter Status /// Gelöschte Daten inkludieren /// Anzahl Bestellungen public async Task CountOrdersByAppUserAsync(string appUserId, OrderStatus status, bool includeDeleted) { if (includeDeleted) return await Repository.CountAsync(c => c.AppUserId == appUserId && c.OrderStatus == status).ConfigureAwait(false); else return await Repository.CountAsync(c => c.AppUserId == appUserId && c.OrderStatus == status && c.Deleted == false).ConfigureAwait(false); } /// /// Setzt die ItemId für einen Artikel einer Bestellung auf null, wenn der Artikel gelöscht wird /// /// Id des Artikels /// Task public async Task ResetProductForOrderItemAsync(int productId) { var orderItems = await _itemRepository.FindAsync(c => c.ProductId == productId); foreach (var orderItem in orderItems) { orderItem.ItemId = null; } } /// /// Gibt eine Bestellung anhand der PayPal-ID zurück /// /// PayPal Id der Bestellung /// Order oder null, wenn nicht gefunden public async Task GetByPayPalIdAsync(string payPalId) { return await Repository.FirstOrDefaultAsync(c => c.PayPalOrderId == payPalId).ConfigureAwait(false); } /// /// Gibt eine Bestellung anhand der Unique-ID zurück /// /// Unique Id der Bestellung /// Order oder null, wenn nicht gefunden public async Task GetByUniqueIdAsync(Guid uniqueId) { return await Repository.FirstOrDefaultAsync(c => c.UniqueId == uniqueId).ConfigureAwait(false); } /// /// Gibt eine Bestellung anhand Bestellnummer zurück /// /// Bestellnummer /// Order oder null, wenn nicht gefunden public async Task GetByOrderNumberAsync(string orderNumber) { return await Repository.FirstOrDefaultAsync(c => c.Number == orderNumber).ConfigureAwait(false); } /// /// Setzt den "Versendet" Status für Bestellungen die via Klarna bezahlt wurden und welche keine physischen Produkte beinhalten! /// /// Task public async Task SetShipmentForKlarnaDigitalOnlyAsync() { var ordersPhysical = await _physicalRepository.FindAsync(c => c.Deleted == false && c.PaymentType == PaymentType.Klarna && c.ShipmentStatus == ShipmentStatus.NotYetShipped && c.OrderStatus != OrderStatus.Cancelled && c.PaymentStatus == PaymentStatus.Paid && c.KlarnaShipmentError == false && c.PhysicalCount == 0).ConfigureAwait(false); if (ordersPhysical.Any()) { foreach (var orderPhysical in ordersPhysical) { var order = await Repository.GetAsync(orderPhysical.Id); order.ShipmentStatus = ShipmentStatus.Shipped; } } } /// /// Gibt eine Liste aller Klarna-Bestellungen zurück, für welche an Klarna der "versendet" Status gesendet werden soll /// /// Liste Bestellungen public async Task> GetOrdersKlarnaToShipAsync() { var items = (await Repository.FindAsync(c => c.PaymentType == PaymentType.Klarna && c.ShipmentStatus == ShipmentStatus.Shipped && c.KlarnaShipmentSentDate == null && c.OrderStatus != OrderStatus.Cancelled && c.PaymentStatus == PaymentStatus.Paid && c.KlarnaShipmentError == false).ConfigureAwait(false)).ToList(); return items; } /// /// Gibt zurück ob eine Bestellung nur digitale Produkte beinhaltet /// /// Id der Bestellung /// true wenn nur digital, false sonst public async Task IsDigitaglOnlyAsync(long orderId) { var physicalOrder = await _physicalRepository.FirstOrDefaultAsync(c => c.Id == orderId && c.Deleted == false); if (physicalOrder != null) return physicalOrder.PhysicalCount == 0; return false; } #region Private /// /// Setzen der Relation zu einer Bestellung und des Zahlungsstatus für spezielle Entitäten in einer Bestellung. /// Das sind Listungen, Banner, Werbungen und Pins /// /// Zahlungsart /// Zahlungsstatus /// Zahlunsdatum /// Benutzer /// Bestellung /// private async Task SetOrderAndPaymentAsync(PaymentType paymentType, PaymentStatus paymentStatus, DateTimeOffset? payedDate, string userName, Order order) { var orderItems = await GetItemsAsync(order.Id); foreach (var orderItem in orderItems) { if (orderItem.ProductType == ProductType.Listing) { var listing = await _listingRepository.GetAsync(orderItem.ItemId); if (listing != null) { listing.OrderId = order.Id; listing.OrderItemId = orderItem.Id; listing.PaymentType = paymentType; listing.PaymentStatus = paymentStatus; listing.PayedDate = payedDate; } } else if (orderItem.ProductType == ProductType.Advertisement) { var advertisement = await _advertisementRepository.GetAsync(orderItem.ItemId); if (advertisement != null) { advertisement.OrderId = order.Id; advertisement.OrderItemId = orderItem.Id; advertisement.PaymentType = paymentType; advertisement.PaymentStatus = paymentStatus; advertisement.PayedDate = payedDate; } } else if (orderItem.ProductType == ProductType.Banner) { var banner = await _bannerRepository.GetAsync(orderItem.ItemId); if (banner != null) { banner.OrderId = order.Id; banner.OrderItemId = orderItem.Id; banner.PaymentType = paymentType; banner.PaymentStatus = paymentStatus; banner.PayedDate = payedDate; } } else if (orderItem.ProductType == ProductType.Pin) { var pin = await _pinRepository.GetAsync(orderItem.ItemId); if (pin != null) { pin.OrderId = order.Id; pin.OrderItemId = orderItem.Id; pin.PaymentType = paymentType; pin.PaymentStatus = paymentStatus; pin.PayedDate = payedDate; } } } await CommitAsync(userName); } /// /// Zurücksetzen der Relation zwischen einer Bestellung und speziellen Entitäten. /// Das sind Listungen, Banner, Werbungen und Pins /// /// Benutzer /// Bestellung /// private async Task ResetOrderAsync(string userName, Order order) { var orderItems = await GetItemsAsync(order.Id); foreach (var orderItem in orderItems) { if (orderItem.ProductType == ProductType.Listing) { var listing = await _listingRepository.GetAsync(orderItem.ItemId); if (listing != null) { listing.OrderId = null; listing.OrderItemId = null; } } else if (orderItem.ProductType == ProductType.Advertisement) { var advertisement = await _advertisementRepository.GetAsync(orderItem.ItemId); if (advertisement != null) { advertisement.OrderId = null; advertisement.OrderItemId = null; } } else if (orderItem.ProductType == ProductType.Banner) { var banner = await _bannerRepository.GetAsync(orderItem.ItemId); if (banner != null) { banner.OrderId = null; banner.OrderItemId = null; } } else if (orderItem.ProductType == ProductType.Pin) { var pin = await _pinRepository.GetAsync(orderItem.ItemId); if (pin != null) { pin.OrderId = null; pin.OrderItemId = null; } } } await CommitAsync(userName); } #endregion } }