diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs
index 76f973a..0677b7d 100644
--- a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs
+++ b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs
@@ -1,5 +1,4 @@
using CMSMicroservice.Domain.Entities.Payment;
-using CMSMicroservice.Domain.Entities.Message;
using CMSMicroservice.Domain.Entities.Order;
using CMSMicroservice.Domain.Entities.DiscountShop;
diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommand.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommand.cs
new file mode 100644
index 0000000..e05e252
--- /dev/null
+++ b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommand.cs
@@ -0,0 +1,22 @@
+using MediatR;
+
+namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
+
+///
+/// خرید پکیج طلایی (شروع فرآیند پرداخت)
+///
+public record PurchaseGoldenPackageCommand : IRequest
+{
+ public long UserId { get; init; }
+ public long PackageId { get; init; }
+ public string ReturnUrl { get; init; } = string.Empty;
+}
+
+public class PurchaseGoldenPackageResponseDto
+{
+ public bool Success { get; set; }
+ public string Message { get; set; } = string.Empty;
+ public long OrderId { get; set; }
+ public string PaymentGatewayUrl { get; set; } = string.Empty;
+ public string TrackingCode { get; set; } = string.Empty;
+}
diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandHandler.cs
new file mode 100644
index 0000000..9e7c7bd
--- /dev/null
+++ b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandHandler.cs
@@ -0,0 +1,88 @@
+using CMSMicroservice.Application.Common.Interfaces;
+using CMSMicroservice.Domain.Enums;
+using MediatR;
+
+namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
+
+public class PurchaseGoldenPackageCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+ private readonly IPaymentGatewayService _paymentGateway;
+ private readonly ILogger _logger;
+
+ public PurchaseGoldenPackageCommandHandler(
+ IApplicationDbContext context,
+ IPaymentGatewayService paymentGateway,
+ ILogger logger)
+ {
+ _context = context;
+ _paymentGateway = paymentGateway;
+ _logger = logger;
+ }
+
+ public async Task Handle(PurchaseGoldenPackageCommand request, CancellationToken cancellationToken)
+ {
+ // TODO: پیادهسازی خرید پکیج طلایی
+ //
+ // 1. پیدا کردن کاربر و بررسی شرایط:
+ // - var user = await _context.Users
+ // .Include(u => u.UserOrders)
+ // .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken)
+ // - if (user == null) throw new NotFoundException("کاربر یافت نشد")
+ // - if (user.PackagePurchaseMethod != PackagePurchaseMethod.None)
+ // throw new InvalidOperationException("شما قبلاً پکیج طلایی خریداری کردهاید")
+ //
+ // 2. پیدا کردن پکیج:
+ // - var package = await _context.Packages
+ // .FirstOrDefaultAsync(p => p.Id == request.PackageId && p.IsAvailable, cancellationToken)
+ // - if (package == null) throw new NotFoundException("پکیج یافت نشد")
+ // - if (package.Name != "طلایی")
+ // throw new InvalidOperationException("فقط پکیج طلایی قابل خرید است")
+ //
+ // 3. ایجاد سفارش:
+ // - var order = new UserOrder {
+ // UserId = user.Id,
+ // OrderNumber = GenerateOrderNumber(), // مثلاً "ORD" + DateTime.UtcNow.Ticks
+ // TotalPrice = package.Price, // 56,000,000
+ // Status = OrderStatus.Pending,
+ // PaymentMethod = PaymentMethod.IPG,
+ // OrderType = OrderType.PackagePurchase, // enum جدید
+ // PackageId = package.Id
+ // }
+ // - _context.UserOrders.Add(order)
+ // - await _context.SaveChangesAsync(cancellationToken)
+ //
+ // 4. شروع پرداخت با درگاه:
+ // - var paymentResult = await _paymentGateway.InitiatePaymentAsync(
+ // orderId: order.Id,
+ // amount: order.TotalPrice,
+ // description: $"خرید پکیج {package.Name}",
+ // returnUrl: request.ReturnUrl,
+ // cancellationToken: cancellationToken
+ // )
+ // - if (!paymentResult.Success)
+ // throw new InvalidOperationException($"خطا در اتصال به درگاه: {paymentResult.ErrorMessage}")
+ //
+ // 5. ذخیره اطلاعات پرداخت:
+ // - order.TrackingCode = paymentResult.TrackingCode
+ // - order.PaymentGatewayToken = paymentResult.Token
+ // - await _context.SaveChangesAsync(cancellationToken)
+ //
+ // 6. برگشت نتیجه:
+ // - _logger.LogInformation("Golden package purchase initiated for user {UserId}, order {OrderId}", user.Id, order.Id)
+ // - return new PurchaseGoldenPackageResponseDto {
+ // Success = true,
+ // Message = "لطفاً به درگاه پرداخت منتقل شوید",
+ // OrderId = order.Id,
+ // PaymentGatewayUrl = paymentResult.PaymentUrl,
+ // TrackingCode = paymentResult.TrackingCode
+ // }
+ //
+ // نکته 1: OrderType.PackagePurchase را به OrderType enum اضافه کنید
+ // نکته 2: PackageId nullable است در UserOrder - مطمئن شوید میتوانید set کنید
+ // نکته 3: کاربر به صفحه paymentResult.PaymentUrl redirect میشود
+ // نکته 4: پس از پرداخت موفق، VerifyGoldenPackagePurchase فراخوانی میشود
+
+ throw new NotImplementedException("PurchaseGoldenPackage needs implementation");
+ }
+}
diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandValidator.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandValidator.cs
new file mode 100644
index 0000000..b9f4411
--- /dev/null
+++ b/src/CMSMicroservice.Application/PackageCQ/Commands/PurchaseGoldenPackage/PurchaseGoldenPackageCommandValidator.cs
@@ -0,0 +1,23 @@
+using FluentValidation;
+
+namespace CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
+
+public class PurchaseGoldenPackageCommandValidator : AbstractValidator
+{
+ public PurchaseGoldenPackageCommandValidator()
+ {
+ RuleFor(x => x.UserId)
+ .GreaterThan(0)
+ .WithMessage("شناسه کاربر باید بزرگتر از 0 باشد");
+
+ RuleFor(x => x.PackageId)
+ .GreaterThan(0)
+ .WithMessage("شناسه پکیج باید بزرگتر از 0 باشد");
+
+ RuleFor(x => x.ReturnUrl)
+ .NotEmpty()
+ .WithMessage("آدرس بازگشت الزامی است")
+ .Must(url => Uri.TryCreate(url, UriKind.Absolute, out _))
+ .WithMessage("آدرس بازگشت معتبر نیست");
+ }
+}
diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommand.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommand.cs
new file mode 100644
index 0000000..d2ec876
--- /dev/null
+++ b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommand.cs
@@ -0,0 +1,23 @@
+using MediatR;
+
+namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
+
+///
+/// تایید پرداخت پکیج طلایی (پس از بازگشت از درگاه)
+///
+public record VerifyGoldenPackagePurchaseCommand : IRequest
+{
+ public long OrderId { get; init; }
+ public string Authority { get; init; } = string.Empty;
+ public string Status { get; init; } = string.Empty; // OK یا NOK
+}
+
+public class VerifyGoldenPackagePurchaseResponseDto
+{
+ public bool Success { get; set; }
+ public string Message { get; set; } = string.Empty;
+ public long OrderId { get; set; }
+ public long TransactionId { get; set; }
+ public string ReferenceCode { get; set; } = string.Empty;
+ public long WalletBalance { get; set; }
+}
diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs
new file mode 100644
index 0000000..df9fe85
--- /dev/null
+++ b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandHandler.cs
@@ -0,0 +1,115 @@
+using CMSMicroservice.Application.Common.Interfaces;
+using CMSMicroservice.Domain.Enums;
+using MediatR;
+
+namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
+
+public class VerifyGoldenPackagePurchaseCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+ private readonly IPaymentGatewayService _paymentGateway;
+ private readonly ILogger _logger;
+
+ public VerifyGoldenPackagePurchaseCommandHandler(
+ IApplicationDbContext context,
+ IPaymentGatewayService paymentGateway,
+ ILogger logger)
+ {
+ _context = context;
+ _paymentGateway = paymentGateway;
+ _logger = logger;
+ }
+
+ public async Task Handle(VerifyGoldenPackagePurchaseCommand request, CancellationToken cancellationToken)
+ {
+ // TODO: پیادهسازی تایید پرداخت پکیج طلایی
+ //
+ // 1. بررسی Status از درگاه:
+ // - if (request.Status != "OK")
+ // throw new InvalidOperationException("پرداخت توسط کاربر لغو شد")
+ //
+ // 2. پیدا کردن سفارش:
+ // - var order = await _context.UserOrders
+ // .Include(o => o.User)
+ // .ThenInclude(u => u.UserWallet)
+ // .FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken)
+ // - if (order == null) throw new NotFoundException("سفارش یافت نشد")
+ // - if (order.Status != OrderStatus.Pending)
+ // throw new InvalidOperationException("این سفارش قبلاً پردازش شده است")
+ //
+ // 3. Verify با درگاه پرداخت:
+ // - var verifyResult = await _paymentGateway.VerifyPaymentAsync(
+ // authority: request.Authority,
+ // amount: order.TotalPrice,
+ // cancellationToken: cancellationToken
+ // )
+ // - if (!verifyResult.Success) {
+ // order.Status = OrderStatus.PaymentFailed
+ // order.PaymentFailureReason = verifyResult.ErrorMessage
+ // await _context.SaveChangesAsync(cancellationToken)
+ // throw new InvalidOperationException($"تایید پرداخت ناموفق: {verifyResult.ErrorMessage}")
+ // }
+ //
+ // 4. شارژ کیف پول:
+ // - var wallet = order.User.UserWallet
+ // - if (wallet == null) {
+ // wallet = new UserWallet { UserId = order.UserId, Balance = 0, DiscountBalance = 0 }
+ // _context.UserWallets.Add(wallet)
+ // }
+ // - wallet.Balance += order.TotalPrice // اضافه شدن 56,000,000
+ //
+ // 5. ثبت Transaction:
+ // - var transaction = new Transaction {
+ // UserId = order.UserId,
+ // Amount = order.TotalPrice,
+ // Type = TransactionType.DepositIpg,
+ // Status = TransactionStatus.Completed,
+ // Description = $"شارژ کیف پول از خرید پکیج طلایی - سفارش {order.OrderNumber}",
+ // ReferenceCode = verifyResult.ReferenceCode,
+ // OrderId = order.Id
+ // }
+ // - _context.Transactions.Add(transaction)
+ //
+ // 6. ثبت UserWalletChangeLog:
+ // - var changeLog = new UserWalletChangeLog {
+ // UserId = order.UserId,
+ // ChangeValue = order.TotalPrice,
+ // ChangeType = ChangeType.Deposit,
+ // CurrentBalance = wallet.Balance,
+ // Description = $"شارژ از خرید پکیج طلایی",
+ // TransactionId = transaction.Id
+ // }
+ // - _context.UserWalletChangeLogs.Add(changeLog)
+ //
+ // 7. Set PackagePurchaseMethod:
+ // - order.User.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase
+ //
+ // 8. بهروزرسانی سفارش:
+ // - order.Status = OrderStatus.Processing // یا Completed
+ // - order.PaymentStatus = PaymentStatus.Paid
+ // - order.PaidAt = DateTime.UtcNow
+ // - order.BankReferenceId = verifyResult.ReferenceCode
+ //
+ // 9. ذخیره همه تغییرات:
+ // - await _context.SaveChangesAsync(cancellationToken)
+ //
+ // 10. Log و برگشت:
+ // - _logger.LogInformation("Golden package verified for user {UserId}, order {OrderId}, wallet charged {Amount}",
+ // order.UserId, order.Id, order.TotalPrice)
+ // - return new VerifyGoldenPackagePurchaseResponseDto {
+ // Success = true,
+ // Message = "پرداخت با موفقیت تایید شد. کیف پول شما شارژ گردید",
+ // OrderId = order.Id,
+ // TransactionId = transaction.Id,
+ // ReferenceCode = verifyResult.ReferenceCode,
+ // WalletBalance = wallet.Balance
+ // }
+ //
+ // نکته 1: کاربر هنوز عضو باشگاه نشده - باید ActivateClubMembership صدا بزند
+ // نکته 2: TransactionType.DepositIpg را بررسی کنید موجود باشد
+ // نکته 3: در صورت خطا، سفارش به PaymentFailed تغییر وضعیت میدهد
+ // نکته 4: این فرآیند idempotent نیست - باید بررسی شود سفارش Pending باشد
+
+ throw new NotImplementedException("VerifyGoldenPackagePurchase needs implementation");
+ }
+}
diff --git a/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandValidator.cs b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandValidator.cs
new file mode 100644
index 0000000..8442702
--- /dev/null
+++ b/src/CMSMicroservice.Application/PackageCQ/Commands/VerifyGoldenPackagePurchase/VerifyGoldenPackagePurchaseCommandValidator.cs
@@ -0,0 +1,23 @@
+using FluentValidation;
+
+namespace CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
+
+public class VerifyGoldenPackagePurchaseCommandValidator : AbstractValidator
+{
+ public VerifyGoldenPackagePurchaseCommandValidator()
+ {
+ RuleFor(x => x.OrderId)
+ .GreaterThan(0)
+ .WithMessage("شناسه سفارش باید بزرگتر از 0 باشد");
+
+ RuleFor(x => x.Authority)
+ .NotEmpty()
+ .WithMessage("کد Authority الزامی است");
+
+ RuleFor(x => x.Status)
+ .NotEmpty()
+ .WithMessage("وضعیت پرداخت الزامی است")
+ .Must(s => s == "OK" || s == "NOK")
+ .WithMessage("وضعیت باید OK یا NOK باشد");
+ }
+}
diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQuery.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQuery.cs
new file mode 100644
index 0000000..6d359c5
--- /dev/null
+++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQuery.cs
@@ -0,0 +1,24 @@
+using MediatR;
+
+namespace CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus;
+
+///
+/// دریافت وضعیت خرید پکیج کاربر
+///
+public record GetUserPackageStatusQuery : IRequest
+{
+ public long UserId { get; init; }
+}
+
+public class UserPackageStatusDto
+{
+ public long UserId { get; set; }
+ public string PackagePurchaseMethod { get; set; } = string.Empty; // None, DayaLoan, DirectPurchase
+ public bool HasPurchasedPackage { get; set; }
+ public bool IsClubMemberActive { get; set; }
+ public long WalletBalance { get; set; }
+ public long DiscountBalance { get; set; }
+ public bool CanActivateClubMembership { get; set; }
+ public string? LastOrderNumber { get; set; }
+ public DateTime? LastPurchaseDate { get; set; }
+}
diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryHandler.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryHandler.cs
new file mode 100644
index 0000000..daf2885
--- /dev/null
+++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryHandler.cs
@@ -0,0 +1,61 @@
+using CMSMicroservice.Application.Common.Interfaces;
+using CMSMicroservice.Domain.Enums;
+using MediatR;
+
+namespace CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus;
+
+public class GetUserPackageStatusQueryHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+
+ public GetUserPackageStatusQueryHandler(IApplicationDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Handle(GetUserPackageStatusQuery request, CancellationToken cancellationToken)
+ {
+ // TODO: پیادهسازی دریافت وضعیت پکیج کاربر
+ //
+ // 1. دریافت اطلاعات کاربر:
+ // - var user = await _context.Users
+ // .Include(u => u.UserWallet)
+ // .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken)
+ // - if (user == null) throw new NotFoundException("کاربر یافت نشد")
+ //
+ // 2. دریافت عضویت باشگاه:
+ // - var clubMembership = await _context.ClubMemberships
+ // .FirstOrDefaultAsync(c => c.UserId == user.Id && c.IsActive, cancellationToken)
+ //
+ // 3. دریافت آخرین سفارش پکیج:
+ // - var lastPackageOrder = await _context.UserOrders
+ // .Where(o => o.UserId == user.Id && o.PackageId != null)
+ // .OrderByDescending(o => o.Created)
+ // .FirstOrDefaultAsync(cancellationToken)
+ //
+ // 4. بررسی شرایط فعالسازی باشگاه:
+ // - var wallet = user.UserWallet
+ // - bool canActivate =
+ // user.PackagePurchaseMethod != PackagePurchaseMethod.None &&
+ // clubMembership == null &&
+ // wallet != null &&
+ // wallet.Balance >= 56_000_000
+ //
+ // 5. برگشت DTO:
+ // - return new UserPackageStatusDto {
+ // UserId = user.Id,
+ // PackagePurchaseMethod = user.PackagePurchaseMethod.ToString(),
+ // HasPurchasedPackage = user.PackagePurchaseMethod != PackagePurchaseMethod.None,
+ // IsClubMemberActive = clubMembership != null,
+ // WalletBalance = wallet?.Balance ?? 0,
+ // DiscountBalance = wallet?.DiscountBalance ?? 0,
+ // CanActivateClubMembership = canActivate,
+ // LastOrderNumber = lastPackageOrder?.OrderNumber,
+ // LastPurchaseDate = lastPackageOrder?.Created
+ // }
+ //
+ // نکته: این query برای UI مفید است تا وضعیت کاربر را نمایش دهد
+
+ throw new NotImplementedException("GetUserPackageStatus needs implementation");
+ }
+}
diff --git a/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryValidator.cs b/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryValidator.cs
new file mode 100644
index 0000000..9016f86
--- /dev/null
+++ b/src/CMSMicroservice.Application/PackageCQ/Queries/GetUserPackageStatus/GetUserPackageStatusQueryValidator.cs
@@ -0,0 +1,13 @@
+using FluentValidation;
+
+namespace CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus;
+
+public class GetUserPackageStatusQueryValidator : AbstractValidator
+{
+ public GetUserPackageStatusQueryValidator()
+ {
+ RuleFor(x => x.UserId)
+ .GreaterThan(0)
+ .WithMessage("شناسه کاربر باید بزرگتر از 0 باشد");
+ }
+}
diff --git a/src/CMSMicroservice.Application/PublicMessageCQ/Commands/ArchiveMessage/ArchiveMessageCommand.cs b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/ArchiveMessage/ArchiveMessageCommand.cs
new file mode 100644
index 0000000..2829800
--- /dev/null
+++ b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/ArchiveMessage/ArchiveMessageCommand.cs
@@ -0,0 +1,16 @@
+namespace CMSMicroservice.Application.PublicMessageCQ.Commands.ArchiveMessage;
+
+///
+/// آرشیو پیام عمومی (غیرفعال و بایگانی)
+///
+public record ArchiveMessageCommand : IRequest
+{
+ public long MessageId { get; init; }
+}
+
+public class ArchiveMessageResponseDto
+{
+ public bool Success { get; set; }
+ public string Message { get; set; } = string.Empty;
+ public DateTime? ArchivedAt { get; set; }
+}
diff --git a/src/CMSMicroservice.Application/PublicMessageCQ/Commands/ArchiveMessage/ArchiveMessageCommandHandler.cs b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/ArchiveMessage/ArchiveMessageCommandHandler.cs
new file mode 100644
index 0000000..ce79ce1
--- /dev/null
+++ b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/ArchiveMessage/ArchiveMessageCommandHandler.cs
@@ -0,0 +1,52 @@
+using CMSMicroservice.Application.Common.Interfaces;
+
+namespace CMSMicroservice.Application.PublicMessageCQ.Commands.ArchiveMessage;
+
+public class ArchiveMessageCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+ private readonly ILogger _logger;
+
+ public ArchiveMessageCommandHandler(
+ IApplicationDbContext context,
+ ILogger logger)
+ {
+ _context = context;
+ _logger = logger;
+ }
+
+ public async Task Handle(ArchiveMessageCommand request, CancellationToken cancellationToken)
+ {
+ // TODO: پیادهسازی آرشیو پیام
+ // 1. پیدا کردن پیام:
+ // - var message = await _context.PublicMessages
+ // .FirstOrDefaultAsync(m => m.Id == request.MessageId, cancellationToken)
+ // - بررسی null و پرتاب NotFoundException
+ //
+ // 2. بررسی وضعیت:
+ // - اگر قبلاً آرشیو شده:
+ // if (message.IsArchived)
+ // return موفقیت با پیام "این پیام قبلاً آرشیو شده است"
+ //
+ // 3. آرشیو کردن:
+ // - message.IsArchived = true
+ // - message.IsActive = false // غیرفعال هم میشود
+ // - message.ArchivedAt = DateTime.UtcNow
+ //
+ // 4. ذخیره و Log:
+ // - await _context.SaveChangesAsync(cancellationToken)
+ // - _logger.LogInformation("Public message {MessageId} archived: {Title}", message.Id, message.Title)
+ //
+ // 5. برگشت Response:
+ // - return new ArchiveMessageResponseDto {
+ // Success = true,
+ // Message = "پیام با موفقیت آرشیو شد",
+ // ArchivedAt = message.ArchivedAt
+ // }
+ //
+ // نکته: پیامهای آرشیو شده دیگر در GetActiveMessages نمایش داده نمیشوند
+ // نکته: میتوان پیامهای آرشیو شده را در یک query جداگانه GetArchivedMessages دریافت کرد
+
+ throw new NotImplementedException("ArchiveMessage needs implementation");
+ }
+}
diff --git a/src/CMSMicroservice.Application/PublicMessageCQ/Commands/ArchiveMessage/ArchiveMessageCommandValidator.cs b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/ArchiveMessage/ArchiveMessageCommandValidator.cs
new file mode 100644
index 0000000..3193164
--- /dev/null
+++ b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/ArchiveMessage/ArchiveMessageCommandValidator.cs
@@ -0,0 +1,11 @@
+namespace CMSMicroservice.Application.PublicMessageCQ.Commands.ArchiveMessage;
+
+public class ArchiveMessageCommandValidator : AbstractValidator
+{
+ public ArchiveMessageCommandValidator()
+ {
+ RuleFor(x => x.MessageId)
+ .GreaterThan(0)
+ .WithMessage("شناسه پیام باید بزرگتر از 0 باشد");
+ }
+}
diff --git a/src/CMSMicroservice.Application/PublicMessageCQ/Commands/CreatePublicMessage/CreatePublicMessageCommandHandler.cs b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/CreatePublicMessage/CreatePublicMessageCommandHandler.cs
index 56a4157..2f24849 100644
--- a/src/CMSMicroservice.Application/PublicMessageCQ/Commands/CreatePublicMessage/CreatePublicMessageCommandHandler.cs
+++ b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/CreatePublicMessage/CreatePublicMessageCommandHandler.cs
@@ -1,5 +1,5 @@
using CMSMicroservice.Application.Common.Interfaces;
-using CMSMicroservice.Domain.Entities.Message;
+using CMSMicroservice.Domain.Entities;
using MediatR;
using Microsoft.Extensions.Logging;
diff --git a/src/CMSMicroservice.Application/PublicMessageCQ/Commands/PublishMessage/PublishMessageCommand.cs b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/PublishMessage/PublishMessageCommand.cs
new file mode 100644
index 0000000..7c3a8c4
--- /dev/null
+++ b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/PublishMessage/PublishMessageCommand.cs
@@ -0,0 +1,16 @@
+namespace CMSMicroservice.Application.PublicMessageCQ.Commands.PublishMessage;
+
+///
+/// انتشار پیام عمومی (فعالسازی)
+///
+public record PublishMessageCommand : IRequest
+{
+ public long MessageId { get; init; }
+}
+
+public class PublishMessageResponseDto
+{
+ public bool Success { get; set; }
+ public string Message { get; set; } = string.Empty;
+ public DateTime? PublishedAt { get; set; }
+}
diff --git a/src/CMSMicroservice.Application/PublicMessageCQ/Commands/PublishMessage/PublishMessageCommandHandler.cs b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/PublishMessage/PublishMessageCommandHandler.cs
new file mode 100644
index 0000000..e83458a
--- /dev/null
+++ b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/PublishMessage/PublishMessageCommandHandler.cs
@@ -0,0 +1,56 @@
+using CMSMicroservice.Application.Common.Interfaces;
+
+namespace CMSMicroservice.Application.PublicMessageCQ.Commands.PublishMessage;
+
+public class PublishMessageCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+ private readonly ILogger _logger;
+
+ public PublishMessageCommandHandler(
+ IApplicationDbContext context,
+ ILogger logger)
+ {
+ _context = context;
+ _logger = logger;
+ }
+
+ public async Task Handle(PublishMessageCommand request, CancellationToken cancellationToken)
+ {
+ // TODO: پیادهسازی انتشار پیام
+ // 1. پیدا کردن پیام:
+ // - var message = await _context.PublicMessages
+ // .FirstOrDefaultAsync(m => m.Id == request.MessageId, cancellationToken)
+ // - بررسی null و پرتاب NotFoundException
+ //
+ // 2. بررسی شرایط انتشار:
+ // - اگر قبلاً منتشر شده:
+ // if (message.IsActive && message.PublishedAt.HasValue)
+ // return موفقیت با پیام "این پیام قبلاً منتشر شده است"
+ // - اگر آرشیو شده:
+ // if (message.IsArchived)
+ // throw new InvalidOperationException("پیام آرشیو شده قابل انتشار نیست")
+ //
+ // 3. فعالسازی پیام:
+ // - message.IsActive = true
+ // - message.PublishedAt = DateTime.UtcNow
+ // - اگر StartDate خالی است، از الان شروع کن:
+ // if (!message.StartDate.HasValue)
+ // message.StartDate = DateTime.UtcNow
+ //
+ // 4. ذخیره و Log:
+ // - await _context.SaveChangesAsync(cancellationToken)
+ // - _logger.LogInformation("Public message {MessageId} published: {Title}", message.Id, message.Title)
+ //
+ // 5. برگشت Response:
+ // - return new PublishMessageResponseDto {
+ // Success = true,
+ // Message = "پیام با موفقیت منتشر شد",
+ // PublishedAt = message.PublishedAt
+ // }
+ //
+ // نکته: پس از publish، پیام برای کاربران قابل مشاهده میشود (GetActiveMessages)
+
+ throw new NotImplementedException("PublishMessage needs implementation");
+ }
+}
diff --git a/src/CMSMicroservice.Application/PublicMessageCQ/Commands/PublishMessage/PublishMessageCommandValidator.cs b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/PublishMessage/PublishMessageCommandValidator.cs
new file mode 100644
index 0000000..20ac2ad
--- /dev/null
+++ b/src/CMSMicroservice.Application/PublicMessageCQ/Commands/PublishMessage/PublishMessageCommandValidator.cs
@@ -0,0 +1,11 @@
+namespace CMSMicroservice.Application.PublicMessageCQ.Commands.PublishMessage;
+
+public class PublishMessageCommandValidator : AbstractValidator
+{
+ public PublishMessageCommandValidator()
+ {
+ RuleFor(x => x.MessageId)
+ .GreaterThan(0)
+ .WithMessage("شناسه پیام باید بزرگتر از 0 باشد");
+ }
+}
diff --git a/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetActiveMessages/PublicMessageDto.cs b/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetActiveMessages/PublicMessageDto.cs
index ffe35cd..3d2375f 100644
--- a/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetActiveMessages/PublicMessageDto.cs
+++ b/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetActiveMessages/PublicMessageDto.cs
@@ -14,8 +14,8 @@ public class PublicMessageDto
public string TypeName { get; set; } = string.Empty;
public MessagePriority Priority { get; set; }
public string PriorityName { get; set; } = string.Empty;
- public DateTime StartsAt { get; set; }
- public DateTime ExpiresAt { get; set; }
+ public DateTime? StartsAt { get; set; }
+ public DateTime? ExpiresAt { get; set; }
public string? LinkUrl { get; set; }
public string? LinkText { get; set; }
public DateTime Created { get; set; }
diff --git a/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetAllMessages/AdminPublicMessageDto.cs b/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetAllMessages/AdminPublicMessageDto.cs
index cd2a9d0..734b105 100644
--- a/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetAllMessages/AdminPublicMessageDto.cs
+++ b/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetAllMessages/AdminPublicMessageDto.cs
@@ -15,9 +15,9 @@ public class AdminPublicMessageDto
public MessagePriority Priority { get; set; }
public string PriorityName { get; set; } = string.Empty;
public bool IsActive { get; set; }
- public DateTime StartsAt { get; set; }
- public DateTime ExpiresAt { get; set; }
- public long CreatedByUserId { get; set; }
+ public DateTime? StartsAt { get; set; }
+ public DateTime? ExpiresAt { get; set; }
+ public long? CreatedByUserId { get; set; }
public int ViewCount { get; set; }
public string? LinkUrl { get; set; }
public string? LinkText { get; set; }
diff --git a/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetPublicMessage/GetPublicMessageQuery.cs b/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetPublicMessage/GetPublicMessageQuery.cs
new file mode 100644
index 0000000..a158917
--- /dev/null
+++ b/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetPublicMessage/GetPublicMessageQuery.cs
@@ -0,0 +1,25 @@
+namespace CMSMicroservice.Application.PublicMessageCQ.Queries.GetPublicMessage;
+
+///
+/// دریافت یک پیام عمومی با شناسه
+///
+public record GetPublicMessageQuery : IRequest
+{
+ public long MessageId { get; init; }
+}
+
+public class PublicMessageDto
+{
+ public long Id { get; set; }
+ public string Title { get; set; } = string.Empty;
+ public string Content { get; set; } = string.Empty;
+ public MessageType MessageType { get; set; }
+ public bool IsActive { get; set; }
+ public bool IsArchived { get; set; }
+ public DateTime? StartDate { get; set; }
+ public DateTime? EndDate { get; set; }
+ public DateTime? PublishedAt { get; set; }
+ public DateTime? ArchivedAt { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public DateTime? LastModifiedAt { get; set; }
+}
diff --git a/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetPublicMessage/GetPublicMessageQueryHandler.cs b/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetPublicMessage/GetPublicMessageQueryHandler.cs
new file mode 100644
index 0000000..dc8638c
--- /dev/null
+++ b/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetPublicMessage/GetPublicMessageQueryHandler.cs
@@ -0,0 +1,46 @@
+using CMSMicroservice.Application.Common.Interfaces;
+
+namespace CMSMicroservice.Application.PublicMessageCQ.Queries.GetPublicMessage;
+
+public class GetPublicMessageQueryHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+
+ public GetPublicMessageQueryHandler(IApplicationDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task Handle(GetPublicMessageQuery request, CancellationToken cancellationToken)
+ {
+ // TODO: پیادهسازی دریافت پیام
+ // 1. پیدا کردن پیام:
+ // - var message = await _context.PublicMessages
+ // .AsNoTracking()
+ // .FirstOrDefaultAsync(m => m.Id == request.MessageId, cancellationToken)
+ //
+ // 2. چک null:
+ // - if (message == null) return null
+ //
+ // 3. Map به DTO:
+ // - return new PublicMessageDto {
+ // Id = message.Id,
+ // Title = message.Title,
+ // Content = message.Content,
+ // MessageType = message.MessageType,
+ // IsActive = message.IsActive,
+ // IsArchived = message.IsArchived,
+ // StartDate = message.StartDate,
+ // EndDate = message.EndDate,
+ // PublishedAt = message.PublishedAt,
+ // ArchivedAt = message.ArchivedAt,
+ // CreatedAt = message.CreatedAt,
+ // LastModifiedAt = message.LastModifiedAt
+ // }
+ //
+ // نکته: این query برای Admin است و همه پیامها (حتی آرشیو شده) را برمیگرداند
+ // نکته: برای کاربران عادی از GetActiveMessages استفاده میشود
+
+ throw new NotImplementedException("GetPublicMessage needs implementation");
+ }
+}
diff --git a/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetPublicMessage/GetPublicMessageQueryValidator.cs b/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetPublicMessage/GetPublicMessageQueryValidator.cs
new file mode 100644
index 0000000..4aac3c2
--- /dev/null
+++ b/src/CMSMicroservice.Application/PublicMessageCQ/Queries/GetPublicMessage/GetPublicMessageQueryValidator.cs
@@ -0,0 +1,11 @@
+namespace CMSMicroservice.Application.PublicMessageCQ.Queries.GetPublicMessage;
+
+public class GetPublicMessageQueryValidator : AbstractValidator
+{
+ public GetPublicMessageQueryValidator()
+ {
+ RuleFor(x => x.MessageId)
+ .GreaterThan(0)
+ .WithMessage("شناسه پیام باید بزرگتر از 0 باشد");
+ }
+}
diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommand.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommand.cs
new file mode 100644
index 0000000..61d04f8
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommand.cs
@@ -0,0 +1,39 @@
+namespace CMSMicroservice.Application.UserOrderCQ.Commands.ApplyDiscountToOrder;
+
+///
+/// اعمال تخفیف به سفارش
+///
+public record ApplyDiscountToOrderCommand : IRequest
+{
+ ///
+ /// شناسه سفارش
+ ///
+ public long OrderId { get; init; }
+
+ ///
+ /// مبلغ تخفیف (ریال)
+ ///
+ public long DiscountAmount { get; init; }
+
+ ///
+ /// دلیل تخفیف
+ ///
+ public string Reason { get; init; } = string.Empty;
+
+ ///
+ /// کد تخفیف (اختیاری)
+ ///
+ public string? DiscountCode { get; init; }
+}
+
+///
+/// پاسخ اعمال تخفیف
+///
+public class ApplyDiscountToOrderResponseDto
+{
+ public bool Success { get; set; }
+ public string Message { get; set; } = string.Empty;
+ public long OriginalAmount { get; set; }
+ public long DiscountAmount { get; set; }
+ public long FinalAmount { get; set; }
+}
diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommandHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommandHandler.cs
new file mode 100644
index 0000000..cfe6519
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommandHandler.cs
@@ -0,0 +1,74 @@
+using CMSMicroservice.Application.Common.Interfaces;
+
+namespace CMSMicroservice.Application.UserOrderCQ.Commands.ApplyDiscountToOrder;
+
+public class ApplyDiscountToOrderCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+ private readonly ILogger _logger;
+
+ public ApplyDiscountToOrderCommandHandler(
+ IApplicationDbContext context,
+ ILogger logger)
+ {
+ _context = context;
+ _logger = logger;
+ }
+
+ public async Task Handle(ApplyDiscountToOrderCommand request, CancellationToken cancellationToken)
+ {
+ // TODO: پیادهسازی اعمال تخفیف به سفارش
+ // 1. پیدا کردن سفارش:
+ // - var order = await _context.UserOrders.FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken)
+ // - بررسی null و پرتاب NotFoundException
+ //
+ // 2. بررسی شرایط اعمال تخفیف:
+ // - سفارش نباید Delivered یا Cancelled باشد
+ // - مبلغ تخفیف نباید بیشتر از Amount باشد
+ // - if (order.DeliveryStatus == DeliveryStatus.Delivered || order.DeliveryStatus == DeliveryStatus.Cancelled)
+ // throw new InvalidOperationException("نمیتوان به این سفارش تخفیف اعمال کرد")
+ // - if (request.DiscountAmount > order.Amount)
+ // throw new InvalidOperationException("مبلغ تخفیف نمیتواند بیشتر از مبلغ سفارش باشد")
+ //
+ // 3. محاسبه مبلغ نهایی:
+ // - var originalAmount = order.Amount
+ // - var newDiscountedPrice = order.Amount - request.DiscountAmount
+ // - مطمئن شوید که منفی نشود: newDiscountedPrice = Math.Max(0, newDiscountedPrice)
+ //
+ // 4. بهروزرسانی سفارش:
+ // - order.DiscountedPrice = newDiscountedPrice
+ // - اگر فیلد OrderDiscountAmount وجود دارد، آن را هم بهروز کنید
+ // - order.OrderDiscountAmount = request.DiscountAmount
+ // - اضافه کردن به توضیحات:
+ // order.DeliveryDescription = (order.DeliveryDescription ?? "") +
+ // $"\nتخفیف اعمال شده: {request.DiscountAmount} ریال - دلیل: {request.Reason}"
+ //
+ // 5. ذخیره Log تخفیف (اختیاری - اگر جدول OrderDiscountLog دارید):
+ // - var discountLog = new OrderDiscountLog {
+ // OrderId = order.Id,
+ // DiscountAmount = request.DiscountAmount,
+ // Reason = request.Reason,
+ // DiscountCode = request.DiscountCode,
+ // AppliedAt = DateTime.UtcNow
+ // }
+ // - await _context.OrderDiscountLogs.AddAsync(discountLog, cancellationToken)
+ //
+ // 6. ذخیره و Log:
+ // - await _context.SaveChangesAsync(cancellationToken)
+ // - _logger.LogInformation("Discount {Amount} applied to order {OrderId}: {Reason}",
+ // request.DiscountAmount, request.OrderId, request.Reason)
+ //
+ // 7. برگشت Response:
+ // - return new ApplyDiscountToOrderResponseDto {
+ // Success = true,
+ // Message = "تخفیف با موفقیت اعمال شد",
+ // OriginalAmount = originalAmount,
+ // DiscountAmount = request.DiscountAmount,
+ // FinalAmount = newDiscountedPrice
+ // }
+ //
+ // نکته: این تخفیف برای تخفیفات دستی Admin است و جدا از تخفیفهای محصول
+
+ throw new NotImplementedException("ApplyDiscountToOrder needs implementation");
+ }
+}
diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommandValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommandValidator.cs
new file mode 100644
index 0000000..6c0fcfb
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserOrderCQ/Commands/ApplyDiscountToOrder/ApplyDiscountToOrderCommandValidator.cs
@@ -0,0 +1,21 @@
+namespace CMSMicroservice.Application.UserOrderCQ.Commands.ApplyDiscountToOrder;
+
+public class ApplyDiscountToOrderCommandValidator : AbstractValidator
+{
+ public ApplyDiscountToOrderCommandValidator()
+ {
+ RuleFor(x => x.OrderId)
+ .GreaterThan(0)
+ .WithMessage("شناسه سفارش باید بزرگتر از 0 باشد");
+
+ RuleFor(x => x.DiscountAmount)
+ .GreaterThan(0)
+ .WithMessage("مبلغ تخفیف باید بزرگتر از 0 باشد");
+
+ RuleFor(x => x.Reason)
+ .NotEmpty()
+ .WithMessage("دلیل تخفیف الزامی است")
+ .MaximumLength(500)
+ .WithMessage("دلیل تخفیف نمیتواند بیشتر از 500 کاراکتر باشد");
+ }
+}
diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommand.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommand.cs
new file mode 100644
index 0000000..5ebc317
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommand.cs
@@ -0,0 +1,34 @@
+using CMSMicroservice.Domain.Enums;
+
+namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus;
+
+///
+/// تغییر وضعیت سفارش
+///
+public record UpdateOrderStatusCommand : IRequest
+{
+ ///
+ /// شناسه سفارش
+ ///
+ public long OrderId { get; init; }
+
+ ///
+ /// وضعیت تحویل جدید
+ ///
+ public DeliveryStatus NewStatus { get; init; }
+
+ ///
+ /// توضیحات (اختیاری)
+ ///
+ public string? Description { get; init; }
+}
+
+///
+/// پاسخ تغییر وضعیت سفارش
+///
+public class UpdateOrderStatusResponseDto
+{
+ public bool Success { get; set; }
+ public string Message { get; set; } = string.Empty;
+ public DeliveryStatus CurrentStatus { get; set; }
+}
diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandHandler.cs
new file mode 100644
index 0000000..96279e7
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandHandler.cs
@@ -0,0 +1,53 @@
+using CMSMicroservice.Application.Common.Interfaces;
+using CMSMicroservice.Domain.Enums;
+
+namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus;
+
+public class UpdateOrderStatusCommandHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+ private readonly ILogger _logger;
+
+ public UpdateOrderStatusCommandHandler(
+ IApplicationDbContext context,
+ ILogger logger)
+ {
+ _context = context;
+ _logger = logger;
+ }
+
+ public async Task Handle(UpdateOrderStatusCommand request, CancellationToken cancellationToken)
+ {
+ // TODO: پیادهسازی تغییر وضعیت سفارش
+ // 1. پیدا کردن سفارش:
+ // - await _context.UserOrders.FirstOrDefaultAsync(o => o.Id == request.OrderId)
+ // - بررسی null و پرتاب NotFoundException
+ //
+ // 2. بررسیهای انتقال وضعیت (State Transition Validation):
+ // - نمیتوان از Delivered به Cancelled رفت
+ // - نمیتوان از Cancelled به سایر وضعیتها رفت
+ // - الگوی معمول: Pending → Processing → Shipped → Delivered
+ // - Cancelled میتواند از Pending, Processing, Shipped باشد
+ //
+ // 3. تغییر وضعیت:
+ // - order.DeliveryStatus = request.NewStatus
+ // - اگر Description داریم: order.DeliveryDescription = request.Description
+ // - تنظیم تاریخهای مربوطه:
+ // * اگر NewStatus == Delivered → order.DeliveredAt = DateTime.UtcNow
+ // * اگر NewStatus == Shipped → order.ShippedAt = DateTime.UtcNow
+ // * اگر NewStatus == Processing → order.ProcessedAt = DateTime.UtcNow
+ //
+ // 4. ذخیره و Log:
+ // - await _context.SaveChangesAsync(cancellationToken)
+ // - _logger.LogInformation("Order {OrderId} status changed to {NewStatus}", request.OrderId, request.NewStatus)
+ //
+ // 5. برگشت Response:
+ // - Success = true
+ // - Message = "وضعیت سفارش با موفقیت تغییر کرد"
+ // - CurrentStatus = order.DeliveryStatus
+ //
+ // نکته: برای validation دقیقتر، میتوان یک State Machine برای انتقالهای مجاز تعریف کرد
+
+ throw new NotImplementedException("UpdateOrderStatus needs implementation");
+ }
+}
diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandValidator.cs
new file mode 100644
index 0000000..d4ebd11
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserOrderCQ/Commands/UpdateOrderStatus/UpdateOrderStatusCommandValidator.cs
@@ -0,0 +1,17 @@
+using CMSMicroservice.Domain.Enums;
+
+namespace CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus;
+
+public class UpdateOrderStatusCommandValidator : AbstractValidator
+{
+ public UpdateOrderStatusCommandValidator()
+ {
+ RuleFor(x => x.OrderId)
+ .GreaterThan(0)
+ .WithMessage("شناسه سفارش باید بزرگتر از 0 باشد");
+
+ RuleFor(x => x.NewStatus)
+ .IsInEnum()
+ .WithMessage("وضعیت تحویل نامعتبر است");
+ }
+}
diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQuery.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQuery.cs
new file mode 100644
index 0000000..c29b1f8
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQuery.cs
@@ -0,0 +1,46 @@
+namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV;
+
+///
+/// محاسبه امتیاز PV سفارش
+///
+public record CalculateOrderPVQuery : IRequest
+{
+ ///
+ /// شناسه سفارش
+ ///
+ public long OrderId { get; init; }
+}
+
+///
+/// پاسخ محاسبه PV سفارش
+///
+public class CalculateOrderPVResponseDto
+{
+ ///
+ /// مجموع امتیاز PV سفارش
+ ///
+ public decimal TotalPV { get; set; }
+
+ ///
+ /// جزئیات PV هر محصول
+ ///
+ public List ProductPVs { get; set; } = new();
+
+ ///
+ /// مبلغ قابل پرداخت
+ ///
+ public long PayableAmount { get; set; }
+}
+
+///
+/// جزئیات PV یک محصول در سفارش
+///
+public class ProductPVDto
+{
+ public long ProductId { get; set; }
+ public string ProductTitle { get; set; } = string.Empty;
+ public int Quantity { get; set; }
+ public decimal UnitPV { get; set; }
+ public decimal TotalPV { get; set; }
+ public long UnitPrice { get; set; }
+}
diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQueryHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQueryHandler.cs
new file mode 100644
index 0000000..54ffe48
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQueryHandler.cs
@@ -0,0 +1,62 @@
+using CMSMicroservice.Application.Common.Interfaces;
+
+namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV;
+
+public class CalculateOrderPVQueryHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+ private readonly ILogger _logger;
+
+ public CalculateOrderPVQueryHandler(
+ IApplicationDbContext context,
+ ILogger logger)
+ {
+ _context = context;
+ _logger = logger;
+ }
+
+ public async Task Handle(CalculateOrderPVQuery request, CancellationToken cancellationToken)
+ {
+ // TODO: پیادهسازی محاسبه PV سفارش
+ // 1. پیدا کردن سفارش و جزئیات:
+ // - var order = await _context.UserOrders
+ // .Include(o => o.FactorDetails)
+ // .ThenInclude(fd => fd.Product)
+ // .FirstOrDefaultAsync(o => o.Id == request.OrderId, cancellationToken)
+ // - بررسی null و پرتاب NotFoundException
+ //
+ // 2. محاسبه PV هر محصول:
+ // - var productPVs = new List()
+ // - decimal totalPV = 0
+ // - foreach (var detail in order.FactorDetails):
+ // * محاسبه PV واحد محصول (فرض: قیمت / 10000 یا از فیلد Product.PV اگر وجود دارد):
+ // decimal unitPV = detail.Product.PV ?? (detail.Product.Price / 10000m)
+ // * محاسبه PV کل این آیتم:
+ // decimal itemTotalPV = unitPV * detail.Count
+ // * اضافه به لیست:
+ // productPVs.Add(new ProductPVDto {
+ // ProductId = detail.ProductId,
+ // ProductTitle = detail.Product.Title,
+ // Quantity = detail.Count,
+ // UnitPV = unitPV,
+ // TotalPV = itemTotalPV,
+ // UnitPrice = detail.Product.Price
+ // })
+ // * اضافه به مجموع:
+ // totalPV += itemTotalPV
+ //
+ // 3. برگشت Response:
+ // - _logger.LogInformation("Calculated PV for order {OrderId}: {TotalPV}", request.OrderId, totalPV)
+ // - return new CalculateOrderPVResponseDto {
+ // TotalPV = totalPV,
+ // ProductPVs = productPVs,
+ // PayableAmount = order.DiscountedPrice
+ // }
+ //
+ // نکته: PV (Point Value) معمولاً برای سیستمهای MLM و کمیسیون شبکه استفاده میشود
+ // نکته: فرمول محاسبه PV باید بر اساس business logic شما باشد (قیمت/10000 فقط مثال است)
+ // نکته: اگر entity Product فیلد PV ندارد، باید اضافه شود یا از Configuration استفاده کنید
+
+ throw new NotImplementedException("CalculateOrderPV needs implementation");
+ }
+}
diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQueryValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQueryValidator.cs
new file mode 100644
index 0000000..671348a
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/CalculateOrderPV/CalculateOrderPVQueryValidator.cs
@@ -0,0 +1,11 @@
+namespace CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV;
+
+public class CalculateOrderPVQueryValidator : AbstractValidator
+{
+ public CalculateOrderPVQueryValidator()
+ {
+ RuleFor(x => x.OrderId)
+ .GreaterThan(0)
+ .WithMessage("شناسه سفارش باید بزرگتر از 0 باشد");
+ }
+}
diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQuery.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQuery.cs
new file mode 100644
index 0000000..67cba45
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQuery.cs
@@ -0,0 +1,66 @@
+using CMSMicroservice.Application.Common.Models;
+using CMSMicroservice.Domain.Enums;
+
+namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange;
+
+///
+/// دریافت سفارشات بر اساس بازه زمانی
+///
+public record GetOrdersByDateRangeQuery : IRequest
+{
+ ///
+ /// تاریخ شروع (UTC)
+ ///
+ public DateTime StartDate { get; init; }
+
+ ///
+ /// تاریخ پایان (UTC)
+ ///
+ public DateTime EndDate { get; init; }
+
+ ///
+ /// فیلتر وضعیت تحویل (اختیاری)
+ ///
+ public DeliveryStatus? Status { get; init; }
+
+ ///
+ /// شناسه کاربر (اختیاری - برای فیلتر بر اساس کاربر)
+ ///
+ public long? UserId { get; init; }
+
+ ///
+ /// شماره صفحه
+ ///
+ public int PageIndex { get; init; } = 1;
+
+ ///
+ /// تعداد در صفحه
+ ///
+ public int PageSize { get; init; } = 20;
+}
+
+///
+/// پاسخ لیست سفارشات
+///
+public class GetOrdersByDateRangeResponseDto
+{
+ public MetaData MetaData { get; set; } = new();
+ public List Orders { get; set; } = new();
+}
+
+///
+/// خلاصه اطلاعات سفارش
+///
+public class OrderSummaryDto
+{
+ public long Id { get; set; }
+ public long UserId { get; set; }
+ public string UserFullName { get; set; } = string.Empty;
+ public long Amount { get; set; }
+ public long DiscountedPrice { get; set; }
+ public DeliveryStatus Status { get; set; }
+ public DateTime Created { get; set; }
+ public DateTime? ShippedAt { get; set; }
+ public DateTime? DeliveredAt { get; set; }
+ public int ItemsCount { get; set; }
+}
diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQueryHandler.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQueryHandler.cs
new file mode 100644
index 0000000..f7bcf95
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQueryHandler.cs
@@ -0,0 +1,80 @@
+using CMSMicroservice.Application.Common.Interfaces;
+using CMSMicroservice.Application.Common.Models;
+
+namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange;
+
+public class GetOrdersByDateRangeQueryHandler : IRequestHandler
+{
+ private readonly IApplicationDbContext _context;
+ private readonly ILogger _logger;
+
+ public GetOrdersByDateRangeQueryHandler(
+ IApplicationDbContext context,
+ ILogger logger)
+ {
+ _context = context;
+ _logger = logger;
+ }
+
+ public async Task Handle(GetOrdersByDateRangeQuery request, CancellationToken cancellationToken)
+ {
+ // TODO: پیادهسازی دریافت سفارشات بر اساس بازه زمانی
+ // 1. شروع Query:
+ // - var query = _context.UserOrders.AsQueryable()
+ // - Include User برای نام کاربر: .Include(o => o.User)
+ // - Include FactorDetails برای شمارش تعداد آیتمها: .Include(o => o.FactorDetails)
+ //
+ // 2. اعمال فیلتر بازه زمانی:
+ // - query = query.Where(o => o.Created >= request.StartDate && o.Created <= request.EndDate)
+ //
+ // 3. اعمال فیلترهای اختیاری:
+ // - اگر request.Status.HasValue:
+ // query = query.Where(o => o.DeliveryStatus == request.Status.Value)
+ // - اگر request.UserId.HasValue:
+ // query = query.Where(o => o.UserId == request.UserId.Value)
+ //
+ // 4. محاسبه تعداد کل:
+ // - var totalCount = await query.CountAsync(cancellationToken)
+ //
+ // 5. مرتبسازی و Pagination:
+ // - query = query.OrderByDescending(o => o.Created)
+ // - query = query.Skip((request.PageIndex - 1) * request.PageSize)
+ // - query = query.Take(request.PageSize)
+ //
+ // 6. دریافت دادهها:
+ // - var orders = await query.ToListAsync(cancellationToken)
+ //
+ // 7. Mapping به DTO:
+ // - var orderDtos = orders.Select(o => new OrderSummaryDto {
+ // Id = o.Id,
+ // UserId = o.UserId,
+ // UserFullName = $"{o.User.Firstname} {o.User.Lastname}",
+ // Amount = o.Amount,
+ // DiscountedPrice = o.DiscountedPrice,
+ // Status = o.DeliveryStatus,
+ // Created = o.Created,
+ // ShippedAt = o.ShippedAt,
+ // DeliveredAt = o.DeliveredAt,
+ // ItemsCount = o.FactorDetails.Count
+ // }).ToList()
+ //
+ // 8. ساخت MetaData:
+ // - var totalPages = (int)Math.Ceiling(totalCount / (double)request.PageSize)
+ // - MetaData = new MetaData {
+ // CurrentPage = request.PageIndex,
+ // TotalPage = totalPages,
+ // PageSize = request.PageSize,
+ // TotalCount = totalCount,
+ // HasNext = request.PageIndex < totalPages,
+ // HasPrevious = request.PageIndex > 1
+ // }
+ //
+ // 9. Log و برگشت:
+ // - _logger.LogInformation("Retrieved {Count} orders for date range {Start} to {End}", orders.Count, request.StartDate, request.EndDate)
+ // - return new GetOrdersByDateRangeResponseDto { MetaData = metaData, Orders = orderDtos }
+ //
+ // نکته: برای performance بهتر، میتوان از AsNoTracking() استفاده کرد
+
+ throw new NotImplementedException("GetOrdersByDateRange needs implementation");
+ }
+}
diff --git a/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQueryValidator.cs b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQueryValidator.cs
new file mode 100644
index 0000000..f11541f
--- /dev/null
+++ b/src/CMSMicroservice.Application/UserOrderCQ/Queries/GetOrdersByDateRange/GetOrdersByDateRangeQueryValidator.cs
@@ -0,0 +1,28 @@
+namespace CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange;
+
+public class GetOrdersByDateRangeQueryValidator : AbstractValidator
+{
+ public GetOrdersByDateRangeQueryValidator()
+ {
+ RuleFor(x => x.StartDate)
+ .LessThanOrEqualTo(x => x.EndDate)
+ .WithMessage("تاریخ شروع باید کوچکتر یا مساوی تاریخ پایان باشد");
+
+ RuleFor(x => x.EndDate)
+ .LessThanOrEqualTo(DateTime.UtcNow.AddDays(1))
+ .WithMessage("تاریخ پایان نمیتواند در آینده باشد");
+
+ RuleFor(x => x.PageIndex)
+ .GreaterThan(0)
+ .WithMessage("شماره صفحه باید بزرگتر از 0 باشد");
+
+ RuleFor(x => x.PageSize)
+ .InclusiveBetween(1, 100)
+ .WithMessage("تعداد در صفحه باید بین 1 تا 100 باشد");
+
+ // بازه زمانی نباید بیش از 1 سال باشد
+ RuleFor(x => x)
+ .Must(x => (x.EndDate - x.StartDate).TotalDays <= 365)
+ .WithMessage("بازه زمانی نمیتواند بیش از 1 سال باشد");
+ }
+}
diff --git a/src/CMSMicroservice.Domain/Entities/Message/PublicMessage.cs b/src/CMSMicroservice.Domain/Entities/PublicMessage.cs
similarity index 56%
rename from src/CMSMicroservice.Domain/Entities/Message/PublicMessage.cs
rename to src/CMSMicroservice.Domain/Entities/PublicMessage.cs
index cd516d6..bab99fd 100644
--- a/src/CMSMicroservice.Domain/Entities/Message/PublicMessage.cs
+++ b/src/CMSMicroservice.Domain/Entities/PublicMessage.cs
@@ -1,11 +1,10 @@
using CMSMicroservice.Domain.Common;
using CMSMicroservice.Domain.Enums;
-namespace CMSMicroservice.Domain.Entities.Message;
+namespace CMSMicroservice.Domain.Entities;
///
-/// پیامهای عمومی سیستم
-/// Admin میتواند پیامهای عمومی برای نمایش در داشبورد کاربران ارسال کند
+/// پیام عمومی برای نمایش در سیستم
///
public class PublicMessage : BaseAuditableEntity
{
@@ -22,37 +21,52 @@ public class PublicMessage : BaseAuditableEntity
///
/// نوع پیام (Announcement, News, Warning, Promotion, SystemUpdate, Event)
///
- public MessageType Type { get; set; }
+ public MessageType Type { get; set; } = MessageType.Announcement;
///
/// اولویت پیام (Low, Medium, High, Urgent)
///
- public MessagePriority Priority { get; set; }
+ public MessagePriority Priority { get; set; } = MessagePriority.Medium;
///
/// وضعیت فعال/غیرفعال
///
- public bool IsActive { get; set; }
+ public bool IsActive { get; set; } = false;
+
+ ///
+ /// آیا آرشیو شده است؟
+ ///
+ public bool IsArchived { get; set; } = false;
///
/// تاریخ شروع نمایش پیام
///
- public DateTime StartsAt { get; set; }
+ public DateTime? StartDate { get; set; }
///
/// تاریخ پایان نمایش پیام
///
- public DateTime ExpiresAt { get; set; }
+ public DateTime? EndDate { get; set; }
+
+ ///
+ /// تاریخ انتشار
+ ///
+ public DateTime? PublishedAt { get; set; }
+
+ ///
+ /// تاریخ آرشیو
+ ///
+ public DateTime? ArchivedAt { get; set; }
///
/// شناسه Admin ایجادکننده
///
- public long CreatedByUserId { get; set; }
+ public long? CreatedByUserId { get; set; }
///
/// تعداد بازدید (اختیاری - برای آمار)
///
- public int ViewCount { get; set; }
+ public int ViewCount { get; set; } = 0;
///
/// لینک اختیاری (برای اطلاعات بیشتر)
@@ -63,4 +77,17 @@ public class PublicMessage : BaseAuditableEntity
/// متن دکمه لینک (مثلاً "اطلاعات بیشتر")
///
public string? LinkText { get; set; }
+
+ // Backward compatibility properties (map to StartDate/EndDate)
+ public DateTime? StartsAt
+ {
+ get => StartDate;
+ set => StartDate = value;
+ }
+
+ public DateTime? ExpiresAt
+ {
+ get => EndDate;
+ set => EndDate = value;
+ }
}
diff --git a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs
index ebc1e6c..7f2d668 100644
--- a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs
+++ b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs
@@ -2,7 +2,7 @@ using System.Reflection;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Entities.Payment;
-using CMSMicroservice.Domain.Entities.Message;
+
using CMSMicroservice.Domain.Entities.Order;
using CMSMicroservice.Domain.Entities.DiscountShop;
using CMSMicroservice.Infrastructure.Persistence.Interceptors;
diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PublicMessageConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PublicMessageConfiguration.cs
index 4983607..c4906ce 100644
--- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PublicMessageConfiguration.cs
+++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PublicMessageConfiguration.cs
@@ -1,4 +1,4 @@
-using CMSMicroservice.Domain.Entities.Message;
+using CMSMicroservice.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
diff --git a/src/CMSMicroservice.Protobuf/Protos/package.proto b/src/CMSMicroservice.Protobuf/Protos/package.proto
index 6424de0..43c0ebd 100644
--- a/src/CMSMicroservice.Protobuf/Protos/package.proto
+++ b/src/CMSMicroservice.Protobuf/Protos/package.proto
@@ -43,6 +43,25 @@ service PackageContract
};
};
+
+ // Package Purchase System
+ rpc PurchaseGoldenPackage(PurchaseGoldenPackageRequest) returns (PurchaseGoldenPackageResponse){
+ option (google.api.http) = {
+ post: "/PurchaseGoldenPackage"
+ body: "*"
+ };
+ };
+ rpc VerifyGoldenPackagePurchase(VerifyGoldenPackagePurchaseRequest) returns (VerifyGoldenPackagePurchaseResponse){
+ option (google.api.http) = {
+ post: "/VerifyGoldenPackagePurchase"
+ body: "*"
+ };
+ };
+ rpc GetUserPackageStatus(GetUserPackageStatusRequest) returns (GetUserPackageStatusResponse){
+ option (google.api.http) = {
+ get: "/GetUserPackageStatus"
+ };
+ };
}
message CreateNewPackageRequest
{
@@ -106,3 +125,55 @@ message GetAllPackageByFilterResponseModel
string image_path = 4;
int64 price = 5;
}
+
+// Package Purchase Messages
+message PurchaseGoldenPackageRequest
+{
+ int64 user_id = 1;
+ int64 package_id = 2;
+ string return_url = 3;
+}
+
+message PurchaseGoldenPackageResponse
+{
+ bool success = 1;
+ string message = 2;
+ int64 order_id = 3;
+ string payment_gateway_url = 4;
+ string tracking_code = 5;
+}
+
+message VerifyGoldenPackagePurchaseRequest
+{
+ int64 order_id = 1;
+ string authority = 2;
+ string status = 3;
+}
+
+message VerifyGoldenPackagePurchaseResponse
+{
+ bool success = 1;
+ string message = 2;
+ int64 order_id = 3;
+ int64 transaction_id = 4;
+ string reference_code = 5;
+ int64 wallet_balance = 6;
+}
+
+message GetUserPackageStatusRequest
+{
+ int64 user_id = 1;
+}
+
+message GetUserPackageStatusResponse
+{
+ int64 user_id = 1;
+ string package_purchase_method = 2;
+ bool has_purchased_package = 3;
+ bool is_club_member_active = 4;
+ int64 wallet_balance = 5;
+ int64 discount_balance = 6;
+ bool can_activate_club_membership = 7;
+ google.protobuf.StringValue last_order_number = 8;
+ google.protobuf.Timestamp last_purchase_date = 9;
+}
diff --git a/src/CMSMicroservice.Protobuf/Protos/public_messages.proto b/src/CMSMicroservice.Protobuf/Protos/public_messages.proto
index c78db04..095918c 100644
--- a/src/CMSMicroservice.Protobuf/Protos/public_messages.proto
+++ b/src/CMSMicroservice.Protobuf/Protos/public_messages.proto
@@ -2,8 +2,60 @@ syntax = "proto3";
package messages;
+import "google/protobuf/empty.proto";
+import "google/protobuf/wrappers.proto";
+import "google/protobuf/timestamp.proto";
+import "google/api/annotations.proto";
+
option csharp_namespace = "CMSMicroservice.Protobuf.Protos";
-service PublicMessageContract{}
+
+service PublicMessageContract{
+ rpc CreatePublicMessage(CreatePublicMessageRequest) returns (CreatePublicMessageResponse){
+ option (google.api.http) = {
+ post: "/CreatePublicMessage"
+ body: "*"
+ };
+ };
+ rpc UpdatePublicMessage(UpdatePublicMessageRequest) returns (google.protobuf.Empty){
+ option (google.api.http) = {
+ put: "/UpdatePublicMessage"
+ body: "*"
+ };
+ };
+ rpc DeletePublicMessage(DeletePublicMessageRequest) returns (google.protobuf.Empty){
+ option (google.api.http) = {
+ delete: "/DeletePublicMessage"
+ body: "*"
+ };
+ };
+ rpc PublishMessage(PublishMessageRequest) returns (PublishMessageResponse){
+ option (google.api.http) = {
+ post: "/PublishMessage"
+ body: "*"
+ };
+ };
+ rpc ArchiveMessage(ArchiveMessageRequest) returns (ArchiveMessageResponse){
+ option (google.api.http) = {
+ post: "/ArchiveMessage"
+ body: "*"
+ };
+ };
+ rpc GetAllMessages(GetAllMessagesRequest) returns (GetAllMessagesResponse){
+ option (google.api.http) = {
+ get: "/GetAllMessages"
+ };
+ };
+ rpc GetActiveMessages(GetActiveMessagesRequest) returns (GetActiveMessagesResponse){
+ option (google.api.http) = {
+ get: "/GetActiveMessages"
+ };
+ };
+ rpc GetPublicMessage(GetPublicMessageRequest) returns (GetPublicMessageResponse){
+ option (google.api.http) = {
+ get: "/GetPublicMessage"
+ };
+ };
+}
message PaginationState
{
int32 page_number = 1;
@@ -68,3 +120,151 @@ enum PaymentMethod
IPG = 0;
Wallet = 1;
}
+
+// Public Message Types
+message CreatePublicMessageRequest
+{
+ string title = 1;
+ string content = 2;
+ int32 type = 3;
+ int32 priority = 4;
+ google.protobuf.Timestamp start_date = 5;
+ google.protobuf.Timestamp end_date = 6;
+ google.protobuf.StringValue link_url = 7;
+ google.protobuf.StringValue link_text = 8;
+}
+
+message CreatePublicMessageResponse
+{
+ int64 id = 1;
+}
+
+message UpdatePublicMessageRequest
+{
+ int64 id = 1;
+ string title = 2;
+ string content = 3;
+ int32 type = 4;
+ int32 priority = 5;
+ google.protobuf.Timestamp start_date = 6;
+ google.protobuf.Timestamp end_date = 7;
+ google.protobuf.StringValue link_url = 8;
+ google.protobuf.StringValue link_text = 9;
+}
+
+message DeletePublicMessageRequest
+{
+ int64 message_id = 1;
+}
+
+message PublishMessageRequest
+{
+ int64 message_id = 1;
+}
+
+message PublishMessageResponse
+{
+ bool success = 1;
+ string message = 2;
+ google.protobuf.Timestamp published_at = 3;
+}
+
+message ArchiveMessageRequest
+{
+ int64 message_id = 1;
+}
+
+message ArchiveMessageResponse
+{
+ bool success = 1;
+ string message = 2;
+ google.protobuf.Timestamp archived_at = 3;
+}
+
+message GetAllMessagesRequest
+{
+ int32 page_number = 1;
+ int32 page_size = 2;
+ google.protobuf.BoolValue is_active = 3;
+ google.protobuf.Int32Value type = 4;
+ google.protobuf.Int32Value priority = 5;
+}
+
+message GetAllMessagesResponse
+{
+ MetaData meta_data = 1;
+ repeated AdminPublicMessageDto messages = 2;
+}
+
+message AdminPublicMessageDto
+{
+ int64 id = 1;
+ string title = 2;
+ string content = 3;
+ int32 type = 4;
+ string type_name = 5;
+ int32 priority = 6;
+ string priority_name = 7;
+ bool is_active = 8;
+ google.protobuf.Timestamp starts_at = 9;
+ google.protobuf.Timestamp expires_at = 10;
+ int64 created_by_user_id = 11;
+ int32 view_count = 12;
+ google.protobuf.StringValue link_url = 13;
+ google.protobuf.StringValue link_text = 14;
+ google.protobuf.Timestamp created = 15;
+ google.protobuf.Timestamp last_modified = 16;
+ bool is_expired = 17;
+}
+
+message GetActiveMessagesRequest
+{
+}
+
+message GetActiveMessagesResponse
+{
+ repeated PublicMessageDto messages = 1;
+}
+
+message PublicMessageDto
+{
+ int64 id = 1;
+ string title = 2;
+ string content = 3;
+ int32 type = 4;
+ string type_name = 5;
+ int32 priority = 6;
+ string priority_name = 7;
+ google.protobuf.Timestamp starts_at = 8;
+ google.protobuf.Timestamp expires_at = 9;
+ google.protobuf.StringValue link_url = 10;
+ google.protobuf.StringValue link_text = 11;
+ google.protobuf.Timestamp created = 12;
+}
+
+message GetPublicMessageRequest
+{
+ int64 message_id = 1;
+}
+
+message GetPublicMessageResponse
+{
+ int64 id = 1;
+ string title = 2;
+ string content = 3;
+ int32 type = 4;
+ int32 priority = 5;
+ bool is_active = 6;
+ bool is_archived = 7;
+ google.protobuf.Timestamp start_date = 8;
+ google.protobuf.Timestamp end_date = 9;
+ google.protobuf.Timestamp published_at = 10;
+ google.protobuf.Timestamp archived_at = 11;
+ int64 created_by_user_id = 12;
+ int32 view_count = 13;
+ google.protobuf.StringValue link_url = 14;
+ google.protobuf.StringValue link_text = 15;
+ google.protobuf.Timestamp created_at = 16;
+ google.protobuf.Timestamp last_modified_at = 17;
+}
+
diff --git a/src/CMSMicroservice.Protobuf/Protos/userorder.proto b/src/CMSMicroservice.Protobuf/Protos/userorder.proto
index f594b63..eba196b 100644
--- a/src/CMSMicroservice.Protobuf/Protos/userorder.proto
+++ b/src/CMSMicroservice.Protobuf/Protos/userorder.proto
@@ -55,6 +55,30 @@ service UserOrderContract
body: "*"
};
};
+
+ // Order Management
+ rpc UpdateOrderStatus(UpdateOrderStatusRequest) returns (UpdateOrderStatusResponse){
+ option (google.api.http) = {
+ post: "/UpdateOrderStatus"
+ body: "*"
+ };
+ };
+ rpc GetOrdersByDateRange(GetOrdersByDateRangeRequest) returns (GetOrdersByDateRangeResponse){
+ option (google.api.http) = {
+ get: "/GetOrdersByDateRange"
+ };
+ };
+ rpc ApplyDiscountToOrder(ApplyDiscountToOrderRequest) returns (ApplyDiscountToOrderResponse){
+ option (google.api.http) = {
+ post: "/ApplyDiscountToOrder"
+ body: "*"
+ };
+ };
+ rpc CalculateOrderPV(CalculateOrderPVRequest) returns (CalculateOrderPVResponse){
+ option (google.api.http) = {
+ get: "/CalculateOrderPV"
+ };
+ };
}
message CreateNewUserOrderRequest
{
@@ -247,3 +271,85 @@ message CancelOrderResponse
string message = 3;
bool refund_processed = 4;
}
+
+// Order Management Messages
+message UpdateOrderStatusRequest
+{
+ int64 order_id = 1;
+ int32 new_status = 2;
+}
+
+message UpdateOrderStatusResponse
+{
+ bool success = 1;
+ string message = 2;
+ int32 old_status = 3;
+ int32 new_status = 4;
+}
+
+message GetOrdersByDateRangeRequest
+{
+ google.protobuf.Timestamp start_date = 1;
+ google.protobuf.Timestamp end_date = 2;
+ google.protobuf.Int32Value status = 3;
+ google.protobuf.Int64Value user_id = 4;
+ int32 page_number = 5;
+ int32 page_size = 6;
+}
+
+message GetOrdersByDateRangeResponse
+{
+ messages.MetaData meta_data = 1;
+ repeated OrderSummaryDto orders = 2;
+}
+
+message OrderSummaryDto
+{
+ int64 order_id = 1;
+ string order_number = 2;
+ int64 user_id = 3;
+ string user_full_name = 4;
+ int64 total_amount = 5;
+ int32 status = 6;
+ string status_name = 7;
+ int32 items_count = 8;
+ google.protobuf.Timestamp created_at = 9;
+}
+
+message ApplyDiscountToOrderRequest
+{
+ int64 order_id = 1;
+ int64 discount_amount = 2;
+ string reason = 3;
+}
+
+message ApplyDiscountToOrderResponse
+{
+ bool success = 1;
+ string message = 2;
+ int64 original_amount = 3;
+ int64 discount_amount = 4;
+ int64 final_amount = 5;
+}
+
+message CalculateOrderPVRequest
+{
+ int64 order_id = 1;
+}
+
+message CalculateOrderPVResponse
+{
+ int64 order_id = 1;
+ int64 total_pv = 2;
+ repeated ProductPVDto products = 3;
+}
+
+message ProductPVDto
+{
+ int64 product_id = 1;
+ string product_title = 2;
+ int32 quantity = 3;
+ int64 unit_pv = 4;
+ int64 total_pv = 5;
+}
+
diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/PackageProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/PackageProfile.cs
index 9e8a8f8..8611951 100644
--- a/src/CMSMicroservice.WebApi/Common/Mappings/PackageProfile.cs
+++ b/src/CMSMicroservice.WebApi/Common/Mappings/PackageProfile.cs
@@ -1,10 +1,58 @@
+using CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
+using CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
+using CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus;
+using CMSMicroservice.Protobuf.Protos.Package;
+using Google.Protobuf.WellKnownTypes;
+
namespace CMSMicroservice.WebApi.Common.Mappings;
public class PackageProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
- //config.NewConfig()
- // .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}");
+ // PurchaseGoldenPackage
+ config.NewConfig()
+ .Map(dest => dest.UserId, src => src.UserId)
+ .Map(dest => dest.PackageId, src => src.PackageId)
+ .Map(dest => dest.ReturnUrl, src => src.ReturnUrl);
+
+ config.NewConfig()
+ .Map(dest => dest.Success, src => src.Success)
+ .Map(dest => dest.Message, src => src.Message)
+ .Map(dest => dest.OrderId, src => src.OrderId)
+ .Map(dest => dest.PaymentGatewayUrl, src => src.PaymentGatewayUrl)
+ .Map(dest => dest.TrackingCode, src => src.TrackingCode);
+
+ // VerifyGoldenPackagePurchase
+ config.NewConfig()
+ .Map(dest => dest.OrderId, src => src.OrderId)
+ .Map(dest => dest.Authority, src => src.Authority)
+ .Map(dest => dest.Status, src => src.Status);
+
+ config.NewConfig()
+ .Map(dest => dest.Success, src => src.Success)
+ .Map(dest => dest.Message, src => src.Message)
+ .Map(dest => dest.OrderId, src => src.OrderId)
+ .Map(dest => dest.TransactionId, src => src.TransactionId)
+ .Map(dest => dest.ReferenceCode, src => src.ReferenceCode)
+ .Map(dest => dest.WalletBalance, src => src.WalletBalance);
+
+ // GetUserPackageStatus
+ config.NewConfig()
+ .Map(dest => dest.UserId, src => src.UserId);
+
+ config.NewConfig()
+ .Map(dest => dest.UserId, src => src.UserId)
+ .Map(dest => dest.PackagePurchaseMethod, src => src.PackagePurchaseMethod)
+ .Map(dest => dest.HasPurchasedPackage, src => src.HasPurchasedPackage)
+ .Map(dest => dest.IsClubMemberActive, src => src.IsClubMemberActive)
+ .Map(dest => dest.WalletBalance, src => src.WalletBalance)
+ .Map(dest => dest.DiscountBalance, src => src.DiscountBalance)
+ .Map(dest => dest.CanActivateClubMembership, src => src.CanActivateClubMembership)
+ .Map(dest => dest.LastOrderNumber, src => src.LastOrderNumber != null ? src.LastOrderNumber : null)
+ .Map(dest => dest.LastPurchaseDate, src => src.LastPurchaseDate.HasValue
+ ? Timestamp.FromDateTime(src.LastPurchaseDate.Value.ToUniversalTime())
+ : null);
}
}
+
diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/UserOrderProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/UserOrderProfile.cs
index 89e2a90..6164a65 100644
--- a/src/CMSMicroservice.WebApi/Common/Mappings/UserOrderProfile.cs
+++ b/src/CMSMicroservice.WebApi/Common/Mappings/UserOrderProfile.cs
@@ -1,3 +1,9 @@
+using CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus;
+using CMSMicroservice.Application.UserOrderCQ.Commands.ApplyDiscountToOrder;
+using CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange;
+using CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV;
+using Google.Protobuf.WellKnownTypes;
+
namespace CMSMicroservice.WebApi.Common.Mappings;
public class UserOrderProfile : IRegister
@@ -8,5 +14,31 @@ public class UserOrderProfile : IRegister
.IgnoreIf((src, dest) => src.Filter == null || !src.Filter.HasPaymentStatus, dest => dest.Filter.PaymentStatus)
.IgnoreIf((src, dest) => src.Filter == null || !src.Filter.HasPaymentMethod, dest => dest.Filter.PaymentMethod)
.IgnoreIf((src, dest) => src.Filter == null || !src.Filter.HasDeliveryStatus, dest => dest.Filter.DeliveryStatus);
+
+ // UpdateOrderStatus
+ config.NewConfig();
+ config.NewConfig();
+
+ // GetOrdersByDateRange
+ config.NewConfig()
+ .Map(dest => dest.StartDate, src => src.StartDate.ToDateTime())
+ .Map(dest => dest.EndDate, src => src.EndDate.ToDateTime())
+ .Map(dest => dest.Status, src => src.Status != null ? (int?)src.Status.Value : null)
+ .Map(dest => dest.UserId, src => src.UserId != null ? (long?)src.UserId.Value : null);
+
+ config.NewConfig()
+ .Map(dest => dest.Orders, src => src.Orders);
+
+ config.NewConfig()
+ .Map(dest => dest.CreatedAt, src => Timestamp.FromDateTime(src.Created.ToUniversalTime()));
+
+ // ApplyDiscountToOrder
+ config.NewConfig();
+ config.NewConfig();
+
+ // CalculateOrderPV
+ config.NewConfig();
+ config.NewConfig();
+ config.NewConfig();
}
}
diff --git a/src/CMSMicroservice.WebApi/Services/PackageService.cs b/src/CMSMicroservice.WebApi/Services/PackageService.cs
index cfb27ea..93ed127 100644
--- a/src/CMSMicroservice.WebApi/Services/PackageService.cs
+++ b/src/CMSMicroservice.WebApi/Services/PackageService.cs
@@ -3,8 +3,11 @@ using CMSMicroservice.WebApi.Common.Services;
using CMSMicroservice.Application.PackageCQ.Commands.CreateNewPackage;
using CMSMicroservice.Application.PackageCQ.Commands.UpdatePackage;
using CMSMicroservice.Application.PackageCQ.Commands.DeletePackage;
+using CMSMicroservice.Application.PackageCQ.Commands.PurchaseGoldenPackage;
+using CMSMicroservice.Application.PackageCQ.Commands.VerifyGoldenPackagePurchase;
using CMSMicroservice.Application.PackageCQ.Queries.GetPackage;
using CMSMicroservice.Application.PackageCQ.Queries.GetAllPackageByFilter;
+using CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus;
namespace CMSMicroservice.WebApi.Services;
public class PackageService : PackageContract.PackageContractBase
{
@@ -34,4 +37,19 @@ public class PackageService : PackageContract.PackageContractBase
{
return await _dispatchRequestToCQRS.Handle(request, context);
}
+
+ public override async Task PurchaseGoldenPackage(PurchaseGoldenPackageRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
+
+ public override async Task VerifyGoldenPackagePurchase(VerifyGoldenPackagePurchaseRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
+
+ public override async Task GetUserPackageStatus(GetUserPackageStatusRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
}
diff --git a/src/CMSMicroservice.WebApi/Services/PublicMessageService.cs b/src/CMSMicroservice.WebApi/Services/PublicMessageService.cs
new file mode 100644
index 0000000..0672b71
--- /dev/null
+++ b/src/CMSMicroservice.WebApi/Services/PublicMessageService.cs
@@ -0,0 +1,63 @@
+using CMSMicroservice.Protobuf.Protos;
+using CMSMicroservice.WebApi.Common.Services;
+using CMSMicroservice.Application.PublicMessageCQ.Commands.CreatePublicMessage;
+using CMSMicroservice.Application.PublicMessageCQ.Commands.UpdatePublicMessage;
+using CMSMicroservice.Application.PublicMessageCQ.Commands.DeletePublicMessage;
+using CMSMicroservice.Application.PublicMessageCQ.Commands.PublishMessage;
+using CMSMicroservice.Application.PublicMessageCQ.Commands.ArchiveMessage;
+using CMSMicroservice.Application.PublicMessageCQ.Queries.GetAllMessages;
+using CMSMicroservice.Application.PublicMessageCQ.Queries.GetActiveMessages;
+using CMSMicroservice.Application.PublicMessageCQ.Queries.GetPublicMessage;
+using Google.Protobuf.WellKnownTypes;
+
+namespace CMSMicroservice.WebApi.Services;
+
+public class PublicMessageService : PublicMessageContract.PublicMessageContractBase
+{
+ private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
+
+ public PublicMessageService(IDispatchRequestToCQRS dispatchRequestToCQRS)
+ {
+ _dispatchRequestToCQRS = dispatchRequestToCQRS;
+ }
+
+ public override async Task CreatePublicMessage(CreatePublicMessageRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
+
+ public override async Task UpdatePublicMessage(UpdatePublicMessageRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
+
+ public override async Task DeletePublicMessage(DeletePublicMessageRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
+
+ public override async Task PublishMessage(PublishMessageRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
+
+ public override async Task ArchiveMessage(ArchiveMessageRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
+
+ public override async Task GetAllMessages(GetAllMessagesRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
+
+ public override async Task GetActiveMessages(GetActiveMessagesRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
+
+ public override async Task GetPublicMessage(GetPublicMessageRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
+}
diff --git a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs
index 96984af..9cf8b31 100644
--- a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs
+++ b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs
@@ -3,8 +3,12 @@ using CMSMicroservice.WebApi.Common.Services;
using CMSMicroservice.Application.UserOrderCQ.Commands.CreateNewUserOrder;
using CMSMicroservice.Application.UserOrderCQ.Commands.UpdateUserOrder;
using CMSMicroservice.Application.UserOrderCQ.Commands.DeleteUserOrder;
+using CMSMicroservice.Application.UserOrderCQ.Commands.UpdateOrderStatus;
+using CMSMicroservice.Application.UserOrderCQ.Commands.ApplyDiscountToOrder;
using CMSMicroservice.Application.UserOrderCQ.Queries.GetUserOrder;
using CMSMicroservice.Application.UserOrderCQ.Queries.GetAllUserOrderByFilter;
+using CMSMicroservice.Application.UserOrderCQ.Queries.GetOrdersByDateRange;
+using CMSMicroservice.Application.UserOrderCQ.Queries.CalculateOrderPV;
using CMSMicroservice.Application.UserOrderCQ.Commands.SubmitShopBuyOrder;
using CMSMicroservice.Application.UserOrderCQ.Commands.CancelOrder;
@@ -46,4 +50,24 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
{
return await _dispatchRequestToCQRS.Handle(request, context);
}
+
+ public override async Task UpdateOrderStatus(UpdateOrderStatusRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
+
+ public override async Task GetOrdersByDateRange(GetOrdersByDateRangeRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
+
+ public override async Task ApplyDiscountToOrder(ApplyDiscountToOrderRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
+
+ public override async Task CalculateOrderPV(CalculateOrderPVRequest request, ServerCallContext context)
+ {
+ return await _dispatchRequestToCQRS.Handle(request, context);
+ }
}