Update Session09 Backend.md
updated to the CreateTicketOrderAsync method
This commit is contained in:
@@ -1,30 +1,17 @@
|
|||||||
Here's a **structured checklist document**, broken into **sections by feature area**. Each section contains tasks in implementation order (oldest to newest), rephrased as clear action items with contextual notes for implementation.
|
# Session 9 - Ticket Reservation
|
||||||
These are created based on this commit messages:
|
As this order is comming to an end, we are completing the functionality of application with the vital feature of reserving tickets
|
||||||
https://github.com/MehrdadShirvani/AlibabaClone-Backend/commits/develop/
|
|
||||||
|
|
||||||
---
|
## Miscellaneous / Fixes
|
||||||
# Miscellaneous / Fixes
|
|
||||||
|
|
||||||
- [ ] Fix GUID generation and async handling in ticket creation. (Use `Guid.NewGuid()` instead of `new Guid()`)
|
- [ ] Fix GUID generation and async handling in ticket creation. (Use `Guid.NewGuid()` instead of `new Guid()`)
|
||||||
- [ ] Make `SerialNumber`, `TicketOrderId`, `BaseAmount` publicly settable in DTOs.
|
```csharp
|
||||||
- [ ] Fix issues in seat mappings and transportation queries.
|
SerialNumber = Guid.NewGuid().ToString("N")
|
||||||
# Fixes and Missing Parts from Session 08
|
```
|
||||||
|
|
||||||
## 👤 Person & Account Info
|
- [ ] Make `SerialNumber`, `TicketOrderId`, `BaseAmount` publicly settable in DTOs if they're private.
|
||||||
|
- [ ] Check `Transaction` and make sure `SerialNumber` is not a required property.
|
||||||
|
|
||||||
- [ ] Add `Person`, `BankAccountDetail`, and navigation properties.
|
## Branching
|
||||||
- [ ] Add `UpsertPersonAsync` and `UpsertBankAccountDetailAsync` in `IAccountService`.
|
|
||||||
- [ ] Add `UpsertPerson`, `UpsertBankDetail` endpoints in `AccountController`.
|
|
||||||
- [ ] Add `GetPeople`, `GetProfileAsync`, and DTOs (`ProfileDto`, `PersonDto`, `BankAccountDetailDto`).
|
|
||||||
- [ ] Handle person editing logic via `UpsertPerson` and adjust classes that used it the old way.
|
|
||||||
## 🔐 Auth & Settings
|
|
||||||
|
|
||||||
- [ ] Complete the error handling of `EditEmailDto`, `EditPasswordDto`.
|
|
||||||
- [ ] Add `GetByEmailAsync` in `AccountRepository`.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Branching
|
|
||||||
- [ ] Create the feature/ticket-reservation branch based on develop
|
- [ ] Create the feature/ticket-reservation branch based on develop
|
||||||
|
|
||||||
# Ticket Ordering System
|
# Ticket Ordering System
|
||||||
@@ -32,16 +19,244 @@ https://github.com/MehrdadShirvani/AlibabaClone-Backend/commits/develop/
|
|||||||
## 🧱 Domain and Infrastructure Setup
|
## 🧱 Domain and Infrastructure Setup
|
||||||
|
|
||||||
- [ ] Check out new ERD: [Here](https://github.com/TheOrderOfPhoenix/ASP.NET/blob/main/ProjectOrientedSessions/docs/AlibabaERD-Version02.pdf)
|
- [ ] Check out new ERD: [Here](https://github.com/TheOrderOfPhoenix/ASP.NET/blob/main/ProjectOrientedSessions/docs/AlibabaERD-Version02.pdf)
|
||||||
- [ ] Create `TicketOrder`, `Ticket`, and `Transaction` entities.
|
|
||||||
- [ ] Add configurations for `TicketOrder`, `Ticket`, and `Transaction` (relationships, constraints).
|
|
||||||
- [ ] Adjust `Transportation` entity configuration to support ticketing.
|
|
||||||
- [ ] Add migrations for the above database changes.
|
- [ ] Add migrations for the above database changes.
|
||||||
|
|
||||||
## 🧑💼 Service Layer
|
## 🧑💼 Service Layer
|
||||||
|
|
||||||
- [ ] Create `ITicketOrderService`, `TicketOrderService` and implement:
|
- [ ] Create DTOs `CreateTravellerTicketDto` and `CreateTicketOrderDto`:
|
||||||
- [ ] `CreateTicketOrderAsync`
|
```csharp
|
||||||
- [ ] `GenerateTicketsPdfAsync`
|
public class CreateTravellerTicketDto
|
||||||
|
{
|
||||||
|
public long Id { get; set; }
|
||||||
|
public long CreatorId { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "First name is required")]
|
||||||
|
public required string FirstName { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "Last name is required")]
|
||||||
|
public required string LastName { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "Id number is required")]
|
||||||
|
[RegularExpression(@"^\d{10}$", ErrorMessage = "National ID number must be exactly 10 digits")]
|
||||||
|
public required string IdNumber { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "Gender should be identified")]
|
||||||
|
public required short GenderId { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "Phone number is required")]
|
||||||
|
[Phone(ErrorMessage = "Invalid phone number format")]
|
||||||
|
public required string PhoneNumber { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "Birth date is required")]
|
||||||
|
public DateTime BirthDate { get; set; }
|
||||||
|
|
||||||
|
public long? SeatId { get; set; }
|
||||||
|
public bool IsVIP { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```csharp
|
||||||
|
public class CreateTicketOrderDto
|
||||||
|
{
|
||||||
|
public long TransportationId { get; set; }
|
||||||
|
public List<CreateTravellerTicketDto> MyProperty { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Note: If you have the **Coupon** feature in your project, then add a `CouponCode` property in `CreateTicketOrderDto`.
|
||||||
|
|
||||||
|
- [ ] Modify SeatRepository to add method `GetSeatsByVehicleIdAsync`
|
||||||
|
```csharp
|
||||||
|
public Task<List<Seat>> GetSeatsByVehicleIdAsync(long vehicleId)
|
||||||
|
{
|
||||||
|
var seats = DbSet
|
||||||
|
.Include(s => s.Vehicle)
|
||||||
|
.Include(s => s.Tickets).ThenInclude(t => t.Traveler)
|
||||||
|
.Where(s => s.VehicleId == vehicleId).ToListAsync();
|
||||||
|
return seats;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- [ ] Add Enum for **TicketStatus**, **VehicleType** and **TransactionType**
|
||||||
|
```csharp
|
||||||
|
public enum TicketStatusEnum
|
||||||
|
{
|
||||||
|
Reserved = 1,
|
||||||
|
Paid = 2,
|
||||||
|
CancelledByUser = 3,
|
||||||
|
CancelledBySystem = 4,
|
||||||
|
Used = 5,
|
||||||
|
Expired = 6
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```csharp
|
||||||
|
public enum VehicleTypeEnum
|
||||||
|
{
|
||||||
|
Airplane = 1,
|
||||||
|
Train = 2,
|
||||||
|
Bus = 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```csharp
|
||||||
|
public enum TransactionTypeEnum
|
||||||
|
{
|
||||||
|
Deposit = 1,
|
||||||
|
Withdraw = 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Add the lock-service to lock the transportation through reservation, then register it:
|
||||||
|
```csharp
|
||||||
|
public class TransportationLockService : ITransportationLockService
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<long, SemaphoreSlim> _locks = new();
|
||||||
|
|
||||||
|
public async Task<IDisposable> AcquireLockAsync(long transportationId)
|
||||||
|
{
|
||||||
|
var semaphore = _locks.GetOrAdd(transportationId, new SemaphoreSlim(1, 1));
|
||||||
|
await semaphore.WaitAsync();
|
||||||
|
return new Releaser(() => semaphore.Release());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Releaser : IDisposable
|
||||||
|
{
|
||||||
|
private readonly Action _release;
|
||||||
|
|
||||||
|
public Releaser(Action release)
|
||||||
|
{
|
||||||
|
_release = release;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Add method `CreateAsync` in `TransportationService`
|
||||||
|
```csharp
|
||||||
|
public async Task<Result<long>> CreateAsync(long accountId, TransactionDto dto)
|
||||||
|
{
|
||||||
|
Transaction transaction = new();
|
||||||
|
_mapper.Map(dto, transaction);
|
||||||
|
transaction.AccountId = accountId;
|
||||||
|
|
||||||
|
await _transactionRepository.InsertAsync(transaction);
|
||||||
|
await _unitOfWork.CompleteAsync();
|
||||||
|
return Result<long>.Success(transaction.Id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Add method `PayForTicketOrderAsync` in `AccountService`
|
||||||
|
```csharp
|
||||||
|
public async Task<Result<long>> PayForTicketOrderAsync(long accountId, long ticketOrderId, decimal baseAmount, decimal finalAmount)
|
||||||
|
{
|
||||||
|
var account = await _accountRepository.GetByIdAsync(accountId);
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
return Result<long>.Error(0, "Account not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (account.Balance < finalAmount)
|
||||||
|
{
|
||||||
|
return Result<long>.Error(0, "Not enough money");
|
||||||
|
}
|
||||||
|
|
||||||
|
account.Withdraw(finalAmount);
|
||||||
|
_accountRepository.Update(account);
|
||||||
|
await _unitOfWork.CompleteAsync();
|
||||||
|
|
||||||
|
TransactionDto dto = new()
|
||||||
|
{
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
Description = "Payment for ticket order #" + ticketOrderId + " at " + DateTime.UtcNow,
|
||||||
|
BaseAmount = baseAmount,
|
||||||
|
FinalAmount = finalAmount,
|
||||||
|
SerialNumber = Guid.NewGuid().ToString("N"),
|
||||||
|
TicketOrderId = ticketOrderId,
|
||||||
|
TransactionTypeId = (int)TransactionTypeEnum.Withdraw,
|
||||||
|
TransactionType = TransactionTypeEnum.Withdraw.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
return await _transactionService.CreateAsync(accountId, dto);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Create `ITicketOrderService`, `TicketOrderService` and implement `CreateTicketOrderAsync`
|
||||||
|
```csharp
|
||||||
|
public async Task<Result<long>> CreateTicketOrderAsync(long accountId, CreateTicketOrderDto dto)
|
||||||
|
{
|
||||||
|
// get the account
|
||||||
|
var account = await _accountRepository.GetByIdAsync(accountId);
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
return Result<long>.Error(0, "Account not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the transportation
|
||||||
|
var transportation = await _transportationRepository.GetByIdAsync(dto.TransportationId);
|
||||||
|
if (transportation == null)
|
||||||
|
{
|
||||||
|
return Result<long>.Error(0, "Transportation not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// lock the transportation through reservation
|
||||||
|
using (await _transportationLockService.AcquireLockAsync(dto.TransportationId))
|
||||||
|
{
|
||||||
|
var baseAmount = transportation.BasePrice * dto.Travellers.Count;
|
||||||
|
if (account.Balance < baseAmount)
|
||||||
|
{
|
||||||
|
return Result<long>.Error(0, "Not enough money");
|
||||||
|
}
|
||||||
|
// check validity of transportation
|
||||||
|
var checkSeats = ValidateTransportationAndSeats(transportation, dto.Travellers);
|
||||||
|
if (!string.IsNullOrEmpty(checkSeats))
|
||||||
|
{
|
||||||
|
return Result<long>.Error(0, checkSeats);
|
||||||
|
}
|
||||||
|
|
||||||
|
var finalAmount = baseAmount;
|
||||||
|
await AssignSeatsIfDynamic(transportation.VehicleId, dto.Travellers);
|
||||||
|
await UpsertTravellers(accountId, dto.Travellers);
|
||||||
|
|
||||||
|
// add the ticket order by the info we have
|
||||||
|
TicketOrder ticketOrder = new()
|
||||||
|
{
|
||||||
|
BuyerId = accountId,
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
Description = "",
|
||||||
|
SerialNumber = Guid.NewGuid().ToString("N"),
|
||||||
|
TransportationId = dto.TransportationId
|
||||||
|
};
|
||||||
|
await _ticketOrderRepository.InsertAsync(ticketOrder);
|
||||||
|
|
||||||
|
foreach (var traveller in dto.Travellers)
|
||||||
|
{
|
||||||
|
if (!traveller.SeatId.HasValue)
|
||||||
|
{
|
||||||
|
return Result<long>.Error(0, "Seat ID is required for each traveller");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ticket ticket = new()
|
||||||
|
{
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
Description = traveller.Description,
|
||||||
|
SeatId = traveller.SeatId.Value,
|
||||||
|
SerialNumber = Guid.NewGuid().ToString("N"),
|
||||||
|
TicketOrder = ticketOrder,
|
||||||
|
TicketStatusId = 1,
|
||||||
|
TravelerId = traveller.Id,
|
||||||
|
};
|
||||||
|
await _ticketRepository.InsertAsync(ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _unitOfWork.CompleteAsync();
|
||||||
|
await _accountService.PayForTicketOrderAsync(account.Id, ticketOrder.Id,
|
||||||
|
baseAmount, finalAmount);
|
||||||
|
return Result<long>.Success(ticketOrder.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
- [ ] Register `TicketOrderService` in DI container.
|
- [ ] Register `TicketOrderService` in DI container.
|
||||||
|
|
||||||
## 🎯 Controller Layer
|
## 🎯 Controller Layer
|
||||||
@@ -125,7 +340,3 @@ https://github.com/MehrdadShirvani/AlibabaClone-Backend/commits/develop/
|
|||||||
# Merge
|
# Merge
|
||||||
- [ ] Create a PR and merge the current branch with develop
|
- [ ] Create a PR and merge the current branch with develop
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user