profileShare

rasmusjy / splitapp-backend-microservices

Read-only snapshot

No repository description.

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