From 2866aad3459d887dde040d3e3bcf4ffb94e46fab Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Sun, 18 Jan 2026 00:44:53 +0700 Subject: [PATCH] feat: Replace generic sample implementations with concrete Ads Billing and Order service domain logic. --- .../Commands/ChangeSampleStatusCommand.cs | 14 -- .../ChangeSampleStatusCommandHandler.cs | 70 ------ .../Commands/ChargeAdvertiserCommand.cs | 16 ++ .../ChargeAdvertiserCommandHandler.cs | 62 ++++++ .../Commands/CreateBillingAccountCommand.cs | 14 ++ .../CreateBillingAccountCommandHandler.cs | 39 ++++ .../Commands/CreateSampleCommand.cs | 21 -- .../Commands/CreateSampleCommandHandler.cs | 46 ---- .../Commands/DeleteSampleCommand.cs | 10 - .../Commands/DeleteSampleCommandHandler.cs | 54 ----- .../Commands/UpdateSampleCommand.cs | 16 -- .../Commands/UpdateSampleCommandHandler.cs | 54 ----- .../Application/Queries/GetSampleQuery.cs | 23 -- .../Queries/GetSampleQueryHandler.cs | 39 ---- .../Application/Queries/GetSamplesQuery.cs | 9 - .../Queries/GetSamplesQueryHandler.cs | 34 --- .../CreateSampleCommandValidator.cs | 25 --- .../UpdateSampleCommandValidator.cs | 29 --- .../Controllers/BillingAccountsController.cs | 53 +++++ .../Controllers/SamplesController.cs | 200 ------------------ .../BillingAccountAggregate/BillingAccount.cs | 1 + .../ChargeAggregate/AdCharge.cs | 94 ++++++++ .../InvoiceAggregate/Invoice.cs | 122 +++++++++++ .../Events/SampleCreatedDomainEvent.cs | 22 -- .../Events/SampleStatusChangedDomainEvent.cs | 39 ---- .../Exceptions/AdsBillingDomainException.cs | 22 ++ .../Exceptions/SampleDomainException.cs | 21 -- ...Context.cs => AdsBillingServiceContext.cs} | 62 +----- .../DependencyInjection.cs | 20 +- .../SampleEntityTypeConfiguration.cs | 61 ------ .../SampleStatusEntityTypeConfiguration.cs | 39 ---- .../Repositories/SampleRepository.cs | 72 ------- .../CreateSampleCommandHandlerTests.cs | 65 ------ .../Domain/SampleAggregateTests.cs | 151 ------------- .../Commands/ChangeSampleStatusCommand.cs | 14 -- .../ChangeSampleStatusCommandHandler.cs | 70 ------ .../Commands/CreateSampleCommand.cs | 21 -- .../Commands/CreateSampleCommandHandler.cs | 46 ---- .../Commands/DeleteSampleCommand.cs | 10 - .../Commands/DeleteSampleCommandHandler.cs | 54 ----- .../Commands/UpdateSampleCommand.cs | 16 -- .../Commands/UpdateSampleCommandHandler.cs | 54 ----- .../Application/Queries/GetSampleQuery.cs | 23 -- .../Queries/GetSampleQueryHandler.cs | 39 ---- .../Application/Queries/GetSamplesQuery.cs | 9 - .../Queries/GetSamplesQueryHandler.cs | 34 --- .../CreateSampleCommandValidator.cs | 25 --- .../UpdateSampleCommandValidator.cs | 29 --- .../Controllers/SamplesController.cs | 200 ------------------ .../CreateSampleCommandHandlerTests.cs | 65 ------ .../Behaviors/TransactionBehavior.cs | 4 +- .../Commands/ChangeSampleStatusCommand.cs | 14 -- .../ChangeSampleStatusCommandHandler.cs | 70 ------ .../Commands/CreateSampleCommand.cs | 21 -- .../Commands/CreateSampleCommandHandler.cs | 46 ---- .../Commands/DeleteSampleCommand.cs | 10 - .../Commands/DeleteSampleCommandHandler.cs | 54 ----- .../Commands/UpdateSampleCommand.cs | 16 -- .../Commands/UpdateSampleCommandHandler.cs | 54 ----- .../Application/Queries/GetSampleQuery.cs | 23 -- .../Queries/GetSampleQueryHandler.cs | 39 ---- .../Application/Queries/GetSamplesQuery.cs | 9 - .../Queries/GetSamplesQueryHandler.cs | 34 --- .../CreateSampleCommandValidator.cs | 25 --- .../UpdateSampleCommandValidator.cs | 29 --- .../Controllers/SamplesController.cs | 200 ------------------ .../OrderService.API/OrderService.API.csproj | 4 + .../DependencyInjection.cs | 8 +- .../OrderEntityTypeConfiguration.cs | 123 +++++++++++ .../OrderItemEntityTypeConfiguration.cs | 26 +++ .../OrderStatusEntityTypeConfiguration.cs | 42 ++++ .../SampleEntityTypeConfiguration.cs | 61 ------ .../SampleStatusEntityTypeConfiguration.cs | 39 ---- .../Idempotency/RequestManager.cs | 4 +- .../{MyServiceContext.cs => OrderContext.cs} | 24 +-- .../Repositories/OrderRepository.cs | 56 +++++ .../Repositories/SampleRepository.cs | 72 ------- .../CustomWebApplicationFactory.cs | 8 +- .../CreateSampleCommandHandlerTests.cs | 65 ------ 79 files changed, 707 insertions(+), 2801 deletions(-) delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChangeSampleStatusCommand.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs create mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChargeAdvertiserCommand.cs create mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChargeAdvertiserCommandHandler.cs create mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateBillingAccountCommand.cs create mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateBillingAccountCommandHandler.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateSampleCommand.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateSampleCommandHandler.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/DeleteSampleCommand.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/DeleteSampleCommandHandler.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/UpdateSampleCommand.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/UpdateSampleCommandHandler.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSampleQuery.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSampleQueryHandler.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSamplesQuery.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSamplesQueryHandler.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/CreateSampleCommandValidator.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/UpdateSampleCommandValidator.cs create mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Controllers/BillingAccountsController.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.API/Controllers/SamplesController.cs create mode 100644 services/ads-billing-service-net/src/AdsBillingService.Domain/AggregatesModel/ChargeAggregate/AdCharge.cs create mode 100644 services/ads-billing-service-net/src/AdsBillingService.Domain/AggregatesModel/InvoiceAggregate/Invoice.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.Domain/Events/SampleCreatedDomainEvent.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.Domain/Events/SampleStatusChangedDomainEvent.cs create mode 100644 services/ads-billing-service-net/src/AdsBillingService.Domain/Exceptions/AdsBillingDomainException.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.Domain/Exceptions/SampleDomainException.cs rename services/ads-billing-service-net/src/AdsBillingService.Infrastructure/{MyServiceContext.cs => AdsBillingServiceContext.cs} (59%) delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs delete mode 100644 services/ads-billing-service-net/src/AdsBillingService.Infrastructure/Repositories/SampleRepository.cs delete mode 100644 services/ads-billing-service-net/tests/AdsBillingService.UnitTests/Application/CreateSampleCommandHandlerTests.cs delete mode 100644 services/ads-billing-service-net/tests/AdsBillingService.UnitTests/Domain/SampleAggregateTests.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Commands/ChangeSampleStatusCommand.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Commands/CreateSampleCommand.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Commands/CreateSampleCommandHandler.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Commands/DeleteSampleCommand.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Commands/DeleteSampleCommandHandler.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Commands/UpdateSampleCommand.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Commands/UpdateSampleCommandHandler.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Queries/GetSampleQuery.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Queries/GetSampleQueryHandler.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Queries/GetSamplesQuery.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Queries/GetSamplesQueryHandler.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Validations/CreateSampleCommandValidator.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Application/Validations/UpdateSampleCommandValidator.cs delete mode 100644 services/catalog-service-net/src/CatalogService.API/Controllers/SamplesController.cs delete mode 100644 services/catalog-service-net/tests/CatalogService.UnitTests/Application/CreateSampleCommandHandlerTests.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Commands/ChangeSampleStatusCommand.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Commands/CreateSampleCommand.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Commands/CreateSampleCommandHandler.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Commands/DeleteSampleCommand.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Commands/DeleteSampleCommandHandler.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Commands/UpdateSampleCommand.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Commands/UpdateSampleCommandHandler.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Queries/GetSampleQuery.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Queries/GetSampleQueryHandler.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Queries/GetSamplesQuery.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Queries/GetSamplesQueryHandler.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Validations/CreateSampleCommandValidator.cs delete mode 100644 services/order-service-net/src/OrderService.API/Application/Validations/UpdateSampleCommandValidator.cs delete mode 100644 services/order-service-net/src/OrderService.API/Controllers/SamplesController.cs create mode 100644 services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs create mode 100644 services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderItemEntityTypeConfiguration.cs create mode 100644 services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderStatusEntityTypeConfiguration.cs delete mode 100644 services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs delete mode 100644 services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs rename services/order-service-net/src/OrderService.Infrastructure/{MyServiceContext.cs => OrderContext.cs} (85%) create mode 100644 services/order-service-net/src/OrderService.Infrastructure/Repositories/OrderRepository.cs delete mode 100644 services/order-service-net/src/OrderService.Infrastructure/Repositories/SampleRepository.cs delete mode 100644 services/order-service-net/tests/OrderService.UnitTests/Application/CreateSampleCommandHandlerTests.cs diff --git a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChangeSampleStatusCommand.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChangeSampleStatusCommand.cs deleted file mode 100644 index 479b3a6e..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChangeSampleStatusCommand.cs +++ /dev/null @@ -1,14 +0,0 @@ -using MediatR; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs deleted file mode 100644 index db91111d..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs +++ /dev/null @@ -1,70 +0,0 @@ -using MediatR; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChargeAdvertiserCommand.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChargeAdvertiserCommand.cs new file mode 100644 index 00000000..f2bea86b --- /dev/null +++ b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChargeAdvertiserCommand.cs @@ -0,0 +1,16 @@ +using MediatR; + +namespace AdsBillingService.API.Application.Commands; + +/// +/// EN: Command to charge advertiser for ad impression/click. +/// VI: Command để charge advertiser cho impression/click quảng cáo. +/// +public record ChargeAdvertiserCommand : IRequest +{ + public Guid AdvertiserId { get; init; } + public Guid CampaignId { get; init; } + public Guid AdId { get; init; } + public string ChargeType { get; init; } = null!; // "impression" or "click" + public decimal Amount { get; init; } +} diff --git a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChargeAdvertiserCommandHandler.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChargeAdvertiserCommandHandler.cs new file mode 100644 index 00000000..fea377c6 --- /dev/null +++ b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/ChargeAdvertiserCommandHandler.cs @@ -0,0 +1,62 @@ +using AdsBillingService.Domain.AggregatesModel.ChargeAggregate; +using MediatR; + +namespace AdsBillingService.API.Application.Commands; + +/// +/// EN: Handler for ChargeAdvertiserCommand - processes ad charges and debits from wallet. +/// VI: Handler cho ChargeAdvertiserCommand - xử lý charge quảng cáo và trừ tiền từ ví. +/// +public class ChargeAdvertiserCommandHandler : IRequestHandler +{ + private readonly ILogger _logger; + // private readonly IWalletService _walletService; + + public ChargeAdvertiserCommandHandler(ILogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task Handle(ChargeAdvertiserCommand request, CancellationToken cancellationToken) + { + // Step 1: Create charge record + var chargeType = request.ChargeType.ToLower() == "impression" + ? ChargeType.Impression + : ChargeType.Click; + + var charge = new AdCharge( + request.AdvertiserId, + request.CampaignId, + request.AdId, + chargeType, + request.Amount + ); + + // TODO: Step 2 - Debit from Wallet Service + // var walletDebitResult = await _walletService.DebitBalance( + // advertiserId: request.AdvertiserId, + // amount: request.Amount, + // reference: charge.Id, + // description: $"Ad {chargeType} charge" + // ); + + // if (!walletDebitResult.Success) + // { + // _logger.LogWarning("Failed to debit wallet for advertiser {AdvertiserId}", request.AdvertiserId); + // return false; + // } + + // Step 3: Mark as processed + charge.MarkAsProcessed(); + + // TODO: Step 4 - Save to repository + // _adChargeRepository.Add(charge); + // await _adChargeRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); + + _logger.LogInformation( + "Charged advertiser {AdvertiserId} amount {Amount} for {ChargeType}", + request.AdvertiserId, request.Amount, chargeType); + + return true; + } +} diff --git a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateBillingAccountCommand.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateBillingAccountCommand.cs new file mode 100644 index 00000000..f3aa6e90 --- /dev/null +++ b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateBillingAccountCommand.cs @@ -0,0 +1,14 @@ +using MediatR; + +namespace AdsBillingService.API.Application.Commands; + +/// +/// EN: Command to create a billing account. +/// VI: Command tạo tài khoản billing. +/// +public record CreateBillingAccountCommand : IRequest +{ + public Guid AdvertiserId { get; init; } + public Guid? WalletId { get; init; } + public string PaymentMethod { get; init; } = "prepaid"; // prepaid, postpaid +} diff --git a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateBillingAccountCommandHandler.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateBillingAccountCommandHandler.cs new file mode 100644 index 00000000..6157ae33 --- /dev/null +++ b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateBillingAccountCommandHandler.cs @@ -0,0 +1,39 @@ +using AdsBillingService.Domain.AggregatesModel.BillingAccountAggregate; +using MediatR; + +namespace AdsBillingService.API.Application.Commands; + +public class CreateBillingAccountCommandHandler : IRequestHandler +{ + private readonly ILogger _logger; + + public CreateBillingAccountCommandHandler(ILogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task Handle(CreateBillingAccountCommand request, CancellationToken cancellationToken) + { + var paymentMethod = request.PaymentMethod.ToLower() switch + { + "prepaid" => PaymentMethodType.Prepaid, + "postpaid" => PaymentMethodType.Postpaid, + "creditcard" => PaymentMethodType.CreditCard, + _ => PaymentMethodType.Prepaid + }; + + var billingAccount = new BillingAccount( + request.AdvertiserId, + request.WalletId, + paymentMethod + ); + + // TODO: Add repository save + // _billingAccountRepository.Add(billingAccount); + // await _billingAccountRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); + + _logger.LogInformation("Billing account created for advertiser {AdvertiserId}", request.AdvertiserId); + + return billingAccount.Id; + } +} diff --git a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateSampleCommand.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateSampleCommand.cs deleted file mode 100644 index cc9d9152..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateSampleCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -using MediatR; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateSampleCommandHandler.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateSampleCommandHandler.cs deleted file mode 100644 index 5bb3caee..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/CreateSampleCommandHandler.cs +++ /dev/null @@ -1,46 +0,0 @@ -using MediatR; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/DeleteSampleCommand.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/DeleteSampleCommand.cs deleted file mode 100644 index f7d923b0..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/DeleteSampleCommand.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MediatR; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/DeleteSampleCommandHandler.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/DeleteSampleCommandHandler.cs deleted file mode 100644 index 9725044f..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/DeleteSampleCommandHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using MediatR; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/UpdateSampleCommand.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/UpdateSampleCommand.cs deleted file mode 100644 index ab2aa414..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/UpdateSampleCommand.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MediatR; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/UpdateSampleCommandHandler.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/UpdateSampleCommandHandler.cs deleted file mode 100644 index a230a5a0..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Commands/UpdateSampleCommandHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using MediatR; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSampleQuery.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSampleQuery.cs deleted file mode 100644 index f2d74ae5..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSampleQuery.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MediatR; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSampleQueryHandler.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSampleQueryHandler.cs deleted file mode 100644 index 63220576..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSampleQueryHandler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediatR; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSamplesQuery.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSamplesQuery.cs deleted file mode 100644 index 9abe7c87..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSamplesQuery.cs +++ /dev/null @@ -1,9 +0,0 @@ -using MediatR; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSamplesQueryHandler.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSamplesQueryHandler.cs deleted file mode 100644 index 8be9a460..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Queries/GetSamplesQueryHandler.cs +++ /dev/null @@ -1,34 +0,0 @@ -using MediatR; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/CreateSampleCommandValidator.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/CreateSampleCommandValidator.cs deleted file mode 100644 index fbc288fe..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/CreateSampleCommandValidator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using FluentValidation; -using AdsBillingService.API.Application.Commands; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/UpdateSampleCommandValidator.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/UpdateSampleCommandValidator.cs deleted file mode 100644 index 3a0b8db6..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Application/Validations/UpdateSampleCommandValidator.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FluentValidation; -using AdsBillingService.API.Application.Commands; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.API/Controllers/BillingAccountsController.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Controllers/BillingAccountsController.cs new file mode 100644 index 00000000..7b7f0da8 --- /dev/null +++ b/services/ads-billing-service-net/src/AdsBillingService.API/Controllers/BillingAccountsController.cs @@ -0,0 +1,53 @@ +using AdsBillingService.API.Application.Commands; +using MediatR; +using Microsoft.AspNetCore.Mvc; + +namespace AdsBillingService.API.Controllers; + +/// +/// EN: API Controller for managing billing accounts. +/// VI: API Controller quản lý tài khoản billing. +/// +[ApiController] +[Route("api/v1/ads-billing/accounts")] +[Produces("application/json")] +public class BillingAccountsController : ControllerBase +{ + private readonly IMediator _mediator; + private readonly ILogger _logger; + + public BillingAccountsController(IMediator mediator, ILogger logger) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// EN: Create a new billing account. + /// VI: Tạo tài khoản billing mới. + /// + [HttpPost] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> CreateBillingAccount([FromBody] CreateBillingAccountCommand command) + { + _logger.LogInformation("Creating billing account for advertiser {AdvertiserId}", command.AdvertiserId); + + var accountId = await _mediator.Send(command); + + return CreatedAtAction(nameof(GetBillingAccount), new { id = accountId }, accountId); + } + + /// + /// EN: Get billing account by ID (placeholder). + /// VI: Lấy tài khoản billing theo ID (placeholder). + /// + [HttpGet("{id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetBillingAccount(Guid id) + { + // TODO: Implement GetBillingAccountQuery + return Ok(new { id, message = "Billing account details" }); + } +} diff --git a/services/ads-billing-service-net/src/AdsBillingService.API/Controllers/SamplesController.cs b/services/ads-billing-service-net/src/AdsBillingService.API/Controllers/SamplesController.cs deleted file mode 100644 index 7f0295b1..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.API/Controllers/SamplesController.cs +++ /dev/null @@ -1,200 +0,0 @@ -using Asp.Versioning; -using MediatR; -using Microsoft.AspNetCore.Mvc; -using AdsBillingService.API.Application.Commands; -using AdsBillingService.API.Application.Queries; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.Domain/AggregatesModel/BillingAccountAggregate/BillingAccount.cs b/services/ads-billing-service-net/src/AdsBillingService.Domain/AggregatesModel/BillingAccountAggregate/BillingAccount.cs index cb3e551a..e38e2194 100644 --- a/services/ads-billing-service-net/src/AdsBillingService.Domain/AggregatesModel/BillingAccountAggregate/BillingAccount.cs +++ b/services/ads-billing-service-net/src/AdsBillingService.Domain/AggregatesModel/BillingAccountAggregate/BillingAccount.cs @@ -1,4 +1,5 @@ using AdsBillingService.Domain.SeedWork; +using AdsBillingService.Domain.Exceptions; namespace AdsBillingService.Domain.AggregatesModel.BillingAccountAggregate; diff --git a/services/ads-billing-service-net/src/AdsBillingService.Domain/AggregatesModel/ChargeAggregate/AdCharge.cs b/services/ads-billing-service-net/src/AdsBillingService.Domain/AggregatesModel/ChargeAggregate/AdCharge.cs new file mode 100644 index 00000000..a33fd5f5 --- /dev/null +++ b/services/ads-billing-service-net/src/AdsBillingService.Domain/AggregatesModel/ChargeAggregate/AdCharge.cs @@ -0,0 +1,94 @@ +using AdsBillingService.Domain.SeedWork; +using AdsBillingService.Domain.Exceptions; + +namespace AdsBillingService.Domain.AggregatesModel.ChargeAggregate; + +/// +/// EN: Ad charge aggregate root - individual ad impression/click charge. +/// VI: Ad charge aggregate root - phí riêng lẻ cho impression/click quảng cáo. +/// +public class AdCharge : Entity, IAggregateRoot +{ + private Guid _advertiserId; + private Guid _campaignId; + private Guid _adId; + private ChargeType _chargeType; + private decimal _amount; + private string _currency; + private DateTime _chargedAt; + private bool _processed; + + public Guid AdvertiserId => _advertiserId; + public Guid CampaignId => _campaignId; + public Guid AdId => _adId; + public ChargeType ChargeType => _chargeType; + public decimal Amount => _amount; + public string Currency => _currency; + public DateTime ChargedAt => _chargedAt; + public bool Processed => _processed; + + protected AdCharge() + { + _currency = "VND"; // Default value for EF Core + } + + public AdCharge( + Guid advertiserId, + Guid campaignId, + Guid adId, + ChargeType chargeType, + decimal amount, + string currency = "VND") + { + if (amount <= 0) + throw new AdsBillingDomainException("Charge amount must be positive"); + + Id = Guid.NewGuid(); + _advertiserId = advertiserId; + _campaignId = campaignId; + _adId = adId; + _chargeType = chargeType; + _amount = amount; + _currency = currency; + _chargedAt = DateTime.UtcNow; + _processed = false; + } + + /// + /// EN: Mark charge as processed (billeddebited from wallet). + /// VI: Đánh dấu charge đã xử lý (đã trừ tiền từ ví). + /// + public void MarkAsProcessed() + { + _processed = true; + } + + /// + /// EN: Factory method for impression charge. + /// VI: Factory method cho charge impression. + /// + public static AdCharge ForImpression(Guid advertiserId, Guid campaignId, Guid adId, decimal cpm) + { + return new AdCharge(advertiserId, campaignId, adId, ChargeType.Impression, cpm / 1000); + } + + /// + /// EN: Factory method for click charge. + /// VI: Factory method cho charge click. + /// + public static AdCharge ForClick(Guid advertiserId, Guid campaignId, Guid adId, decimal cpc) + { + return new AdCharge(advertiserId, campaignId, adId, ChargeType.Click, cpc); + } +} + +/// +/// EN: Charge type enumeration. +/// VI: Enum loại charge. +/// +public enum ChargeType +{ + Impression = 1, + Click = 2, + Conversion = 3 +} diff --git a/services/ads-billing-service-net/src/AdsBillingService.Domain/AggregatesModel/InvoiceAggregate/Invoice.cs b/services/ads-billing-service-net/src/AdsBillingService.Domain/AggregatesModel/InvoiceAggregate/Invoice.cs new file mode 100644 index 00000000..f7784371 --- /dev/null +++ b/services/ads-billing-service-net/src/AdsBillingService.Domain/AggregatesModel/InvoiceAggregate/Invoice.cs @@ -0,0 +1,122 @@ +using AdsBillingService.Domain.SeedWork; +using AdsBillingService.Domain.Exceptions; + +namespace AdsBillingService.Domain.AggregatesModel.InvoiceAggregate; + +/// +/// EN: Invoice aggregate root - monthly billing invoice. +/// VI: Invoice aggregate root - hóa đơn thanh toán hàng tháng. +/// +public class Invoice : Entity, IAggregateRoot +{ + private List _lineItems = new(); + private Guid _billingAccountId; + private string _invoiceNumber = null!; + private InvoiceStatus _status; + private DateTime _issueDate; + private DateTime _dueDate; + private decimal _totalAmount; + + public Guid BillingAccountId => _billingAccountId; + public string InvoiceNumber => _invoiceNumber; + public InvoiceStatus Status => _status; + public DateTime IssueDate => _issueDate; + public DateTime DueDate => _dueDate; + public decimal TotalAmount => _totalAmount; + public IReadOnlyCollection LineItems => _lineItems.AsReadOnly(); + + protected Invoice() { } + + public Invoice(Guid billingAccountId, DateTime issueDate, DateTime dueDate) + { + Id = Guid.NewGuid(); + _billingAccountId = billingAccountId; + _invoiceNumber = GenerateInvoiceNumber(); + _status = InvoiceStatus.Draft; + _issueDate = issueDate; + _dueDate = dueDate; + _totalAmount = 0; + } + + /// + /// EN: Add line item for campaign charges. + /// VI: Thêm dòng chi tiết cho chi phí campaign. + /// + public void AddLineItem(Guid campaignId, string description, int quantity, decimal unitPrice) + { + var lineItem = new InvoiceLineItem(campaignId, description, quantity, unitPrice); + _lineItems.Add(lineItem); + RecalculateTotal(); + } + + /// + /// EN: Mark invoice as issued (sent to customer). + /// VI: Đánh dấu hóa đơn đã phát hành (gửi cho khách hàng). + /// + public void Issue() + { + if (_status != InvoiceStatus.Draft) + throw new AdsBillingDomainException("Can only issue draft invoices"); + + _status = InvoiceStatus.Issued; + } + + /// + /// EN: Mark invoice as paid. + /// VI: Đánh dấu hóa đơn đã thanh toán. + /// + public void MarkAsPaid() + { + if (_status != InvoiceStatus.Issued) + throw new AdsBillingDomainException("Can only pay issued invoices"); + + _status = InvoiceStatus.Paid; + } + + private void RecalculateTotal() + { + _totalAmount = _lineItems.Sum(li => li.TotalAmount); + } + + private static string GenerateInvoiceNumber() + { + return $"INV-{DateTime.UtcNow:yyyyMMdd}-{Guid.NewGuid().ToString("N")[..8].ToUpper()}"; + } +} + +/// +/// EN: Invoice line item entity - per-campaign charges. +/// VI: Entity dòng hóa đơn - chi phí theo campaign. +/// +public class InvoiceLineItem : Entity +{ + public Guid CampaignId { get; private set; } + public string Description { get; private set; } = null!; + public int Quantity { get; private set; } + public decimal UnitPrice { get; private set; } + public decimal TotalAmount => Quantity * UnitPrice; + + protected InvoiceLineItem() { } + + public InvoiceLineItem(Guid campaignId, string description, int quantity, decimal unitPrice) + { + Id = Guid.NewGuid(); + CampaignId = campaignId; + Description = description; + Quantity = quantity; + UnitPrice = unitPrice; + } +} + +/// +/// EN: Invoice status enumeration. +/// VI: Enum trạng thái hóa đơn. +/// +public enum InvoiceStatus +{ + Draft = 1, + Issued = 2, + Paid = 3, + Overdue = 4, + Cancelled = 5 +} diff --git a/services/ads-billing-service-net/src/AdsBillingService.Domain/Events/SampleCreatedDomainEvent.cs b/services/ads-billing-service-net/src/AdsBillingService.Domain/Events/SampleCreatedDomainEvent.cs deleted file mode 100644 index 21f7e219..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.Domain/Events/SampleCreatedDomainEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using MediatR; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.Domain/Events/SampleStatusChangedDomainEvent.cs b/services/ads-billing-service-net/src/AdsBillingService.Domain/Events/SampleStatusChangedDomainEvent.cs deleted file mode 100644 index 5854a6ac..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.Domain/Events/SampleStatusChangedDomainEvent.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediatR; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.Domain/Exceptions/AdsBillingDomainException.cs b/services/ads-billing-service-net/src/AdsBillingService.Domain/Exceptions/AdsBillingDomainException.cs new file mode 100644 index 00000000..9e9f1d2a --- /dev/null +++ b/services/ads-billing-service-net/src/AdsBillingService.Domain/Exceptions/AdsBillingDomainException.cs @@ -0,0 +1,22 @@ +namespace AdsBillingService.Domain.Exceptions; + +/// +/// EN: Base exception for all Ads Billing domain exceptions. +/// VI: Exception cơ sở cho tất cả các exception của Ads Billing domain. +/// +public class AdsBillingDomainException : Exception +{ + public AdsBillingDomainException() + { + } + + public AdsBillingDomainException(string message) + : base(message) + { + } + + public AdsBillingDomainException(string message, Exception innerException) + : base(message, innerException) + { + } +} diff --git a/services/ads-billing-service-net/src/AdsBillingService.Domain/Exceptions/SampleDomainException.cs b/services/ads-billing-service-net/src/AdsBillingService.Domain/Exceptions/SampleDomainException.cs deleted file mode 100644 index a8d1b996..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.Domain/Exceptions/SampleDomainException.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.Infrastructure/MyServiceContext.cs b/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/AdsBillingServiceContext.cs similarity index 59% rename from services/ads-billing-service-net/src/AdsBillingService.Infrastructure/MyServiceContext.cs rename to services/ads-billing-service-net/src/AdsBillingService.Infrastructure/AdsBillingServiceContext.cs index 678a841d..2c148f93 100644 --- a/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/MyServiceContext.cs +++ b/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/AdsBillingServiceContext.cs @@ -1,37 +1,23 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; +using AdsBillingService.Domain.AggregatesModel.BillingAccountAggregate; +using AdsBillingService.Domain.AggregatesModel.InvoiceAggregate; +using AdsBillingService.Domain.AggregatesModel.ChargeAggregate; using AdsBillingService.Domain.SeedWork; -using AdsBillingService.Infrastructure.EntityConfigurations; namespace AdsBillingService.Infrastructure; -/// -/// EN: EF Core DbContext for AdsBillingService. -/// VI: EF Core DbContext cho AdsBillingService. -/// public class AdsBillingServiceContext : DbContext, IUnitOfWork { private readonly IMediator _mediator; private IDbContextTransaction? _currentTransaction; - /// - /// EN: Samples table. - /// VI: Bảng Samples. - /// - public DbSet Samples => Set(); + public DbSet BillingAccounts => Set(); + public DbSet Invoices => Set(); + public DbSet AdCharges => 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 AdsBillingServiceContext(DbContextOptions options) : base(options) @@ -42,56 +28,30 @@ public class AdsBillingServiceContext : DbContext, IUnitOfWork public AdsBillingServiceContext(DbContextOptions options, IMediator mediator) : base(options) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); - - System.Diagnostics.Debug.WriteLine("AdsBillingServiceContext::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()); + // Entity configurations will be applied here } - /// - /// 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"); @@ -115,10 +75,6 @@ public class AdsBillingServiceContext : DbContext, IUnitOfWork } } - /// - /// EN: Rollback the current transaction. - /// VI: Rollback transaction hiện tại. - /// public void RollbackTransaction() { try @@ -135,10 +91,6 @@ public class AdsBillingServiceContext : DbContext, IUnitOfWork } } - /// - /// 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 diff --git a/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/DependencyInjection.cs b/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/DependencyInjection.cs index 206b18f6..d9fd1444 100644 --- a/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/DependencyInjection.cs +++ b/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/DependencyInjection.cs @@ -1,27 +1,17 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; using AdsBillingService.Infrastructure.Idempotency; -using AdsBillingService.Infrastructure.Repositories; namespace AdsBillingService.Infrastructure; -/// -/// EN: Dependency injection extensions for Infrastructure layer. -/// VI: Extensions dependency injection cho lớp Infrastructure. -/// public static class DependencyInjection { - /// - /// EN: Add infrastructure services to the DI container. - /// VI: Thêm các services infrastructure vào DI container. - /// public static IServiceCollection AddInfrastructure( this IServiceCollection services, IConfiguration configuration) { - // EN: Add DbContext with PostgreSQL / VI: Thêm DbContext với PostgreSQL + // Add DbContext with PostgreSQL services.AddDbContext(options => { var connectionString = configuration.GetConnectionString("DefaultConnection") @@ -37,8 +27,6 @@ public static class DependencyInjection errorCodesToAdd: null); }); - // EN: Enable sensitive data logging in development only - // VI: Chỉ bật sensitive data logging trong development if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development") { options.EnableSensitiveDataLogging(); @@ -46,10 +34,10 @@ public static class DependencyInjection } }); - // EN: Register repositories / VI: Đăng ký repositories - services.AddScoped(); + // Register repositories (when needed) + // services.AddScoped(); - // EN: Register idempotency services / VI: Đăng ký idempotency services + // Register idempotency services services.AddScoped(); return services; diff --git a/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs b/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs deleted file mode 100644 index 4bff4f5f..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs b/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs deleted file mode 100644 index 31d5d503..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; - -namespace AdsBillingService.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/ads-billing-service-net/src/AdsBillingService.Infrastructure/Repositories/SampleRepository.cs b/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/Repositories/SampleRepository.cs deleted file mode 100644 index f16b14dc..00000000 --- a/services/ads-billing-service-net/src/AdsBillingService.Infrastructure/Repositories/SampleRepository.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; -using AdsBillingService.Domain.SeedWork; - -namespace AdsBillingService.Infrastructure.Repositories; - -/// -/// EN: Repository implementation for Sample aggregate. -/// VI: Triển khai repository cho Sample aggregate. -/// -public class SampleRepository : ISampleRepository -{ - private readonly AdsBillingServiceContext _context; - - /// - /// EN: Unit of work for transaction management. - /// VI: Unit of work cho quản lý transaction. - /// - public IUnitOfWork UnitOfWork => _context; - - public SampleRepository(AdsBillingServiceContext 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/ads-billing-service-net/tests/AdsBillingService.UnitTests/Application/CreateSampleCommandHandlerTests.cs b/services/ads-billing-service-net/tests/AdsBillingService.UnitTests/Application/CreateSampleCommandHandlerTests.cs deleted file mode 100644 index 633b2c88..00000000 --- a/services/ads-billing-service-net/tests/AdsBillingService.UnitTests/Application/CreateSampleCommandHandlerTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.Logging; -using Moq; -using AdsBillingService.API.Application.Commands; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; -using AdsBillingService.Domain.SeedWork; -using Xunit; - -namespace AdsBillingService.UnitTests.Application; - -/// -/// EN: Unit tests for CreateSampleCommandHandler. -/// VI: Unit tests cho CreateSampleCommandHandler. -/// -public class CreateSampleCommandHandlerTests -{ - private readonly Mock _mockRepository; - private readonly Mock> _mockLogger; - private readonly CreateSampleCommandHandler _handler; - - public CreateSampleCommandHandlerTests() - { - _mockRepository = new Mock(); - _mockLogger = new Mock>(); - - var mockUnitOfWork = new Mock(); - mockUnitOfWork.Setup(u => u.SaveEntitiesAsync(It.IsAny())) - .ReturnsAsync(true); - - _mockRepository.SetupGet(r => r.UnitOfWork).Returns(mockUnitOfWork.Object); - - _handler = new CreateSampleCommandHandler(_mockRepository.Object, _mockLogger.Object); - } - - [Fact] - public async Task Handle_WithValidCommand_ShouldCreateSampleAndReturnId() - { - // Arrange - var command = new CreateSampleCommand("Test Sample", "Test Description"); - - _mockRepository.Setup(r => r.Add(It.IsAny())) - .Returns((Sample s) => s); - - // Act - var result = await _handler.Handle(command, CancellationToken.None); - - // Assert - result.Should().NotBeNull(); - result.Id.Should().NotBeEmpty(); - _mockRepository.Verify(r => r.Add(It.IsAny()), Times.Once); - } - - [Fact] - public async Task Handle_WithValidCommand_ShouldCallSaveEntities() - { - // Arrange - var command = new CreateSampleCommand("Test Sample", null); - - // Act - await _handler.Handle(command, CancellationToken.None); - - // Assert - _mockRepository.Verify(r => r.UnitOfWork.SaveEntitiesAsync(It.IsAny()), Times.Once); - } -} diff --git a/services/ads-billing-service-net/tests/AdsBillingService.UnitTests/Domain/SampleAggregateTests.cs b/services/ads-billing-service-net/tests/AdsBillingService.UnitTests/Domain/SampleAggregateTests.cs deleted file mode 100644 index 9575b181..00000000 --- a/services/ads-billing-service-net/tests/AdsBillingService.UnitTests/Domain/SampleAggregateTests.cs +++ /dev/null @@ -1,151 +0,0 @@ -using FluentAssertions; -using AdsBillingService.Domain.AggregatesModel.SampleAggregate; -using AdsBillingService.Domain.Exceptions; -using Xunit; - -namespace AdsBillingService.UnitTests.Domain; - -/// -/// EN: Unit tests for Sample aggregate. -/// VI: Unit tests cho Sample aggregate. -/// -public class SampleAggregateTests -{ - [Fact] - public void CreateSample_WithValidName_ShouldCreateWithDraftStatus() - { - // Arrange - var name = "Test Sample"; - var description = "Test Description"; - - // Act - var sample = new Sample(name, description); - - // Assert - sample.Name.Should().Be(name); - sample.Description.Should().Be(description); - sample.Status.Should().Be(SampleStatus.Draft); - sample.Id.Should().NotBeEmpty(); - sample.DomainEvents.Should().ContainSingle(); // SampleCreatedDomainEvent - } - - [Fact] - public void CreateSample_WithEmptyName_ShouldThrowException() - { - // Arrange - var name = ""; - - // Act - var act = () => new Sample(name); - - // Assert - act.Should().Throw() - .WithMessage("Sample name cannot be empty"); - } - - [Fact] - public void Activate_WhenDraft_ShouldChangeToActive() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.ClearDomainEvents(); - - // Act - sample.Activate(); - - // Assert - sample.Status.Should().Be(SampleStatus.Active); - sample.DomainEvents.Should().ContainSingle(); // SampleStatusChangedDomainEvent - } - - [Fact] - public void Activate_WhenNotDraft_ShouldThrowException() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.Activate(); - - // Act - var act = () => sample.Activate(); - - // Assert - act.Should().Throw() - .WithMessage("Only draft samples can be activated"); - } - - [Fact] - public void Complete_WhenActive_ShouldChangeToCompleted() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.Activate(); - sample.ClearDomainEvents(); - - // Act - sample.Complete(); - - // Assert - sample.Status.Should().Be(SampleStatus.Completed); - } - - [Fact] - public void Cancel_WhenDraftOrActive_ShouldChangeToCancelled() - { - // Arrange - var sample = new Sample("Test Sample"); - - // Act - sample.Cancel(); - - // Assert - sample.Status.Should().Be(SampleStatus.Cancelled); - } - - [Fact] - public void Cancel_WhenCompleted_ShouldThrowException() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.Activate(); - sample.Complete(); - - // Act - var act = () => sample.Cancel(); - - // Assert - act.Should().Throw() - .WithMessage("Cannot cancel a completed sample"); - } - - [Fact] - public void Update_WhenNotCancelled_ShouldUpdateNameAndDescription() - { - // Arrange - var sample = new Sample("Original Name", "Original Description"); - var newName = "Updated Name"; - var newDescription = "Updated Description"; - - // Act - sample.Update(newName, newDescription); - - // Assert - sample.Name.Should().Be(newName); - sample.Description.Should().Be(newDescription); - sample.UpdatedAt.Should().NotBeNull(); - } - - [Fact] - public void Update_WhenCancelled_ShouldThrowException() - { - // Arrange - var sample = new Sample("Test Sample"); - sample.Cancel(); - - // Act - var act = () => sample.Update("New Name", null); - - // Assert - act.Should().Throw() - .WithMessage("Cannot update a cancelled sample"); - } -} diff --git a/services/catalog-service-net/src/CatalogService.API/Application/Commands/ChangeSampleStatusCommand.cs b/services/catalog-service-net/src/CatalogService.API/Application/Commands/ChangeSampleStatusCommand.cs deleted file mode 100644 index 9877c18d..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Commands/ChangeSampleStatusCommand.cs +++ /dev/null @@ -1,14 +0,0 @@ -using MediatR; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs b/services/catalog-service-net/src/CatalogService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs deleted file mode 100644 index 45723e24..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs +++ /dev/null @@ -1,70 +0,0 @@ -using MediatR; -using CatalogService.Domain.AggregatesModel.SampleAggregate; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Application/Commands/CreateSampleCommand.cs b/services/catalog-service-net/src/CatalogService.API/Application/Commands/CreateSampleCommand.cs deleted file mode 100644 index 4a3a55fa..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Commands/CreateSampleCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -using MediatR; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Application/Commands/CreateSampleCommandHandler.cs b/services/catalog-service-net/src/CatalogService.API/Application/Commands/CreateSampleCommandHandler.cs deleted file mode 100644 index 38159eac..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Commands/CreateSampleCommandHandler.cs +++ /dev/null @@ -1,46 +0,0 @@ -using MediatR; -using CatalogService.Domain.AggregatesModel.SampleAggregate; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Application/Commands/DeleteSampleCommand.cs b/services/catalog-service-net/src/CatalogService.API/Application/Commands/DeleteSampleCommand.cs deleted file mode 100644 index 5efdacbb..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Commands/DeleteSampleCommand.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MediatR; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Application/Commands/DeleteSampleCommandHandler.cs b/services/catalog-service-net/src/CatalogService.API/Application/Commands/DeleteSampleCommandHandler.cs deleted file mode 100644 index 10236b54..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Commands/DeleteSampleCommandHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using MediatR; -using CatalogService.Domain.AggregatesModel.SampleAggregate; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Application/Commands/UpdateSampleCommand.cs b/services/catalog-service-net/src/CatalogService.API/Application/Commands/UpdateSampleCommand.cs deleted file mode 100644 index b41fc95a..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Commands/UpdateSampleCommand.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MediatR; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Application/Commands/UpdateSampleCommandHandler.cs b/services/catalog-service-net/src/CatalogService.API/Application/Commands/UpdateSampleCommandHandler.cs deleted file mode 100644 index d6356ee8..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Commands/UpdateSampleCommandHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using MediatR; -using CatalogService.Domain.AggregatesModel.SampleAggregate; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Application/Queries/GetSampleQuery.cs b/services/catalog-service-net/src/CatalogService.API/Application/Queries/GetSampleQuery.cs deleted file mode 100644 index ded09d79..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Queries/GetSampleQuery.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MediatR; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Application/Queries/GetSampleQueryHandler.cs b/services/catalog-service-net/src/CatalogService.API/Application/Queries/GetSampleQueryHandler.cs deleted file mode 100644 index 7377b77c..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Queries/GetSampleQueryHandler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediatR; -using CatalogService.Domain.AggregatesModel.SampleAggregate; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Application/Queries/GetSamplesQuery.cs b/services/catalog-service-net/src/CatalogService.API/Application/Queries/GetSamplesQuery.cs deleted file mode 100644 index cd14eb74..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Queries/GetSamplesQuery.cs +++ /dev/null @@ -1,9 +0,0 @@ -using MediatR; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Application/Queries/GetSamplesQueryHandler.cs b/services/catalog-service-net/src/CatalogService.API/Application/Queries/GetSamplesQueryHandler.cs deleted file mode 100644 index 864f6f7e..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Queries/GetSamplesQueryHandler.cs +++ /dev/null @@ -1,34 +0,0 @@ -using MediatR; -using CatalogService.Domain.AggregatesModel.SampleAggregate; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Application/Validations/CreateSampleCommandValidator.cs b/services/catalog-service-net/src/CatalogService.API/Application/Validations/CreateSampleCommandValidator.cs deleted file mode 100644 index 64e8697f..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Validations/CreateSampleCommandValidator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using FluentValidation; -using CatalogService.API.Application.Commands; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Application/Validations/UpdateSampleCommandValidator.cs b/services/catalog-service-net/src/CatalogService.API/Application/Validations/UpdateSampleCommandValidator.cs deleted file mode 100644 index c078e179..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Application/Validations/UpdateSampleCommandValidator.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FluentValidation; -using CatalogService.API.Application.Commands; - -namespace CatalogService.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/catalog-service-net/src/CatalogService.API/Controllers/SamplesController.cs b/services/catalog-service-net/src/CatalogService.API/Controllers/SamplesController.cs deleted file mode 100644 index 0002da87..00000000 --- a/services/catalog-service-net/src/CatalogService.API/Controllers/SamplesController.cs +++ /dev/null @@ -1,200 +0,0 @@ -using Asp.Versioning; -using MediatR; -using Microsoft.AspNetCore.Mvc; -using CatalogService.API.Application.Commands; -using CatalogService.API.Application.Queries; - -namespace CatalogService.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/catalog-service-net/tests/CatalogService.UnitTests/Application/CreateSampleCommandHandlerTests.cs b/services/catalog-service-net/tests/CatalogService.UnitTests/Application/CreateSampleCommandHandlerTests.cs deleted file mode 100644 index 657d76c2..00000000 --- a/services/catalog-service-net/tests/CatalogService.UnitTests/Application/CreateSampleCommandHandlerTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.Logging; -using Moq; -using CatalogService.API.Application.Commands; -using CatalogService.Domain.AggregatesModel.SampleAggregate; -using CatalogService.Domain.SeedWork; -using Xunit; - -namespace CatalogService.UnitTests.Application; - -/// -/// EN: Unit tests for CreateSampleCommandHandler. -/// VI: Unit tests cho CreateSampleCommandHandler. -/// -public class CreateSampleCommandHandlerTests -{ - private readonly Mock _mockRepository; - private readonly Mock> _mockLogger; - private readonly CreateSampleCommandHandler _handler; - - public CreateSampleCommandHandlerTests() - { - _mockRepository = new Mock(); - _mockLogger = new Mock>(); - - var mockUnitOfWork = new Mock(); - mockUnitOfWork.Setup(u => u.SaveEntitiesAsync(It.IsAny())) - .ReturnsAsync(true); - - _mockRepository.SetupGet(r => r.UnitOfWork).Returns(mockUnitOfWork.Object); - - _handler = new CreateSampleCommandHandler(_mockRepository.Object, _mockLogger.Object); - } - - [Fact] - public async Task Handle_WithValidCommand_ShouldCreateSampleAndReturnId() - { - // Arrange - var command = new CreateSampleCommand("Test Sample", "Test Description"); - - _mockRepository.Setup(r => r.Add(It.IsAny())) - .Returns((Sample s) => s); - - // Act - var result = await _handler.Handle(command, CancellationToken.None); - - // Assert - result.Should().NotBeNull(); - result.Id.Should().NotBeEmpty(); - _mockRepository.Verify(r => r.Add(It.IsAny()), Times.Once); - } - - [Fact] - public async Task Handle_WithValidCommand_ShouldCallSaveEntities() - { - // Arrange - var command = new CreateSampleCommand("Test Sample", null); - - // Act - await _handler.Handle(command, CancellationToken.None); - - // Assert - _mockRepository.Verify(r => r.UnitOfWork.SaveEntitiesAsync(It.IsAny()), Times.Once); - } -} diff --git a/services/order-service-net/src/OrderService.API/Application/Behaviors/TransactionBehavior.cs b/services/order-service-net/src/OrderService.API/Application/Behaviors/TransactionBehavior.cs index 0933f6bc..78100a0a 100644 --- a/services/order-service-net/src/OrderService.API/Application/Behaviors/TransactionBehavior.cs +++ b/services/order-service-net/src/OrderService.API/Application/Behaviors/TransactionBehavior.cs @@ -13,11 +13,11 @@ namespace OrderService.API.Application.Behaviors; public class TransactionBehavior : IPipelineBehavior where TRequest : IRequest { - private readonly OrderServiceContext _dbContext; + private readonly OrderContext _dbContext; private readonly ILogger> _logger; public TransactionBehavior( - OrderServiceContext dbContext, + OrderContext dbContext, ILogger> logger) { _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); diff --git a/services/order-service-net/src/OrderService.API/Application/Commands/ChangeSampleStatusCommand.cs b/services/order-service-net/src/OrderService.API/Application/Commands/ChangeSampleStatusCommand.cs deleted file mode 100644 index d5282db4..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Commands/ChangeSampleStatusCommand.cs +++ /dev/null @@ -1,14 +0,0 @@ -using MediatR; - -namespace OrderService.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/order-service-net/src/OrderService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs b/services/order-service-net/src/OrderService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs deleted file mode 100644 index 4f8e8974..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Commands/ChangeSampleStatusCommandHandler.cs +++ /dev/null @@ -1,70 +0,0 @@ -using MediatR; -using OrderService.Domain.AggregatesModel.SampleAggregate; - -namespace OrderService.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/order-service-net/src/OrderService.API/Application/Commands/CreateSampleCommand.cs b/services/order-service-net/src/OrderService.API/Application/Commands/CreateSampleCommand.cs deleted file mode 100644 index 4bd435c2..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Commands/CreateSampleCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -using MediatR; - -namespace OrderService.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/order-service-net/src/OrderService.API/Application/Commands/CreateSampleCommandHandler.cs b/services/order-service-net/src/OrderService.API/Application/Commands/CreateSampleCommandHandler.cs deleted file mode 100644 index 9f7320e4..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Commands/CreateSampleCommandHandler.cs +++ /dev/null @@ -1,46 +0,0 @@ -using MediatR; -using OrderService.Domain.AggregatesModel.SampleAggregate; - -namespace OrderService.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/order-service-net/src/OrderService.API/Application/Commands/DeleteSampleCommand.cs b/services/order-service-net/src/OrderService.API/Application/Commands/DeleteSampleCommand.cs deleted file mode 100644 index 8181c7ea..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Commands/DeleteSampleCommand.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MediatR; - -namespace OrderService.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/order-service-net/src/OrderService.API/Application/Commands/DeleteSampleCommandHandler.cs b/services/order-service-net/src/OrderService.API/Application/Commands/DeleteSampleCommandHandler.cs deleted file mode 100644 index fe84d8f6..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Commands/DeleteSampleCommandHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using MediatR; -using OrderService.Domain.AggregatesModel.SampleAggregate; - -namespace OrderService.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/order-service-net/src/OrderService.API/Application/Commands/UpdateSampleCommand.cs b/services/order-service-net/src/OrderService.API/Application/Commands/UpdateSampleCommand.cs deleted file mode 100644 index 7120c8be..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Commands/UpdateSampleCommand.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MediatR; - -namespace OrderService.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/order-service-net/src/OrderService.API/Application/Commands/UpdateSampleCommandHandler.cs b/services/order-service-net/src/OrderService.API/Application/Commands/UpdateSampleCommandHandler.cs deleted file mode 100644 index e8bdcb85..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Commands/UpdateSampleCommandHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -using MediatR; -using OrderService.Domain.AggregatesModel.SampleAggregate; - -namespace OrderService.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/order-service-net/src/OrderService.API/Application/Queries/GetSampleQuery.cs b/services/order-service-net/src/OrderService.API/Application/Queries/GetSampleQuery.cs deleted file mode 100644 index 683452eb..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Queries/GetSampleQuery.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MediatR; - -namespace OrderService.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/order-service-net/src/OrderService.API/Application/Queries/GetSampleQueryHandler.cs b/services/order-service-net/src/OrderService.API/Application/Queries/GetSampleQueryHandler.cs deleted file mode 100644 index d1b164ff..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Queries/GetSampleQueryHandler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediatR; -using OrderService.Domain.AggregatesModel.SampleAggregate; - -namespace OrderService.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/order-service-net/src/OrderService.API/Application/Queries/GetSamplesQuery.cs b/services/order-service-net/src/OrderService.API/Application/Queries/GetSamplesQuery.cs deleted file mode 100644 index bee0ad2e..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Queries/GetSamplesQuery.cs +++ /dev/null @@ -1,9 +0,0 @@ -using MediatR; - -namespace OrderService.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/order-service-net/src/OrderService.API/Application/Queries/GetSamplesQueryHandler.cs b/services/order-service-net/src/OrderService.API/Application/Queries/GetSamplesQueryHandler.cs deleted file mode 100644 index a58205a1..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Queries/GetSamplesQueryHandler.cs +++ /dev/null @@ -1,34 +0,0 @@ -using MediatR; -using OrderService.Domain.AggregatesModel.SampleAggregate; - -namespace OrderService.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/order-service-net/src/OrderService.API/Application/Validations/CreateSampleCommandValidator.cs b/services/order-service-net/src/OrderService.API/Application/Validations/CreateSampleCommandValidator.cs deleted file mode 100644 index e6f20d3a..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Validations/CreateSampleCommandValidator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using FluentValidation; -using OrderService.API.Application.Commands; - -namespace OrderService.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/order-service-net/src/OrderService.API/Application/Validations/UpdateSampleCommandValidator.cs b/services/order-service-net/src/OrderService.API/Application/Validations/UpdateSampleCommandValidator.cs deleted file mode 100644 index ca9f6bc3..00000000 --- a/services/order-service-net/src/OrderService.API/Application/Validations/UpdateSampleCommandValidator.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FluentValidation; -using OrderService.API.Application.Commands; - -namespace OrderService.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/order-service-net/src/OrderService.API/Controllers/SamplesController.cs b/services/order-service-net/src/OrderService.API/Controllers/SamplesController.cs deleted file mode 100644 index b5085ba2..00000000 --- a/services/order-service-net/src/OrderService.API/Controllers/SamplesController.cs +++ /dev/null @@ -1,200 +0,0 @@ -using Asp.Versioning; -using MediatR; -using Microsoft.AspNetCore.Mvc; -using OrderService.API.Application.Commands; -using OrderService.API.Application.Queries; - -namespace OrderService.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/order-service-net/src/OrderService.API/OrderService.API.csproj b/services/order-service-net/src/OrderService.API/OrderService.API.csproj index e16dda24..50dcdef1 100644 --- a/services/order-service-net/src/OrderService.API/OrderService.API.csproj +++ b/services/order-service-net/src/OrderService.API/OrderService.API.csproj @@ -14,6 +14,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/services/order-service-net/src/OrderService.Infrastructure/DependencyInjection.cs b/services/order-service-net/src/OrderService.Infrastructure/DependencyInjection.cs index b6809de5..1e383afb 100644 --- a/services/order-service-net/src/OrderService.Infrastructure/DependencyInjection.cs +++ b/services/order-service-net/src/OrderService.Infrastructure/DependencyInjection.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using OrderService.Domain.AggregatesModel.SampleAggregate; +using OrderService.Domain.AggregatesModel.OrderAggregate; using OrderService.Infrastructure.Idempotency; using OrderService.Infrastructure.Repositories; @@ -22,7 +22,7 @@ public static class DependencyInjection IConfiguration configuration) { // EN: Add DbContext with PostgreSQL / VI: Thêm DbContext với PostgreSQL - services.AddDbContext(options => + services.AddDbContext(options => { var connectionString = configuration.GetConnectionString("DefaultConnection") ?? configuration["DATABASE_URL"] @@ -30,7 +30,7 @@ public static class DependencyInjection options.UseNpgsql(connectionString, npgsqlOptions => { - npgsqlOptions.MigrationsAssembly(typeof(OrderServiceContext).Assembly.FullName); + npgsqlOptions.MigrationsAssembly(typeof(OrderContext).Assembly.FullName); npgsqlOptions.EnableRetryOnFailure( maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), @@ -47,7 +47,7 @@ public static class DependencyInjection }); // EN: Register repositories / VI: Đăng ký repositories - services.AddScoped(); + services.AddScoped(); // EN: Register idempotency services / VI: Đăng ký idempotency services services.AddScoped(); diff --git a/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs b/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs new file mode 100644 index 00000000..6beeadae --- /dev/null +++ b/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderEntityTypeConfiguration.cs @@ -0,0 +1,123 @@ +// EN: Order entity configuration. +// VI: Cấu hình entity Order. + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using OrderService.Domain.AggregatesModel.OrderAggregate; + +namespace OrderService.Infrastructure.EntityConfigurations; + +/// +/// EN: EF Core configuration for Order entity. +/// VI: Cấu hình EF Core cho Order entity. +/// +public class OrderEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("orders"); + + builder.HasKey(o => o.Id); + + builder.Property(o => o.Id) + .HasColumnName("id") + .ValueGeneratedNever(); + + builder.Property("_shopId") + .HasColumnName("shop_id") + .IsRequired(); + + builder.Property("_customerId") + .HasColumnName("customer_id"); + + builder.Property(o => o.StatusId) + .HasColumnName("status_id") + .IsRequired(); + + builder.Property("_totalAmount") + .HasColumnName("total_amount") + .HasColumnType("decimal(18,2)") + .IsRequired(); + + builder.Property("_notes") + .HasColumnName("notes") + .HasMaxLength(2000); + + builder.Property("_createdAt") + .HasColumnName("created_at") + .IsRequired(); + + builder.Property("_updatedAt") + .HasColumnName("updated_at"); + + // EN: OrderItems collection + // VI: Collection OrderItems + builder.OwnsMany("_orderItems", oi => + { + oi.ToTable("order_items"); + + oi.WithOwner().HasForeignKey("OrderId"); + + oi.Property("OrderId") + .HasColumnName("order_id"); + + oi.HasKey("Id"); + + oi.Property(x => x.Id) + .HasColumnName("id") + .ValueGeneratedNever(); + + oi.Property("_productId") + .HasColumnName("product_id") + .IsRequired(); + + oi.Property("_productName") + .HasColumnName("product_name") + .HasMaxLength(255) + .IsRequired(); + + oi.Property(x => x.ProductTypeId) + .HasColumnName("product_type_id") + .IsRequired(); + + oi.Property("_quantity") + .HasColumnName("quantity") + .IsRequired(); + + oi.Property("_unitPrice") + .HasColumnName("unit_price") + .HasColumnType("decimal(18,2)") + .IsRequired(); + + oi.Property("_totalPrice") + .HasColumnName("total_price") + .HasColumnType("decimal(18,2)") + .IsRequired(); + + oi.Ignore(x => x.ProductId); + oi.Ignore(x => x.ProductName); + oi.Ignore(x => x.ProductType); + oi.Ignore(x => x.Quantity); + oi.Ignore(x => x.UnitPrice); + oi.Ignore(x => x.TotalPrice); + }); + + // EN: Indexes + // VI: Indexes + builder.HasIndex("_shopId").HasDatabaseName("ix_orders_shop_id"); + builder.HasIndex("_customerId").HasDatabaseName("ix_orders_customer_id"); + builder.HasIndex(o => o.StatusId).HasDatabaseName("ix_orders_status_id"); + builder.HasIndex("_createdAt").HasDatabaseName("ix_orders_created_at"); + + // EN: Ignore calculated properties + // VI: Bỏ qua các properties được tính toán + builder.Ignore(o => o.ShopId); + builder.Ignore(o => o.CustomerId); + builder.Ignore(o => o.Status); + builder.Ignore(o => o.OrderItems); + builder.Ignore(o => o.TotalAmount); + builder.Ignore(o => o.Notes); + builder.Ignore(o => o.CreatedAt); + builder.Ignore(o => o.UpdatedAt); + } +} diff --git a/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderItemEntityTypeConfiguration.cs b/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderItemEntityTypeConfiguration.cs new file mode 100644 index 00000000..062ed4f8 --- /dev/null +++ b/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderItemEntityTypeConfiguration.cs @@ -0,0 +1,26 @@ +// EN: OrderItem entity configuration (owned type). +// VI: Cấu hình entity OrderItem (owned type). + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using OrderService.Domain.AggregatesModel.OrderAggregate; + +namespace OrderService.Infrastructure.EntityConfigurations; + +/// +/// EN: EF Core configuration for OrderItem entity (configured as owned type in OrderEntityTypeConfiguration). +/// VI: Cấu hình EF Core cho OrderItem entity (được cấu hình như owned type trong OrderEntityTypeConfiguration). +/// +/// +/// EN: OrderItem is configured as an owned type within Order aggregate. +/// VI: OrderItem được cấu hình như owned type trong Order aggregate. +/// +public class OrderItemEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + // EN: OrderItem is configured as owned type in OrderEntityTypeConfiguration + // VI: OrderItem được cấu hình như owned type trong OrderEntityTypeConfiguration + builder.Ignore(o => o.Id); + } +} diff --git a/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderStatusEntityTypeConfiguration.cs b/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderStatusEntityTypeConfiguration.cs new file mode 100644 index 00000000..9598aaef --- /dev/null +++ b/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/OrderStatusEntityTypeConfiguration.cs @@ -0,0 +1,42 @@ +// EN: OrderStatus enumeration entity configuration. +// VI: Cấu hình entity cho OrderStatus enumeration. + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using OrderService.Domain.AggregatesModel.OrderAggregate; + +namespace OrderService.Infrastructure.EntityConfigurations; + +/// +/// EN: EF Core configuration for OrderStatus enumeration. +/// VI: Cấu hình EF Core cho OrderStatus enumeration. +/// +public class OrderStatusEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("order_statuses"); + + builder.HasKey(s => s.Id); + + builder.Property(s => s.Id) + .HasColumnName("id") + .ValueGeneratedNever(); + + builder.Property(s => s.Name) + .HasColumnName("name") + .HasMaxLength(50) + .IsRequired(); + + // EN: Seed data for OrderStatus + // VI: Dữ liệu seed cho OrderStatus + builder.HasData( + OrderStatus.Draft, + OrderStatus.Validated, + OrderStatus.Paid, + OrderStatus.Processing, + OrderStatus.Completed, + OrderStatus.Cancelled + ); + } +} diff --git a/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs b/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs deleted file mode 100644 index cef94859..00000000 --- a/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/SampleEntityTypeConfiguration.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using OrderService.Domain.AggregatesModel.SampleAggregate; - -namespace OrderService.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/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs b/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs deleted file mode 100644 index bba3d930..00000000 --- a/services/order-service-net/src/OrderService.Infrastructure/EntityConfigurations/SampleStatusEntityTypeConfiguration.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using OrderService.Domain.AggregatesModel.SampleAggregate; - -namespace OrderService.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/order-service-net/src/OrderService.Infrastructure/Idempotency/RequestManager.cs b/services/order-service-net/src/OrderService.Infrastructure/Idempotency/RequestManager.cs index 61dcfa50..6747f8b4 100644 --- a/services/order-service-net/src/OrderService.Infrastructure/Idempotency/RequestManager.cs +++ b/services/order-service-net/src/OrderService.Infrastructure/Idempotency/RequestManager.cs @@ -8,9 +8,9 @@ namespace OrderService.Infrastructure.Idempotency; /// public class RequestManager : IRequestManager { - private readonly OrderServiceContext _context; + private readonly OrderContext _context; - public RequestManager(OrderServiceContext context) + public RequestManager(OrderContext context) { _context = context ?? throw new ArgumentNullException(nameof(context)); } diff --git a/services/order-service-net/src/OrderService.Infrastructure/MyServiceContext.cs b/services/order-service-net/src/OrderService.Infrastructure/OrderContext.cs similarity index 85% rename from services/order-service-net/src/OrderService.Infrastructure/MyServiceContext.cs rename to services/order-service-net/src/OrderService.Infrastructure/OrderContext.cs index 536bbff1..b972ca90 100644 --- a/services/order-service-net/src/OrderService.Infrastructure/MyServiceContext.cs +++ b/services/order-service-net/src/OrderService.Infrastructure/OrderContext.cs @@ -1,7 +1,7 @@ using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; -using OrderService.Domain.AggregatesModel.SampleAggregate; +using OrderService.Domain.AggregatesModel.OrderAggregate; using OrderService.Domain.SeedWork; using OrderService.Infrastructure.EntityConfigurations; @@ -11,16 +11,16 @@ namespace OrderService.Infrastructure; /// EN: EF Core DbContext for OrderService. /// VI: EF Core DbContext cho OrderService. /// -public class OrderServiceContext : DbContext, IUnitOfWork +public class OrderContext : DbContext, IUnitOfWork { private readonly IMediator _mediator; private IDbContextTransaction? _currentTransaction; /// - /// EN: Samples table. - /// VI: Bảng Samples. + /// EN: Orders table. + /// VI: Bảng Orders. /// - public DbSet Samples => Set(); + public DbSet Orders => Set(); /// /// EN: Read-only access to current transaction. @@ -34,24 +34,20 @@ public class OrderServiceContext : DbContext, IUnitOfWork /// public bool HasActiveTransaction => _currentTransaction != null; - public OrderServiceContext(DbContextOptions options) : base(options) - { - _mediator = null!; - } - - public OrderServiceContext(DbContextOptions options, IMediator mediator) : base(options) + public OrderContext(DbContextOptions options, IMediator mediator) : base(options) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); - System.Diagnostics.Debug.WriteLine("OrderServiceContext::ctor - " + GetHashCode()); + System.Diagnostics.Debug.WriteLine("OrderContext::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()); + modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration()); + modelBuilder.ApplyConfiguration(new OrderItemEntityTypeConfiguration()); + modelBuilder.ApplyConfiguration(new OrderStatusEntityTypeConfiguration()); } /// diff --git a/services/order-service-net/src/OrderService.Infrastructure/Repositories/OrderRepository.cs b/services/order-service-net/src/OrderService.Infrastructure/Repositories/OrderRepository.cs new file mode 100644 index 00000000..574c144a --- /dev/null +++ b/services/order-service-net/src/OrderService.Infrastructure/Repositories/OrderRepository.cs @@ -0,0 +1,56 @@ +// EN: Order repository implementation. +// VI: Implementation repository Order. + +using OrderService.Domain.AggregatesModel.OrderAggregate; +using OrderService.Domain.SeedWork; +using Microsoft.EntityFrameworkCore; + +namespace OrderService.Infrastructure.Repositories; + +/// +/// EN: Repository implementation for Order aggregate. +/// VI: Implementation repository cho Order aggregate. +/// +public class OrderRepository : IOrderRepository +{ + private readonly OrderContext _context; + + public IUnitOfWork UnitOfWork => _context; + + public OrderRepository(OrderContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public Order Add(Order order) + { + return _context.Orders.Add(order).Entity; + } + + public void Update(Order order) + { + _context.Entry(order).State = EntityState.Modified; + } + + public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) + { + return await _context.Orders + .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); + } + + public async Task> GetByShopIdAsync(Guid shopId, CancellationToken cancellationToken = default) + { + return await _context.Orders + .Where(o => o.ShopId == shopId) + .OrderByDescending(o => o.CreatedAt) + .ToListAsync(cancellationToken); + } + + public async Task> GetByCustomerIdAsync(Guid customerId, CancellationToken cancellationToken = default) + { + return await _context.Orders + .Where(o => o.CustomerId == customerId) + .OrderByDescending(o => o.CreatedAt) + .ToListAsync(cancellationToken); + } +} diff --git a/services/order-service-net/src/OrderService.Infrastructure/Repositories/SampleRepository.cs b/services/order-service-net/src/OrderService.Infrastructure/Repositories/SampleRepository.cs deleted file mode 100644 index 8e558ea5..00000000 --- a/services/order-service-net/src/OrderService.Infrastructure/Repositories/SampleRepository.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using OrderService.Domain.AggregatesModel.SampleAggregate; -using OrderService.Domain.SeedWork; - -namespace OrderService.Infrastructure.Repositories; - -/// -/// EN: Repository implementation for Sample aggregate. -/// VI: Triển khai repository cho Sample aggregate. -/// -public class SampleRepository : ISampleRepository -{ - private readonly OrderServiceContext _context; - - /// - /// EN: Unit of work for transaction management. - /// VI: Unit of work cho quản lý transaction. - /// - public IUnitOfWork UnitOfWork => _context; - - public SampleRepository(OrderServiceContext 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/order-service-net/tests/OrderService.FunctionalTests/CustomWebApplicationFactory.cs b/services/order-service-net/tests/OrderService.FunctionalTests/CustomWebApplicationFactory.cs index e31c00a1..f451cb91 100644 --- a/services/order-service-net/tests/OrderService.FunctionalTests/CustomWebApplicationFactory.cs +++ b/services/order-service-net/tests/OrderService.FunctionalTests/CustomWebApplicationFactory.cs @@ -21,7 +21,7 @@ public class CustomWebApplicationFactory : WebApplicationFactory // EN: Remove the existing DbContext registration // VI: Xóa đăng ký DbContext hiện tại var descriptor = services.SingleOrDefault( - d => d.ServiceType == typeof(DbContextOptions)); + d => d.ServiceType == typeof(DbContextOptions)); if (descriptor != null) { @@ -31,7 +31,7 @@ public class CustomWebApplicationFactory : WebApplicationFactory // EN: Remove DbContext service // VI: Xóa DbContext service var dbContextDescriptor = services.SingleOrDefault( - d => d.ServiceType == typeof(OrderServiceContext)); + d => d.ServiceType == typeof(OrderContext)); if (dbContextDescriptor != null) { @@ -40,7 +40,7 @@ public class CustomWebApplicationFactory : WebApplicationFactory // EN: Add in-memory database for testing // VI: Thêm in-memory database để test - services.AddDbContext(options => + services.AddDbContext(options => { options.UseInMemoryDatabase("TestDatabase_" + Guid.NewGuid().ToString()); }); @@ -49,7 +49,7 @@ public class CustomWebApplicationFactory : WebApplicationFactory // VI: Đảm bảo database được tạo với seed data var sp = services.BuildServiceProvider(); using var scope = sp.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); + var db = scope.ServiceProvider.GetRequiredService(); db.Database.EnsureCreated(); }); } diff --git a/services/order-service-net/tests/OrderService.UnitTests/Application/CreateSampleCommandHandlerTests.cs b/services/order-service-net/tests/OrderService.UnitTests/Application/CreateSampleCommandHandlerTests.cs deleted file mode 100644 index caa846be..00000000 --- a/services/order-service-net/tests/OrderService.UnitTests/Application/CreateSampleCommandHandlerTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.Logging; -using Moq; -using OrderService.API.Application.Commands; -using OrderService.Domain.AggregatesModel.SampleAggregate; -using OrderService.Domain.SeedWork; -using Xunit; - -namespace OrderService.UnitTests.Application; - -/// -/// EN: Unit tests for CreateSampleCommandHandler. -/// VI: Unit tests cho CreateSampleCommandHandler. -/// -public class CreateSampleCommandHandlerTests -{ - private readonly Mock _mockRepository; - private readonly Mock> _mockLogger; - private readonly CreateSampleCommandHandler _handler; - - public CreateSampleCommandHandlerTests() - { - _mockRepository = new Mock(); - _mockLogger = new Mock>(); - - var mockUnitOfWork = new Mock(); - mockUnitOfWork.Setup(u => u.SaveEntitiesAsync(It.IsAny())) - .ReturnsAsync(true); - - _mockRepository.SetupGet(r => r.UnitOfWork).Returns(mockUnitOfWork.Object); - - _handler = new CreateSampleCommandHandler(_mockRepository.Object, _mockLogger.Object); - } - - [Fact] - public async Task Handle_WithValidCommand_ShouldCreateSampleAndReturnId() - { - // Arrange - var command = new CreateSampleCommand("Test Sample", "Test Description"); - - _mockRepository.Setup(r => r.Add(It.IsAny())) - .Returns((Sample s) => s); - - // Act - var result = await _handler.Handle(command, CancellationToken.None); - - // Assert - result.Should().NotBeNull(); - result.Id.Should().NotBeEmpty(); - _mockRepository.Verify(r => r.Add(It.IsAny()), Times.Once); - } - - [Fact] - public async Task Handle_WithValidCommand_ShouldCallSaveEntities() - { - // Arrange - var command = new CreateSampleCommand("Test Sample", null); - - // Act - await _handler.Handle(command, CancellationToken.None); - - // Assert - _mockRepository.Verify(r => r.UnitOfWork.SaveEntitiesAsync(It.IsAny()), Times.Once); - } -}