| | | 1 | | using MechanicsSoftware.Domain.Enums; |
| | | 2 | | using MechanicsSoftware.Domain.Exceptions; |
| | | 3 | | using MechanicsSoftware.Domain.ValueObjects; |
| | | 4 | | |
| | | 5 | | namespace MechanicsSoftware.Domain.Entities; |
| | | 6 | | |
| | | 7 | | public sealed class ServiceOrder : Entity<Guid> |
| | | 8 | | { |
| | 236 | 9 | | private readonly List<ServiceItem> _serviceItems = []; |
| | 236 | 10 | | private readonly List<PartItem> _partItems = []; |
| | | 11 | | |
| | | 12 | | public Guid CustomerId { get; private set; } |
| | | 13 | | public Guid VehicleId { get; private set; } |
| | | 14 | | public ServiceOrderStatus Status { get; private set; } = null!; |
| | | 15 | | public Budget? Budget { get; private set; } |
| | | 16 | | public DateTime CreatedAt { get; private set; } |
| | | 17 | | public DateTime? CompletedAt { get; private set; } |
| | | 18 | | public DateTime? DeliveredAt { get; private set; } |
| | | 19 | | |
| | 42 | 20 | | public IReadOnlyCollection<ServiceItem> ServiceItems => _serviceItems.AsReadOnly(); |
| | 50 | 21 | | public IReadOnlyCollection<PartItem> PartItems => _partItems.AsReadOnly(); |
| | | 22 | | |
| | 708 | 23 | | private ServiceOrder() { } |
| | | 24 | | |
| | | 25 | | public static ServiceOrder Create(Guid id, Guid customerId, Guid vehicleId) |
| | 220 | 26 | | { |
| | 220 | 27 | | if (customerId == Guid.Empty) |
| | 2 | 28 | | throw new DomainException("CustomerId is required."); |
| | | 29 | | |
| | 218 | 30 | | if (vehicleId == Guid.Empty) |
| | 2 | 31 | | throw new DomainException("VehicleId is required."); |
| | | 32 | | |
| | 216 | 33 | | return new ServiceOrder |
| | 216 | 34 | | { |
| | 216 | 35 | | Id = id, |
| | 216 | 36 | | CustomerId = customerId, |
| | 216 | 37 | | VehicleId = vehicleId, |
| | 216 | 38 | | Status = ServiceOrderStatus.CreateReceived(), |
| | 216 | 39 | | CreatedAt = DateTime.UtcNow |
| | 216 | 40 | | }; |
| | 216 | 41 | | } |
| | | 42 | | |
| | | 43 | | public void StartDiagnosis() |
| | 172 | 44 | | { |
| | 172 | 45 | | Status = Status.TransitionTo(ServiceOrderStatus.Status.InDiagnosis); |
| | 166 | 46 | | } |
| | | 47 | | |
| | | 48 | | public void SendBudget() |
| | 86 | 49 | | { |
| | 86 | 50 | | if (Budget is null) |
| | 6 | 51 | | throw new DomainException("A budget must be generated before it can be sent."); |
| | | 52 | | |
| | 80 | 53 | | Status = Status.TransitionTo(ServiceOrderStatus.Status.AwaitingApproval); |
| | 80 | 54 | | } |
| | | 55 | | |
| | | 56 | | public void Approve() |
| | 54 | 57 | | { |
| | 54 | 58 | | if (Budget is null) |
| | 6 | 59 | | throw new DomainException("Cannot approve an order without a budget."); |
| | | 60 | | |
| | 48 | 61 | | Status = Status.TransitionTo(ServiceOrderStatus.Status.InExecution); |
| | 48 | 62 | | Budget.Approve(); |
| | 48 | 63 | | } |
| | | 64 | | |
| | | 65 | | public void Reject() |
| | 20 | 66 | | { |
| | 20 | 67 | | if (Budget is null) |
| | 4 | 68 | | throw new DomainException("Cannot reject an order without a budget."); |
| | | 69 | | |
| | 16 | 70 | | Status = Status.TransitionTo(ServiceOrderStatus.Status.Cancelled); |
| | 16 | 71 | | Budget.Reject(); |
| | 16 | 72 | | } |
| | | 73 | | |
| | | 74 | | public void StartExecution() |
| | 10 | 75 | | { |
| | 10 | 76 | | if (!Status.Is(ServiceOrderStatus.Status.InExecution)) |
| | 4 | 77 | | throw new DomainException( |
| | 4 | 78 | | $"Order must be IN_EXECUTION to start work. Current status: {Status}."); |
| | 6 | 79 | | } |
| | | 80 | | |
| | | 81 | | public void Complete() |
| | 28 | 82 | | { |
| | 28 | 83 | | Status = Status.TransitionTo(ServiceOrderStatus.Status.Completed); |
| | 24 | 84 | | CompletedAt = DateTime.UtcNow; |
| | 24 | 85 | | } |
| | | 86 | | |
| | | 87 | | public void Deliver() |
| | 16 | 88 | | { |
| | 16 | 89 | | Status = Status.TransitionTo(ServiceOrderStatus.Status.Delivered); |
| | 12 | 90 | | DeliveredAt = DateTime.UtcNow; |
| | 12 | 91 | | } |
| | | 92 | | |
| | | 93 | | public ServiceItem AddServiceItem( |
| | | 94 | | Guid serviceId, |
| | | 95 | | string serviceName, |
| | | 96 | | Money unitPrice, |
| | | 97 | | int quantity) |
| | 112 | 98 | | { |
| | 112 | 99 | | EnsureInDiagnosis(); |
| | | 100 | | |
| | 110 | 101 | | var item = ServiceItem.Create(Id, serviceId, serviceName, unitPrice, quantity); |
| | 100 | 102 | | _serviceItems.Add(item); |
| | 100 | 103 | | return item; |
| | 100 | 104 | | } |
| | | 105 | | |
| | | 106 | | public PartItem AddPartItem( |
| | | 107 | | Guid partId, |
| | | 108 | | string partName, |
| | | 109 | | Money unitPrice, |
| | | 110 | | int quantity, |
| | | 111 | | PartAvailability availability) |
| | 34 | 112 | | { |
| | 34 | 113 | | EnsureInDiagnosis(); |
| | | 114 | | |
| | 32 | 115 | | var item = PartItem.Create(Id, partId, partName, unitPrice, quantity, availability); |
| | 22 | 116 | | _partItems.Add(item); |
| | 22 | 117 | | return item; |
| | 22 | 118 | | } |
| | | 119 | | |
| | | 120 | | public Budget GenerateBudget() |
| | 104 | 121 | | { |
| | 104 | 122 | | if (Budget is not null) |
| | 4 | 123 | | throw new DomainException("Budget already generated. Cannot overwrite an existing budget."); |
| | | 124 | | |
| | 100 | 125 | | EnsureInDiagnosis(); |
| | | 126 | | |
| | 98 | 127 | | var serviceItems = _serviceItems; |
| | 98 | 128 | | if (serviceItems.Count == 0) |
| | 4 | 129 | | throw new DomainException( |
| | 4 | 130 | | "Cannot generate a budget: at least one service item is required."); |
| | | 131 | | |
| | 94 | 132 | | var servicesTotal = serviceItems |
| | 94 | 133 | | .Aggregate(new Money(0), (acc, item) => acc.Add(item.Total)); |
| | | 134 | | |
| | 94 | 135 | | var partsTotal = _partItems |
| | 94 | 136 | | .Where(p => p.Availability == PartAvailability.Available) |
| | 94 | 137 | | .Aggregate(new Money(0), (acc, item) => acc.Add(item.Total)); |
| | | 138 | | |
| | 94 | 139 | | var total = servicesTotal.Add(partsTotal); |
| | | 140 | | |
| | 94 | 141 | | Budget = Budget.Create(Id, total); |
| | 94 | 142 | | return Budget; |
| | 94 | 143 | | } |
| | | 144 | | |
| | | 145 | | private void EnsureInDiagnosis() |
| | 246 | 146 | | { |
| | 246 | 147 | | if (!Status.Is(ServiceOrderStatus.Status.InDiagnosis)) |
| | 6 | 148 | | throw new DomainException( |
| | 6 | 149 | | $"Items can only be added or a budget generated when the order is IN_DIAGNOSIS. " + |
| | 6 | 150 | | $"Current status: {Status}."); |
| | 240 | 151 | | } |
| | | 152 | | } |