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);
- }
-}