using CMSMicroservice.Application.Common.Authorization; using CMSMicroservice.Protobuf.Protos.UserOrder; using CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrders; using CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrder; using CMSMicroservice.Application.UserOrderCQ.Queries.GetCustomerOrderHistory; using CMSMicroservice.Application.OrderManagementCQ.Commands.UpdateOrderStatus; using CMSMicroservice.Application.OrderManagementCQ.Commands.CancelOrderByAdmin; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Common; using CMSMicroservice.Domain.Entities; using CMSMicroservice.Domain.Entities.Club; using CMSMicroservice.Domain.Entities.Order; using CMSMicroservice.Domain.Enums; using AppModels = CMSMicroservice.Application.Common.Models; using Grpc.Core; using Google.Protobuf.WellKnownTypes; using System.Collections.Generic; using System.Linq; using CMSMicroservice.Protobuf.Protos; using MediatR; using Mapster; using Microsoft.EntityFrameworkCore; using System.Linq; namespace CMSMicroservice.WebApi.Services; public class UserOrderService : UserOrderContract.UserOrderContractBase { private readonly ISender _sender; private readonly IApplicationDbContext _context; private readonly ICurrentUserService _currentUserService; public UserOrderService(ISender sender, IApplicationDbContext context, ICurrentUserService currentUserService) { _sender = sender; _context = context; _currentUserService = currentUserService; } [RequiresPermission(PermissionNames.OrdersCreate)] public override async Task CreateNewUserOrder(CreateNewUserOrderRequest request, ServerCallContext context) { var order = new UserOrder { Amount = request.Amount, PackageId = request.PackageId > 0 ? request.PackageId : null, TransactionId = request.TransactionId, PaymentStatus = request.HasPaymentStatus ? (Domain.Enums.PaymentStatus)(int)request.PaymentStatus : Domain.Enums.PaymentStatus.Pending, PaymentDate = request.PaymentDate?.ToDateTime(), UserId = request.UserId, UserAddressId = request.UserAddressId, PaymentMethod = request.HasPaymentMethod ? (Domain.Enums.PaymentMethod)(int)request.PaymentMethod : null, DeliveryStatus = Domain.Enums.DeliveryStatus.Pending }; _context.UserOrders.Add(order); await _context.SaveChangesAsync(context.CancellationToken); return new CreateNewUserOrderResponse { Id = order.Id }; } [RequiresPermission(PermissionNames.OrdersUpdate)] public override async Task UpdateUserOrder(UpdateUserOrderRequest request, ServerCallContext context) { var order = await _context.UserOrders.FindAsync(new object[] { request.Id }, context.CancellationToken); if (order == null) throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); if (request.Amount != null) order.Amount = request.Amount.Value; if (request.PackageId != null) order.PackageId = request.PackageId.Value; if (request.TransactionId != null) order.TransactionId = request.TransactionId.Value; if (request.HasPaymentStatus) order.PaymentStatus = (Domain.Enums.PaymentStatus)(int)request.PaymentStatus; if (request.PaymentDate != null) order.PaymentDate = request.PaymentDate.ToDateTime(); if (request.UserId != null) order.UserId = request.UserId.Value; if (request.UserAddressId != null) order.UserAddressId = request.UserAddressId.Value; if (request.HasPaymentMethod) order.PaymentMethod = (Domain.Enums.PaymentMethod)(int)request.PaymentMethod; if (request.HasDeliveryStatus) order.DeliveryStatus = (Domain.Enums.DeliveryStatus)(int)request.DeliveryStatus; if (request.TrackingCode != null) order.TrackingCode = request.TrackingCode; if (request.DeliveryDescription != null) order.DeliveryDescription = request.DeliveryDescription; await _context.SaveChangesAsync(context.CancellationToken); return new Google.Protobuf.WellKnownTypes.Empty(); } [RequiresPermission(PermissionNames.OrdersDelete)] public override async Task DeleteUserOrder(DeleteUserOrderRequest request, ServerCallContext context) { var order = await _context.UserOrders.FindAsync(new object[] { request.Id }, context.CancellationToken); if (order == null) throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); order.IsDeleted = true; await _context.SaveChangesAsync(context.CancellationToken); return new Google.Protobuf.WellKnownTypes.Empty(); } [RequiresPermission(PermissionNames.OrdersView)] public override async Task GetUserOrder(GetUserOrderRequest request, ServerCallContext context) { var query = new GetCustomerOrderQuery { OrderId = request.Id, UserId = 0 // Admin: no user filter, can view any order by ID }; var result = await _sender.Send(query, context.CancellationToken); var response = new GetUserOrderResponse { Id = result.Id, Amount = result.Amount, PackageId = result.PackageId ?? 0, TransactionId = result.TransactionId, UserId = result.UserId, UserAddressId = result.UserAddressId, UserAddressText = result.UserAddressText, TrackingCode = result.TrackingCode, DeliveryDescription = result.DeliveryDescription, UserFullName = result.UserFullName, UserNationalCode = result.UserNationalCode }; // VAT Info if (result.VatAmount > 0) { response.VatInfo = new OrderVATInfo { VatRate = result.VatPercentage / 100, BaseAmount = result.Amount - result.VatAmount, VatAmount = result.VatAmount, TotalAmount = result.Amount, IsPaid = result.PaymentStatus == Domain.Enums.PaymentStatus.Success }; } response.PaymentStatus = (CMSMicroservice.Protobuf.Protos.PaymentStatus)result.PaymentStatus; if (result.PaymentDate.HasValue) response.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(result.PaymentDate.Value, DateTimeKind.Utc)); if (result.PaymentMethod.HasValue) response.PaymentMethod = (CMSMicroservice.Protobuf.Protos.PaymentMethod)result.PaymentMethod.Value; response.DeliveryStatus = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)result.DeliveryStatus; foreach (var fd in result.FactorDetails) { response.FactorDetails.Add(new GetUserOrderResponseFactorDetail { ProductId = fd.ProductId, ProductTitle = fd.ProductTitle, ProductThumbnailPath = fd.ProductThumbnailPath, UnitPrice = fd.UnitPrice, Count = fd.Count, UnitDiscountPrice = fd.UnitDiscountPrice }); } return response; } [RequiresPermission(PermissionNames.OrdersView)] public override async Task GetAllUserOrderByFilter(GetAllUserOrderByFilterRequest request, ServerCallContext context) { // Admin API - can view all orders or filter by specific user var query = new GetCustomerOrdersQuery { UserId = request.Filter?.UserId ?? 0, // 0 means all users (admin view) PaginationState = request.PaginationState?.Adapt(), PaymentStatusFilter = request.Filter?.HasPaymentStatus == true ? (int?)request.Filter.PaymentStatus : null, DeliveryStatusFilter = request.Filter?.HasDeliveryStatus == true ? (int?)request.Filter.DeliveryStatus : null, FromDate = request.Filter?.FromDate?.ToDateTime() ?? request.Filter?.PaymentDate?.ToDateTime(), ToDate = request.Filter?.ToDate?.ToDateTime() }; var result = await _sender.Send(query, context.CancellationToken); var response = new GetAllUserOrderByFilterResponse { MetaData = result.MetaData.Adapt() }; foreach (var model in result.Models) { var orderModel = new GetAllUserOrderByFilterResponseModel { Id = model.Id, Amount = model.Amount, PackageId = model.PackageId ?? 0, TransactionId = model.TransactionId, UserId = model.UserId, UserAddressId = model.UserAddressId, UserAddressText = model.UserAddressText, TrackingCode = model.TrackingCode, DeliveryDescription = model.DeliveryDescription, UserFullName = model.UserFullName, UserNationalCode = model.UserNationalCode, VatAmount = model.VatAmount, VatPercentage = model.VatPercentage }; orderModel.PaymentStatus = (CMSMicroservice.Protobuf.Protos.PaymentStatus)model.PaymentStatus; if (model.PaymentDate.HasValue) orderModel.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(model.PaymentDate.Value, DateTimeKind.Utc)); if (model.PaymentMethod.HasValue) orderModel.PaymentMethod = (CMSMicroservice.Protobuf.Protos.PaymentMethod)model.PaymentMethod.Value; orderModel.DeliveryStatus = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)model.DeliveryStatus; foreach (var fd in model.FactorDetails) { orderModel.FactorDetails.Add(new GetAllUserOrderByFilterResponseModelFactorDetail { ProductId = fd.ProductId, ProductTitle = fd.ProductTitle, ProductThumbnailPath = fd.ProductThumbnailPath, UnitPrice = fd.UnitPrice, Count = fd.Count, UnitDiscountPrice = fd.UnitDiscountPrice }); } response.Models.Add(orderModel); } return response; } public override async Task SubmitShopBuyOrder(SubmitShopBuyOrderRequest request, ServerCallContext context) { // Get UserId from JWT or request var userId = !string.IsNullOrEmpty(_currentUserService.UserId) ? long.Parse(_currentUserService.UserId) : request.UserId; if (userId == 0) { throw new RpcException(new Status(StatusCode.Unauthenticated, "User not authenticated")); } // Get user's cart items with product details var cartItems = await _context.UserCarts .Include(c => c.Product) .Where(c => c.UserId == userId && !c.IsDeleted) .ToListAsync(context.CancellationToken); if (!cartItems.Any()) { throw new RpcException(new Status(StatusCode.FailedPrecondition, "سبد خرید خالی است")); } // Get user's default address var defaultAddress = await _context.UserAddresses .Where(a => a.UserId == userId && a.IsDefault && !a.IsDeleted) .FirstOrDefaultAsync(context.CancellationToken); if (defaultAddress == null) { // Check if user has any address at all var hasAnyAddress = await _context.UserAddresses .AnyAsync(a => a.UserId == userId && !a.IsDeleted, context.CancellationToken); throw new RpcException(new Status(StatusCode.FailedPrecondition, hasAnyAddress ? "لطفاً یک آدرس را به عنوان پیش‌فرض انتخاب کنید." : "آدرسی ثبت نشده است. لطفاً ابتدا یک آدرس اضافه کنید.")); } // Calculate amounts const decimal vatRate = 0.10m; long baseAmount = cartItems.Sum(c => c.Product.Price * c.Count); long vatAmount = (long)(baseAmount * vatRate); long totalAmount = baseAmount + vatAmount; // Validate total amount if (request.TotalAmount > 0 && Math.Abs(totalAmount - request.TotalAmount) > 100) { throw new RpcException(new Status(StatusCode.InvalidArgument, $"مبلغ نامعتبر است. محاسبه شده: {totalAmount}, دریافتی: {request.TotalAmount}")); } // Get user's wallet var wallet = await _context.UserWallets .Where(w => w.UserId == userId) .FirstOrDefaultAsync(context.CancellationToken); if (wallet == null) { throw new RpcException(new Status(StatusCode.FailedPrecondition, "کیف پول یافت نشد")); } // Check wallet balance if (wallet.Balance < totalAmount) { throw new RpcException(new Status(StatusCode.FailedPrecondition, $"موجودی کیف پول کافی نیست. موجودی: {wallet.Balance:N0} تومان، مورد نیاز: {totalAmount:N0} تومان")); } // Create transaction var transaction = new Transaction { Amount = totalAmount, Description = $"خرید محصولات - سفارش شماره در حال ایجاد", PaymentStatus = CMSMicroservice.Domain.Enums.PaymentStatus.Success, PaymentDate = DateTime.UtcNow, Type = CMSMicroservice.Domain.Enums.TransactionType.Buy, RefId = $"SHOP_{DateTime.UtcNow.Ticks}" }; _context.Transactions.Add(transaction); await _context.SaveChangesAsync(context.CancellationToken); // Deduct from wallet var oldBalance = wallet.Balance; wallet.Balance -= totalAmount; // Create wallet change log var walletLog = new UserWalletChangeLog { WalletId = wallet.Id, CurrentBalance = wallet.Balance, ChangeValue = -totalAmount, CurrentNetworkBalance = wallet.NetworkBalance, ChangeNerworkValue = 0, CurrentDiscountBalance = wallet.DiscountBalance, ChangeDiscountValue = 0, IsIncrease = false, RefrenceId = transaction.Id }; _context.UserWalletChangeLogs.Add(walletLog); // ═══ Magic Wallet: Entry / Exit Trigger ═══ if (wallet.Balance == 0) { var user = await _context.Users .Include(u => u.ClubMembership) .FirstOrDefaultAsync(u => u.Id == userId, context.CancellationToken); if (wallet.WalletMode == WalletMode.Magic && wallet.MagicTotalDeposited >= SystemConstants.MagicWalletMaxDeposit) { // ── EXIT Magic Mode ── // هر دو شرط: Balance=0 و سقف 100M پر شده wallet.WalletMode = WalletMode.Normal; wallet.MagicCompletedAt = DateTime.UtcNow; var currentCycle = await _context.ClubMembershipCycles .FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, context.CancellationToken); if (currentCycle != null) { currentCycle.MagicCompletedAt = DateTime.UtcNow; } } else if (wallet.WalletMode == WalletMode.Normal && user != null && user.PackagePurchaseMethod != PackagePurchaseMethod.None && user.ClubMembership?.IsActive == true) { // ── ENTER Magic Mode ── wallet.WalletMode = WalletMode.Magic; wallet.MagicActivatedAt = DateTime.UtcNow; wallet.MagicCompletedAt = null; wallet.MagicTotalDeposited = 0; wallet.MagicTotalCredited = 0; var currentCycle = await _context.ClubMembershipCycles .FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, context.CancellationToken); if (currentCycle != null) { currentCycle.MagicStartedAt = DateTime.UtcNow; } } // ⚠️ اگه Magic باشه و Balance=0 ولی سقف پر نشده → هنوز Magic! // کاربر میتونه دوباره شارژ کنه } // Create order var order = new UserOrder { UserId = userId, UserAddressId = defaultAddress.Id, Amount = totalAmount, TransactionId = transaction.Id, PaymentStatus = CMSMicroservice.Domain.Enums.PaymentStatus.Success, PaymentDate = DateTime.UtcNow, PaymentMethod = CMSMicroservice.Domain.Enums.PaymentMethod.Wallet, DeliveryStatus = CMSMicroservice.Domain.Enums.DeliveryStatus.Pending, HasVAT = true }; _context.UserOrders.Add(order); await _context.SaveChangesAsync(context.CancellationToken); // Update transaction description with order ID transaction.Description = $"خرید محصولات - سفارش #{order.Id}"; await _context.SaveChangesAsync(context.CancellationToken); // Create order VAT record var orderVat = new OrderVAT { OrderId = order.Id, VATRate = vatRate, BaseAmount = baseAmount, VATAmount = vatAmount, TotalAmount = totalAmount }; _context.OrderVATs.Add(orderVat); // Create factor details for each cart item foreach (var cartItem in cartItems) { var factorDetail = new FactorDetails { OrderId = order.Id, ProductId = cartItem.ProductId, Count = cartItem.Count, UnitPrice = cartItem.Product.Price, UnitDiscount = 0, UnitDiscountPrice = cartItem.Product.Price, IsChangePrice = false }; _context.FactorDetails.Add(factorDetail); } await _context.SaveChangesAsync(context.CancellationToken); // Clear user's cart foreach (var cartItem in cartItems) { cartItem.IsDeleted = true; } await _context.SaveChangesAsync(context.CancellationToken); return new SubmitShopBuyOrderResponse { Id = order.Id }; } [RequiresPermission(PermissionNames.OrdersCancel)] public override async Task CancelOrder(CancelOrderRequest request, ServerCallContext context) { var command = new CancelOrderByAdminCommand { OrderId = request.OrderId, CancelReason = request.CancelReason, RefundToWallet = request.RefundPayment }; await _sender.Send(command, context.CancellationToken); return new CancelOrderResponse { OrderId = request.OrderId, Status = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)(int)Domain.Enums.DeliveryStatus.Cancelled, Message = "سفارش با موفقیت لغو شد", RefundProcessed = request.RefundPayment }; } [RequiresPermission(PermissionNames.OrdersUpdate)] public override async Task UpdateOrderStatus(UpdateOrderStatusRequest request, ServerCallContext context) { var order = await _context.UserOrders.FindAsync(new object[] { request.OrderId }, context.CancellationToken); if (order == null) throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); var oldStatus = (int)order.DeliveryStatus; var command = new Application.OrderManagementCQ.Commands.UpdateOrderStatus.UpdateOrderStatusCommand { OrderId = request.OrderId, NewStatus = (Domain.Enums.DeliveryStatus)request.NewStatus }; await _sender.Send(command, context.CancellationToken); return new UpdateOrderStatusResponse { Success = true, Message = "وضعیت سفارش با موفقیت تغییر کرد", OldStatus = oldStatus, NewStatus = request.NewStatus }; } [RequiresPermission(PermissionNames.ReportsView)] public override async Task GetOrdersByDateRange(GetOrdersByDateRangeRequest request, ServerCallContext context) { var pageNumber = request.PageNumber > 0 ? request.PageNumber : 1; var pageSize = request.PageSize > 0 ? request.PageSize : 20; var query = _context.UserOrders .Include(o => o.User) .Include(o => o.FactorDetails) .Where(o => !o.IsDeleted); if (request.StartDate != null) query = query.Where(o => o.Created >= request.StartDate.ToDateTime()); if (request.EndDate != null) query = query.Where(o => o.Created <= request.EndDate.ToDateTime()); if (request.Status != null) query = query.Where(o => (int)o.DeliveryStatus == request.Status.Value); if (request.UserId != null) query = query.Where(o => o.UserId == request.UserId.Value); var totalCount = await query.CountAsync(context.CancellationToken); var orders = await query .OrderByDescending(o => o.Created) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .Select(o => new OrderSummaryDto { OrderId = o.Id, OrderNumber = $"ORD-{o.Id:D6}", UserId = o.UserId, UserFullName = o.User != null ? (o.User.FirstName + " " + o.User.LastName) : string.Empty, TotalAmount = o.Amount, Status = (int)o.DeliveryStatus, StatusName = o.DeliveryStatus.ToString(), ItemsCount = o.FactorDetails.Count, CreatedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(o.Created, DateTimeKind.Utc)) }) .ToListAsync(context.CancellationToken); return new GetOrdersByDateRangeResponse { MetaData = new CMSMicroservice.Protobuf.Protos.MetaData { CurrentPage = pageNumber, PageSize = pageSize, TotalCount = totalCount, TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize), HasPrevious = pageNumber > 1, HasNext = pageNumber * pageSize < totalCount }, Orders = { orders } }; } [RequiresPermission(PermissionNames.OrdersUpdate)] public override async Task ApplyDiscountToOrder(ApplyDiscountToOrderRequest request, ServerCallContext context) { var order = await _context.UserOrders.FindAsync(new object[] { request.OrderId }, context.CancellationToken); if (order == null) throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); if (order.PaymentStatus == Domain.Enums.PaymentStatus.Success) return new ApplyDiscountToOrderResponse { Success = false, Message = "امکان اعمال تخفیف برای سفارش پرداخت شده وجود ندارد" }; var originalAmount = order.Amount; var discountAmount = Math.Min(request.DiscountAmount, originalAmount); order.Amount = originalAmount - discountAmount; await _context.SaveChangesAsync(context.CancellationToken); return new ApplyDiscountToOrderResponse { Success = true, Message = $"تخفیف {discountAmount:N0} تومان با موفقیت اعمال شد", OriginalAmount = originalAmount, DiscountAmount = discountAmount, FinalAmount = order.Amount }; } [RequiresPermission(PermissionNames.OrdersView)] public override async Task CalculateOrderPV(CalculateOrderPVRequest request, ServerCallContext context) { var order = await _context.UserOrders .Include(o => o.FactorDetails) .ThenInclude(fd => fd.Product) .Where(o => o.Id == request.OrderId && !o.IsDeleted) .FirstOrDefaultAsync(context.CancellationToken); if (order == null) throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); var products = new List(); long totalPV = 0; foreach (var fd in order.FactorDetails.Where(f => !f.IsDeleted)) { // PV = UnitPrice * Count (simplified - can be customized) long unitPV = fd.UnitPrice; long itemPV = unitPV * fd.Count; totalPV += itemPV; products.Add(new ProductPVDto { ProductId = fd.ProductId, ProductTitle = fd.Product?.Title ?? string.Empty, Quantity = fd.Count, UnitPv = unitPV, TotalPv = itemPV }); } return new CalculateOrderPVResponse { OrderId = request.OrderId, TotalPv = totalPV, Products = { products } }; } // ============= Customer-specific Methods ============= public override async Task CreateNewOrderForCustomer(CreateNewUserOrderRequest request, ServerCallContext context) { // For now, return empty response - will be implemented properly later return new CreateNewUserOrderResponse(); } public override async Task SubmitOrderForCustomer(SubmitShopBuyOrderRequest request, ServerCallContext context) { // For now, return empty response - will be implemented properly later return new SubmitShopBuyOrderResponse(); } public override async Task GetCustomerOrders(GetAllUserOrderByFilterRequest request, ServerCallContext context) { // Customer endpoint: ALWAYS resolve userId from JWT (customer can only see own orders) var customerUserId = long.TryParse(_currentUserService.UserId, out var uid) ? uid : 0; if (customerUserId == 0) throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است")); var query = new GetCustomerOrdersQuery { UserId = customerUserId, PaginationState = request.PaginationState?.Adapt(), PaymentStatusFilter = request.Filter?.HasPaymentStatus == true ? (int?)request.Filter.PaymentStatus : null, DeliveryStatusFilter = request.Filter?.HasDeliveryStatus == true ? (int?)request.Filter.DeliveryStatus : null, FromDate = request.Filter?.FromDate?.ToDateTime() ?? request.Filter?.PaymentDate?.ToDateTime(), ToDate = request.Filter?.ToDate?.ToDateTime() }; var result = await _sender.Send(query, context.CancellationToken); var response = new GetAllUserOrderByFilterResponse { MetaData = result.MetaData.Adapt() }; foreach (var model in result.Models) { var orderModel = new GetAllUserOrderByFilterResponseModel { Id = model.Id, Amount = model.Amount, PackageId = model.PackageId ?? 0, TransactionId = model.TransactionId, UserId = model.UserId, UserAddressId = model.UserAddressId, UserAddressText = model.UserAddressText, TrackingCode = model.TrackingCode, DeliveryDescription = model.DeliveryDescription, UserFullName = model.UserFullName, UserNationalCode = model.UserNationalCode, VatAmount = model.VatAmount, VatPercentage = model.VatPercentage }; orderModel.PaymentStatus = (CMSMicroservice.Protobuf.Protos.PaymentStatus)model.PaymentStatus; if (model.PaymentDate.HasValue) orderModel.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(model.PaymentDate.Value, DateTimeKind.Utc)); if (model.PaymentMethod.HasValue) orderModel.PaymentMethod = (CMSMicroservice.Protobuf.Protos.PaymentMethod)model.PaymentMethod.Value; orderModel.DeliveryStatus = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)model.DeliveryStatus; foreach (var fd in model.FactorDetails) { orderModel.FactorDetails.Add(new GetAllUserOrderByFilterResponseModelFactorDetail { ProductId = fd.ProductId, ProductTitle = fd.ProductTitle, ProductThumbnailPath = fd.ProductThumbnailPath, UnitPrice = fd.UnitPrice, Count = fd.Count, UnitDiscountPrice = fd.UnitDiscountPrice }); } response.Models.Add(orderModel); } return response; } public override async Task GetCustomerOrder(GetUserOrderRequest request, ServerCallContext context) { // Customer endpoint: ALWAYS resolve userId from JWT (customer can only see own orders) var customerUserId = long.TryParse(_currentUserService.UserId, out var uid) ? uid : 0; if (customerUserId == 0) throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است")); var query = new GetCustomerOrderQuery { OrderId = request.Id, UserId = customerUserId }; var result = await _sender.Send(query, context.CancellationToken); var response = new GetUserOrderResponse { Id = result.Id, Amount = result.Amount, PackageId = result.PackageId ?? 0, TransactionId = result.TransactionId, UserId = result.UserId, UserAddressId = result.UserAddressId, UserAddressText = result.UserAddressText, TrackingCode = result.TrackingCode, DeliveryDescription = result.DeliveryDescription, UserFullName = result.UserFullName, UserNationalCode = result.UserNationalCode }; // VAT Info if (result.VatAmount > 0) { response.VatInfo = new OrderVATInfo { VatRate = result.VatPercentage / 100, BaseAmount = result.Amount - result.VatAmount, VatAmount = result.VatAmount, TotalAmount = result.Amount, IsPaid = result.PaymentStatus == Domain.Enums.PaymentStatus.Success }; } response.PaymentStatus = (CMSMicroservice.Protobuf.Protos.PaymentStatus)result.PaymentStatus; if (result.PaymentDate.HasValue) response.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(result.PaymentDate.Value, DateTimeKind.Utc)); if (result.PaymentMethod.HasValue) response.PaymentMethod = (CMSMicroservice.Protobuf.Protos.PaymentMethod)result.PaymentMethod.Value; response.DeliveryStatus = (CMSMicroservice.Protobuf.Protos.DeliveryStatus)result.DeliveryStatus; foreach (var fd in result.FactorDetails) { response.FactorDetails.Add(new GetUserOrderResponseFactorDetail { ProductId = fd.ProductId, ProductTitle = fd.ProductTitle, ProductThumbnailPath = fd.ProductThumbnailPath, UnitPrice = fd.UnitPrice, Count = fd.Count, UnitDiscountPrice = fd.UnitDiscountPrice }); } return response; } // ============= Customer-specific Method Implementations ============= public override async Task CustomerCancelOrder(CustomerCancelOrderRequest request, ServerCallContext context) { var order = await _context.UserOrders .Where(o => o.Id == request.OrderId && !o.IsDeleted) .FirstOrDefaultAsync(context.CancellationToken); if (order == null) return new CustomerCancelOrderResponse { Success = false, Message = "سفارش یافت نشد" }; if (order.DeliveryStatus != Domain.Enums.DeliveryStatus.Pending) return new CustomerCancelOrderResponse { Success = false, Message = "فقط سفارش‌های در انتظار قابل لغو هستند" }; var refundAmount = order.Amount; order.DeliveryStatus = Domain.Enums.DeliveryStatus.Cancelled; // Refund to wallet if payment was from wallet if (order.PaymentStatus == Domain.Enums.PaymentStatus.Success && order.PaymentMethod == Domain.Enums.PaymentMethod.Wallet) { var wallet = await _context.UserWallets .Where(w => w.UserId == order.UserId) .FirstOrDefaultAsync(context.CancellationToken); if (wallet != null) { wallet.Balance += refundAmount; _context.UserWalletChangeLogs.Add(new UserWalletChangeLog { WalletId = wallet.Id, CurrentBalance = wallet.Balance, ChangeValue = refundAmount, CurrentNetworkBalance = wallet.NetworkBalance, ChangeNerworkValue = 0, CurrentDiscountBalance = wallet.DiscountBalance, ChangeDiscountValue = 0, IsIncrease = true, RefrenceId = order.TransactionId ?? 0 }); } } await _context.SaveChangesAsync(context.CancellationToken); return new CustomerCancelOrderResponse { Success = true, Message = "سفارش شما با موفقیت لغو شد", RefundAmount = refundAmount, RefundTransactionId = $"REF{order.Id}" }; } public override async Task GetCustomerOrderHistory(GetCustomerOrderHistoryRequest request, ServerCallContext context) { var query = new GetCustomerOrderHistoryQuery { UserId = request.UserId, PaginationState = request.PaginationState?.Adapt(), StatusFilter = request.StatusFilter != OrderStatusEnum.OrderStatusPending ? (int?)request.StatusFilter : null, FromDate = request.FromDate?.ToDateTime(), ToDate = request.ToDate?.ToDateTime() }; var result = await _sender.Send(query, context.CancellationToken); var response = new GetCustomerOrderHistoryResponse { MetaData = result.MetaData.Adapt() }; foreach (var order in result.Orders) { response.Orders.Add(new Protobuf.Protos.UserOrder.CustomerOrderModel { Id = order.Id, Amount = order.Amount, PackageId = order.PackageId ?? 0, PackageName = order.PackageName, Status = (OrderStatusEnum)order.Status, StatusMessage = order.StatusMessage, OrderDate = Timestamp.FromDateTime(DateTime.SpecifyKind(order.OrderDate, DateTimeKind.Utc)), DeliveryDate = order.DeliveryDate.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(order.DeliveryDate.Value, DateTimeKind.Utc)) : null, TrackingCode = order.TrackingCode, ItemsCount = order.ItemsCount, CanCancel = order.CanCancel, CanReorder = order.CanReorder }); } return response; } public override async Task CustomerTrackOrder(CustomerTrackOrderRequest request, ServerCallContext context) { var order = await _context.UserOrders .Include(o => o.FactorDetails) .Include(o => o.Package) .Where(o => o.Id == request.OrderId && !o.IsDeleted) .FirstOrDefaultAsync(context.CancellationToken); if (order == null) throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد")); var orderModel = new Protobuf.Protos.UserOrder.CustomerOrderModel { Id = order.Id, Amount = order.Amount, PackageId = order.PackageId ?? 0, PackageName = order.Package?.Title ?? string.Empty, Status = (OrderStatusEnum)(int)order.DeliveryStatus, StatusMessage = GetDeliveryStatusPersian(order.DeliveryStatus), OrderDate = Timestamp.FromDateTime(DateTime.SpecifyKind(order.Created, DateTimeKind.Utc)), TrackingCode = order.TrackingCode ?? string.Empty, ItemsCount = order.FactorDetails.Count(f => !f.IsDeleted), CanCancel = order.DeliveryStatus == Domain.Enums.DeliveryStatus.Pending, CanReorder = order.DeliveryStatus == Domain.Enums.DeliveryStatus.Delivered }; var response = new CustomerTrackOrderResponse { Order = orderModel, DeliveryInfo = new DeliveryTrackingInfo { TrackingCode = order.TrackingCode ?? string.Empty, CourierName = "پست پیشتاز", EstimatedDelivery = order.DeliveryDescription ?? string.Empty, CurrentLocation = string.Empty } }; // Add current status to history response.StatusHistory.Add(new OrderStatusHistory { Status = (OrderStatusEnum)(int)order.DeliveryStatus, StatusMessage = GetDeliveryStatusPersian(order.DeliveryStatus), ChangedAt = order.LastModified.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(order.LastModified.Value, DateTimeKind.Utc)) : Timestamp.FromDateTime(DateTime.SpecifyKind(order.Created, DateTimeKind.Utc)), ChangedBy = "سیستم" }); return response; } public override async Task CustomerReorderPreviousOrder(CustomerReorderRequest request, ServerCallContext context) { var originalOrder = await _context.UserOrders .Include(o => o.FactorDetails) .ThenInclude(fd => fd.Product) .Where(o => o.Id == request.OriginalOrderId && !o.IsDeleted) .FirstOrDefaultAsync(context.CancellationToken); if (originalOrder == null) return new CustomerReorderResponse { Success = false, Message = "سفارش اصلی یافت نشد" }; var userId = originalOrder.UserId; // Add items from old order to cart foreach (var fd in originalOrder.FactorDetails.Where(f => !f.IsDeleted)) { var existingCartItem = await _context.UserCarts .Where(c => c.UserId == userId && c.ProductId == fd.ProductId && !c.IsDeleted) .FirstOrDefaultAsync(context.CancellationToken); if (existingCartItem != null) { existingCartItem.Count += fd.Count; } else { _context.UserCarts.Add(new UserCart { UserId = userId, ProductId = fd.ProductId, Count = fd.Count }); } } await _context.SaveChangesAsync(context.CancellationToken); // Calculate total with current prices long totalAmount = originalOrder.FactorDetails .Where(f => !f.IsDeleted) .Sum(fd => request.UseCurrentPrices && fd.Product != null ? fd.Product.Price * fd.Count : fd.UnitPrice * fd.Count); return new CustomerReorderResponse { Success = true, Message = "محصولات به سبد خرید اضافه شدند", NewOrderId = 0, // Cart items added, no order created yet TotalAmount = totalAmount }; } public override Task GetVATRate(Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context) { // VAT Rate for Iran: 10% (نرخ مالیات بر ارزش افزوده ایران) return Task.FromResult(new GetVATRateResponse { VatRate = 0.10, VatPercentage = 10, IsEnabled = true }); } // ============= Helper Methods ============= private static string GetDeliveryStatusPersian(Domain.Enums.DeliveryStatus status) => status switch { Domain.Enums.DeliveryStatus.None => "نامشخص", Domain.Enums.DeliveryStatus.Pending => "در انتظار ارسال", Domain.Enums.DeliveryStatus.InTransit => "در حال ارسال", Domain.Enums.DeliveryStatus.Delivered => "تحویل داده شده", Domain.Enums.DeliveryStatus.Returned => "مرجوع شده", Domain.Enums.DeliveryStatus.Cancelled => "لغو شده", _ => status.ToString() }; }