profileShare

rasmusjy / splitapp-backend-modular-monolith

Read-only snapshot

No repository description.

main default branch 418 files Expires Sep 13, 2026, 9:06 AM
TripService.cs 8,963 bytes
1 using SplitApp.WebApp.Application.DTO;
2 using SplitApp.WebApp.Application.Mappers;
3 using SplitApp.Modules.Trips.Domain.Entities;
4 using SplitApp.Modules.Trips.Domain.Enums;
5 using SplitApp.Modules.Expenses.Domain.Entities;
6 using SplitApp.Modules.Expenses.Domain.Enums;
7 using SplitApp.Modules.Users.Domain.Entities;
8 using SplitApp.WebApp.Application.Contracts;
9
10 namespace SplitApp.WebApp.Application.Services;
11
12 public class TripService : ITripService
13 {
14 private readonly IAppUnitOfWork _uow;
15 private readonly ISettlementService _settlementService;
16
17 public TripService(IAppUnitOfWork uow, ISettlementService settlementService)
18 {
19 _uow = uow;
20 _settlementService = settlementService;
21 }
22
23 public async Task<TripBllDto> CreateTripAsync(TripBllDto trip, Guid userId)
24 {
25 var entity = TripBllDtoFactory.ToEntity(trip);
26 entity.Id = Guid.NewGuid();
27 entity.CreatedById = userId;
28 entity.Status = ETripStatus.Active;
29
30 _uow.Trips.Add(entity);
31
32 var participant = new TripParticipant
33 {
34 Id = Guid.NewGuid(),
35 TripId = entity.Id,
36 UserId = userId,
37 Role = EParticipantRole.Organizer,
38 JoinedAt = DateTime.UtcNow,
39 IsActive = true
40 };
41
42 _uow.TripParticipants.Add(participant);
43
44 await _uow.SaveChangesAsync();
45
46 return TripBllDtoFactory.Create(entity);
47 }
48
49 public async Task<List<TripBllDto>> GetUserTripsAsync(Guid userId)
50 {
51 var trips = await _uow.Trips.GetUserTripsAsync(userId);
52 return TripBllDtoFactory.CreateList(trips, includeParticipants: true);
53 }
54
55 public async Task<TripBllDto?> GetByIdAsync(Guid tripId, Guid userId)
56 {
57 if (!await _uow.TripParticipants.IsParticipantAsync(tripId, userId)) return null;
58 var trip = await _uow.Trips.GetByIdAsync(tripId);
59 return trip == null ? null : TripBllDtoFactory.Create(trip);
60 }
61
62 public async Task<TripBllDto?> GetByIdWithDetailsAsync(Guid tripId, Guid userId)
63 {
64 if (!await _uow.TripParticipants.IsParticipantAsync(tripId, userId)) return null;
65 var trip = await _uow.Trips.GetByIdWithDetailsAsync(tripId);
66 return trip == null ? null : TripBllDtoFactory.Create(trip, includeParticipants: true, includeExpenses: true);
67 }
68
69 public async Task<TripBllDto?> GetByIdForOrganizerAsync(Guid tripId, Guid userId)
70 {
71 if (!await _uow.TripParticipants.IsOrganizerAsync(tripId, userId)) return null;
72 var trip = await _uow.Trips.GetByIdAsync(tripId);
73 return trip == null ? null : TripBllDtoFactory.Create(trip);
74 }
75
76 public async Task<TripBllDto?> GetRawByIdAsync(Guid tripId)
77 {
78 var trip = await _uow.Trips.GetByIdAsync(tripId);
79 return trip == null ? null : TripBllDtoFactory.Create(trip);
80 }
81
82 public Task<bool> IsParticipantAsync(Guid tripId, Guid userId)
83 => _uow.TripParticipants.IsParticipantAsync(tripId, userId);
84
85 public Task<bool> IsOrganizerAsync(Guid tripId, Guid userId)
86 => _uow.TripParticipants.IsOrganizerAsync(tripId, userId);
87
88 public async Task<TripBllDto?> UpdateAsync(TripBllDto trip, Guid userId)
89 {
90 if (!await _uow.TripParticipants.IsOrganizerAsync(trip.Id, userId)) return null;
91
92 var existing = await _uow.Trips.GetByIdAsync(trip.Id);
93 if (existing == null) return null;
94
95 existing.Name = trip.Name;
96 existing.Description = trip.Description;
97 existing.Destination = trip.Destination;
98 existing.StartDate = trip.StartDate;
99 existing.EndDate = trip.EndDate;
100 existing.DefaultCurrencyId = trip.DefaultCurrencyId;
101 existing.Status = trip.Status;
102
103 _uow.Trips.Update(existing);
104 await _uow.SaveChangesAsync();
105 return TripBllDtoFactory.Create(existing);
106 }
107
108 public async Task<bool> DeleteAsync(Guid tripId, Guid userId)
109 {
110 if (!await _uow.TripParticipants.IsOrganizerAsync(tripId, userId)) return false;
111
112 var trip = await _uow.Trips.GetByIdAsync(tripId);
113 if (trip == null) return false;
114
115 await _uow.Trips.RemoveAsync(tripId);
116 await _uow.SaveChangesAsync();
117 return true;
118 }
119
120 public async Task<List<TripParticipantBllDto>> GetParticipantsAsync(Guid tripId, Guid userId)
121 {
122 if (!await _uow.TripParticipants.IsParticipantAsync(tripId, userId))
123 return new List<TripParticipantBllDto>();
124 var participants = await _uow.TripParticipants.GetByTripIdAsync(tripId);
125 return TripParticipantBllDtoFactory.CreateList(participants);
126 }
127
128 public Task<List<TripParticipantBllDto>> GetParticipantsForIndexAsync(Guid tripId, Guid userId)
129 => GetParticipantsAsync(tripId, userId);
130
131 public async Task<TripParticipantBllDto?> GetParticipantByIdAsync(Guid participantId)
132 {
133 var participant = await _uow.TripParticipants.GetByIdAsync(participantId);
134 return participant == null ? null : TripParticipantBllDtoFactory.Create(participant);
135 }
136
137 public async Task<(bool success, string? errorCode)> RemoveParticipantAsync(Guid tripId, Guid participantUserId, Guid currentUserId)
138 {
139 if (!await _uow.TripParticipants.IsOrganizerAsync(tripId, currentUserId))
140 return (false, "forbidden");
141
142 var participants = (await _uow.TripParticipants.GetByTripIdAsync(tripId)).ToList();
143 var participant = participants.FirstOrDefault(tp => tp.UserId == participantUserId);
144 if (participant == null) return (false, "notfound");
145
146 if (participant.Role == EParticipantRole.Organizer)
147 return (false, "organizer");
148 if (participant.UserId == currentUserId)
149 return (false, "self");
150
151 participant.IsActive = false;
152 participant.LeftAt = DateTime.UtcNow;
153 _uow.TripParticipants.Update(participant);
154 await _uow.SaveChangesAsync();
155
156 return (true, null);
157 }
158
159 public async Task<bool> RemoveParticipantByIdAsync(Guid tripId, Guid participantId, Guid currentUserId)
160 {
161 if (!await _uow.TripParticipants.IsOrganizerAsync(tripId, currentUserId))
162 return false;
163
164 var participant = await _uow.TripParticipants.GetByIdAsync(participantId);
165 if (participant == null || participant.TripId != tripId) return false;
166
167 // Cannot remove yourself
168 if (participant.UserId == currentUserId) return false;
169
170 participant.IsActive = false;
171 participant.LeftAt = DateTime.UtcNow;
172 _uow.TripParticipants.Update(participant);
173 await _uow.SaveChangesAsync();
174
175 return true;
176 }
177
178 public async Task<(bool success, string? errorCode)> FinalizeTripAsync(Guid tripId, Guid userId)
179 {
180 if (!await _uow.TripParticipants.IsOrganizerAsync(tripId, userId))
181 return (false, "forbidden");
182
183 var trip = await _uow.Trips.GetByIdAsync(tripId);
184 if (trip == null) return (false, "notfound");
185 if (trip.Status != ETripStatus.Active) return (false, "badstatus");
186
187 trip.Status = ETripStatus.Finalizing;
188 _uow.Trips.Update(trip);
189 await _uow.SaveChangesAsync();
190
191 // Auto-create settlement plan
192 var createdPlan = await _settlementService.CalculateSettlementAsync(tripId, userId);
193
194 // If nobody owes anyone, skip straight to Settled.
195 if (createdPlan == null)
196 {
197 trip.Status = ETripStatus.Settled;
198 await _uow.SaveChangesAsync();
199 }
200
201 return (true, null);
202 }
203
204 public async Task<(bool success, string? errorCode)> ReopenTripAsync(Guid tripId, Guid userId)
205 {
206 if (!await _uow.TripParticipants.IsOrganizerAsync(tripId, userId))
207 return (false, "forbidden");
208
209 var trip = await _uow.Trips.GetByIdAsync(tripId);
210 if (trip == null) return (false, "notfound");
211 if (trip.Status != ETripStatus.Finalizing && trip.Status != ETripStatus.Settled)
212 return (false, "badstatus");
213
214 var plan = await _uow.SettlementPlans.GetLatestByTripIdAsync(tripId);
215 if (plan?.Payments != null && plan.Payments.Any(p => p.Status == EPaymentStatus.Confirmed))
216 return (false, "payments-confirmed");
217
218 if (plan != null)
219 {
220 await _uow.SettlementPlans.DeletePlanWithPaymentsAsync(plan.Id);
221 }
222
223 var tripToUpdate = await _uow.Trips.GetByIdAsync(tripId);
224 if (tripToUpdate == null) return (false, "notfound");
225 tripToUpdate.Status = ETripStatus.Active;
226 _uow.Trips.Update(tripToUpdate);
227 await _uow.SaveChangesAsync();
228
229 return (true, null);
230 }
231
232 public async Task<List<CurrencyBllDto>> GetAllCurrenciesAsync()
233 {
234 var currencies = await _uow.GetRepository<Currency>().GetAllAsync();
235 return currencies
236 .OrderBy(c => c.Code)
237 .Select(CurrencyBllDtoFactory.Create)
238 .ToList();
239 }
240 }
241