541b0c2ce9
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 14m42s
Fixes CI compile error CS1061 on DbSet.Where; remove duplicate using in UserOrderService. Co-authored-by: Cursor <cursoragent@cursor.com>
1060 lines
45 KiB
C#
1060 lines
45 KiB
C#
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;
|
|
|
|
namespace CMSMicroservice.WebApi.Services;
|
|
|
|
public class UserOrderService : UserOrderContract.UserOrderContractBase
|
|
{
|
|
private readonly ISender _sender;
|
|
private readonly IApplicationDbContext _context;
|
|
private readonly ICurrentUserService _currentUserService;
|
|
private readonly IInventoryService _inventoryService;
|
|
|
|
public UserOrderService(ISender sender, IApplicationDbContext context, ICurrentUserService currentUserService, IInventoryService inventoryService)
|
|
{
|
|
_sender = sender;
|
|
_context = context;
|
|
_currentUserService = currentUserService;
|
|
_inventoryService = inventoryService;
|
|
}
|
|
[RequiresPermission(PermissionNames.OrdersCreate)]
|
|
public override async Task<CreateNewUserOrderResponse> 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<Google.Protobuf.WellKnownTypes.Empty> 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<Google.Protobuf.WellKnownTypes.Empty> 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<GetUserOrderResponse> 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<GetAllUserOrderByFilterResponse> 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<AppModels.PaginationState>(),
|
|
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<MetaData>()
|
|
};
|
|
|
|
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<SubmitShopBuyOrderResponse> 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}"));
|
|
}
|
|
|
|
// Validate stock availability for all cart items before any financial operation
|
|
foreach (var cartItem in cartItems)
|
|
{
|
|
var available = await _inventoryService.GetAvailableQuantityAsync(
|
|
cartItem.ProductId, ProductType.RegularProduct, ct: context.CancellationToken);
|
|
if (available < cartItem.Count)
|
|
{
|
|
throw new RpcException(new Status(StatusCode.FailedPrecondition,
|
|
$"موجودی «{cartItem.Product.Title}» کافی نیست. موجود: {available}، درخواستی: {cartItem.Count}"));
|
|
}
|
|
}
|
|
|
|
// 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 UserWalletHistory
|
|
{
|
|
WalletId = wallet.Id,
|
|
CurrentBalance = wallet.Balance,
|
|
ChangeValue = -totalAmount,
|
|
CurrentNetworkBalance = wallet.NetworkBalance,
|
|
ChangeNerworkValue = 0,
|
|
CurrentDiscountBalance = wallet.DiscountBalance,
|
|
ChangeDiscountValue = 0,
|
|
IsIncrease = false,
|
|
RefrenceId = transaction.Id
|
|
};
|
|
|
|
_context.UserWalletHistories.Add(walletLog);
|
|
|
|
// ═══ Magic Wallet: Entry / Exit Trigger ═══
|
|
// Q24: آستانه <= 100,000 تومان به جای == 0 (چون قیمت محصولات متفاوت است)
|
|
if (wallet.Balance <= SystemConstants.MagicWalletEntryThreshold)
|
|
{
|
|
var user = await _context.Users
|
|
.Include(u => u.ClubMembership)
|
|
.FirstOrDefaultAsync(u => u.Id == userId, context.CancellationToken);
|
|
|
|
if (wallet.WalletMode == WalletMode.Magic)
|
|
{
|
|
// بارگذاری پکیج کاربر برای سقف کیفپول جادویی
|
|
var userCycle = await _context.ClubMembershipCycles
|
|
.FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, context.CancellationToken);
|
|
var userPackage = userCycle != null
|
|
? await _context.Packages.FirstOrDefaultAsync(p => p.Id == userCycle.PackageId, context.CancellationToken)
|
|
: await _context.Packages.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, context.CancellationToken);
|
|
var magicMaxDeposit = userPackage?.MagicWalletMaxDeposit
|
|
?? throw new InvalidOperationException($"Package not found for magic wallet exit check, UserId={userId}");
|
|
|
|
if (wallet.MagicTotalDeposited >= magicMaxDeposit)
|
|
{
|
|
// ── EXIT Magic Mode ──
|
|
// هر دو شرط: Balance<=Threshold و سقف MagicMaxDeposit پر شده
|
|
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;
|
|
currentCycle.IsCurrentCycle = false;
|
|
}
|
|
|
|
// B6 fix: ریست PackagePurchaseMethod تا Guards G1-G3 خرید مجدد را مسدود نکنند
|
|
if (user != null)
|
|
{
|
|
user.PackagePurchaseMethod = PackagePurchaseMethod.None;
|
|
}
|
|
}
|
|
}
|
|
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<=Threshold ولی سقف پر نشده → هنوز 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);
|
|
|
|
// Deduct inventory and update SaleCount for each purchased item
|
|
foreach (var cartItem in cartItems)
|
|
{
|
|
await _inventoryService.ConfirmSaleAsync(
|
|
cartItem.ProductId, ProductType.RegularProduct,
|
|
cartItem.Count, order.Id, context.CancellationToken);
|
|
|
|
cartItem.Product.SaleCount += cartItem.Count;
|
|
}
|
|
|
|
await _context.SaveChangesAsync(context.CancellationToken);
|
|
|
|
return new SubmitShopBuyOrderResponse
|
|
{
|
|
Id = order.Id
|
|
};
|
|
}
|
|
|
|
[RequiresPermission(PermissionNames.OrdersCancel)]
|
|
public override async Task<CancelOrderResponse> 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<UpdateOrderStatusResponse> 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
|
|
};
|
|
}
|
|
|
|
#region [ARCHIVED] GetOrdersByDateRange — تکراری: GetAllUserOrderByFilter همین قابلیت + فیلترهای بیشتر دارد
|
|
[RequiresPermission(PermissionNames.ReportsView)]
|
|
[Obsolete("ARCHIVED: Use GetAllUserOrderByFilter instead — has date range + more filters")]
|
|
public override async Task<GetOrdersByDateRangeResponse> 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 }
|
|
};
|
|
}
|
|
#endregion
|
|
|
|
[RequiresPermission(PermissionNames.OrdersUpdate)]
|
|
public override async Task<ApplyDiscountToOrderResponse> 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<CalculateOrderPVResponse> 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<ProductPVDto>();
|
|
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 =============
|
|
|
|
#region [ARCHIVED] CreateNewOrderForCustomer & SubmitOrderForCustomer — Stub خالی: مسیر سفارش مشتری از SubmitShopBuyOrder میگذرد
|
|
[Obsolete("ARCHIVED: Empty stub — customer order flow uses SubmitShopBuyOrder")]
|
|
public override async Task<CreateNewUserOrderResponse> CreateNewOrderForCustomer(CreateNewUserOrderRequest request, ServerCallContext context)
|
|
{
|
|
// [ARCHIVED] Empty stub — مسیر سفارش مشتری از SubmitShopBuyOrder میگذرد
|
|
return new CreateNewUserOrderResponse();
|
|
}
|
|
|
|
[Obsolete("ARCHIVED: Empty stub — customer order flow uses SubmitShopBuyOrder")]
|
|
public override async Task<SubmitShopBuyOrderResponse> SubmitOrderForCustomer(SubmitShopBuyOrderRequest request, ServerCallContext context)
|
|
{
|
|
// [ARCHIVED] Empty stub — مسیر سفارش مشتری از SubmitShopBuyOrder میگذرد
|
|
return new SubmitShopBuyOrderResponse();
|
|
}
|
|
#endregion
|
|
|
|
public override async Task<GetAllUserOrderByFilterResponse> 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<AppModels.PaginationState>(),
|
|
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<MetaData>()
|
|
};
|
|
|
|
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<GetUserOrderResponse> 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<CustomerCancelOrderResponse> 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.UserWalletHistories.Add(new UserWalletHistory
|
|
{
|
|
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<GetCustomerOrderHistoryResponse> GetCustomerOrderHistory(GetCustomerOrderHistoryRequest request, ServerCallContext context)
|
|
{
|
|
var query = new GetCustomerOrderHistoryQuery
|
|
{
|
|
UserId = request.UserId,
|
|
PaginationState = request.PaginationState?.Adapt<AppModels.PaginationState>(),
|
|
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<MetaData>()
|
|
};
|
|
|
|
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<CustomerTrackOrderResponse> 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<CustomerReorderResponse> 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<GetVATRateResponse> 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()
|
|
};
|
|
}
|