diff --git a/services/booking-service-net/src/BookingService.API/Application/Commands/ChangeSampleStatusCommand.cs b/services/booking-service-net/src/BookingService.API/Application/Commands/ChangeSampleStatusCommand.cs deleted file mode 100644 index c2d393cc..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Commands/ChangeSampleStatusCommand.cs +++ /dev/null @@ -1,14 +0,0 @@ -using MediatR; - -namespace BookingService.API.Application.Commands; - -/// -/// EN: Command to change status of a Sample. -/// VI: Command để thay đổi trạng thái của Sample. -/// -/// EN: Sample ID / VI: ID sample -/// EN: New status (activate, complete, cancel) / VI: Trạng thái mới (activate, complete, cancel) -public record ChangeSampleStatusCommand( - Guid SampleId, - string NewStatus -) : IRequest; diff --git a/services/booking-service-net/src/BookingService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs b/services/booking-service-net/src/BookingService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs deleted file mode 100644 index 7c2be169..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs +++ /dev/null @@ -1,70 +0,0 @@ -using MediatR; -using BookingService.Domain.AggregatesModel.SampleAggregate; - -namespace BookingService.API.Application.Commands; - -/// -/// EN: Handler for ChangeSampleStatusCommand. -/// VI: Handler cho ChangeSampleStatusCommand. -/// -public class ChangeSampleStatusCommandHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - private readonly ILogger _logger; - - public ChangeSampleStatusCommandHandler( - ISampleRepository sampleRepository, - ILogger logger) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle( - ChangeSampleStatusCommand request, - CancellationToken cancellationToken) - { - _logger.LogInformation( - "Changing status of sample {SampleId} to {NewStatus} / Thay đổi trạng thái sample {SampleId} thành {NewStatus}", - request.SampleId, request.NewStatus); - - // EN: Get existing sample / VI: Lấy sample đã tồn tại - var sample = await _sampleRepository.GetAsync(request.SampleId); - - if (sample is null) - { - _logger.LogWarning( - "Sample {SampleId} not found / Sample {SampleId} không tìm thấy", - request.SampleId); - return false; - } - - // EN: Change status based on action / VI: Thay đổi trạng thái dựa trên action - switch (request.NewStatus.ToLowerInvariant()) - { - case "activate": - sample.Activate(); - break; - case "complete": - sample.Complete(); - break; - case "cancel": - sample.Cancel(); - break; - default: - _logger.LogWarning( - "Invalid status action: {NewStatus} / Action trạng thái không hợp lệ: {NewStatus}", - request.NewStatus); - return false; - } - - // EN: Save changes / VI: Lưu thay đổi - await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); - - _logger.LogInformation( - "Sample {SampleId} status changed to {NewStatus} / Trạng thái sample {SampleId} đã đổi thành {NewStatus}", - request.SampleId, request.NewStatus); - - return true; - } -} diff --git a/services/booking-service-net/src/BookingService.API/Application/Commands/CreateSampleCommand.cs b/services/booking-service-net/src/BookingService.API/Application/Commands/CreateSampleCommand.cs deleted file mode 100644 index 3ece960b..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Commands/CreateSampleCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -using MediatR; - -namespace BookingService.API.Application.Commands; - -/// -/// EN: Command to create a new Sample. -/// VI: Command để tạo một Sample mới. -/// -/// EN: Sample name / VI: Tên sample -/// EN: Optional description / VI: Mô tả tùy chọn -public record CreateSampleCommand( - string Name, - string? Description -) : IRequest; - -/// -/// EN: Result of CreateSampleCommand. -/// VI: Kết quả của CreateSampleCommand. -/// -/// EN: Created sample ID / VI: ID sample đã tạo -public record CreateSampleCommandResult(Guid Id); diff --git a/services/booking-service-net/src/BookingService.API/Application/Commands/CreateSampleCommandHandler.cs b/services/booking-service-net/src/BookingService.API/Application/Commands/CreateSampleCommandHandler.cs deleted file mode 100644 index c386e30c..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Commands/CreateSampleCommandHandler.cs +++ /dev/null @@ -1,46 +0,0 @@ -using MediatR; -using BookingService.Domain.AggregatesModel.SampleAggregate; - -namespace BookingService.API.Application.Commands; - -/// -/// EN: Handler for CreateSampleCommand. -/// VI: Handler cho CreateSampleCommand. -/// -public class CreateSampleCommandHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - private readonly ILogger _logger; - - public CreateSampleCommandHandler( - ISampleRepository sampleRepository, - ILogger logger) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle( - CreateSampleCommand request, - CancellationToken cancellationToken) - { - _logger.LogInformation( - "Creating new sample with name: {Name} / Tạo sample mới với tên: {Name}", - request.Name); - - // EN: Create domain entity / VI: Tạo domain entity - var sample = new Sample(request.Name, request.Description); - - // EN: Add to repository / VI: Thêm vào repository - _sampleRepository.Add(sample); - - // EN: Save changes (dispatches domain events) / VI: Lưu thay đổi (dispatch domain events) - await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); - - _logger.LogInformation( - "Sample created successfully with ID: {SampleId} / Sample đã tạo thành công với ID: {SampleId}", - sample.Id); - - return new CreateSampleCommandResult(sample.Id); - } -} diff --git a/services/booking-service-net/src/BookingService.API/Application/Commands/DeleteSampleCommand.cs b/services/booking-service-net/src/BookingService.API/Application/Commands/DeleteSampleCommand.cs deleted file mode 100644 index d99b2340..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Commands/DeleteSampleCommand.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MediatR; - -namespace BookingService.API.Application.Commands; - -/// -/// EN: Command to delete a Sample. -/// VI: Command để xóa một Sample. -/// -/// EN: Sample ID to delete / VI: ID sample cần xóa -public record DeleteSampleCommand(Guid SampleId) : IRequest; diff --git a/services/booking-service-net/src/BookingService.API/Application/Commands/DeleteSampleCommandHandler.cs b/services/booking-service-net/src/BookingService.API/Application/Commands/DeleteSampleCommandHandler.cs deleted file mode 100644 index 5f4b6dd8..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Commands/DeleteSampleCommandHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using MediatR; -using BookingService.Domain.AggregatesModel.SampleAggregate; - -namespace BookingService.API.Application.Commands; - -/// -/// EN: Handler for DeleteSampleCommand. -/// VI: Handler cho DeleteSampleCommand. -/// -public class DeleteSampleCommandHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - private readonly ILogger _logger; - - public DeleteSampleCommandHandler( - ISampleRepository sampleRepository, - ILogger logger) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle( - DeleteSampleCommand request, - CancellationToken cancellationToken) - { - _logger.LogInformation( - "Deleting sample {SampleId} / Xóa sample {SampleId}", - request.SampleId); - - // EN: Get existing sample / VI: Lấy sample đã tồn tại - var sample = await _sampleRepository.GetAsync(request.SampleId); - - if (sample is null) - { - _logger.LogWarning( - "Sample {SampleId} not found / Sample {SampleId} không tìm thấy", - request.SampleId); - return false; - } - - // EN: Delete sample / VI: Xóa sample - _sampleRepository.Delete(sample); - - // EN: Save changes / VI: Lưu thay đổi - await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); - - _logger.LogInformation( - "Sample {SampleId} deleted successfully / Sample {SampleId} đã xóa thành công", - request.SampleId); - - return true; - } -} diff --git a/services/booking-service-net/src/BookingService.API/Application/Commands/UpdateSampleCommand.cs b/services/booking-service-net/src/BookingService.API/Application/Commands/UpdateSampleCommand.cs deleted file mode 100644 index bde94f72..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Commands/UpdateSampleCommand.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MediatR; - -namespace BookingService.API.Application.Commands; - -/// -/// EN: Command to update an existing Sample. -/// VI: Command để cập nhật một Sample đã tồn tại. -/// -/// EN: Sample ID to update / VI: ID sample cần cập nhật -/// EN: New name / VI: Tên mới -/// EN: New description / VI: Mô tả mới -public record UpdateSampleCommand( - Guid SampleId, - string Name, - string? Description -) : IRequest; diff --git a/services/booking-service-net/src/BookingService.API/Application/Commands/UpdateSampleCommandHandler.cs b/services/booking-service-net/src/BookingService.API/Application/Commands/UpdateSampleCommandHandler.cs deleted file mode 100644 index ebca581c..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Commands/UpdateSampleCommandHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using MediatR; -using BookingService.Domain.AggregatesModel.SampleAggregate; - -namespace BookingService.API.Application.Commands; - -/// -/// EN: Handler for UpdateSampleCommand. -/// VI: Handler cho UpdateSampleCommand. -/// -public class UpdateSampleCommandHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - private readonly ILogger _logger; - - public UpdateSampleCommandHandler( - ISampleRepository sampleRepository, - ILogger logger) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task Handle( - UpdateSampleCommand request, - CancellationToken cancellationToken) - { - _logger.LogInformation( - "Updating sample {SampleId} / Cập nhật sample {SampleId}", - request.SampleId); - - // EN: Get existing sample / VI: Lấy sample đã tồn tại - var sample = await _sampleRepository.GetAsync(request.SampleId); - - if (sample is null) - { - _logger.LogWarning( - "Sample {SampleId} not found / Sample {SampleId} không tìm thấy", - request.SampleId); - return false; - } - - // EN: Update sample using domain method / VI: Cập nhật sample sử dụng domain method - sample.Update(request.Name, request.Description); - - // EN: Save changes / VI: Lưu thay đổi - await _sampleRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); - - _logger.LogInformation( - "Sample {SampleId} updated successfully / Sample {SampleId} đã cập nhật thành công", - request.SampleId); - - return true; - } -} diff --git a/services/booking-service-net/src/BookingService.API/Application/Queries/GetSampleQuery.cs b/services/booking-service-net/src/BookingService.API/Application/Queries/GetSampleQuery.cs deleted file mode 100644 index 25f31497..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Queries/GetSampleQuery.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MediatR; - -namespace BookingService.API.Application.Queries; - -/// -/// EN: Query to get a Sample by ID. -/// VI: Query để lấy một Sample theo ID. -/// -/// EN: Sample ID / VI: ID sample -public record GetSampleQuery(Guid SampleId) : IRequest; - -/// -/// EN: Sample view model for API responses. -/// VI: Sample view model cho API responses. -/// -public record SampleViewModel( - Guid Id, - string Name, - string? Description, - string Status, - DateTime CreatedAt, - DateTime? UpdatedAt -); diff --git a/services/booking-service-net/src/BookingService.API/Application/Queries/GetSampleQueryHandler.cs b/services/booking-service-net/src/BookingService.API/Application/Queries/GetSampleQueryHandler.cs deleted file mode 100644 index b1a17658..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Queries/GetSampleQueryHandler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediatR; -using BookingService.Domain.AggregatesModel.SampleAggregate; - -namespace BookingService.API.Application.Queries; - -/// -/// EN: Handler for GetSampleQuery. -/// VI: Handler cho GetSampleQuery. -/// -public class GetSampleQueryHandler : IRequestHandler -{ - private readonly ISampleRepository _sampleRepository; - - public GetSampleQueryHandler(ISampleRepository sampleRepository) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - } - - public async Task Handle( - GetSampleQuery request, - CancellationToken cancellationToken) - { - var sample = await _sampleRepository.GetAsync(request.SampleId); - - if (sample is null) - { - return null; - } - - return new SampleViewModel( - sample.Id, - sample.Name, - sample.Description, - sample.Status.Name, - sample.CreatedAt, - sample.UpdatedAt - ); - } -} diff --git a/services/booking-service-net/src/BookingService.API/Application/Queries/GetSamplesQuery.cs b/services/booking-service-net/src/BookingService.API/Application/Queries/GetSamplesQuery.cs deleted file mode 100644 index ced4e6ac..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Queries/GetSamplesQuery.cs +++ /dev/null @@ -1,9 +0,0 @@ -using MediatR; - -namespace BookingService.API.Application.Queries; - -/// -/// EN: Query to get all Samples. -/// VI: Query để lấy tất cả Samples. -/// -public record GetSamplesQuery : IRequest>; diff --git a/services/booking-service-net/src/BookingService.API/Application/Queries/GetSamplesQueryHandler.cs b/services/booking-service-net/src/BookingService.API/Application/Queries/GetSamplesQueryHandler.cs deleted file mode 100644 index dc2515b0..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Queries/GetSamplesQueryHandler.cs +++ /dev/null @@ -1,34 +0,0 @@ -using MediatR; -using BookingService.Domain.AggregatesModel.SampleAggregate; - -namespace BookingService.API.Application.Queries; - -/// -/// EN: Handler for GetSamplesQuery. -/// VI: Handler cho GetSamplesQuery. -/// -public class GetSamplesQueryHandler : IRequestHandler> -{ - private readonly ISampleRepository _sampleRepository; - - public GetSamplesQueryHandler(ISampleRepository sampleRepository) - { - _sampleRepository = sampleRepository ?? throw new ArgumentNullException(nameof(sampleRepository)); - } - - public async Task> Handle( - GetSamplesQuery request, - CancellationToken cancellationToken) - { - var samples = await _sampleRepository.GetAllAsync(); - - return samples.Select(sample => new SampleViewModel( - sample.Id, - sample.Name, - sample.Description, - sample.Status.Name, - sample.CreatedAt, - sample.UpdatedAt - )); - } -} diff --git a/services/booking-service-net/src/BookingService.API/Application/Validations/CreateSampleCommandValidator.cs b/services/booking-service-net/src/BookingService.API/Application/Validations/CreateSampleCommandValidator.cs deleted file mode 100644 index a11274b4..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Validations/CreateSampleCommandValidator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using FluentValidation; -using BookingService.API.Application.Commands; - -namespace BookingService.API.Application.Validations; - -/// -/// EN: Validator for CreateSampleCommand. -/// VI: Validator cho CreateSampleCommand. -/// -public class CreateSampleCommandValidator : AbstractValidator -{ - public CreateSampleCommandValidator() - { - RuleFor(x => x.Name) - .NotEmpty() - .WithMessage("Name is required / Tên là bắt buộc") - .MaximumLength(200) - .WithMessage("Name must be less than 200 characters / Tên phải ít hơn 200 ký tự"); - - RuleFor(x => x.Description) - .MaximumLength(1000) - .WithMessage("Description must be less than 1000 characters / Mô tả phải ít hơn 1000 ký tự") - .When(x => x.Description != null); - } -} diff --git a/services/booking-service-net/src/BookingService.API/Application/Validations/UpdateSampleCommandValidator.cs b/services/booking-service-net/src/BookingService.API/Application/Validations/UpdateSampleCommandValidator.cs deleted file mode 100644 index c5be27df..00000000 --- a/services/booking-service-net/src/BookingService.API/Application/Validations/UpdateSampleCommandValidator.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FluentValidation; -using BookingService.API.Application.Commands; - -namespace BookingService.API.Application.Validations; - -/// -/// EN: Validator for UpdateSampleCommand. -/// VI: Validator cho UpdateSampleCommand. -/// -public class UpdateSampleCommandValidator : AbstractValidator -{ - public UpdateSampleCommandValidator() - { - RuleFor(x => x.SampleId) - .NotEmpty() - .WithMessage("Sample ID is required / ID sample là bắt buộc"); - - RuleFor(x => x.Name) - .NotEmpty() - .WithMessage("Name is required / Tên là bắt buộc") - .MaximumLength(200) - .WithMessage("Name must be less than 200 characters / Tên phải ít hơn 200 ký tự"); - - RuleFor(x => x.Description) - .MaximumLength(1000) - .WithMessage("Description must be less than 1000 characters / Mô tả phải ít hơn 1000 ký tự") - .When(x => x.Description != null); - } -} diff --git a/services/booking-service-net/src/BookingService.API/BookingService.API.csproj b/services/booking-service-net/src/BookingService.API/BookingService.API.csproj index b5e0f768..4fa3b98c 100644 --- a/services/booking-service-net/src/BookingService.API/BookingService.API.csproj +++ b/services/booking-service-net/src/BookingService.API/BookingService.API.csproj @@ -14,6 +14,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/services/booking-service-net/src/BookingService.API/Controllers/SamplesController.cs b/services/booking-service-net/src/BookingService.API/Controllers/SamplesController.cs deleted file mode 100644 index a6c86169..00000000 --- a/services/booking-service-net/src/BookingService.API/Controllers/SamplesController.cs +++ /dev/null @@ -1,200 +0,0 @@ -using Asp.Versioning; -using MediatR; -using Microsoft.AspNetCore.Mvc; -using BookingService.API.Application.Commands; -using BookingService.API.Application.Queries; - -namespace BookingService.API.Controllers; - -/// -/// EN: Controller for Sample CRUD operations using CQRS pattern. -/// VI: Controller cho các thao tác CRUD Sample sử dụng pattern CQRS. -/// -[ApiController] -[ApiVersion("1.0")] -[Route("api/v{version:apiVersion}/[controller]")] -[Produces("application/json")] -public class SamplesController : ControllerBase -{ - private readonly IMediator _mediator; - private readonly ILogger _logger; - - public SamplesController(IMediator mediator, ILogger logger) - { - _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - /// - /// EN: Get all samples. - /// VI: Lấy tất cả samples. - /// - /// EN: List of samples / VI: Danh sách samples - [HttpGet] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - public async Task GetSamples() - { - var samples = await _mediator.Send(new GetSamplesQuery()); - return Ok(new { success = true, data = samples }); - } - - /// - /// EN: Get a sample by ID. - /// VI: Lấy một sample theo ID. - /// - /// EN: Sample ID / VI: ID sample - /// EN: Sample details / VI: Chi tiết sample - [HttpGet("{id:guid}")] - [ProducesResponseType(typeof(SampleViewModel), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetSample(Guid id) - { - var sample = await _mediator.Send(new GetSampleQuery(id)); - - if (sample is null) - { - return NotFound(new - { - success = false, - error = new - { - code = "SAMPLE_NOT_FOUND", - message = $"Sample with ID {id} not found / Sample với ID {id} không tìm thấy" - } - }); - } - - return Ok(new { success = true, data = sample }); - } - - /// - /// EN: Create a new sample. - /// VI: Tạo một sample mới. - /// - /// EN: Create request / VI: Request tạo - /// EN: Created sample ID / VI: ID sample đã tạo - [HttpPost] - [ProducesResponseType(typeof(CreateSampleCommandResult), StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task CreateSample([FromBody] CreateSampleRequest request) - { - var command = new CreateSampleCommand(request.Name, request.Description); - var result = await _mediator.Send(command); - - return CreatedAtAction( - nameof(GetSample), - new { id = result.Id }, - new { success = true, data = result }); - } - - /// - /// EN: Update an existing sample. - /// VI: Cập nhật một sample đã tồn tại. - /// - /// EN: Sample ID / VI: ID sample - /// EN: Update request / VI: Request cập nhật - /// EN: Success status / VI: Trạng thái thành công - [HttpPut("{id:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task UpdateSample(Guid id, [FromBody] UpdateSampleRequest request) - { - var command = new UpdateSampleCommand(id, request.Name, request.Description); - var result = await _mediator.Send(command); - - if (!result) - { - return NotFound(new - { - success = false, - error = new - { - code = "SAMPLE_NOT_FOUND", - message = $"Sample with ID {id} not found / Sample với ID {id} không tìm thấy" - } - }); - } - - return Ok(new { success = true, message = "Sample updated successfully / Sample đã cập nhật thành công" }); - } - - /// - /// EN: Delete a sample. - /// VI: Xóa một sample. - /// - /// EN: Sample ID / VI: ID sample - /// EN: Success status / VI: Trạng thái thành công - [HttpDelete("{id:guid}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task DeleteSample(Guid id) - { - var command = new DeleteSampleCommand(id); - var result = await _mediator.Send(command); - - if (!result) - { - return NotFound(new - { - success = false, - error = new - { - code = "SAMPLE_NOT_FOUND", - message = $"Sample with ID {id} not found / Sample với ID {id} không tìm thấy" - } - }); - } - - return NoContent(); - } - - /// - /// EN: Change sample status. - /// VI: Thay đổi trạng thái sample. - /// - /// EN: Sample ID / VI: ID sample - /// EN: Status change request / VI: Request thay đổi trạng thái - /// EN: Success status / VI: Trạng thái thành công - [HttpPatch("{id:guid}/status")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task ChangeSampleStatus(Guid id, [FromBody] ChangeStatusRequest request) - { - var command = new ChangeSampleStatusCommand(id, request.Status); - var result = await _mediator.Send(command); - - if (!result) - { - return BadRequest(new - { - success = false, - error = new - { - code = "STATUS_CHANGE_FAILED", - message = "Failed to change sample status / Thay đổi trạng thái sample thất bại" - } - }); - } - - return Ok(new { success = true, message = "Sample status changed successfully / Trạng thái sample đã thay đổi thành công" }); - } -} - -/// -/// EN: Request model for creating a sample. -/// VI: Model request để tạo sample. -/// -public record CreateSampleRequest(string Name, string? Description); - -/// -/// EN: Request model for updating a sample. -/// VI: Model request để cập nhật sample. -/// -public record UpdateSampleRequest(string Name, string? Description); - -/// -/// EN: Request model for changing sample status. -/// VI: Model request để thay đổi trạng thái sample. -/// -public record ChangeStatusRequest(string Status); diff --git a/services/booking-service-net/src/BookingService.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs b/services/booking-service-net/src/BookingService.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs deleted file mode 100644 index b9b72f64..00000000 --- a/services/booking-service-net/src/BookingService.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs +++ /dev/null @@ -1,61 +0,0 @@ -using BookingService.Domain.SeedWork; - -namespace BookingService.Domain.AggregatesModel.SampleAggregate; - -/// -/// EN: Repository interface for Sample aggregate. -/// VI: Interface repository cho Sample aggregate. -/// -/// -/// EN: Following repository pattern, this interface defines the contract -/// for data access operations on Sample aggregate. -/// VI: Theo pattern repository, interface này định nghĩa contract -/// cho các thao tác truy cập dữ liệu trên Sample aggregate. -/// -public interface ISampleRepository : IRepository -{ - /// - /// EN: Get a sample by its ID. - /// VI: Lấy một sample theo ID. - /// - /// EN: The sample ID / VI: ID của sample - /// EN: The sample or null if not found / VI: Sample hoặc null nếu không tìm thấy - Task GetAsync(Guid sampleId); - - /// - /// EN: Get all samples. - /// VI: Lấy tất cả samples. - /// - /// EN: List of samples / VI: Danh sách samples - Task> GetAllAsync(); - - /// - /// EN: Add a new sample. - /// VI: Thêm một sample mới. - /// - /// EN: The sample to add / VI: Sample cần thêm - /// EN: The added sample / VI: Sample đã thêm - Sample Add(Sample sample); - - /// - /// EN: Update an existing sample. - /// VI: Cập nhật một sample đã tồn tại. - /// - /// EN: The sample to update / VI: Sample cần cập nhật - void Update(Sample sample); - - /// - /// EN: Delete a sample. - /// VI: Xóa một sample. - /// - /// EN: The sample to delete / VI: Sample cần xóa - void Delete(Sample sample); - - /// - /// EN: Get samples by status. - /// VI: Lấy samples theo trạng thái. - /// - /// EN: The status ID / VI: ID trạng thái - /// EN: List of samples with given status / VI: Danh sách samples với trạng thái cho trước - Task> GetByStatusAsync(int statusId); -} diff --git a/services/booking-service-net/src/BookingService.Domain/AggregatesModel/SampleAggregate/Sample.cs b/services/booking-service-net/src/BookingService.Domain/AggregatesModel/SampleAggregate/Sample.cs deleted file mode 100644 index fe5e327f..00000000 --- a/services/booking-service-net/src/BookingService.Domain/AggregatesModel/SampleAggregate/Sample.cs +++ /dev/null @@ -1,158 +0,0 @@ -using BookingService.Domain.Events; -using BookingService.Domain.Exceptions; -using BookingService.Domain.SeedWork; - -namespace BookingService.Domain.AggregatesModel.SampleAggregate; - -/// -/// EN: Sample aggregate root demonstrating DDD patterns. -/// VI: Sample aggregate root minh họa các pattern DDD. -/// -public class Sample : Entity, IAggregateRoot -{ - // EN: Private fields for encapsulation - // VI: Fields private để đóng gói - private string _name = null!; - private string? _description; - private SampleStatus _status = null!; - private DateTime _createdAt; - private DateTime? _updatedAt; - - /// - /// EN: Sample name (required). - /// VI: Tên sample (bắt buộc). - /// - public string Name => _name; - - /// - /// EN: Optional description. - /// VI: Mô tả tùy chọn. - /// - public string? Description => _description; - - /// - /// EN: Current status. - /// VI: Trạng thái hiện tại. - /// - public SampleStatus Status => _status; - - /// - /// EN: Status ID for EF Core mapping. - /// VI: ID trạng thái cho EF Core mapping. - /// - public int StatusId { get; private set; } - - /// - /// EN: Creation timestamp. - /// VI: Thời gian tạo. - /// - public DateTime CreatedAt => _createdAt; - - /// - /// EN: Last update timestamp. - /// VI: Thời gian cập nhật cuối. - /// - public DateTime? UpdatedAt => _updatedAt; - - /// - /// EN: Private constructor for EF Core. - /// VI: Constructor private cho EF Core. - /// - protected Sample() - { - } - - /// - /// EN: Create a new Sample with required information. - /// VI: Tạo một Sample mới với thông tin bắt buộc. - /// - /// EN: Sample name / VI: Tên sample - /// EN: Optional description / VI: Mô tả tùy chọn - public Sample(string name, string? description = null) : this() - { - if (string.IsNullOrWhiteSpace(name)) - throw new SampleDomainException("Sample name cannot be empty"); - - Id = Guid.NewGuid(); - _name = name; - _description = description; - _status = SampleStatus.Draft; - StatusId = SampleStatus.Draft.Id; - _createdAt = DateTime.UtcNow; - - // EN: Add domain event for creation - // VI: Thêm domain event cho việc tạo - AddDomainEvent(new SampleCreatedDomainEvent(this)); - } - - /// - /// EN: Update sample information. - /// VI: Cập nhật thông tin sample. - /// - public void Update(string name, string? description) - { - if (string.IsNullOrWhiteSpace(name)) - throw new SampleDomainException("Sample name cannot be empty"); - - if (_status == SampleStatus.Cancelled) - throw new SampleDomainException("Cannot update a cancelled sample"); - - _name = name; - _description = description; - _updatedAt = DateTime.UtcNow; - } - - /// - /// EN: Activate the sample. - /// VI: Kích hoạt sample. - /// - public void Activate() - { - if (_status != SampleStatus.Draft) - throw new SampleDomainException("Only draft samples can be activated"); - - var previousStatus = _status; - _status = SampleStatus.Active; - StatusId = SampleStatus.Active.Id; - _updatedAt = DateTime.UtcNow; - - AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status)); - } - - /// - /// EN: Complete the sample. - /// VI: Hoàn thành sample. - /// - public void Complete() - { - if (_status != SampleStatus.Active) - throw new SampleDomainException("Only active samples can be completed"); - - var previousStatus = _status; - _status = SampleStatus.Completed; - StatusId = SampleStatus.Completed.Id; - _updatedAt = DateTime.UtcNow; - - AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status)); - } - - /// - /// EN: Cancel the sample. - /// VI: Hủy sample. - /// - public void Cancel() - { - if (_status == SampleStatus.Completed) - throw new SampleDomainException("Cannot cancel a completed sample"); - - if (_status == SampleStatus.Cancelled) - throw new SampleDomainException("Sample is already cancelled"); - - var previousStatus = _status; - _status = SampleStatus.Cancelled; - StatusId = SampleStatus.Cancelled.Id; - _updatedAt = DateTime.UtcNow; - - AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status)); - } -} diff --git a/services/booking-service-net/src/BookingService.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs b/services/booking-service-net/src/BookingService.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs deleted file mode 100644 index 03304826..00000000 --- a/services/booking-service-net/src/BookingService.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs +++ /dev/null @@ -1,77 +0,0 @@ -using BookingService.Domain.SeedWork; - -namespace BookingService.Domain.AggregatesModel.SampleAggregate; - -/// -/// EN: Sample status enumeration following type-safe enum pattern. -/// VI: Enumeration trạng thái Sample theo pattern enum an toàn kiểu. -/// -public class SampleStatus : Enumeration -{ - /// - /// EN: Draft status - initial state - /// VI: Trạng thái nháp - trạng thái ban đầu - /// - public static SampleStatus Draft = new(1, nameof(Draft)); - - /// - /// EN: Active status - ready for use - /// VI: Trạng thái hoạt động - sẵn sàng sử dụng - /// - public static SampleStatus Active = new(2, nameof(Active)); - - /// - /// EN: Completed status - finished processing - /// VI: Trạng thái hoàn thành - đã xử lý xong - /// - public static SampleStatus Completed = new(3, nameof(Completed)); - - /// - /// EN: Cancelled status - cancelled by user - /// VI: Trạng thái đã hủy - bị hủy bởi người dùng - /// - public static SampleStatus Cancelled = new(4, nameof(Cancelled)); - - public SampleStatus(int id, string name) : base(id, name) - { - } - - /// - /// EN: Get all available statuses. - /// VI: Lấy tất cả các trạng thái có sẵn. - /// - public static IEnumerable List() => GetAll(); - - /// - /// EN: Parse status from name. - /// VI: Parse trạng thái từ tên. - /// - public static SampleStatus FromName(string name) - { - var status = List().SingleOrDefault(s => - string.Equals(s.Name, name, StringComparison.CurrentCultureIgnoreCase)); - - if (status is null) - { - throw new ArgumentException($"Possible values for SampleStatus: {string.Join(",", List().Select(s => s.Name))}"); - } - - return status; - } - - /// - /// EN: Parse status from ID. - /// VI: Parse trạng thái từ ID. - /// - public static SampleStatus From(int id) - { - var status = List().SingleOrDefault(s => s.Id == id); - - if (status is null) - { - throw new ArgumentException($"Possible values for SampleStatus: {string.Join(",", List().Select(s => s.Name))}"); - } - - return status; - } -} diff --git a/services/booking-service-net/src/BookingService.Domain/Events/SampleCreatedDomainEvent.cs b/services/booking-service-net/src/BookingService.Domain/Events/SampleCreatedDomainEvent.cs deleted file mode 100644 index 127044cf..00000000 --- a/services/booking-service-net/src/BookingService.Domain/Events/SampleCreatedDomainEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using MediatR; -using BookingService.Domain.AggregatesModel.SampleAggregate; - -namespace BookingService.Domain.Events; - -/// -/// EN: Domain event raised when a new Sample is created. -/// VI: Domain event được phát ra khi một Sample mới được tạo. -/// -public class SampleCreatedDomainEvent : INotification -{ - /// - /// EN: The newly created sample. - /// VI: Sample mới được tạo. - /// - public Sample Sample { get; } - - public SampleCreatedDomainEvent(Sample sample) - { - Sample = sample; - } -} diff --git a/services/booking-service-net/src/BookingService.Domain/Events/SampleStatusChangedDomainEvent.cs b/services/booking-service-net/src/BookingService.Domain/Events/SampleStatusChangedDomainEvent.cs deleted file mode 100644 index a51f62f4..00000000 --- a/services/booking-service-net/src/BookingService.Domain/Events/SampleStatusChangedDomainEvent.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediatR; -using BookingService.Domain.AggregatesModel.SampleAggregate; - -namespace BookingService.Domain.Events; - -/// -/// EN: Domain event raised when Sample status changes. -/// VI: Domain event được phát ra khi trạng thái Sample thay đổi. -/// -public class SampleStatusChangedDomainEvent : INotification -{ - /// - /// EN: The sample ID. - /// VI: ID của sample. - /// - public Guid SampleId { get; } - - /// - /// EN: Previous status before the change. - /// VI: Trạng thái trước khi thay đổi. - /// - public SampleStatus PreviousStatus { get; } - - /// - /// EN: New status after the change. - /// VI: Trạng thái mới sau khi thay đổi. - /// - public SampleStatus NewStatus { get; } - - public SampleStatusChangedDomainEvent( - Guid sampleId, - SampleStatus previousStatus, - SampleStatus newStatus) - { - SampleId = sampleId; - PreviousStatus = previousStatus; - NewStatus = newStatus; - } -} diff --git a/services/booking-service-net/src/BookingService.Domain/Exceptions/SampleDomainException.cs b/services/booking-service-net/src/BookingService.Domain/Exceptions/SampleDomainException.cs deleted file mode 100644 index 5397122d..00000000 --- a/services/booking-service-net/src/BookingService.Domain/Exceptions/SampleDomainException.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace BookingService.Domain.Exceptions; - -/// -/// EN: Exception for Sample aggregate domain errors. -/// VI: Exception cho các lỗi domain của Sample aggregate. -/// -public class SampleDomainException : DomainException -{ - public SampleDomainException() - { - } - - public SampleDomainException(string message) : base(message) - { - } - - public SampleDomainException(string message, Exception innerException) - : base(message, innerException) - { - } -} diff --git a/services/booking-service-net/src/BookingService.Infrastructure/BookingContext.cs b/services/booking-service-net/src/BookingService.Infrastructure/BookingContext.cs new file mode 100644 index 00000000..5cb2fb43 --- /dev/null +++ b/services/booking-service-net/src/BookingService.Infrastructure/BookingContext.cs @@ -0,0 +1,66 @@ +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using BookingService.Domain.AggregatesModel.AppointmentAggregate; +using BookingService.Domain.SeedWork; +using BookingService.Infrastructure.EntityConfigurations; + +namespace BookingService.Infrastructure; + +public class BookingContext : DbContext, IUnitOfWork +{ + private readonly IMediator _mediator; + private IDbContextTransaction? _currentTransaction; + + public DbSet Appointments => Set(); + public IDbContextTransaction? CurrentTransaction => _currentTransaction; + public bool HasActiveTransaction => _currentTransaction != null; + + public BookingContext(DbContextOptions options, IMediator mediator) : base(options) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new AppointmentEntityTypeConfiguration()); + modelBuilder.ApplyConfiguration(new AppointmentStatusEntityTypeConfiguration()); + } + + public async Task SaveEntitiesAsync(CancellationToken cancellationToken = default) + { + await DispatchDomainEventsAsync(); + await base.SaveChangesAsync(cancellationToken); + return true; + } + + public async Task BeginTransactionAsync() + { + if (_currentTransaction != null) return null; + _currentTransaction = await Database.BeginTransactionAsync(System.Data.IsolationLevel.ReadCommitted); + return _currentTransaction; + } + + public async Task CommitTransactionAsync(IDbContextTransaction transaction) + { + ArgumentNullException.ThrowIfNull(transaction); + if (transaction != _currentTransaction) throw new InvalidOperationException($"Transaction {transaction.TransactionId} is not current"); + try { await SaveChangesAsync(); await transaction.CommitAsync(); } + catch { RollbackTransaction(); throw; } + finally { if (_currentTransaction != null) { _currentTransaction.Dispose(); _currentTransaction = null; } } + } + + public void RollbackTransaction() + { + try { _currentTransaction?.Rollback(); } + finally { if (_currentTransaction != null) { _currentTransaction.Dispose(); _currentTransaction = null; } } + } + + private async Task DispatchDomainEventsAsync() + { + var domainEntities = ChangeTracker.Entries().Where(x => x.Entity.DomainEvents.Any()).ToList(); + var domainEvents = domainEntities.SelectMany(x => x.Entity.DomainEvents).ToList(); + domainEntities.ForEach(entity => entity.Entity.ClearDomainEvents()); + foreach (var domainEvent in domainEvents) await _mediator.Publish(domainEvent); + } +} diff --git a/services/booking-service-net/src/BookingService.Infrastructure/DependencyInjection.cs b/services/booking-service-net/src/BookingService.Infrastructure/DependencyInjection.cs index 17c88958..2fcf7edb 100644 --- a/services/booking-service-net/src/BookingService.Infrastructure/DependencyInjection.cs +++ b/services/booking-service-net/src/BookingService.Infrastructure/DependencyInjection.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using BookingService.Domain.AggregatesModel.SampleAggregate; +using BookingService.Domain.AggregatesModel.AppointmentAggregate; using BookingService.Infrastructure.Idempotency; using BookingService.Infrastructure.Repositories; diff --git a/services/booking-service-net/src/BookingService.Infrastructure/Entity Configurations/AppointmentEntityTypeConfiguration.cs b/services/booking-service-net/src/BookingService.Infrastructure/Entity Configurations/AppointmentEntityTypeConfiguration.cs new file mode 100644 index 00000000..b4bf2c30 --- /dev/null +++ b/services/booking-service-net/src/BookingService.Infrastructure/Entity Configurations/AppointmentEntityTypeConfiguration.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using BookingService.Domain.AggregatesModel.AppointmentAggregate; + +namespace BookingService.Infrastructure.EntityConfigurations; + +public class AppointmentEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("appointments"); + builder.HasKey(a => a.Id); + builder.Property(a => a.Id).HasColumnName("id").ValueGeneratedNever(); + builder.Property("_shopId").HasColumnName("shop_id").IsRequired(); + builder.Property("_customerId").HasColumnName("customer_id").IsRequired(); + builder.Property("_serviceId").HasColumnName("service_id").IsRequired(); + builder.Property(a => a.StatusId).HasColumnName("status_id").IsRequired(); + builder.Property("_scheduledAt").HasColumnName("scheduled_at").IsRequired(); + builder.Property("_durationMinutes").HasColumnName("duration_minutes").IsRequired(); + builder.Property("_staffId").HasColumnName("staff_id"); + builder.Property("_resourceId").HasColumnName("resource_id"); + builder.Property("_notes").HasColumnName("notes").HasMaxLength(1000); + + builder.HasIndex("_shopId").HasDatabaseName("ix_appointments_shop_id"); + builder.HasIndex("_customerId").HasDatabaseName("ix_appointments_customer_id"); + builder.HasIndex("_scheduledAt").HasDatabaseName("ix_appointments_scheduled_at"); + + builder.Ignore(a => a.ShopId); + builder.Ignore(a => a.CustomerId); + builder.Ignore(a => a.ServiceId); + builder.Ignore(a => a.Status); + builder.Ignore(a => a.ScheduledAt); + builder.Ignore(a => a.DurationMinutes); + builder.Ignore(a => a.StaffId); + builder.Ignore(a => a.ResourceId); + builder.Ignore(a => a.Notes); + } +} diff --git a/services/booking-service-net/src/BookingService.Infrastructure/EntityConfigurations/AppointmentStatusEntityTypeConfiguration.cs b/services/booking-service-net/src/BookingService.Infrastructure/EntityConfigurations/AppointmentStatusEntityTypeConfiguration.cs new file mode 100644 index 00000000..2b4f661f --- /dev/null +++ b/services/booking-service-net/src/BookingService.Infrastructure/EntityConfigurations/AppointmentStatusEntityTypeConfiguration.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using BookingService.Domain.AggregatesModel.AppointmentAggregate; + +namespace BookingService.Infrastructure.EntityConfigurations; + +public class AppointmentStatusEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("appointment_statuses"); + builder.HasKey(s => s.Id); + builder.Property(s => s.Id).HasColumnName("id").ValueGeneratedNever(); + builder.Property(s => s.Name).HasColumnName("name").HasMaxLength(50).IsRequired(); + builder.HasData( + AppointmentStatus.Pending, + AppointmentStatus.Confirmed, + AppointmentStatus.InProgress, + AppointmentStatus.Completed, + AppointmentStatus.Cancelled, + AppointmentStatus.NoShow + ); + } +} diff --git a/services/booking-service-net/src/BookingService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs b/services/booking-service-net/src/BookingService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs deleted file mode 100644 index c0e0dae6..00000000 --- a/services/booking-service-net/src/BookingService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using BookingService.Domain.AggregatesModel.SampleAggregate; - -namespace BookingService.Infrastructure.EntityConfigurations; - -/// -/// EN: EF Core configuration for Sample entity. -/// VI: Cấu hình EF Core cho entity Sample. -/// -public class SampleEntityTypeConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - // EN: Table name / VI: Tên bảng - builder.ToTable("samples"); - - // EN: Primary key / VI: Khóa chính - builder.HasKey(s => s.Id); - - // EN: Ignore domain events (not persisted) - // VI: Bỏ qua domain events (không lưu) - builder.Ignore(s => s.DomainEvents); - - // EN: Properties / VI: Các thuộc tính - builder.Property(s => s.Id) - .HasColumnName("id") - .IsRequired(); - - builder.Property("_name") - .HasColumnName("name") - .HasMaxLength(200) - .IsRequired(); - - builder.Property("_description") - .HasColumnName("description") - .HasMaxLength(1000); - - builder.Property("_createdAt") - .HasColumnName("created_at") - .IsRequired(); - - builder.Property("_updatedAt") - .HasColumnName("updated_at"); - - // EN: Status relationship / VI: Quan hệ với Status - builder.Property(s => s.StatusId) - .HasColumnName("status_id") - .IsRequired(); - - builder.HasOne(s => s.Status) - .WithMany() - .HasForeignKey(s => s.StatusId) - .OnDelete(DeleteBehavior.Restrict); - - // EN: Indexes / VI: Các index - builder.HasIndex("_name"); - builder.HasIndex(s => s.StatusId); - builder.HasIndex("_createdAt"); - } -} diff --git a/services/booking-service-net/src/BookingService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs b/services/booking-service-net/src/BookingService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs deleted file mode 100644 index 28ed123b..00000000 --- a/services/booking-service-net/src/BookingService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using BookingService.Domain.AggregatesModel.SampleAggregate; - -namespace BookingService.Infrastructure.EntityConfigurations; - -/// -/// EN: EF Core configuration for SampleStatus enumeration. -/// VI: Cấu hình EF Core cho enumeration SampleStatus. -/// -public class SampleStatusEntityTypeConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - // EN: Table name / VI: Tên bảng - builder.ToTable("sample_statuses"); - - // EN: Primary key / VI: Khóa chính - builder.HasKey(s => s.Id); - - builder.Property(s => s.Id) - .HasColumnName("id") - .ValueGeneratedNever() - .IsRequired(); - - builder.Property(s => s.Name) - .HasColumnName("name") - .HasMaxLength(50) - .IsRequired(); - - // EN: Seed initial data / VI: Seed dữ liệu ban đầu - builder.HasData( - SampleStatus.Draft, - SampleStatus.Active, - SampleStatus.Completed, - SampleStatus.Cancelled - ); - } -} diff --git a/services/booking-service-net/src/BookingService.Infrastructure/MyServiceContext.cs b/services/booking-service-net/src/BookingService.Infrastructure/MyServiceContext.cs deleted file mode 100644 index df42730a..00000000 --- a/services/booking-service-net/src/BookingService.Infrastructure/MyServiceContext.cs +++ /dev/null @@ -1,160 +0,0 @@ -using MediatR; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage; -using BookingService.Domain.AggregatesModel.SampleAggregate; -using BookingService.Domain.SeedWork; -using BookingService.Infrastructure.EntityConfigurations; - -namespace BookingService.Infrastructure; - -/// -/// EN: EF Core DbContext for BookingService. -/// VI: EF Core DbContext cho BookingService. -/// -public class BookingServiceContext : DbContext, IUnitOfWork -{ - private readonly IMediator _mediator; - private IDbContextTransaction? _currentTransaction; - - /// - /// EN: Samples table. - /// VI: Bảng Samples. - /// - public DbSet Samples => Set(); - - /// - /// EN: Read-only access to current transaction. - /// VI: Truy cập chỉ đọc đến transaction hiện tại. - /// - public IDbContextTransaction? CurrentTransaction => _currentTransaction; - - /// - /// EN: Check if there is an active transaction. - /// VI: Kiểm tra xem có transaction đang hoạt động không. - /// - public bool HasActiveTransaction => _currentTransaction != null; - - public BookingServiceContext(DbContextOptions options) : base(options) - { - _mediator = null!; - } - - public BookingServiceContext(DbContextOptions options, IMediator mediator) : base(options) - { - _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); - - System.Diagnostics.Debug.WriteLine("BookingServiceContext::ctor - " + GetHashCode()); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - // EN: Apply entity configurations - // VI: Áp dụng các cấu hình entity - modelBuilder.ApplyConfiguration(new SampleEntityTypeConfiguration()); - modelBuilder.ApplyConfiguration(new SampleStatusEntityTypeConfiguration()); - } - - /// - /// EN: Save entities and dispatch domain events. - /// VI: Lưu entities và dispatch domain events. - /// - public async Task SaveEntitiesAsync(CancellationToken cancellationToken = default) - { - // EN: Dispatch domain events before saving (side effects) - // VI: Dispatch domain events trước khi lưu (side effects) - await DispatchDomainEventsAsync(); - - // EN: Save changes to database - // VI: Lưu thay đổi vào database - await base.SaveChangesAsync(cancellationToken); - - return true; - } - - /// - /// EN: Begin a new transaction if none is active. - /// VI: Bắt đầu một transaction mới nếu không có transaction nào đang hoạt động. - /// - public async Task BeginTransactionAsync() - { - if (_currentTransaction != null) return null; - - _currentTransaction = await Database.BeginTransactionAsync(System.Data.IsolationLevel.ReadCommitted); - - return _currentTransaction; - } - - /// - /// EN: Commit the current transaction. - /// VI: Commit transaction hiện tại. - /// - public async Task CommitTransactionAsync(IDbContextTransaction transaction) - { - ArgumentNullException.ThrowIfNull(transaction); - - if (transaction != _currentTransaction) - throw new InvalidOperationException($"Transaction {transaction.TransactionId} is not current"); - - try - { - await SaveChangesAsync(); - await transaction.CommitAsync(); - } - catch - { - RollbackTransaction(); - throw; - } - finally - { - if (_currentTransaction != null) - { - _currentTransaction.Dispose(); - _currentTransaction = null; - } - } - } - - /// - /// EN: Rollback the current transaction. - /// VI: Rollback transaction hiện tại. - /// - public void RollbackTransaction() - { - try - { - _currentTransaction?.Rollback(); - } - finally - { - if (_currentTransaction != null) - { - _currentTransaction.Dispose(); - _currentTransaction = null; - } - } - } - - /// - /// EN: Dispatch all domain events from tracked entities. - /// VI: Dispatch tất cả domain events từ các entities đang được track. - /// - private async Task DispatchDomainEventsAsync() - { - var domainEntities = ChangeTracker - .Entries() - .Where(x => x.Entity.DomainEvents.Any()) - .ToList(); - - var domainEvents = domainEntities - .SelectMany(x => x.Entity.DomainEvents) - .ToList(); - - domainEntities.ForEach(entity => entity.Entity.ClearDomainEvents()); - - foreach (var domainEvent in domainEvents) - { - await _mediator.Publish(domainEvent); - } - } -} diff --git a/services/booking-service-net/src/BookingService.Infrastructure/Repositories/SampleRepository.cs b/services/booking-service-net/src/BookingService.Infrastructure/Repositories/SampleRepository.cs deleted file mode 100644 index f880bb59..00000000 --- a/services/booking-service-net/src/BookingService.Infrastructure/Repositories/SampleRepository.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using BookingService.Domain.AggregatesModel.SampleAggregate; -using BookingService.Domain.SeedWork; - -namespace BookingService.Infrastructure.Repositories; - -/// -/// EN: Repository implementation for Sample aggregate. -/// VI: Triển khai repository cho Sample aggregate. -/// -public class SampleRepository : ISampleRepository -{ - private readonly BookingServiceContext _context; - - /// - /// EN: Unit of work for transaction management. - /// VI: Unit of work cho quản lý transaction. - /// - public IUnitOfWork UnitOfWork => _context; - - public SampleRepository(BookingServiceContext context) - { - _context = context ?? throw new ArgumentNullException(nameof(context)); - } - - /// - public async Task GetAsync(Guid sampleId) - { - var sample = await _context.Samples - .Include(s => s.Status) - .FirstOrDefaultAsync(s => s.Id == sampleId); - - return sample; - } - - /// - public async Task> GetAllAsync() - { - return await _context.Samples - .Include(s => s.Status) - .OrderByDescending(s => s.CreatedAt) - .ToListAsync(); - } - - /// - public Sample Add(Sample sample) - { - return _context.Samples.Add(sample).Entity; - } - - /// - public void Update(Sample sample) - { - _context.Entry(sample).State = EntityState.Modified; - } - - /// - public void Delete(Sample sample) - { - _context.Samples.Remove(sample); - } - - /// - public async Task> GetByStatusAsync(int statusId) - { - return await _context.Samples - .Include(s => s.Status) - .Where(s => s.StatusId == statusId) - .OrderByDescending(s => s.CreatedAt) - .ToListAsync(); - } -} diff --git a/services/fnb-engine-net/src/FnbEngine.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs b/services/fnb-engine-net/src/FnbEngine.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs deleted file mode 100644 index 10e7d359..00000000 --- a/services/fnb-engine-net/src/FnbEngine.Domain/AggregatesModel/SampleAggregate/ISampleRepository.cs +++ /dev/null @@ -1,61 +0,0 @@ -using FnbEngine.Domain.SeedWork; - -namespace FnbEngine.Domain.AggregatesModel.SampleAggregate; - -/// -/// EN: Repository interface for Sample aggregate. -/// VI: Interface repository cho Sample aggregate. -/// -/// -/// EN: Following repository pattern, this interface defines the contract -/// for data access operations on Sample aggregate. -/// VI: Theo pattern repository, interface này định nghĩa contract -/// cho các thao tác truy cập dữ liệu trên Sample aggregate. -/// -public interface ISampleRepository : IRepository -{ - /// - /// EN: Get a sample by its ID. - /// VI: Lấy một sample theo ID. - /// - /// EN: The sample ID / VI: ID của sample - /// EN: The sample or null if not found / VI: Sample hoặc null nếu không tìm thấy - Task GetAsync(Guid sampleId); - - /// - /// EN: Get all samples. - /// VI: Lấy tất cả samples. - /// - /// EN: List of samples / VI: Danh sách samples - Task> GetAllAsync(); - - /// - /// EN: Add a new sample. - /// VI: Thêm một sample mới. - /// - /// EN: The sample to add / VI: Sample cần thêm - /// EN: The added sample / VI: Sample đã thêm - Sample Add(Sample sample); - - /// - /// EN: Update an existing sample. - /// VI: Cập nhật một sample đã tồn tại. - /// - /// EN: The sample to update / VI: Sample cần cập nhật - void Update(Sample sample); - - /// - /// EN: Delete a sample. - /// VI: Xóa một sample. - /// - /// EN: The sample to delete / VI: Sample cần xóa - void Delete(Sample sample); - - /// - /// EN: Get samples by status. - /// VI: Lấy samples theo trạng thái. - /// - /// EN: The status ID / VI: ID trạng thái - /// EN: List of samples with given status / VI: Danh sách samples với trạng thái cho trước - Task> GetByStatusAsync(int statusId); -} diff --git a/services/fnb-engine-net/src/FnbEngine.Domain/AggregatesModel/SampleAggregate/Sample.cs b/services/fnb-engine-net/src/FnbEngine.Domain/AggregatesModel/SampleAggregate/Sample.cs deleted file mode 100644 index bbca3285..00000000 --- a/services/fnb-engine-net/src/FnbEngine.Domain/AggregatesModel/SampleAggregate/Sample.cs +++ /dev/null @@ -1,158 +0,0 @@ -using FnbEngine.Domain.Events; -using FnbEngine.Domain.Exceptions; -using FnbEngine.Domain.SeedWork; - -namespace FnbEngine.Domain.AggregatesModel.SampleAggregate; - -/// -/// EN: Sample aggregate root demonstrating DDD patterns. -/// VI: Sample aggregate root minh họa các pattern DDD. -/// -public class Sample : Entity, IAggregateRoot -{ - // EN: Private fields for encapsulation - // VI: Fields private để đóng gói - private string _name = null!; - private string? _description; - private SampleStatus _status = null!; - private DateTime _createdAt; - private DateTime? _updatedAt; - - /// - /// EN: Sample name (required). - /// VI: Tên sample (bắt buộc). - /// - public string Name => _name; - - /// - /// EN: Optional description. - /// VI: Mô tả tùy chọn. - /// - public string? Description => _description; - - /// - /// EN: Current status. - /// VI: Trạng thái hiện tại. - /// - public SampleStatus Status => _status; - - /// - /// EN: Status ID for EF Core mapping. - /// VI: ID trạng thái cho EF Core mapping. - /// - public int StatusId { get; private set; } - - /// - /// EN: Creation timestamp. - /// VI: Thời gian tạo. - /// - public DateTime CreatedAt => _createdAt; - - /// - /// EN: Last update timestamp. - /// VI: Thời gian cập nhật cuối. - /// - public DateTime? UpdatedAt => _updatedAt; - - /// - /// EN: Private constructor for EF Core. - /// VI: Constructor private cho EF Core. - /// - protected Sample() - { - } - - /// - /// EN: Create a new Sample with required information. - /// VI: Tạo một Sample mới với thông tin bắt buộc. - /// - /// EN: Sample name / VI: Tên sample - /// EN: Optional description / VI: Mô tả tùy chọn - public Sample(string name, string? description = null) : this() - { - if (string.IsNullOrWhiteSpace(name)) - throw new SampleDomainException("Sample name cannot be empty"); - - Id = Guid.NewGuid(); - _name = name; - _description = description; - _status = SampleStatus.Draft; - StatusId = SampleStatus.Draft.Id; - _createdAt = DateTime.UtcNow; - - // EN: Add domain event for creation - // VI: Thêm domain event cho việc tạo - AddDomainEvent(new SampleCreatedDomainEvent(this)); - } - - /// - /// EN: Update sample information. - /// VI: Cập nhật thông tin sample. - /// - public void Update(string name, string? description) - { - if (string.IsNullOrWhiteSpace(name)) - throw new SampleDomainException("Sample name cannot be empty"); - - if (_status == SampleStatus.Cancelled) - throw new SampleDomainException("Cannot update a cancelled sample"); - - _name = name; - _description = description; - _updatedAt = DateTime.UtcNow; - } - - /// - /// EN: Activate the sample. - /// VI: Kích hoạt sample. - /// - public void Activate() - { - if (_status != SampleStatus.Draft) - throw new SampleDomainException("Only draft samples can be activated"); - - var previousStatus = _status; - _status = SampleStatus.Active; - StatusId = SampleStatus.Active.Id; - _updatedAt = DateTime.UtcNow; - - AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status)); - } - - /// - /// EN: Complete the sample. - /// VI: Hoàn thành sample. - /// - public void Complete() - { - if (_status != SampleStatus.Active) - throw new SampleDomainException("Only active samples can be completed"); - - var previousStatus = _status; - _status = SampleStatus.Completed; - StatusId = SampleStatus.Completed.Id; - _updatedAt = DateTime.UtcNow; - - AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status)); - } - - /// - /// EN: Cancel the sample. - /// VI: Hủy sample. - /// - public void Cancel() - { - if (_status == SampleStatus.Completed) - throw new SampleDomainException("Cannot cancel a completed sample"); - - if (_status == SampleStatus.Cancelled) - throw new SampleDomainException("Sample is already cancelled"); - - var previousStatus = _status; - _status = SampleStatus.Cancelled; - StatusId = SampleStatus.Cancelled.Id; - _updatedAt = DateTime.UtcNow; - - AddDomainEvent(new SampleStatusChangedDomainEvent(Id, previousStatus, _status)); - } -} diff --git a/services/fnb-engine-net/src/FnbEngine.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs b/services/fnb-engine-net/src/FnbEngine.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs deleted file mode 100644 index fcde41cf..00000000 --- a/services/fnb-engine-net/src/FnbEngine.Domain/AggregatesModel/SampleAggregate/SampleStatus.cs +++ /dev/null @@ -1,77 +0,0 @@ -using FnbEngine.Domain.SeedWork; - -namespace FnbEngine.Domain.AggregatesModel.SampleAggregate; - -/// -/// EN: Sample status enumeration following type-safe enum pattern. -/// VI: Enumeration trạng thái Sample theo pattern enum an toàn kiểu. -/// -public class SampleStatus : Enumeration -{ - /// - /// EN: Draft status - initial state - /// VI: Trạng thái nháp - trạng thái ban đầu - /// - public static SampleStatus Draft = new(1, nameof(Draft)); - - /// - /// EN: Active status - ready for use - /// VI: Trạng thái hoạt động - sẵn sàng sử dụng - /// - public static SampleStatus Active = new(2, nameof(Active)); - - /// - /// EN: Completed status - finished processing - /// VI: Trạng thái hoàn thành - đã xử lý xong - /// - public static SampleStatus Completed = new(3, nameof(Completed)); - - /// - /// EN: Cancelled status - cancelled by user - /// VI: Trạng thái đã hủy - bị hủy bởi người dùng - /// - public static SampleStatus Cancelled = new(4, nameof(Cancelled)); - - public SampleStatus(int id, string name) : base(id, name) - { - } - - /// - /// EN: Get all available statuses. - /// VI: Lấy tất cả các trạng thái có sẵn. - /// - public static IEnumerable List() => GetAll(); - - /// - /// EN: Parse status from name. - /// VI: Parse trạng thái từ tên. - /// - public static SampleStatus FromName(string name) - { - var status = List().SingleOrDefault(s => - string.Equals(s.Name, name, StringComparison.CurrentCultureIgnoreCase)); - - if (status is null) - { - throw new ArgumentException($"Possible values for SampleStatus: {string.Join(",", List().Select(s => s.Name))}"); - } - - return status; - } - - /// - /// EN: Parse status from ID. - /// VI: Parse trạng thái từ ID. - /// - public static SampleStatus From(int id) - { - var status = List().SingleOrDefault(s => s.Id == id); - - if (status is null) - { - throw new ArgumentException($"Possible values for SampleStatus: {string.Join(",", List().Select(s => s.Name))}"); - } - - return status; - } -} diff --git a/services/fnb-engine-net/src/FnbEngine.Domain/Events/SampleCreatedDomainEvent.cs b/services/fnb-engine-net/src/FnbEngine.Domain/Events/SampleCreatedDomainEvent.cs deleted file mode 100644 index 6502fafb..00000000 --- a/services/fnb-engine-net/src/FnbEngine.Domain/Events/SampleCreatedDomainEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using MediatR; -using FnbEngine.Domain.AggregatesModel.SampleAggregate; - -namespace FnbEngine.Domain.Events; - -/// -/// EN: Domain event raised when a new Sample is created. -/// VI: Domain event được phát ra khi một Sample mới được tạo. -/// -public class SampleCreatedDomainEvent : INotification -{ - /// - /// EN: The newly created sample. - /// VI: Sample mới được tạo. - /// - public Sample Sample { get; } - - public SampleCreatedDomainEvent(Sample sample) - { - Sample = sample; - } -} diff --git a/services/fnb-engine-net/src/FnbEngine.Domain/Events/SampleStatusChangedDomainEvent.cs b/services/fnb-engine-net/src/FnbEngine.Domain/Events/SampleStatusChangedDomainEvent.cs deleted file mode 100644 index c84ea1b2..00000000 --- a/services/fnb-engine-net/src/FnbEngine.Domain/Events/SampleStatusChangedDomainEvent.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediatR; -using FnbEngine.Domain.AggregatesModel.SampleAggregate; - -namespace FnbEngine.Domain.Events; - -/// -/// EN: Domain event raised when Sample status changes. -/// VI: Domain event được phát ra khi trạng thái Sample thay đổi. -/// -public class SampleStatusChangedDomainEvent : INotification -{ - /// - /// EN: The sample ID. - /// VI: ID của sample. - /// - public Guid SampleId { get; } - - /// - /// EN: Previous status before the change. - /// VI: Trạng thái trước khi thay đổi. - /// - public SampleStatus PreviousStatus { get; } - - /// - /// EN: New status after the change. - /// VI: Trạng thái mới sau khi thay đổi. - /// - public SampleStatus NewStatus { get; } - - public SampleStatusChangedDomainEvent( - Guid sampleId, - SampleStatus previousStatus, - SampleStatus newStatus) - { - SampleId = sampleId; - PreviousStatus = previousStatus; - NewStatus = newStatus; - } -} diff --git a/services/fnb-engine-net/src/FnbEngine.Domain/Exceptions/SampleDomainException.cs b/services/fnb-engine-net/src/FnbEngine.Domain/Exceptions/SampleDomainException.cs deleted file mode 100644 index b028afb7..00000000 --- a/services/fnb-engine-net/src/FnbEngine.Domain/Exceptions/SampleDomainException.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace FnbEngine.Domain.Exceptions; - -/// -/// EN: Exception for Sample aggregate domain errors. -/// VI: Exception cho các lỗi domain của Sample aggregate. -/// -public class SampleDomainException : DomainException -{ - public SampleDomainException() - { - } - - public SampleDomainException(string message) : base(message) - { - } - - public SampleDomainException(string message, Exception innerException) - : base(message, innerException) - { - } -} diff --git a/services/inventory-service-net/src/InventoryService.Infrastructure/EntityConfigurations/InventoryItemEntityTypeConfiguration.cs b/services/inventory-service-net/src/InventoryService.Infrastructure/EntityConfigurations/InventoryItemEntityTypeConfiguration.cs index cbd98533..d14fe365 100644 --- a/services/inventory-service-net/src/InventoryService.Infrastructure/EntityConfigurations/InventoryItemEntityTypeConfiguration.cs +++ b/services/inventory-service-net/src/InventoryService.Infrastructure/EntityConfigurations/InventoryItemEntityTypeConfiguration.cs @@ -18,10 +18,10 @@ public class InventoryItemEntityTypeConfiguration : IEntityTypeConfiguration("_productId").HasColumnName("product_id").IsRequired(); builder.Property("_shopId").HasColumnName("shop_id").IsRequired(); - builder.Property("_quantityOnHand").HasColumnName("quantity_on_hand").IsRequired(); + builder.Property("_quantity").HasColumnName("quantity").IsRequired(); builder.Property("_reservedQuantity").HasColumnName("reserved_quantity").IsRequired(); builder.Property("_reorderLevel").HasColumnName("reorder_level").HasDefaultValue(10); - builder.Property("_updatedAt").HasColumnName("updated_at").IsRequired(); + builder.Property("_updatedAt").HasColumnName("updated_at"); // Owned InventoryTransaction collection builder.OwnsMany(i => i.Transactions, txn => @@ -50,9 +50,9 @@ public class InventoryItemEntityTypeConfiguration : IEntityTypeConfiguration i.ProductId); builder.Ignore(i => i.ShopId); builder.Ignore(i => i.Quantity); - builder.Ignore(i => i.Reserved); - builder.Ignore(i => i.Available); - builder.Ignore(i => i.ReorderPoint); + builder.Ignore(i => i.ReservedQuantity); + builder.Ignore(i => i.AvailableQuantity); + builder.Ignore(i => i.ReorderLevel); builder.Ignore(i => i.UpdatedAt); } }