make fields readonly
This commit is contained in:
parent
4c530da377
commit
99d47401c0
@ -4,43 +4,41 @@ using System;
|
|||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate
|
||||||
{
|
{
|
||||||
public class PaymentMethod
|
public class PaymentMethod
|
||||||
: Entity
|
: Entity
|
||||||
{
|
{
|
||||||
private string _alias;
|
private readonly string _alias;
|
||||||
private string _cardNumber;
|
private readonly string _cardNumber;
|
||||||
private string _securityNumber;
|
private readonly string _securityNumber;
|
||||||
private string _cardHolderName;
|
private readonly string _cardHolderName;
|
||||||
private DateTime _expiration;
|
private readonly DateTime _expiration;
|
||||||
|
|
||||||
private int _cardTypeId;
|
private readonly int _cardTypeId;
|
||||||
public CardType CardType { get; private set; }
|
public CardType CardType { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
protected PaymentMethod() { }
|
protected PaymentMethod() { }
|
||||||
|
|
||||||
public PaymentMethod(int cardTypeId, string alias, string cardNumber, string securityNumber, string cardHolderName, DateTime expiration)
|
public PaymentMethod(int cardTypeId, string alias, string cardNumber, string securityNumber, string cardHolderName, DateTime expiration)
|
||||||
{
|
{
|
||||||
|
|
||||||
_cardNumber = !string.IsNullOrWhiteSpace(cardNumber) ? cardNumber : throw new OrderingDomainException(nameof(cardNumber));
|
_cardNumber = !string.IsNullOrWhiteSpace(cardNumber) ? cardNumber : throw new OrderingDomainException(nameof(cardNumber));
|
||||||
_securityNumber = !string.IsNullOrWhiteSpace(securityNumber) ? securityNumber : throw new OrderingDomainException(nameof(securityNumber));
|
_securityNumber = !string.IsNullOrWhiteSpace(securityNumber) ? securityNumber : throw new OrderingDomainException(nameof(securityNumber));
|
||||||
_cardHolderName = !string.IsNullOrWhiteSpace(cardHolderName) ? cardHolderName : throw new OrderingDomainException(nameof(cardHolderName));
|
_cardHolderName = !string.IsNullOrWhiteSpace(cardHolderName) ? cardHolderName : throw new OrderingDomainException(nameof(cardHolderName));
|
||||||
|
|
||||||
if (expiration < DateTime.UtcNow)
|
if (expiration < DateTime.UtcNow)
|
||||||
{
|
{
|
||||||
throw new OrderingDomainException(nameof(expiration));
|
throw new OrderingDomainException(nameof(expiration));
|
||||||
}
|
}
|
||||||
|
|
||||||
_alias = alias;
|
_alias = alias;
|
||||||
_expiration = expiration;
|
_expiration = expiration;
|
||||||
_cardTypeId = cardTypeId;
|
_cardTypeId = cardTypeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsEqualTo(int cardTypeId, string cardNumber,DateTime expiration)
|
public bool IsEqualTo(int cardTypeId, string cardNumber, DateTime expiration)
|
||||||
{
|
=> _cardTypeId == cardTypeId
|
||||||
return _cardTypeId == cardTypeId
|
&& _cardNumber == cardNumber
|
||||||
&& _cardNumber == cardNumber
|
&& _expiration == expiration;
|
||||||
&& _expiration == expiration;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,194 +7,189 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
|
||||||
{
|
{
|
||||||
public class Order
|
public class Order
|
||||||
: Entity, IAggregateRoot
|
: Entity, IAggregateRoot
|
||||||
{
|
{
|
||||||
// DDD Patterns comment
|
// DDD Patterns comment
|
||||||
// Using private fields, allowed since EF Core 1.1, is a much better encapsulation
|
// Using private fields, allowed since EF Core 1.1, is a much better encapsulation
|
||||||
// aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)
|
// aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)
|
||||||
private DateTime _orderDate;
|
private readonly DateTime _orderDate;
|
||||||
|
|
||||||
// Address is a Value Object pattern example persisted as EF Core 2.0 owned entity
|
// Address is a Value Object pattern example persisted as EF Core 2.0 owned entity
|
||||||
public Address Address { get; private set; }
|
public Address Address { get; private set; }
|
||||||
|
|
||||||
public int? GetBuyerId => _buyerId;
|
public int? GetBuyerId => _buyerId;
|
||||||
private int? _buyerId;
|
private int? _buyerId;
|
||||||
|
|
||||||
public OrderStatus OrderStatus { get; private set; }
|
public OrderStatus OrderStatus { get; private set; }
|
||||||
private int _orderStatusId;
|
private int _orderStatusId;
|
||||||
|
|
||||||
private string _description;
|
private string _description;
|
||||||
|
|
||||||
|
|
||||||
// Draft orders have this set to true. Currently we don't check anywhere the draft status of an Order, but we could do it if needed
|
// Draft orders have this set to true. Currently we don't check anywhere the draft status of an Order, but we could do it if needed
|
||||||
private bool _isDraft;
|
private bool _isDraft;
|
||||||
|
|
||||||
// DDD Patterns comment
|
// DDD Patterns comment
|
||||||
// Using a private collection field, better for DDD Aggregate's encapsulation
|
// Using a private collection field, better for DDD Aggregate's encapsulation
|
||||||
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
|
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
|
||||||
// but only through the method OrderAggrergateRoot.AddOrderItem() which includes behaviour.
|
// but only through the method OrderAggrergateRoot.AddOrderItem() which includes behaviour.
|
||||||
private readonly List<OrderItem> _orderItems;
|
private readonly List<OrderItem> _orderItems;
|
||||||
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
|
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
|
||||||
|
|
||||||
private int? _paymentMethodId;
|
private int? _paymentMethodId;
|
||||||
|
|
||||||
public static Order NewDraft()
|
public static Order NewDraft()
|
||||||
{
|
{
|
||||||
var order = new Order();
|
var order = new Order
|
||||||
order._isDraft = true;
|
{
|
||||||
return order;
|
_isDraft = true
|
||||||
}
|
};
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
protected Order() {
|
protected Order()
|
||||||
_orderItems = new List<OrderItem>();
|
{
|
||||||
_isDraft = false;
|
_orderItems = new List<OrderItem>();
|
||||||
}
|
_isDraft = false;
|
||||||
|
}
|
||||||
|
|
||||||
public Order(string userId, string userName, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
|
public Order(string userId, string userName, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
|
||||||
string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null) : this()
|
string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null) : this()
|
||||||
{
|
{
|
||||||
_buyerId = buyerId;
|
_buyerId = buyerId;
|
||||||
_paymentMethodId = paymentMethodId;
|
_paymentMethodId = paymentMethodId;
|
||||||
_orderStatusId = OrderStatus.Submitted.Id;
|
_orderStatusId = OrderStatus.Submitted.Id;
|
||||||
_orderDate = DateTime.UtcNow;
|
_orderDate = DateTime.UtcNow;
|
||||||
Address = address;
|
Address = address;
|
||||||
|
|
||||||
// Add the OrderStarterDomainEvent to the domain events collection
|
// Add the OrderStarterDomainEvent to the domain events collection
|
||||||
// to be raised/dispatched when comitting changes into the Database [ After DbContext.SaveChanges() ]
|
// to be raised/dispatched when comitting changes into the Database [ After DbContext.SaveChanges() ]
|
||||||
AddOrderStartedDomainEvent(userId, userName, cardTypeId, cardNumber,
|
AddOrderStartedDomainEvent(userId, userName, cardTypeId, cardNumber,
|
||||||
cardSecurityNumber, cardHolderName, cardExpiration);
|
cardSecurityNumber, cardHolderName, cardExpiration);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DDD Patterns comment
|
// DDD Patterns comment
|
||||||
// This Order AggregateRoot's method "AddOrderitem()" should be the only way to add Items to the Order,
|
// This Order AggregateRoot's method "AddOrderitem()" should be the only way to add Items to the Order,
|
||||||
// so any behavior (discounts, etc.) and validations are controlled by the AggregateRoot
|
// so any behavior (discounts, etc.) and validations are controlled by the AggregateRoot
|
||||||
// in order to maintain consistency between the whole Aggregate.
|
// in order to maintain consistency between the whole Aggregate.
|
||||||
public void AddOrderItem(int productId, string productName, decimal unitPrice, decimal discount, string pictureUrl, int units = 1)
|
public void AddOrderItem(int productId, string productName, decimal unitPrice, decimal discount, string pictureUrl, int units = 1)
|
||||||
{
|
{
|
||||||
var existingOrderForProduct = _orderItems.Where(o => o.ProductId == productId)
|
var existingOrderForProduct = _orderItems.Where(o => o.ProductId == productId)
|
||||||
.SingleOrDefault();
|
.SingleOrDefault();
|
||||||
|
|
||||||
if (existingOrderForProduct != null)
|
if (existingOrderForProduct != null)
|
||||||
{
|
{
|
||||||
//if previous line exist modify it with higher discount and units..
|
//if previous line exist modify it with higher discount and units..
|
||||||
|
|
||||||
if (discount > existingOrderForProduct.GetCurrentDiscount())
|
if (discount > existingOrderForProduct.GetCurrentDiscount())
|
||||||
{
|
{
|
||||||
existingOrderForProduct.SetNewDiscount(discount);
|
existingOrderForProduct.SetNewDiscount(discount);
|
||||||
}
|
}
|
||||||
|
|
||||||
existingOrderForProduct.AddUnits(units);
|
existingOrderForProduct.AddUnits(units);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//add validated new order item
|
//add validated new order item
|
||||||
|
|
||||||
var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units);
|
var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units);
|
||||||
_orderItems.Add(orderItem);
|
_orderItems.Add(orderItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPaymentId(int id)
|
public void SetPaymentId(int id)
|
||||||
{
|
=> _paymentMethodId = id;
|
||||||
_paymentMethodId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetBuyerId(int id)
|
public void SetBuyerId(int id)
|
||||||
{
|
=> _buyerId = id;
|
||||||
_buyerId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetAwaitingValidationStatus()
|
public void SetAwaitingValidationStatus()
|
||||||
{
|
{
|
||||||
if (_orderStatusId == OrderStatus.Submitted.Id)
|
if (_orderStatusId == OrderStatus.Submitted.Id)
|
||||||
{
|
{
|
||||||
AddDomainEvent(new OrderStatusChangedToAwaitingValidationDomainEvent(Id, _orderItems));
|
AddDomainEvent(new OrderStatusChangedToAwaitingValidationDomainEvent(Id, _orderItems));
|
||||||
_orderStatusId = OrderStatus.AwaitingValidation.Id;
|
_orderStatusId = OrderStatus.AwaitingValidation.Id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetStockConfirmedStatus()
|
public void SetStockConfirmedStatus()
|
||||||
{
|
{
|
||||||
if (_orderStatusId == OrderStatus.AwaitingValidation.Id)
|
if (_orderStatusId == OrderStatus.AwaitingValidation.Id)
|
||||||
{
|
{
|
||||||
AddDomainEvent(new OrderStatusChangedToStockConfirmedDomainEvent(Id));
|
AddDomainEvent(new OrderStatusChangedToStockConfirmedDomainEvent(Id));
|
||||||
|
|
||||||
_orderStatusId = OrderStatus.StockConfirmed.Id;
|
_orderStatusId = OrderStatus.StockConfirmed.Id;
|
||||||
_description = "All the items were confirmed with available stock.";
|
_description = "All the items were confirmed with available stock.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPaidStatus()
|
public void SetPaidStatus()
|
||||||
{
|
{
|
||||||
if (_orderStatusId == OrderStatus.StockConfirmed.Id)
|
if (_orderStatusId == OrderStatus.StockConfirmed.Id)
|
||||||
{
|
{
|
||||||
AddDomainEvent(new OrderStatusChangedToPaidDomainEvent(Id, OrderItems));
|
AddDomainEvent(new OrderStatusChangedToPaidDomainEvent(Id, OrderItems));
|
||||||
|
|
||||||
_orderStatusId = OrderStatus.Paid.Id;
|
_orderStatusId = OrderStatus.Paid.Id;
|
||||||
_description = "The payment was performed at a simulated \"American Bank checking bank account endinf on XX35071\"";
|
_description = "The payment was performed at a simulated \"American Bank checking bank account endinf on XX35071\"";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetShippedStatus()
|
public void SetShippedStatus()
|
||||||
{
|
{
|
||||||
if (_orderStatusId != OrderStatus.Paid.Id)
|
if (_orderStatusId != OrderStatus.Paid.Id)
|
||||||
{
|
{
|
||||||
StatusChangeException(OrderStatus.Shipped);
|
StatusChangeException(OrderStatus.Shipped);
|
||||||
}
|
}
|
||||||
|
|
||||||
_orderStatusId = OrderStatus.Shipped.Id;
|
_orderStatusId = OrderStatus.Shipped.Id;
|
||||||
_description = "The order was shipped.";
|
_description = "The order was shipped.";
|
||||||
AddDomainEvent(new OrderShippedDomainEvent(this));
|
AddDomainEvent(new OrderShippedDomainEvent(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCancelledStatus()
|
public void SetCancelledStatus()
|
||||||
{
|
{
|
||||||
if (_orderStatusId == OrderStatus.Paid.Id ||
|
if (_orderStatusId == OrderStatus.Paid.Id ||
|
||||||
_orderStatusId == OrderStatus.Shipped.Id)
|
_orderStatusId == OrderStatus.Shipped.Id)
|
||||||
{
|
{
|
||||||
StatusChangeException(OrderStatus.Cancelled);
|
StatusChangeException(OrderStatus.Cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
_orderStatusId = OrderStatus.Cancelled.Id;
|
_orderStatusId = OrderStatus.Cancelled.Id;
|
||||||
_description = $"The order was cancelled.";
|
_description = $"The order was cancelled.";
|
||||||
AddDomainEvent(new OrderCancelledDomainEvent(this));
|
AddDomainEvent(new OrderCancelledDomainEvent(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCancelledStatusWhenStockIsRejected(IEnumerable<int> orderStockRejectedItems)
|
public void SetCancelledStatusWhenStockIsRejected(IEnumerable<int> orderStockRejectedItems)
|
||||||
{
|
{
|
||||||
if (_orderStatusId == OrderStatus.AwaitingValidation.Id)
|
if (_orderStatusId == OrderStatus.AwaitingValidation.Id)
|
||||||
{
|
{
|
||||||
_orderStatusId = OrderStatus.Cancelled.Id;
|
_orderStatusId = OrderStatus.Cancelled.Id;
|
||||||
|
|
||||||
var itemsStockRejectedProductNames = OrderItems
|
var itemsStockRejectedProductNames = OrderItems
|
||||||
.Where(c => orderStockRejectedItems.Contains(c.ProductId))
|
.Where(c => orderStockRejectedItems.Contains(c.ProductId))
|
||||||
.Select(c => c.GetOrderItemProductName());
|
.Select(c => c.GetOrderItemProductName());
|
||||||
|
|
||||||
var itemsStockRejectedDescription = string.Join(", ", itemsStockRejectedProductNames);
|
var itemsStockRejectedDescription = string.Join(", ", itemsStockRejectedProductNames);
|
||||||
_description = $"The product items don't have stock: ({itemsStockRejectedDescription}).";
|
_description = $"The product items don't have stock: ({itemsStockRejectedDescription}).";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddOrderStartedDomainEvent(string userId, string userName, int cardTypeId, string cardNumber,
|
private void AddOrderStartedDomainEvent(string userId, string userName, int cardTypeId, string cardNumber,
|
||||||
string cardSecurityNumber, string cardHolderName, DateTime cardExpiration)
|
string cardSecurityNumber, string cardHolderName, DateTime cardExpiration)
|
||||||
{
|
{
|
||||||
var orderStartedDomainEvent = new OrderStartedDomainEvent(this, userId, userName, cardTypeId,
|
var orderStartedDomainEvent = new OrderStartedDomainEvent(this, userId, userName, cardTypeId,
|
||||||
cardNumber, cardSecurityNumber,
|
cardNumber, cardSecurityNumber,
|
||||||
cardHolderName, cardExpiration);
|
cardHolderName, cardExpiration);
|
||||||
|
|
||||||
this.AddDomainEvent(orderStartedDomainEvent);
|
AddDomainEvent(orderStartedDomainEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StatusChangeException(OrderStatus orderStatusToChange)
|
private void StatusChangeException(OrderStatus orderStatusToChange)
|
||||||
{
|
=> throw new OrderingDomainException($"Is not possible to change the order status from {OrderStatus.Name} to {orderStatusToChange.Name}.");
|
||||||
throw new OrderingDomainException($"Is not possible to change the order status from {OrderStatus.Name} to {orderStatusToChange.Name}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public decimal GetTotal()
|
public decimal GetTotal()
|
||||||
{
|
=> _orderItems.Sum(o => o.GetUnits() * o.GetUnitPrice());
|
||||||
return _orderItems.Sum(o => o.GetUnits() * o.GetUnitPrice());
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,83 +1,76 @@
|
|||||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
|
||||||
using Ordering.Domain.Exceptions;
|
using Ordering.Domain.Exceptions;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
|
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
|
||||||
{
|
{
|
||||||
public class OrderItem
|
public class OrderItem
|
||||||
: Entity
|
: Entity
|
||||||
{
|
{
|
||||||
// DDD Patterns comment
|
// DDD Patterns comment
|
||||||
// Using private fields, allowed since EF Core 1.1, is a much better encapsulation
|
// Using private fields, allowed since EF Core 1.1, is a much better encapsulation
|
||||||
// aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)
|
// aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)
|
||||||
private string _productName;
|
private readonly string _productName;
|
||||||
private string _pictureUrl;
|
private readonly string _pictureUrl;
|
||||||
private decimal _unitPrice;
|
private readonly decimal _unitPrice;
|
||||||
private decimal _discount;
|
private decimal _discount;
|
||||||
private int _units;
|
private int _units;
|
||||||
|
|
||||||
public int ProductId { get; private set; }
|
public int ProductId { get; private set; }
|
||||||
|
|
||||||
protected OrderItem() { }
|
protected OrderItem() { }
|
||||||
|
|
||||||
public OrderItem(int productId, string productName, decimal unitPrice, decimal discount, string PictureUrl, int units = 1)
|
public OrderItem(int productId, string productName, decimal unitPrice, decimal discount, string PictureUrl, int units = 1)
|
||||||
{
|
{
|
||||||
if (units <= 0)
|
if (units <= 0)
|
||||||
{
|
{
|
||||||
throw new OrderingDomainException("Invalid number of units");
|
throw new OrderingDomainException("Invalid number of units");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((unitPrice * units) < discount)
|
if ((unitPrice * units) < discount)
|
||||||
{
|
{
|
||||||
throw new OrderingDomainException("The total of order item is lower than applied discount");
|
throw new OrderingDomainException("The total of order item is lower than applied discount");
|
||||||
}
|
}
|
||||||
|
|
||||||
ProductId = productId;
|
ProductId = productId;
|
||||||
|
|
||||||
_productName = productName;
|
_productName = productName;
|
||||||
_unitPrice = unitPrice;
|
_unitPrice = unitPrice;
|
||||||
_discount = discount;
|
_discount = discount;
|
||||||
_units = units;
|
_units = units;
|
||||||
_pictureUrl = PictureUrl;
|
_pictureUrl = PictureUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetPictureUri() => _pictureUrl;
|
public string GetPictureUri() => _pictureUrl;
|
||||||
|
|
||||||
public decimal GetCurrentDiscount()
|
public decimal GetCurrentDiscount()
|
||||||
{
|
=> _discount;
|
||||||
return _discount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetUnits()
|
public int GetUnits()
|
||||||
{
|
=> _units;
|
||||||
return _units;
|
|
||||||
}
|
|
||||||
|
|
||||||
public decimal GetUnitPrice()
|
public decimal GetUnitPrice()
|
||||||
{
|
=> _unitPrice;
|
||||||
return _unitPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetOrderItemProductName() => _productName;
|
public string GetOrderItemProductName() => _productName;
|
||||||
|
|
||||||
public void SetNewDiscount(decimal discount)
|
public void SetNewDiscount(decimal discount)
|
||||||
{
|
{
|
||||||
if (discount < 0)
|
if (discount < 0)
|
||||||
{
|
{
|
||||||
throw new OrderingDomainException("Discount is not valid");
|
throw new OrderingDomainException("Discount is not valid");
|
||||||
}
|
}
|
||||||
|
|
||||||
_discount = discount;
|
_discount = discount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddUnits(int units)
|
public void AddUnits(int units)
|
||||||
{
|
{
|
||||||
if (units < 0)
|
if (units < 0)
|
||||||
{
|
{
|
||||||
throw new OrderingDomainException("Invalid units");
|
throw new OrderingDomainException("Invalid units");
|
||||||
}
|
}
|
||||||
|
|
||||||
_units += units;
|
_units += units;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user