feat: Implement file management and authorization features
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 3m9s
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 3m9s
- Add RequiresPermissionAttribute for gRPC method access control. - Create IFileManagementService interface for file upload and management. - Implement AddProductImageCommand and handler for adding product images. - Implement CreateNewProductsCommand and handler for creating new products with image uploads. - Implement DeleteProductsCommand and handler for deleting products and their associations. - Implement RemoveProductImageCommand and handler for removing product images from galleries. - Implement UpdateProductsCommand and handler for updating product details and images. - Create GetProductGalleryQuery and handler for retrieving product galleries. - Implement PermissionService for role-based access control using JWT claims. - Implement FileManagementService for handling file uploads and image optimization. - Define gRPC service and messages for file management in fms.proto. - Add FluentValidation for request validation in various commands. - Create PermissionInterceptor for enforcing permissions on gRPC methods.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
using CMSMicroservice.Application.Common.Authorization;
|
||||
using Grpc.Core;
|
||||
using Grpc.Core.Interceptors;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CMSMicroservice.WebApi.Interceptors;
|
||||
|
||||
/// <summary>
|
||||
/// gRPC Interceptor برای بررسی مجوز دسترسی
|
||||
/// بر اساس [RequiresPermission] attribute روی سرویسها/متدها
|
||||
/// </summary>
|
||||
public class PermissionInterceptor : Interceptor
|
||||
{
|
||||
private readonly IPermissionService _permissionService;
|
||||
private readonly ILogger<PermissionInterceptor> _logger;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public PermissionInterceptor(
|
||||
IPermissionService permissionService,
|
||||
ILogger<PermissionInterceptor> logger,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_permissionService = permissionService;
|
||||
_logger = logger;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
|
||||
TRequest request, ServerCallContext context,
|
||||
UnaryServerMethod<TRequest, TResponse> continuation)
|
||||
{
|
||||
await EnsureHasPermissionAsync(context);
|
||||
return await continuation(request, context);
|
||||
}
|
||||
|
||||
public override async Task<TResponse> ClientStreamingServerHandler<TRequest, TResponse>(
|
||||
IAsyncStreamReader<TRequest> requestStream, ServerCallContext context,
|
||||
ClientStreamingServerMethod<TRequest, TResponse> continuation)
|
||||
{
|
||||
await EnsureHasPermissionAsync(context);
|
||||
return await continuation(requestStream, context);
|
||||
}
|
||||
|
||||
private async Task EnsureHasPermissionAsync(ServerCallContext context)
|
||||
{
|
||||
var httpContext = context.GetHttpContext() ?? _httpContextAccessor.HttpContext;
|
||||
if (httpContext == null) return;
|
||||
|
||||
var endpoint = httpContext.GetEndpoint();
|
||||
if (endpoint == null) return;
|
||||
|
||||
var permissionAttributes = endpoint.Metadata.GetOrderedMetadata<RequiresPermissionAttribute>();
|
||||
if (permissionAttributes == null || permissionAttributes.Count == 0) return;
|
||||
|
||||
foreach (var attribute in permissionAttributes)
|
||||
{
|
||||
var hasPermission = await _permissionService.HasPermissionAsync(
|
||||
attribute.Permission, httpContext.RequestAborted);
|
||||
|
||||
if (!hasPermission)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Permission denied: {Permission} for method {Method}",
|
||||
attribute.Permission, context.Method);
|
||||
|
||||
throw new RpcException(new Status(
|
||||
StatusCode.PermissionDenied,
|
||||
$"شما مجوز دسترسی به این عملیات را ندارید ({attribute.Permission})"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,7 @@ builder.Services.AddGrpc(options =>
|
||||
{
|
||||
options.Interceptors.Add<LoggingBehaviour>();
|
||||
options.Interceptors.Add<PerformanceBehaviour>();
|
||||
options.Interceptors.Add<CMSMicroservice.WebApi.Interceptors.PermissionInterceptor>();
|
||||
//options.Interceptors.Add<ExceptionHandlingBehaviour>();
|
||||
options.EnableDetailedErrors = true;
|
||||
options.MaxReceiveMessageSize = 1000 * 1024 * 1024; // 1 GB
|
||||
@@ -339,6 +340,7 @@ app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true }); // Configure the H
|
||||
|
||||
// Map SignalR Hub for token notifications
|
||||
app.MapHub<TokenNotificationHub>("/hubs/token-notification");
|
||||
app.MapHub<TokenNotificationHub>("/hubs/token-relay"); // Alias for FrontOffice backward compatibility
|
||||
|
||||
app.ConfigureGrpcEndpoints(Assembly.GetExecutingAssembly(), endpoints =>
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using CMSMicroservice.Application.Common.Authorization;
|
||||
using CMSMicroservice.Protobuf.Protos.AppVersion;
|
||||
using CMSMicroservice.WebApi.Common.Services;
|
||||
using CMSMicroservice.Application.AppVersionCQ.Queries.GetAppVersion;
|
||||
@@ -15,16 +16,19 @@ public class AppVersionService : AppVersionContract.AppVersionContractBase
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
}
|
||||
|
||||
[RequiresPermission(PermissionNames.SettingsView)]
|
||||
public override async Task<GetAppVersionResponse> GetAppVersion(GetAppVersionRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetAppVersionRequest, GetAppVersionQuery, GetAppVersionResponse>(request, context);
|
||||
}
|
||||
|
||||
[RequiresPermission(PermissionNames.SettingsManageConfiguration)]
|
||||
public override async Task<Empty> UpdateAppVersion(UpdateAppVersionRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<UpdateAppVersionRequest, UpdateAppVersionCommand>(request, context);
|
||||
}
|
||||
|
||||
[RequiresPermission(PermissionNames.SettingsView)]
|
||||
public override async Task<GetAllAppVersionsResponse> GetAllAppVersions(GetAllAppVersionsRequest request, ServerCallContext context)
|
||||
{
|
||||
return await _dispatchRequestToCQRS.Handle<GetAllAppVersionsRequest, GetAllAppVersionsQuery, GetAllAppVersionsResponse>(request, context);
|
||||
|
||||
@@ -100,7 +100,22 @@ public class CategoryService : CategoryContract.CategoryContractBase
|
||||
|
||||
public override async Task<GetCategoryByIdForCustomerResponse> GetCategoryByIdForCustomer(GetCategoryByIdForCustomerRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement using existing CMS Category Application layer
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "GetCategoryByIdForCustomer not implemented yet"));
|
||||
var query = new GetCategoryQuery { Id = request.Id };
|
||||
var result = await _sender.Send(query, context.CancellationToken);
|
||||
|
||||
return new GetCategoryByIdForCustomerResponse
|
||||
{
|
||||
Category = new GetAllCategoryFilterResponseModel
|
||||
{
|
||||
Id = result.Id,
|
||||
Name = result.Name,
|
||||
Title = result.Title,
|
||||
Description = result.Description ?? string.Empty,
|
||||
ImagePath = result.ImagePath ?? string.Empty,
|
||||
ParentId = result.ParentId ?? 0,
|
||||
IsActive = result.IsActive,
|
||||
SortOrder = result.SortOrder
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
using CMSMicroservice.Protobuf.Protos.City;
|
||||
using CMSMicroservice.WebApi.Common.Services;
|
||||
using CMSMicroservice.Application.CityCQ.Queries.GetAllCitiesByFilter;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using System.Linq;
|
||||
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
|
||||
public class CityService : CityContract.CityContractBase
|
||||
{
|
||||
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public CityService(IDispatchRequestToCQRS dispatchRequestToCQRS)
|
||||
public CityService(IDispatchRequestToCQRS dispatchRequestToCQRS, IApplicationDbContext context)
|
||||
{
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public override async Task<GetAllCitiesByFilterResponse> GetAllCitiesByFilter(
|
||||
@@ -28,18 +34,50 @@ public class CityService : CityContract.CityContractBase
|
||||
public override async Task<GetCitiesForCustomerResponse> GetCitiesForCustomer(
|
||||
GetCitiesForCustomerRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement using existing CMS City Application layer
|
||||
// For now, return empty response
|
||||
var pageNumber = request.PageNumber > 0 ? request.PageNumber : 1;
|
||||
var pageSize = request.PageSize > 0 ? request.PageSize : 50;
|
||||
|
||||
var query = _context.Cities
|
||||
.Include(c => c.State)
|
||||
.Where(c => !c.IsDeleted);
|
||||
|
||||
if (request.StateId != null)
|
||||
query = query.Where(c => c.StateId == request.StateId.Value);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.SearchTerm))
|
||||
query = query.Where(c => c.Name.Contains(request.SearchTerm) || c.Native.Contains(request.SearchTerm));
|
||||
|
||||
var totalCount = await query.CountAsync(context.CancellationToken);
|
||||
|
||||
var cities = await query
|
||||
.OrderBy(c => c.Native)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(c => new CityDto
|
||||
{
|
||||
Id = c.Id,
|
||||
ExternalId = c.ExternalId,
|
||||
Name = c.Name,
|
||||
Native = c.Native,
|
||||
Latitude = c.Latitude ?? string.Empty,
|
||||
Longitude = c.Longitude ?? string.Empty,
|
||||
StateId = c.StateId,
|
||||
StateName = c.State != null ? c.State.Name : string.Empty,
|
||||
StateNative = c.State != null ? c.State.Native : string.Empty
|
||||
})
|
||||
.ToListAsync(context.CancellationToken);
|
||||
|
||||
return new GetCitiesForCustomerResponse
|
||||
{
|
||||
Cities = { cities },
|
||||
MetaData = new CMSMicroservice.Protobuf.Protos.City.MetaData
|
||||
{
|
||||
CurrentPage = request.PageNumber,
|
||||
PageSize = request.PageSize,
|
||||
TotalCount = 0,
|
||||
TotalPage = 0,
|
||||
HasNext = false,
|
||||
HasPrevious = false
|
||||
CurrentPage = pageNumber,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize),
|
||||
HasNext = pageNumber * pageSize < totalCount,
|
||||
HasPrevious = pageNumber > 1
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -47,34 +85,126 @@ public class CityService : CityContract.CityContractBase
|
||||
public override async Task<GetCityByIdForCustomerResponse> GetCityByIdForCustomer(
|
||||
GetCityByIdForCustomerRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement using existing CMS City Application layer
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "GetCityByIdForCustomer not implemented yet"));
|
||||
var city = await _context.Cities
|
||||
.Include(c => c.State)
|
||||
.Where(c => c.Id == request.Id && !c.IsDeleted)
|
||||
.Select(c => new CityDto
|
||||
{
|
||||
Id = c.Id,
|
||||
ExternalId = c.ExternalId,
|
||||
Name = c.Name,
|
||||
Native = c.Native,
|
||||
Latitude = c.Latitude ?? string.Empty,
|
||||
Longitude = c.Longitude ?? string.Empty,
|
||||
StateId = c.StateId,
|
||||
StateName = c.State != null ? c.State.Name : string.Empty,
|
||||
StateNative = c.State != null ? c.State.Native : string.Empty
|
||||
})
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
if (city == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "شهر یافت نشد"));
|
||||
|
||||
return new GetCityByIdForCustomerResponse { City = city };
|
||||
}
|
||||
|
||||
public override async Task<GetCitiesByStateForCustomerResponse> GetCitiesByStateForCustomer(
|
||||
GetCitiesByStateForCustomerRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement using existing CMS City Application layer
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "GetCitiesByStateForCustomer not implemented yet"));
|
||||
var pageNumber = request.PageNumber > 0 ? request.PageNumber : 1;
|
||||
var pageSize = request.PageSize > 0 ? request.PageSize : 100;
|
||||
|
||||
var query = _context.Cities
|
||||
.Include(c => c.State)
|
||||
.Where(c => c.StateId == request.StateId && !c.IsDeleted);
|
||||
|
||||
var totalCount = await query.CountAsync(context.CancellationToken);
|
||||
|
||||
var cities = await query
|
||||
.OrderBy(c => c.Native)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(c => new CityDto
|
||||
{
|
||||
Id = c.Id,
|
||||
ExternalId = c.ExternalId,
|
||||
Name = c.Name,
|
||||
Native = c.Native,
|
||||
Latitude = c.Latitude ?? string.Empty,
|
||||
Longitude = c.Longitude ?? string.Empty,
|
||||
StateId = c.StateId,
|
||||
StateName = c.State != null ? c.State.Name : string.Empty,
|
||||
StateNative = c.State != null ? c.State.Native : string.Empty
|
||||
})
|
||||
.ToListAsync(context.CancellationToken);
|
||||
|
||||
return new GetCitiesByStateForCustomerResponse
|
||||
{
|
||||
Cities = { cities },
|
||||
MetaData = new CMSMicroservice.Protobuf.Protos.City.MetaData
|
||||
{
|
||||
CurrentPage = pageNumber,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize),
|
||||
HasNext = pageNumber * pageSize < totalCount,
|
||||
HasPrevious = pageNumber > 1
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Admin Methods placeholder for future expansion
|
||||
// Admin Methods
|
||||
public override async Task<CreateCityResponse> CreateCity(
|
||||
CreateCityRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "CreateCity not implemented yet"));
|
||||
var city = new Domain.Entities.Geography.City
|
||||
{
|
||||
ExternalId = request.ExternalId,
|
||||
Name = request.Name,
|
||||
Native = request.Native,
|
||||
Latitude = request.Latitude ?? string.Empty,
|
||||
Longitude = request.Longitude ?? string.Empty,
|
||||
StateId = request.StateId
|
||||
};
|
||||
|
||||
_context.Cities.Add(city);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
return new CreateCityResponse
|
||||
{
|
||||
Id = city.Id,
|
||||
Message = "شهر با موفقیت ایجاد شد"
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<Empty> UpdateCity(
|
||||
UpdateCityRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "UpdateCity not implemented yet"));
|
||||
var city = await _context.Cities.FindAsync(new object[] { request.Id }, context.CancellationToken);
|
||||
if (city == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "شهر یافت نشد"));
|
||||
|
||||
city.ExternalId = request.ExternalId;
|
||||
city.Name = request.Name;
|
||||
city.Native = request.Native;
|
||||
if (request.Latitude != null) city.Latitude = request.Latitude;
|
||||
if (request.Longitude != null) city.Longitude = request.Longitude;
|
||||
city.StateId = request.StateId;
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
public override async Task<Empty> DeleteCity(
|
||||
DeleteCityRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "DeleteCity not implemented yet"));
|
||||
var city = await _context.Cities.FindAsync(new object[] { request.Id }, context.CancellationToken);
|
||||
if (city == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "شهر یافت نشد"));
|
||||
|
||||
city.IsDeleted = true;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using CMSMicroservice.Application.ClubFeatureCQ.Queries.GetUserClubFeatures;
|
||||
using CMSMicroservice.Application.Common.Authorization;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Common;
|
||||
using CMSMicroservice.Protobuf.Protos.Configuration;
|
||||
@@ -109,6 +110,7 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
/// <summary>
|
||||
/// دریافت تمام تنظیمات
|
||||
/// </summary>
|
||||
[RequiresPermission(PermissionNames.SettingsView)]
|
||||
public override Task<GetAllConfigurationsResponse> GetAllConfigurations(GetAllConfigurationsRequest request, ServerCallContext context)
|
||||
{
|
||||
var response = new GetAllConfigurationsResponse();
|
||||
@@ -145,16 +147,18 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
|
||||
/// <summary>
|
||||
/// سایر عملیاتها که فعلاً پیادهسازی نشدهاند (چون از constant استفاده میکنیم)
|
||||
/// </summary>
|
||||
[RequiresPermission(PermissionNames.SettingsManageConfiguration)]
|
||||
public override Task<Empty> CreateOrUpdateConfiguration(CreateOrUpdateConfigurationRequest request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogWarning("CreateOrUpdateConfiguration called but SystemConstants are read-only");
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "تنظیمات سیستم فقط خواندنی هستند"));
|
||||
_logger.LogWarning("CreateOrUpdateConfiguration called but SystemConstants are read-only. Key: {Key}", request.Key);
|
||||
throw new RpcException(new Status(StatusCode.FailedPrecondition, "تنظیمات سیستم فقط خواندنی هستند. تغییر از طریق کد انجام میشود"));
|
||||
}
|
||||
|
||||
[RequiresPermission(PermissionNames.SettingsManageConfiguration)]
|
||||
public override Task<Empty> DeactivateConfiguration(DeactivateConfigurationRequest request, ServerCallContext context)
|
||||
{
|
||||
_logger.LogWarning("DeactivateConfiguration called but SystemConstants are read-only");
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "تنظیمات سیستم فقط خواندنی هستند"));
|
||||
_logger.LogWarning("DeactivateConfiguration called but SystemConstants are read-only. Key: {Key}", request.Key);
|
||||
throw new RpcException(new Status(StatusCode.FailedPrecondition, "تنظیمات سیستم فقط خواندنی هستند. غیرفعالسازی از طریق کد انجام میشود"));
|
||||
}
|
||||
|
||||
public override Task<GetConfigurationHistoryResponse> GetConfigurationHistory(GetConfigurationHistoryRequest request, ServerCallContext context)
|
||||
|
||||
@@ -18,9 +18,12 @@ using CMSMicroservice.Application.InventoryItemCQ.Queries.GetLowStockItems;
|
||||
using CMSMicroservice.Application.StockMovementCQ.Commands.CreateStockMovement;
|
||||
using CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovements;
|
||||
using CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovementsByInventoryItem;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Grpc.Core;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
|
||||
@@ -28,11 +31,13 @@ public class InventoryService : InventoryContract.InventoryContractBase
|
||||
{
|
||||
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public InventoryService(IDispatchRequestToCQRS dispatchRequestToCQRS, IMediator mediator)
|
||||
public InventoryService(IDispatchRequestToCQRS dispatchRequestToCQRS, IMediator mediator, IApplicationDbContext context)
|
||||
{
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
_mediator = mediator;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// ========== Warehouse Management ==========
|
||||
@@ -198,28 +203,109 @@ public class InventoryService : InventoryContract.InventoryContractBase
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<ReserveStockResponse> ReserveStock(ReserveStockRequest request, ServerCallContext context)
|
||||
public override async Task<ReserveStockResponse> ReserveStock(ReserveStockRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement with product lookup
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "ReserveStock requires product lookup - not yet implemented"));
|
||||
var inventoryItem = await _mediator.Send(
|
||||
new GetInventoryByProductQuery
|
||||
{
|
||||
ProductId = request.ProductId,
|
||||
ProductType = (Domain.Enums.ProductType)request.ProductType
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
if (inventoryItem == null)
|
||||
return new ReserveStockResponse { Success = false, Message = "آیتم موجودی یافت نشد", AvailableQuantity = 0 };
|
||||
|
||||
var available = inventoryItem.Quantity - inventoryItem.ReservedQuantity;
|
||||
if (available < request.Quantity)
|
||||
return new ReserveStockResponse { Success = false, Message = $"موجودی کافی نیست. موجود: {available}", AvailableQuantity = available };
|
||||
|
||||
await _mediator.Send(
|
||||
new ReserveInventoryCommand
|
||||
{
|
||||
Id = inventoryItem.Id,
|
||||
Quantity = request.Quantity,
|
||||
ReferenceNumber = request.OrderId != null ? $"ORDER-{request.OrderId.Value}" : $"RESERVE-{DateTime.UtcNow.Ticks}"
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
return new ReserveStockResponse { Success = true, Message = "رزرو با موفقیت انجام شد", AvailableQuantity = available - request.Quantity };
|
||||
}
|
||||
|
||||
public override Task<Empty> ReleaseReservation(ReleaseReservationRequest request, ServerCallContext context)
|
||||
public override async Task<Empty> ReleaseReservation(ReleaseReservationRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement with product lookup
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "ReleaseReservation requires product lookup - not yet implemented"));
|
||||
var inventoryItem = await _mediator.Send(
|
||||
new GetInventoryByProductQuery
|
||||
{
|
||||
ProductId = request.ProductId,
|
||||
ProductType = (Domain.Enums.ProductType)request.ProductType
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
if (inventoryItem == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد"));
|
||||
|
||||
await _mediator.Send(
|
||||
new ReleaseReservedInventoryCommand
|
||||
{
|
||||
Id = inventoryItem.Id,
|
||||
Quantity = request.Quantity,
|
||||
ReferenceNumber = request.OrderId != null ? $"RELEASE-ORDER-{request.OrderId.Value}" : $"RELEASE-{DateTime.UtcNow.Ticks}"
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
public override Task<Empty> ConfirmSale(ConfirmSaleRequest request, ServerCallContext context)
|
||||
public override async Task<Empty> ConfirmSale(ConfirmSaleRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement with product lookup
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "ConfirmSale requires product lookup - not yet implemented"));
|
||||
var inventoryItem = await _mediator.Send(
|
||||
new GetInventoryByProductQuery
|
||||
{
|
||||
ProductId = request.ProductId,
|
||||
ProductType = (Domain.Enums.ProductType)request.ProductType
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
if (inventoryItem == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد"));
|
||||
|
||||
await _mediator.Send(
|
||||
new ReduceInventoryCommand
|
||||
{
|
||||
Id = inventoryItem.Id,
|
||||
Quantity = request.Quantity,
|
||||
FromReserved = request.FromReservation,
|
||||
ReferenceNumber = request.OrderId != null ? $"SALE-ORDER-{request.OrderId.Value}" : $"SALE-{DateTime.UtcNow.Ticks}"
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
public override Task<ProcessReturnResponse> ProcessReturn(ProcessReturnRequest request, ServerCallContext context)
|
||||
public override async Task<ProcessReturnResponse> ProcessReturn(ProcessReturnRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement with product lookup
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "ProcessReturn requires product lookup - not yet implemented"));
|
||||
var inventoryItem = await _mediator.Send(
|
||||
new GetInventoryByProductQuery
|
||||
{
|
||||
ProductId = request.ProductId,
|
||||
ProductType = (Domain.Enums.ProductType)request.ProductType
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
if (inventoryItem == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد"));
|
||||
|
||||
var result = await _mediator.Send(
|
||||
new IncreaseInventoryCommand
|
||||
{
|
||||
Id = inventoryItem.Id,
|
||||
Quantity = request.Quantity,
|
||||
ReferenceNumber = request.OrderId != null ? $"RETURN-ORDER-{request.OrderId.Value}" : $"RETURN-{DateTime.UtcNow.Ticks}"
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
return new ProcessReturnResponse { NewQuantity = result.NewQuantity };
|
||||
}
|
||||
|
||||
public override async Task<Empty> RecordLoss(RecordLossRequest request, ServerCallContext context)
|
||||
@@ -255,10 +341,48 @@ public class InventoryService : InventoryContract.InventoryContractBase
|
||||
|
||||
// ========== Bulk Operations ==========
|
||||
|
||||
public override Task<BulkAddStockResponse> BulkAddStock(BulkAddStockRequest request, ServerCallContext context)
|
||||
public override async Task<BulkAddStockResponse> BulkAddStock(BulkAddStockRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement with product lookup
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "BulkAddStock requires product lookup - not yet implemented"));
|
||||
var response = new BulkAddStockResponse();
|
||||
|
||||
foreach (var item in request.Items)
|
||||
{
|
||||
try
|
||||
{
|
||||
var inventoryItem = await _mediator.Send(
|
||||
new GetInventoryByProductQuery
|
||||
{
|
||||
ProductId = item.ProductId,
|
||||
ProductType = (Domain.Enums.ProductType)item.ProductType
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
if (inventoryItem == null)
|
||||
{
|
||||
response.FailedCount++;
|
||||
response.Errors.Add($"ProductId={item.ProductId}: آیتم موجودی یافت نشد");
|
||||
continue;
|
||||
}
|
||||
|
||||
await _mediator.Send(
|
||||
new IncreaseInventoryCommand
|
||||
{
|
||||
Id = inventoryItem.Id,
|
||||
Quantity = item.Quantity,
|
||||
ReferenceNumber = request.ReferenceNumber ?? $"BULK-{DateTime.UtcNow.Ticks}"
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
response.SuccessCount++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.FailedCount++;
|
||||
response.Errors.Add($"ProductId={item.ProductId}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// ========== Stock Movements ==========
|
||||
@@ -275,28 +399,74 @@ public class InventoryService : InventoryContract.InventoryContractBase
|
||||
|
||||
// ========== Reports ==========
|
||||
|
||||
public override Task<GetInventorySummaryResponse> GetInventorySummary(GetInventorySummaryRequest request, ServerCallContext context)
|
||||
public override async Task<GetInventorySummaryResponse> GetInventorySummary(GetInventorySummaryRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement summary query
|
||||
return Task.FromResult(new GetInventorySummaryResponse
|
||||
var query = _context.InventoryItems
|
||||
.Include(i => i.Product)
|
||||
.Include(i => i.DiscountProduct)
|
||||
.Where(i => !i.IsDeleted);
|
||||
|
||||
if (request.WarehouseId != null)
|
||||
query = query.Where(i => i.WarehouseId == request.WarehouseId.Value);
|
||||
|
||||
var items = await query.ToListAsync(context.CancellationToken);
|
||||
|
||||
var regularProducts = items.Where(i => i.ProductType == Domain.Enums.ProductType.RegularProduct).ToList();
|
||||
var discountProducts = items.Where(i => i.ProductType == Domain.Enums.ProductType.DiscountProduct).ToList();
|
||||
|
||||
long totalStockValue = items.Sum(i =>
|
||||
{
|
||||
TotalProducts = 0,
|
||||
TotalDiscountProducts = 0,
|
||||
TotalQuantity = 0,
|
||||
TotalReserved = 0,
|
||||
LowStockCount = 0,
|
||||
OutOfStockCount = 0,
|
||||
TotalStockValue = 0
|
||||
long unitPrice = i.Product?.Price ?? i.DiscountProduct?.Price ?? 0;
|
||||
return (long)i.Quantity * unitPrice;
|
||||
});
|
||||
|
||||
return new GetInventorySummaryResponse
|
||||
{
|
||||
TotalProducts = regularProducts.Count,
|
||||
TotalDiscountProducts = discountProducts.Count,
|
||||
TotalQuantity = items.Sum(i => i.Quantity),
|
||||
TotalReserved = items.Sum(i => i.ReservedQuantity),
|
||||
LowStockCount = items.Count(i => i.Quantity <= i.LowStockThreshold && i.Quantity > 0),
|
||||
OutOfStockCount = items.Count(i => i.Quantity == 0),
|
||||
TotalStockValue = totalStockValue
|
||||
};
|
||||
}
|
||||
|
||||
public override Task<GetStockValueReportResponse> GetStockValueReport(GetStockValueReportRequest request, ServerCallContext context)
|
||||
public override async Task<GetStockValueReportResponse> GetStockValueReport(GetStockValueReportRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement stock value report query
|
||||
return Task.FromResult(new GetStockValueReportResponse
|
||||
var query = _context.InventoryItems
|
||||
.Include(i => i.Product)
|
||||
.Include(i => i.DiscountProduct)
|
||||
.Where(i => !i.IsDeleted);
|
||||
|
||||
if (request.WarehouseId != null)
|
||||
query = query.Where(i => i.WarehouseId == request.WarehouseId.Value);
|
||||
|
||||
if (request.ProductType != ProductType.Unspecified)
|
||||
query = query.Where(i => i.ProductType == (Domain.Enums.ProductType)request.ProductType);
|
||||
|
||||
var dbItems = await query.ToListAsync(context.CancellationToken);
|
||||
|
||||
var items = dbItems.Select(i =>
|
||||
{
|
||||
TotalValue = 0,
|
||||
TotalItems = 0
|
||||
});
|
||||
long unitPrice = i.Product?.Price ?? i.DiscountProduct?.Price ?? 0;
|
||||
string title = i.Product?.Title ?? i.DiscountProduct?.Title ?? string.Empty;
|
||||
return new StockValueItem
|
||||
{
|
||||
ProductId = i.ProductId ?? i.DiscountProductId ?? 0,
|
||||
ProductTitle = title,
|
||||
ProductType = (ProductType)i.ProductType,
|
||||
Quantity = i.Quantity,
|
||||
UnitPrice = unitPrice,
|
||||
TotalValue = (long)i.Quantity * unitPrice
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
return new GetStockValueReportResponse
|
||||
{
|
||||
Items = { items },
|
||||
TotalValue = items.Sum(i => i.TotalValue),
|
||||
TotalItems = items.Count
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using CMSMicroservice.Application.Common.Authorization;
|
||||
using CMSMicroservice.Protobuf.Protos.ManualPayment;
|
||||
using CMSMicroservice.WebApi.Common.Services;
|
||||
using CMSMicroservice.Application.ManualPaymentCQ.Commands.CreateManualPayment;
|
||||
@@ -24,6 +25,7 @@ public class ManualPaymentService : ManualPaymentContract.ManualPaymentContractB
|
||||
_sender = sender;
|
||||
}
|
||||
|
||||
[RequiresPermission(PermissionNames.ManualPaymentsCreate)]
|
||||
public override async Task<CreateManualPaymentResponse> CreateManualPayment(
|
||||
CreateManualPaymentRequest request,
|
||||
ServerCallContext context)
|
||||
@@ -37,6 +39,7 @@ public class ManualPaymentService : ManualPaymentContract.ManualPaymentContractB
|
||||
};
|
||||
}
|
||||
|
||||
[RequiresPermission(PermissionNames.ManualPaymentsApprove)]
|
||||
public override async Task<Google.Protobuf.WellKnownTypes.Empty> ApproveManualPayment(
|
||||
ApproveManualPaymentRequest request,
|
||||
ServerCallContext context)
|
||||
@@ -44,6 +47,7 @@ public class ManualPaymentService : ManualPaymentContract.ManualPaymentContractB
|
||||
return await _dispatchRequestToCQRS.Handle<ApproveManualPaymentRequest, ApproveManualPaymentCommand>(request, context);
|
||||
}
|
||||
|
||||
[RequiresPermission(PermissionNames.ManualPaymentsApprove)]
|
||||
public override async Task<Google.Protobuf.WellKnownTypes.Empty> RejectManualPayment(
|
||||
RejectManualPaymentRequest request,
|
||||
ServerCallContext context)
|
||||
@@ -51,6 +55,7 @@ public class ManualPaymentService : ManualPaymentContract.ManualPaymentContractB
|
||||
return await _dispatchRequestToCQRS.Handle<RejectManualPaymentRequest, RejectManualPaymentCommand>(request, context);
|
||||
}
|
||||
|
||||
[RequiresPermission(PermissionNames.ManualPaymentsView)]
|
||||
public override async Task<GetAllManualPaymentsResponse> GetAllManualPayments(
|
||||
GetAllManualPaymentsRequest request,
|
||||
ServerCallContext context)
|
||||
@@ -58,6 +63,7 @@ public class ManualPaymentService : ManualPaymentContract.ManualPaymentContractB
|
||||
return await _dispatchRequestToCQRS.Handle<GetAllManualPaymentsRequest, GetAllManualPaymentsQuery, GetAllManualPaymentsResponse>(request, context);
|
||||
}
|
||||
|
||||
[RequiresPermission(PermissionNames.ManualPaymentsCreate)]
|
||||
public override async Task<ProcessManualMembershipPaymentResponse> ProcessManualMembershipPayment(
|
||||
ProcessManualMembershipPaymentRequest request,
|
||||
ServerCallContext context)
|
||||
|
||||
@@ -13,11 +13,14 @@ using CMSMicroservice.Application.PackageCQ.Queries.GetUserPackageStatus;
|
||||
using CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackages;
|
||||
using CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPackageDetails;
|
||||
using CMSMicroservice.Application.PackageCQ.Queries.GetCustomerPurchaseHistory;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
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 Microsoft.EntityFrameworkCore;
|
||||
using MediatR;
|
||||
using Mapster;
|
||||
|
||||
@@ -26,11 +29,22 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
{
|
||||
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||
private readonly ISender _sender;
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
|
||||
public PackageService(IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender)
|
||||
public PackageService(
|
||||
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
||||
ISender sender,
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUserService,
|
||||
IPaymentGatewayService paymentGateway)
|
||||
{
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
_sender = sender;
|
||||
_context = context;
|
||||
_currentUserService = currentUserService;
|
||||
_paymentGateway = paymentGateway;
|
||||
}
|
||||
public override async Task<CreateNewPackageResponse> CreateNewPackage(CreateNewPackageRequest request, ServerCallContext context)
|
||||
{
|
||||
@@ -142,45 +156,161 @@ public class PackageService : PackageContract.PackageContractBase
|
||||
|
||||
public override async Task<CustomerPurchasePackageResponse> CustomerPurchasePackage(CustomerPurchasePackageRequest request, ServerCallContext context)
|
||||
{
|
||||
// Mock Customer package purchase with realistic Persian response
|
||||
var orderId = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
var authority = "A" + orderId.ToString("D19");
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
// Lookup package
|
||||
var package = await _context.Packages
|
||||
.AsNoTracking()
|
||||
.Where(p => p.Id == request.PackageId && !p.IsDeleted)
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
if (package == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "پکیج مورد نظر یافت نشد"));
|
||||
|
||||
// Create transaction
|
||||
var transaction = new CMSMicroservice.Domain.Entities.Transaction
|
||||
{
|
||||
Amount = package.Price,
|
||||
Description = $"خرید پکیج {package.Title}",
|
||||
PaymentStatus = Domain.Enums.PaymentStatus.Pending,
|
||||
Type = Domain.Enums.TransactionType.Buy
|
||||
};
|
||||
_context.Transactions.Add(transaction);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
// Create purchase record
|
||||
var purchaseMethod = request.PurchaseMethod == PurchaseMethodEnum.PurchaseMethodGateway
|
||||
? Domain.Enums.PackagePurchaseMethod.DirectPurchase
|
||||
: Domain.Enums.PackagePurchaseMethod.DayaLoan;
|
||||
|
||||
var purchase = new CMSMicroservice.Domain.Entities.UserPackagePurchase
|
||||
{
|
||||
UserId = userId,
|
||||
PackageId = package.Id,
|
||||
PurchaseMethod = purchaseMethod,
|
||||
PurchasedAt = DateTime.UtcNow,
|
||||
Amount = package.Price,
|
||||
TransactionId = transaction.Id
|
||||
};
|
||||
_context.UserPackagePurchases.Add(purchase);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
// Initiate payment with gateway
|
||||
var user = await _context.Users
|
||||
.AsNoTracking()
|
||||
.Where(u => u.Id == userId)
|
||||
.Select(u => new { u.Mobile })
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest
|
||||
{
|
||||
Amount = package.Price,
|
||||
UserId = userId,
|
||||
Mobile = user?.Mobile ?? string.Empty,
|
||||
Description = $"خرید پکیج {package.Title}",
|
||||
CallbackUrl = request.CallbackUrl
|
||||
}, context.CancellationToken);
|
||||
|
||||
if (!paymentResult.IsSuccess)
|
||||
{
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
return new CustomerPurchasePackageResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = paymentResult.ErrorMessage ?? "خطا در ارتباط با درگاه پرداخت"
|
||||
};
|
||||
}
|
||||
|
||||
// Save RefId
|
||||
transaction.RefId = paymentResult.RefId;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
return new CustomerPurchasePackageResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "درخواست خرید پکیج با موفقیت ثبت شد",
|
||||
OrderId = orderId,
|
||||
PaymentGatewayUrl = $"https://payment.gateway.com/payment?authority={authority}&amount=5600000",
|
||||
Authority = authority
|
||||
OrderId = purchase.Id,
|
||||
PaymentGatewayUrl = paymentResult.GatewayUrl ?? string.Empty,
|
||||
Authority = paymentResult.RefId ?? string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<CustomerVerifyPackagePurchaseResponse> CustomerVerifyPackagePurchase(CustomerVerifyPackagePurchaseRequest request, ServerCallContext context)
|
||||
{
|
||||
// Mock Customer purchase verification with realistic Persian data
|
||||
var transactionId = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
var referenceCode = "REF" + transactionId.ToString();
|
||||
// Find purchase record
|
||||
var purchase = await _context.UserPackagePurchases
|
||||
.Include(p => p.Package)
|
||||
.Where(p => p.Id == request.OrderId && !p.IsDeleted)
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
var isSuccessful = request.Status == "OK";
|
||||
if (purchase == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "سفارش یافت نشد"));
|
||||
|
||||
// Find the associated transaction
|
||||
var transaction = purchase.TransactionId.HasValue
|
||||
? await _context.Transactions
|
||||
.Where(t => t.Id == purchase.TransactionId.Value)
|
||||
.FirstOrDefaultAsync(context.CancellationToken)
|
||||
: null;
|
||||
|
||||
// If status from gateway callback is not OK
|
||||
if (request.Status != "OK")
|
||||
{
|
||||
if (transaction != null)
|
||||
{
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
}
|
||||
|
||||
return new CustomerVerifyPackagePurchaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "پرداخت توسط کاربر لغو شد"
|
||||
};
|
||||
}
|
||||
|
||||
// Verify with payment gateway
|
||||
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
|
||||
request.Authority, request.Status, context.CancellationToken);
|
||||
|
||||
if (verifyResult.IsSuccess && transaction != null)
|
||||
{
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Success;
|
||||
transaction.PaymentDate = DateTime.UtcNow;
|
||||
transaction.RefId = verifyResult.RefId;
|
||||
}
|
||||
else if (transaction != null)
|
||||
{
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
return new CustomerVerifyPackagePurchaseResponse
|
||||
{
|
||||
Success = isSuccessful,
|
||||
Message = isSuccessful ? "خرید پکیج با موفقیت تایید شد" : "خرید پکیج ناموفق بود",
|
||||
TransactionId = transactionId,
|
||||
ReferenceCode = referenceCode,
|
||||
PurchaseInfo = isSuccessful ? new PackagePurchaseInfo
|
||||
Success = verifyResult.IsSuccess,
|
||||
Message = verifyResult.IsSuccess ? "خرید پکیج با موفقیت تایید شد" : (verifyResult.Message ?? "خرید پکیج ناموفق بود"),
|
||||
TransactionId = transaction?.Id ?? 0,
|
||||
ReferenceCode = verifyResult.RefId ?? string.Empty,
|
||||
PurchaseInfo = verifyResult.IsSuccess ? new PackagePurchaseInfo
|
||||
{
|
||||
PackageId = 1,
|
||||
PackageName = "پکیج طلایی",
|
||||
AmountPaid = 5600000,
|
||||
PurchaseDate = Timestamp.FromDateTime(DateTime.UtcNow),
|
||||
ExpiryDate = Timestamp.FromDateTime(DateTime.UtcNow.AddDays(365))
|
||||
PackageId = purchase.PackageId,
|
||||
PackageName = purchase.Package?.Title ?? string.Empty,
|
||||
AmountPaid = purchase.Amount,
|
||||
PurchaseDate = Timestamp.FromDateTime(DateTime.SpecifyKind(purchase.PurchasedAt, DateTimeKind.Utc))
|
||||
} : null
|
||||
};
|
||||
}
|
||||
|
||||
private long GetCurrentUserId()
|
||||
{
|
||||
if (long.TryParse(_currentUserService.UserId, out var userId) && userId > 0)
|
||||
return userId;
|
||||
throw new RpcException(new Status(StatusCode.Unauthenticated, "لطفاً وارد حساب کاربری خود شوید"));
|
||||
}
|
||||
|
||||
public override async Task<GetCustomerPurchaseHistoryResponse> GetCustomerPurchaseHistory(GetCustomerPurchaseHistoryRequest request, ServerCallContext context)
|
||||
{
|
||||
var query = new GetCustomerPurchaseHistoryQuery
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
using CMSMicroservice.Protobuf.Protos.Products;
|
||||
using Grpc.Core;
|
||||
using MediatR;
|
||||
using CMSMicroservice.Application.ProductsCQ.Commands.CreateNewProducts;
|
||||
using CMSMicroservice.Application.ProductsCQ.Commands.UpdateProducts;
|
||||
using CMSMicroservice.Application.ProductsCQ.Commands.DeleteProducts;
|
||||
using CMSMicroservice.Application.ProductsCQ.Commands.AddProductImage;
|
||||
using CMSMicroservice.Application.ProductsCQ.Commands.RemoveProductImage;
|
||||
using CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProducts;
|
||||
using CMSMicroservice.Application.ProductsCQ.Queries.GetCustomerProductsByFilter;
|
||||
using CMSMicroservice.Application.ProductsCQ.Queries.GetProductGallery;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using Mapster;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using AppModels = CMSMicroservice.Application.Common.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -13,29 +21,142 @@ namespace CMSMicroservice.WebApi.Services;
|
||||
public class ProductsService : ProductsContract.ProductsContractBase
|
||||
{
|
||||
private readonly ISender _sender;
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public ProductsService(ISender sender)
|
||||
public ProductsService(ISender sender, IApplicationDbContext context)
|
||||
{
|
||||
_sender = sender;
|
||||
_context = context;
|
||||
}
|
||||
public override async Task<CreateNewProductsResponse> CreateNewProducts(CreateNewProductsRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
var command = new CreateNewProductsCommand
|
||||
{
|
||||
Title = request.Title,
|
||||
Description = request.Description,
|
||||
ShortInfomation = request.ShortInfomation,
|
||||
FullInformation = request.FullInformation,
|
||||
Price = request.Price,
|
||||
Discount = request.Discount,
|
||||
Rate = request.Rate,
|
||||
ImagePath = request.ImagePath,
|
||||
ThumbnailPath = request.ThumbnailPath,
|
||||
SaleCount = request.SaleCount,
|
||||
ViewCount = request.ViewCount,
|
||||
RemainingCount = request.RemainingCount,
|
||||
CategoryIds = request.CategoryIds?.ToList() ?? new List<long>(),
|
||||
ImageFileBytes = request.ImageFile?.File?.ToByteArray(),
|
||||
ImageFileMime = request.ImageFile?.Mime,
|
||||
ImageFileName = request.ImageFile?.FileName,
|
||||
ThumbnailFileBytes = request.ThumbnailFile?.File?.ToByteArray(),
|
||||
ThumbnailFileMime = request.ThumbnailFile?.Mime,
|
||||
ThumbnailFileName = request.ThumbnailFile?.FileName
|
||||
};
|
||||
|
||||
var result = await _sender.Send(command, context.CancellationToken);
|
||||
return new CreateNewProductsResponse { Id = result.Id };
|
||||
}
|
||||
|
||||
public override async Task<Google.Protobuf.WellKnownTypes.Empty> UpdateProducts(UpdateProductsRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
var command = new UpdateProductsCommand
|
||||
{
|
||||
Id = request.Id,
|
||||
Title = request.Title,
|
||||
Description = request.Description,
|
||||
ShortInfomation = request.ShortInfomation,
|
||||
FullInformation = request.FullInformation,
|
||||
Price = request.Price,
|
||||
Discount = request.Discount,
|
||||
Rate = request.Rate,
|
||||
ImagePath = request.ImagePath,
|
||||
ThumbnailPath = request.ThumbnailPath,
|
||||
SaleCount = request.SaleCount,
|
||||
ViewCount = request.ViewCount,
|
||||
RemainingCount = request.RemainingCount,
|
||||
CategoryIds = request.CategoryIds?.ToList() ?? new List<long>(),
|
||||
ImageFileBytes = request.ImageFile?.File?.ToByteArray(),
|
||||
ImageFileMime = request.ImageFile?.Mime,
|
||||
ImageFileName = request.ImageFile?.FileName,
|
||||
ThumbnailFileBytes = request.ThumbnailFile?.File?.ToByteArray(),
|
||||
ThumbnailFileMime = request.ThumbnailFile?.Mime,
|
||||
ThumbnailFileName = request.ThumbnailFile?.FileName
|
||||
};
|
||||
|
||||
await _sender.Send(command, context.CancellationToken);
|
||||
return new Google.Protobuf.WellKnownTypes.Empty();
|
||||
}
|
||||
|
||||
public override async Task<Google.Protobuf.WellKnownTypes.Empty> DeleteProducts(DeleteProductsRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
var command = new DeleteProductsCommand { Id = request.Id };
|
||||
await _sender.Send(command, context.CancellationToken);
|
||||
return new Google.Protobuf.WellKnownTypes.Empty();
|
||||
}
|
||||
|
||||
public override async Task<GetProductsResponse> GetProducts(GetProductsRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
var query = new GetCustomerProductsQuery { Id = request.Id };
|
||||
var result = await _sender.Send(query, context.CancellationToken);
|
||||
|
||||
var response = new GetProductsResponse
|
||||
{
|
||||
Id = result.Id,
|
||||
Title = result.Title,
|
||||
Description = result.Description,
|
||||
ShortInfomation = result.ShortInfomation,
|
||||
FullInformation = result.FullInformation,
|
||||
Price = result.Price,
|
||||
Discount = result.Discount,
|
||||
Rate = result.Rate,
|
||||
ImagePath = result.ImagePath,
|
||||
ThumbnailPath = result.ThumbnailPath,
|
||||
SaleCount = result.SaleCount,
|
||||
ViewCount = result.ViewCount,
|
||||
RemainingCount = result.RemainingCount
|
||||
};
|
||||
|
||||
if (result.Gallery != null)
|
||||
{
|
||||
foreach (var item in result.Gallery)
|
||||
{
|
||||
response.Gallery.Add(new ProductGalleryItem
|
||||
{
|
||||
ProductGalleryId = item.ProductGalleryId,
|
||||
ProductImageId = item.ProductImageId,
|
||||
Title = item.Title,
|
||||
ImagePath = item.ImagePath,
|
||||
ImageThumbnailPath = item.ImageThumbnailPath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (result.Categories != null)
|
||||
{
|
||||
foreach (var cat in result.Categories)
|
||||
{
|
||||
var categoryPath = new ProductCategoryPath
|
||||
{
|
||||
CategoryId = cat.CategoryId,
|
||||
Title = cat.Title
|
||||
};
|
||||
if (cat.Path != null)
|
||||
{
|
||||
foreach (var node in cat.Path)
|
||||
{
|
||||
categoryPath.Path.Add(new CategoryNode
|
||||
{
|
||||
Id = node.Id,
|
||||
Title = node.Title,
|
||||
ParentId = node.ParentId
|
||||
});
|
||||
}
|
||||
}
|
||||
response.Categories.Add(categoryPath);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public override async Task<GetAllProductsByFilterResponse> GetAllProductsByFilter(GetAllProductsByFilterRequest request, ServerCallContext context)
|
||||
@@ -102,22 +223,256 @@ public class ProductsService : ProductsContract.ProductsContractBase
|
||||
|
||||
public override async Task<BulkUpdateProductPricesResponse> BulkUpdateProductPrices(BulkUpdateProductPricesRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
var response = new BulkUpdateProductPricesResponse { Total = request.Products.Count };
|
||||
|
||||
foreach (var item in request.Products)
|
||||
{
|
||||
try
|
||||
{
|
||||
var product = await _context.Products.FindAsync(new object[] { item.ProductId }, context.CancellationToken);
|
||||
if (product == null)
|
||||
{
|
||||
response.Failed++;
|
||||
response.Errors.Add(new BulkOperationError { ProductId = item.ProductId, ErrorMessage = "محصول یافت نشد" });
|
||||
continue;
|
||||
}
|
||||
|
||||
product.Price = item.NewPrice;
|
||||
if (item.NewDiscount != null) product.Discount = item.NewDiscount.Value;
|
||||
if (item.NewClubDiscountPercent != null) product.ClubDiscountPercent = item.NewClubDiscountPercent.Value;
|
||||
|
||||
response.Succeeded++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.Failed++;
|
||||
response.Errors.Add(new BulkOperationError { ProductId = item.ProductId, ErrorMessage = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
return response;
|
||||
}
|
||||
|
||||
public override async Task<BulkUpdateProductStockResponse> BulkUpdateProductStock(BulkUpdateProductStockRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
var response = new BulkUpdateProductStockResponse { Total = request.Products.Count };
|
||||
|
||||
foreach (var item in request.Products)
|
||||
{
|
||||
try
|
||||
{
|
||||
var product = await _context.Products.FindAsync(new object[] { item.ProductId }, context.CancellationToken);
|
||||
if (product == null)
|
||||
{
|
||||
response.Failed++;
|
||||
response.Errors.Add(new BulkOperationError { ProductId = item.ProductId, ErrorMessage = "محصول یافت نشد" });
|
||||
continue;
|
||||
}
|
||||
|
||||
product.RemainingCount = request.UpdateType switch
|
||||
{
|
||||
StockUpdateType.Set => item.Quantity,
|
||||
StockUpdateType.Add => product.RemainingCount + item.Quantity,
|
||||
StockUpdateType.Subtract => Math.Max(0, product.RemainingCount - item.Quantity),
|
||||
_ => item.Quantity
|
||||
};
|
||||
|
||||
response.Succeeded++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.Failed++;
|
||||
response.Errors.Add(new BulkOperationError { ProductId = item.ProductId, ErrorMessage = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
return response;
|
||||
}
|
||||
|
||||
public override async Task<GetLowStockProductsResponse> GetLowStockProducts(GetLowStockProductsRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
var threshold = request.Threshold > 0 ? request.Threshold : 10;
|
||||
var pageIndex = request.PageIndex > 0 ? request.PageIndex : 1;
|
||||
var pageSize = request.PageSize > 0 ? request.PageSize : 20;
|
||||
|
||||
var query = _context.Products
|
||||
.Where(p => !p.IsDeleted && p.RemainingCount <= threshold);
|
||||
|
||||
if (request.IsClubExclusive != null)
|
||||
query = query.Where(p => p.IsClubExclusive == request.IsClubExclusive.Value);
|
||||
|
||||
var totalCount = await query.CountAsync(context.CancellationToken);
|
||||
|
||||
var products = await query
|
||||
.OrderBy(p => p.RemainingCount)
|
||||
.Skip((pageIndex - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(p => new LowStockProduct
|
||||
{
|
||||
Id = p.Id,
|
||||
Title = p.Title,
|
||||
RemainingCount = p.RemainingCount,
|
||||
Price = p.Price,
|
||||
IsClubExclusive = p.IsClubExclusive
|
||||
})
|
||||
.ToListAsync(context.CancellationToken);
|
||||
|
||||
return new GetLowStockProductsResponse
|
||||
{
|
||||
MetaData = new CMSMicroservice.Protobuf.Protos.MetaData
|
||||
{
|
||||
CurrentPage = pageIndex,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
TotalPage = (int)Math.Ceiling(totalCount / (double)pageSize),
|
||||
HasPrevious = pageIndex > 1,
|
||||
HasNext = pageIndex * pageSize < totalCount
|
||||
},
|
||||
Products = { products }
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<ToggleProductStatusResponse> ToggleProductStatus(ToggleProductStatusRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
var response = new ToggleProductStatusResponse { Total = request.ProductIds.Count };
|
||||
|
||||
foreach (var productId in request.ProductIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
var product = await _context.Products.FindAsync(new object[] { productId }, context.CancellationToken);
|
||||
if (product == null)
|
||||
{
|
||||
response.Failed++;
|
||||
response.Errors.Add(new BulkOperationError { ProductId = productId, ErrorMessage = "محصول یافت نشد" });
|
||||
continue;
|
||||
}
|
||||
|
||||
product.IsDeleted = !request.Enable;
|
||||
if (request.Enable && request.DefaultStock > 0 && product.RemainingCount == 0)
|
||||
product.RemainingCount = request.DefaultStock;
|
||||
|
||||
response.Succeeded++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.Failed++;
|
||||
response.Errors.Add(new BulkOperationError { ProductId = productId, ErrorMessage = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
return response;
|
||||
}
|
||||
|
||||
// ============= Category-Product DragDrop Methods =============
|
||||
|
||||
public override async Task<GetProductsForCategoryResponse> GetProductsForCategory(GetProductsForCategoryRequest request, ServerCallContext context)
|
||||
{
|
||||
var assignedProductIds = await _context.ProductCategories
|
||||
.Where(pc => pc.CategoryId == request.CategoryId && !pc.IsDeleted)
|
||||
.Select(pc => pc.ProductId)
|
||||
.ToListAsync(context.CancellationToken);
|
||||
|
||||
var allProducts = await _context.Products
|
||||
.Where(p => !p.IsDeleted)
|
||||
.OrderBy(p => p.Title)
|
||||
.Select(p => new CategoryProductItem
|
||||
{
|
||||
Id = p.Id,
|
||||
Title = p.Title,
|
||||
Selected = assignedProductIds.Contains(p.Id)
|
||||
})
|
||||
.ToListAsync(context.CancellationToken);
|
||||
|
||||
return new GetProductsForCategoryResponse { Items = { allProducts } };
|
||||
}
|
||||
|
||||
public override async Task<GetCategoriesResponse> GetCategories(GetCategoriesRequest request, ServerCallContext context)
|
||||
{
|
||||
var assignedCategoryIds = await _context.ProductCategories
|
||||
.Where(pc => pc.ProductId == request.ProductId && !pc.IsDeleted)
|
||||
.Select(pc => pc.CategoryId)
|
||||
.ToListAsync(context.CancellationToken);
|
||||
|
||||
var allCategories = await _context.Categories
|
||||
.Where(c => !c.IsDeleted && c.IsActive)
|
||||
.OrderBy(c => c.SortOrder)
|
||||
.Select(c => new CategoryItem
|
||||
{
|
||||
Id = c.Id,
|
||||
Title = c.Title,
|
||||
Selected = assignedCategoryIds.Contains(c.Id)
|
||||
})
|
||||
.ToListAsync(context.CancellationToken);
|
||||
|
||||
return new GetCategoriesResponse { Items = { allCategories } };
|
||||
}
|
||||
|
||||
public override async Task<Google.Protobuf.WellKnownTypes.Empty> UpdateProductCategories(UpdateProductCategoriesRequest request, ServerCallContext context)
|
||||
{
|
||||
var existingLinks = await _context.ProductCategories
|
||||
.Where(pc => pc.ProductId == request.ProductId)
|
||||
.ToListAsync(context.CancellationToken);
|
||||
|
||||
// حذف لینکهای قبلی
|
||||
foreach (var link in existingLinks)
|
||||
link.IsDeleted = true;
|
||||
|
||||
// ایجاد لینکهای جدید
|
||||
foreach (var categoryId in request.CategoryIds)
|
||||
{
|
||||
var existing = existingLinks.FirstOrDefault(l => l.CategoryId == categoryId);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.IsDeleted = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.ProductCategories.Add(new Domain.Entities.ProductCategory
|
||||
{
|
||||
ProductId = request.ProductId,
|
||||
CategoryId = categoryId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
return new Google.Protobuf.WellKnownTypes.Empty();
|
||||
}
|
||||
|
||||
public override async Task<Google.Protobuf.WellKnownTypes.Empty> UpdateCategoryProducts(UpdateCategoryProductsRequest request, ServerCallContext context)
|
||||
{
|
||||
var existingLinks = await _context.ProductCategories
|
||||
.Where(pc => pc.CategoryId == request.CategoryId)
|
||||
.ToListAsync(context.CancellationToken);
|
||||
|
||||
// حذف لینکهای قبلی
|
||||
foreach (var link in existingLinks)
|
||||
link.IsDeleted = true;
|
||||
|
||||
// ایجاد لینکهای جدید
|
||||
foreach (var productId in request.ProductIds)
|
||||
{
|
||||
var existing = existingLinks.FirstOrDefault(l => l.ProductId == productId);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.IsDeleted = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.ProductCategories.Add(new Domain.Entities.ProductCategory
|
||||
{
|
||||
ProductId = productId,
|
||||
CategoryId = request.CategoryId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
return new Google.Protobuf.WellKnownTypes.Empty();
|
||||
}
|
||||
|
||||
// ============= Customer-specific Methods =============
|
||||
@@ -279,4 +634,56 @@ public class ProductsService : ProductsContract.ProductsContractBase
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// ============= Product Image Management =============
|
||||
|
||||
public override async Task<AddProductImageResponse> AddProductImage(AddProductImageRequest request, ServerCallContext context)
|
||||
{
|
||||
var command = new AddProductImageCommand
|
||||
{
|
||||
ProductId = request.ProductId,
|
||||
Title = request.Title,
|
||||
ImageFileBytes = request.ImageFile?.File?.ToByteArray(),
|
||||
ImageFileMime = request.ImageFile?.Mime,
|
||||
ImageFileName = request.ImageFile?.FileName
|
||||
};
|
||||
|
||||
var result = await _sender.Send(command, context.CancellationToken);
|
||||
|
||||
return new AddProductImageResponse
|
||||
{
|
||||
ProductGalleryId = result.ProductGalleryId,
|
||||
ProductImageId = result.ProductImageId,
|
||||
Title = result.Title,
|
||||
ImagePath = result.ImagePath,
|
||||
ImageThumbnailPath = result.ImageThumbnailPath
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<Google.Protobuf.WellKnownTypes.Empty> RemoveProductImage(RemoveProductImageRequest request, ServerCallContext context)
|
||||
{
|
||||
var command = new RemoveProductImageCommand { ProductGalleryId = request.ProductGalleryId };
|
||||
await _sender.Send(command, context.CancellationToken);
|
||||
return new Google.Protobuf.WellKnownTypes.Empty();
|
||||
}
|
||||
|
||||
public override async Task<GetProductGalleryResponse> GetProductGallery(GetProductGalleryRequest request, ServerCallContext context)
|
||||
{
|
||||
var query = new GetProductGalleryQuery { ProductId = request.ProductId };
|
||||
var result = await _sender.Send(query, context.CancellationToken);
|
||||
|
||||
var response = new GetProductGalleryResponse();
|
||||
foreach (var item in result.Items)
|
||||
{
|
||||
response.Items.Add(new ProductGalleryItem
|
||||
{
|
||||
ProductGalleryId = item.ProductGalleryId,
|
||||
ProductImageId = item.ProductImageId,
|
||||
Title = item.Title,
|
||||
ImagePath = item.ImagePath,
|
||||
ImageThumbnailPath = item.ImageThumbnailPath
|
||||
});
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,20 +9,35 @@ using CMSMicroservice.Application.TransactionsCQ.Commands.VerifyTransaction;
|
||||
using CMSMicroservice.Application.TransactionsCQ.Commands.RefundTransaction;
|
||||
using CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransaction;
|
||||
using CMSMicroservice.Application.TransactionsCQ.Queries.GetCustomerTransactionsByFilter;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using AppModels = CMSMicroservice.Application.Common.Models;
|
||||
using Grpc.Core;
|
||||
using MediatR;
|
||||
using Mapster;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
{
|
||||
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||
private readonly ISender _sender;
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IPaymentGatewayService _paymentGateway;
|
||||
|
||||
public TransactionsService(IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender)
|
||||
public TransactionsService(
|
||||
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
||||
ISender sender,
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUserService,
|
||||
IPaymentGatewayService paymentGateway)
|
||||
{
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
_sender = sender;
|
||||
_context = context;
|
||||
_currentUserService = currentUserService;
|
||||
_paymentGateway = paymentGateway;
|
||||
}
|
||||
public override async Task<CreateNewTransactionsResponse> CreateNewTransactions(CreateNewTransactionsRequest request, ServerCallContext context)
|
||||
{
|
||||
@@ -121,26 +136,114 @@ public class TransactionsService : TransactionsContract.TransactionsContractBase
|
||||
|
||||
public override async Task<CustomerPaymentRequestResponse> CustomerPaymentRequest(CustomerPaymentRequestRequest request, ServerCallContext context)
|
||||
{
|
||||
// Mock payment gateway response
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
// Get user mobile for payment gateway
|
||||
var user = await _context.Users
|
||||
.AsNoTracking()
|
||||
.Where(u => u.Id == userId)
|
||||
.Select(u => new { u.Mobile, u.Email })
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
// Create transaction record in DB
|
||||
var transaction = new CMSMicroservice.Domain.Entities.Transaction
|
||||
{
|
||||
Amount = request.Amount,
|
||||
Description = request.Description ?? "پرداخت آنلاین",
|
||||
PaymentStatus = Domain.Enums.PaymentStatus.Pending,
|
||||
Type = Domain.Enums.TransactionType.DepositIpg
|
||||
};
|
||||
|
||||
_context.Transactions.Add(transaction);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
// Initiate payment with gateway
|
||||
var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest
|
||||
{
|
||||
Amount = request.Amount,
|
||||
UserId = userId,
|
||||
Mobile = request.Mobile ?? user?.Mobile ?? string.Empty,
|
||||
Description = request.Description ?? "پرداخت آنلاین",
|
||||
CallbackUrl = request.CallbackUrl
|
||||
}, context.CancellationToken);
|
||||
|
||||
if (!paymentResult.IsSuccess)
|
||||
{
|
||||
// Update transaction status to failed
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
throw new RpcException(new Status(StatusCode.Internal,
|
||||
paymentResult.ErrorMessage ?? "خطا در ارتباط با درگاه پرداخت"));
|
||||
}
|
||||
|
||||
// Save RefId from gateway
|
||||
transaction.RefId = paymentResult.RefId;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
return new CustomerPaymentRequestResponse
|
||||
{
|
||||
PaymentGWUrl = $"https://payment.gateway.com/payment?amount={request.Amount}&callback={request.CallbackUrl}&description={request.Description}"
|
||||
PaymentGWUrl = paymentResult.GatewayUrl ?? string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<CustomerPaymentVerificationResponse> CustomerPaymentVerification(CustomerPaymentVerificationRequest request, ServerCallContext context)
|
||||
{
|
||||
// Mock payment verification response
|
||||
bool isSuccessful = request.Status == "OK";
|
||||
// Find the transaction by authority/refId
|
||||
var transaction = await _context.Transactions
|
||||
.Where(t => t.RefId == request.Authority && !t.IsDeleted)
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
if (transaction == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "تراکنش یافت نشد"));
|
||||
|
||||
// If status from gateway callback is not OK, mark as rejected
|
||||
if (request.Status != "OK")
|
||||
{
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
return new CustomerPaymentVerificationResponse
|
||||
{
|
||||
Id = transaction.Id,
|
||||
PaymentStatus = false,
|
||||
Message = "پرداخت توسط کاربر لغو شد",
|
||||
VerificationStatusCode = -1
|
||||
};
|
||||
}
|
||||
|
||||
// Verify with gateway
|
||||
var verifyResult = await _paymentGateway.VerifyPaymentAsync(
|
||||
request.Authority, request.Status, context.CancellationToken);
|
||||
|
||||
if (verifyResult.IsSuccess)
|
||||
{
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Success;
|
||||
transaction.PaymentDate = DateTime.UtcNow;
|
||||
transaction.RefId = verifyResult.RefId;
|
||||
}
|
||||
else
|
||||
{
|
||||
transaction.PaymentStatus = Domain.Enums.PaymentStatus.Reject;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
return new CustomerPaymentVerificationResponse
|
||||
{
|
||||
Id = 12345,
|
||||
PaymentStatus = isSuccessful,
|
||||
Message = isSuccessful ? "پرداخت با موفقیت انجام شد" : "پرداخت ناموفق",
|
||||
RefId = isSuccessful ? "REF123456789" : null,
|
||||
OrderId = "ORDER001",
|
||||
VerificationStatusCode = isSuccessful ? 101 : 102
|
||||
Id = transaction.Id,
|
||||
PaymentStatus = verifyResult.IsSuccess,
|
||||
Message = verifyResult.IsSuccess ? "پرداخت با موفقیت انجام شد" : (verifyResult.Message ?? "پرداخت ناموفق"),
|
||||
RefId = verifyResult.RefId ?? string.Empty,
|
||||
OrderId = string.Empty,
|
||||
VerificationStatusCode = verifyResult.IsSuccess ? 100 : -1
|
||||
};
|
||||
}
|
||||
|
||||
private long GetCurrentUserId()
|
||||
{
|
||||
if (long.TryParse(_currentUserService.UserId, out var userId) && userId > 0)
|
||||
return userId;
|
||||
throw new RpcException(new Status(StatusCode.Unauthenticated, "لطفاً وارد حساب کاربری خود شوید"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ using CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCustomerCart;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCustomerCart;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCustomerCartItem;
|
||||
using CMSMicroservice.Application.DiscountShopCQ.Queries.GetCustomerCart;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Protobuf.Protos.UserCarts;
|
||||
using CMSMicroservice.WebApi.Common.Services;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MediatR;
|
||||
using System.Linq;
|
||||
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
|
||||
@@ -13,11 +16,13 @@ public class UserCartsService : UserCartsContract.UserCartsContractBase
|
||||
{
|
||||
private readonly IDispatchRequestToCQRS _dispatcher;
|
||||
private readonly ISender _sender;
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public UserCartsService(IDispatchRequestToCQRS dispatcher, ISender sender)
|
||||
public UserCartsService(IDispatchRequestToCQRS dispatcher, ISender sender, IApplicationDbContext context)
|
||||
{
|
||||
_dispatcher = dispatcher;
|
||||
_sender = sender;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
#region Customer Methods
|
||||
@@ -117,31 +122,116 @@ public class UserCartsService : UserCartsContract.UserCartsContractBase
|
||||
public override async Task<AddNewUserCartResponse> AddNewUserCart(
|
||||
AddNewUserCartRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "AddNewUserCart not implemented yet"));
|
||||
var entity = new Domain.Entities.UserCart
|
||||
{
|
||||
ProductId = request.ProductId,
|
||||
UserId = request.UserId,
|
||||
Count = request.Count
|
||||
};
|
||||
|
||||
_context.UserCarts.Add(entity);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
return new AddNewUserCartResponse { Id = entity.Id };
|
||||
}
|
||||
|
||||
public override async Task<Empty> UpdateUserCart(
|
||||
UpdateUserCartRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "UpdateUserCart not implemented yet"));
|
||||
var cartId = request.Id > 0 ? request.Id : request.UserCartId;
|
||||
var cart = await _context.UserCarts.FindAsync(new object[] { cartId }, context.CancellationToken);
|
||||
if (cart == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "آیتم سبد خرید یافت نشد"));
|
||||
|
||||
cart.Count = request.Count;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
public override async Task<Empty> DeleteUserCart(
|
||||
DeleteUserCartRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "DeleteUserCart not implemented yet"));
|
||||
var cart = await _context.UserCarts.FindAsync(new object[] { request.Id }, context.CancellationToken);
|
||||
if (cart == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "آیتم سبد خرید یافت نشد"));
|
||||
|
||||
cart.IsDeleted = true;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
public override async Task<GetUserCartResponse> GetUserCart(
|
||||
GetUserCartRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "GetUserCart not implemented yet"));
|
||||
var cart = await _context.UserCarts
|
||||
.Where(c => c.Id == request.Id && !c.IsDeleted)
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
if (cart == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "آیتم سبد خرید یافت نشد"));
|
||||
|
||||
return new GetUserCartResponse
|
||||
{
|
||||
Id = cart.Id,
|
||||
ProductId = cart.ProductId,
|
||||
UserId = cart.UserId,
|
||||
Count = cart.Count
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<GetAllUserCartsByFilterResponse> GetAllUserCartsByFilter(
|
||||
GetAllUserCartsByFilterRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "GetAllUserCartsByFilter not implemented yet"));
|
||||
var pageNumber = request.PaginationState?.PageNumber ?? 1;
|
||||
var pageSize = request.PaginationState?.PageSize ?? 20;
|
||||
if (pageNumber < 1) pageNumber = 1;
|
||||
if (pageSize < 1) pageSize = 20;
|
||||
|
||||
var query = _context.UserCarts
|
||||
.Include(c => c.Product)
|
||||
.Where(c => !c.IsDeleted);
|
||||
|
||||
if (request.Filter?.Id != null)
|
||||
query = query.Where(c => c.Id == request.Filter.Id.Value);
|
||||
if (request.Filter?.ProductId != null)
|
||||
query = query.Where(c => c.ProductId == request.Filter.ProductId.Value);
|
||||
if (request.Filter?.UserId != null)
|
||||
query = query.Where(c => c.UserId == request.Filter.UserId.Value);
|
||||
|
||||
var totalCount = await query.CountAsync(context.CancellationToken);
|
||||
|
||||
var items = await query
|
||||
.OrderByDescending(c => c.Created)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(c => new GetAllUserCartsByFilterResponseModel
|
||||
{
|
||||
Id = c.Id,
|
||||
ProductId = c.ProductId,
|
||||
UserId = c.UserId,
|
||||
Count = c.Count,
|
||||
ProductTitle = c.Product != null ? c.Product.Title : string.Empty,
|
||||
ProductShortInfomation = c.Product != null ? (c.Product.ShortInfomation ?? string.Empty) : string.Empty,
|
||||
ProductPrice = c.Product != null ? c.Product.Price : 0,
|
||||
ProductDiscount = c.Product != null ? c.Product.Discount : 0,
|
||||
ProductThumbnailPath = c.Product != null ? (c.Product.ThumbnailPath ?? string.Empty) : string.Empty,
|
||||
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(c.Created, DateTimeKind.Utc))
|
||||
})
|
||||
.ToListAsync(context.CancellationToken);
|
||||
|
||||
return new GetAllUserCartsByFilterResponse
|
||||
{
|
||||
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
|
||||
},
|
||||
Models = { items }
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
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.Entities;
|
||||
using CMSMicroservice.Domain.Entities.Order;
|
||||
@@ -15,6 +18,7 @@ using CMSMicroservice.Protobuf.Protos;
|
||||
using MediatR;
|
||||
using Mapster;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
|
||||
@@ -30,21 +34,68 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
_context = context;
|
||||
_currentUserService = currentUserService;
|
||||
}
|
||||
[RequiresPermission(PermissionNames.OrdersCreate)]
|
||||
public override async Task<CreateNewUserOrderResponse> CreateNewUserOrder(CreateNewUserOrderRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
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)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
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)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
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
|
||||
@@ -108,6 +159,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
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
|
||||
@@ -342,29 +394,176 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
};
|
||||
}
|
||||
|
||||
[RequiresPermission(PermissionNames.OrdersCancel)]
|
||||
public override async Task<CancelOrderResponse> CancelOrder(CancelOrderRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
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)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
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<GetOrdersByDateRangeResponse> GetOrdersByDateRange(GetOrdersByDateRangeRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
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<ApplyDiscountToOrderResponse> ApplyDiscountToOrder(ApplyDiscountToOrderRequest request, ServerCallContext context)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
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)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "Method not implemented yet"));
|
||||
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 =============
|
||||
@@ -518,13 +717,53 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
|
||||
public override async Task<CustomerCancelOrderResponse> CustomerCancelOrder(CustomerCancelOrderRequest request, ServerCallContext context)
|
||||
{
|
||||
// Mock Customer order cancellation with realistic Persian response
|
||||
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 = 180000,
|
||||
RefundTransactionId = "REF" + DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
RefundAmount = refundAmount,
|
||||
RefundTransactionId = $"REF{order.Id}"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -574,105 +813,105 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
|
||||
public override async Task<CustomerTrackOrderResponse> CustomerTrackOrder(CustomerTrackOrderRequest request, ServerCallContext context)
|
||||
{
|
||||
// Mock Customer order tracking with detailed Persian information
|
||||
var statusHistory = new List<OrderStatusHistory>
|
||||
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
|
||||
{
|
||||
new OrderStatusHistory
|
||||
{
|
||||
Status = OrderStatusEnum.OrderStatusPending,
|
||||
StatusMessage = "در انتظار تایید",
|
||||
ChangedAt = Timestamp.FromDateTime(DateTime.UtcNow.AddDays(-5)),
|
||||
ChangedBy = "سیستم"
|
||||
},
|
||||
new OrderStatusHistory
|
||||
{
|
||||
Status = OrderStatusEnum.OrderStatusConfirmed,
|
||||
StatusMessage = "تایید شده",
|
||||
ChangedAt = Timestamp.FromDateTime(DateTime.UtcNow.AddDays(-4)),
|
||||
ChangedBy = "کارشناس فروش"
|
||||
},
|
||||
new OrderStatusHistory
|
||||
{
|
||||
Status = OrderStatusEnum.OrderStatusProcessing,
|
||||
StatusMessage = "در حال آمادهسازی",
|
||||
ChangedAt = Timestamp.FromDateTime(DateTime.UtcNow.AddDays(-3)),
|
||||
ChangedBy = "انبار"
|
||||
},
|
||||
new OrderStatusHistory
|
||||
{
|
||||
Status = OrderStatusEnum.OrderStatusShipped,
|
||||
StatusMessage = "ارسال شده",
|
||||
ChangedAt = Timestamp.FromDateTime(DateTime.UtcNow.AddDays(-2)),
|
||||
ChangedBy = "پست پیشتاز"
|
||||
}
|
||||
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 deliverySteps = new List<DeliveryStep>
|
||||
var response = new CustomerTrackOrderResponse
|
||||
{
|
||||
new DeliveryStep
|
||||
{
|
||||
StepName = "دریافت از فروشنده",
|
||||
StepDescription = "بسته از فروشنده دریافت شد",
|
||||
StepTime = Timestamp.FromDateTime(DateTime.UtcNow.AddDays(-2)),
|
||||
IsCompleted = true
|
||||
},
|
||||
new DeliveryStep
|
||||
{
|
||||
StepName = "مرکز پردازش تهران",
|
||||
StepDescription = "بسته در مرکز پردازش تهران",
|
||||
StepTime = Timestamp.FromDateTime(DateTime.UtcNow.AddDays(-1)),
|
||||
IsCompleted = true
|
||||
},
|
||||
new DeliveryStep
|
||||
{
|
||||
StepName = "در حال ارسال",
|
||||
StepDescription = "بسته در حال ارسال به آدرس مقصد",
|
||||
StepTime = Timestamp.FromDateTime(DateTime.UtcNow.AddHours(-8)),
|
||||
IsCompleted = false
|
||||
}
|
||||
};
|
||||
|
||||
return new CustomerTrackOrderResponse
|
||||
{
|
||||
Order = new Protobuf.Protos.UserOrder.CustomerOrderModel
|
||||
{
|
||||
Id = request.OrderId,
|
||||
Amount = 180000,
|
||||
PackageId = 1,
|
||||
PackageName = "پکیج ویژه",
|
||||
Status = OrderStatusEnum.OrderStatusShipped,
|
||||
StatusMessage = "در حال ارسال",
|
||||
OrderDate = Timestamp.FromDateTime(DateTime.UtcNow.AddDays(-5)),
|
||||
TrackingCode = "TRK" + request.OrderId.ToString("000"),
|
||||
ItemsCount = 4,
|
||||
CanCancel = false,
|
||||
CanReorder = true
|
||||
},
|
||||
StatusHistory = { statusHistory },
|
||||
Order = orderModel,
|
||||
DeliveryInfo = new DeliveryTrackingInfo
|
||||
{
|
||||
TrackingCode = "TRK" + request.OrderId.ToString("000"),
|
||||
TrackingCode = order.TrackingCode ?? string.Empty,
|
||||
CourierName = "پست پیشتاز",
|
||||
EstimatedDelivery = "فردا تا ساعت 18:00",
|
||||
CurrentLocation = "مرکز پخش منطقه 5 تهران",
|
||||
DeliverySteps = { deliverySteps }
|
||||
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)
|
||||
{
|
||||
// Mock Customer reorder functionality
|
||||
var newOrderId = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
var totalAmount = request.UseCurrentPrices ? 280000 : 250000;
|
||||
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 = request.UseCurrentPrices ?
|
||||
"سفارش مجدد با قیمتهای جدید ثبت شد" :
|
||||
"سفارش مجدد با قیمتهای قبلی ثبت شد",
|
||||
NewOrderId = newOrderId,
|
||||
Message = "محصولات به سبد خرید اضافه شدند",
|
||||
NewOrderId = 0, // Cart items added, no order created yet
|
||||
TotalAmount = totalAmount
|
||||
};
|
||||
}
|
||||
@@ -687,4 +926,17 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
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()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@ using CMSMicroservice.Application.UserCQ.Commands.AcceptContract;
|
||||
using CMSMicroservice.Application.UserCQ.Queries.GetCustomerProfile;
|
||||
using CMSMicroservice.Application.UserCQ.Queries.GetCustomerReferrals;
|
||||
using CMSMicroservice.Application.UserCQ.Queries.GetCustomerSettings;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Grpc.Core;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediatR;
|
||||
@@ -28,11 +31,25 @@ public class UserService : UserContract.UserContractBase
|
||||
{
|
||||
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||
private readonly ISender _sender;
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IHashService _hashService;
|
||||
private readonly IFileManagementService _fileManagementService;
|
||||
|
||||
public UserService(IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender)
|
||||
public UserService(
|
||||
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
||||
ISender sender,
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUserService,
|
||||
IHashService hashService,
|
||||
IFileManagementService fileManagementService)
|
||||
{
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
_sender = sender;
|
||||
_context = context;
|
||||
_currentUserService = currentUserService;
|
||||
_hashService = hashService;
|
||||
_fileManagementService = fileManagementService;
|
||||
}
|
||||
public override async Task<CreateNewUserResponse> CreateNewUser(CreateNewUserRequest request, ServerCallContext context)
|
||||
{
|
||||
@@ -90,33 +107,58 @@ public class UserService : UserContract.UserContractBase
|
||||
|
||||
public override async Task<GetUserForCustomerResponse> GetUserForCustomer(GetUserForCustomerRequest request, ServerCallContext context)
|
||||
{
|
||||
// Mock implementation for Customer Get User
|
||||
await Task.Delay(10); // Simulate async operation
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
var user = await _context.Users
|
||||
.AsNoTracking()
|
||||
.Where(u => u.Id == userId && !u.IsDeleted)
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
if (user == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
|
||||
|
||||
return new GetUserForCustomerResponse
|
||||
{
|
||||
Id = 123,
|
||||
FirstName = "احمد",
|
||||
LastName = "محمدی",
|
||||
Mobile = "09123456789",
|
||||
Email = "ahmad.mohammadi@example.com",
|
||||
NationalCode = "1234567890",
|
||||
AvatarPath = "/avatars/user_123.jpg",
|
||||
ParentId = 100,
|
||||
ReferralCode = "REF123456",
|
||||
IsMobileVerified = true,
|
||||
MobileVerifiedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(new DateTime(2024, 1, 15), DateTimeKind.Utc)),
|
||||
EmailNotifications = true,
|
||||
SmsNotifications = true,
|
||||
PushNotifications = false,
|
||||
BirthDate = Timestamp.FromDateTime(DateTime.SpecifyKind(new DateTime(1990, 5, 20), DateTimeKind.Utc))
|
||||
Id = user.Id,
|
||||
FirstName = user.FirstName ?? string.Empty,
|
||||
LastName = user.LastName ?? string.Empty,
|
||||
Mobile = user.Mobile,
|
||||
Email = user.Email ?? string.Empty,
|
||||
NationalCode = user.NationalCode ?? string.Empty,
|
||||
AvatarPath = user.AvatarPath ?? string.Empty,
|
||||
ParentId = user.NetworkParentId,
|
||||
ReferralCode = user.ReferralCode ?? string.Empty,
|
||||
IsMobileVerified = user.IsMobileVerified,
|
||||
MobileVerifiedAt = user.MobileVerifiedAt.HasValue
|
||||
? Timestamp.FromDateTime(DateTime.SpecifyKind(user.MobileVerifiedAt.Value, DateTimeKind.Utc))
|
||||
: null,
|
||||
EmailNotifications = user.EmailNotifications,
|
||||
SmsNotifications = user.SmsNotifications,
|
||||
PushNotifications = user.PushNotifications,
|
||||
BirthDate = user.BirthDate.HasValue
|
||||
? Timestamp.FromDateTime(DateTime.SpecifyKind(user.BirthDate.Value, DateTimeKind.Utc))
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<Empty> UpdateCustomerProfile(UpdateCustomerProfileRequest request, ServerCallContext context)
|
||||
{
|
||||
// Mock implementation for Update Customer Profile
|
||||
await Task.Delay(10);
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
var user = await _context.Users
|
||||
.Where(u => u.Id == userId && !u.IsDeleted)
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
if (user == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
|
||||
|
||||
if (request.FirstName != null) user.FirstName = request.FirstName;
|
||||
if (request.LastName != null) user.LastName = request.LastName;
|
||||
if (request.Email != null) user.Email = request.Email;
|
||||
if (request.NationalCode != null) user.NationalCode = request.NationalCode;
|
||||
if (request.BirthDate != null) user.BirthDate = request.BirthDate.ToDateTime();
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
@@ -153,9 +195,6 @@ public class UserService : UserContract.UserContractBase
|
||||
|
||||
public override async Task<ChangeCustomerPasswordResponse> ChangeCustomerPassword(ChangeCustomerPasswordRequest request, ServerCallContext context)
|
||||
{
|
||||
// Mock implementation for Change Customer Password
|
||||
await Task.Delay(10);
|
||||
|
||||
if (request.NewPassword != request.ConfirmPassword)
|
||||
{
|
||||
return new ChangeCustomerPasswordResponse
|
||||
@@ -174,6 +213,32 @@ public class UserService : UserContract.UserContractBase
|
||||
};
|
||||
}
|
||||
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
var user = await _context.Users
|
||||
.Where(u => u.Id == userId && !u.IsDeleted)
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
if (user == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
|
||||
|
||||
// Verify current password
|
||||
if (!string.IsNullOrEmpty(user.HashPassword))
|
||||
{
|
||||
if (!_hashService.VerifyPassword(request.CurrentPassword, user.HashPassword))
|
||||
{
|
||||
return new ChangeCustomerPasswordResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "رمز عبور فعلی نادرست است"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Hash and save new password
|
||||
user.HashPassword = _hashService.HashPassword(request.NewPassword);
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
return new ChangeCustomerPasswordResponse
|
||||
{
|
||||
Success = true,
|
||||
@@ -233,9 +298,6 @@ public class UserService : UserContract.UserContractBase
|
||||
|
||||
public override async Task<UploadCustomerAvatarResponse> UploadCustomerAvatar(UploadCustomerAvatarRequest request, ServerCallContext context)
|
||||
{
|
||||
// Mock implementation for Upload Customer Avatar
|
||||
await Task.Delay(10);
|
||||
|
||||
if (request.FileData == null || request.FileData.Length == 0)
|
||||
{
|
||||
return new UploadCustomerAvatarResponse
|
||||
@@ -264,10 +326,36 @@ public class UserService : UserContract.UserContractBase
|
||||
};
|
||||
}
|
||||
|
||||
// Simulate file upload and generate URL
|
||||
var fileName = $"avatar_{DateTime.Now.Ticks}.{request.FileMimeType?.Split('/').LastOrDefault()}";
|
||||
var avatarUrl = $"/uploads/avatars/{fileName}";
|
||||
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
var user = await _context.Users
|
||||
.Where(u => u.Id == userId && !u.IsDeleted)
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
if (user == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
|
||||
|
||||
// Upload to FMS
|
||||
var fileBytes = request.FileData.ToByteArray();
|
||||
var fileName = $"avatar_{userId}_{DateTime.UtcNow.Ticks}";
|
||||
var mime = request.FileMimeType ?? "image/jpeg";
|
||||
|
||||
var avatarUrl = await _fileManagementService.UploadFileAsync(
|
||||
"Avatars", fileBytes, mime, fileName, context.CancellationToken);
|
||||
|
||||
if (string.IsNullOrEmpty(avatarUrl))
|
||||
{
|
||||
return new UploadCustomerAvatarResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = "خطا در آپلود فایل. لطفاً مجدد تلاش کنید"
|
||||
};
|
||||
}
|
||||
|
||||
// Update user avatar path in DB
|
||||
user.AvatarPath = avatarUrl;
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
return new UploadCustomerAvatarResponse
|
||||
{
|
||||
Success = true,
|
||||
@@ -295,8 +383,32 @@ public class UserService : UserContract.UserContractBase
|
||||
|
||||
public override async Task<Empty> UpdateCustomerSettings(UpdateCustomerSettingsRequest request, ServerCallContext context)
|
||||
{
|
||||
// Mock implementation for Update Customer Settings
|
||||
await Task.Delay(10);
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
var user = await _context.Users
|
||||
.Where(u => u.Id == userId && !u.IsDeleted)
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
if (user == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد"));
|
||||
|
||||
user.EmailNotifications = request.EmailNotifications;
|
||||
user.SmsNotifications = request.SmsNotifications;
|
||||
user.PushNotifications = request.PushNotifications;
|
||||
// MarketingNotifications, PreferredLanguage, TimeZone, TwoFactorAuth
|
||||
// are stored at application level if needed in future
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
// ============= Helper Methods =============
|
||||
|
||||
private long GetCurrentUserId()
|
||||
{
|
||||
if (long.TryParse(_currentUserService.UserId, out var userId) && userId > 0)
|
||||
return userId;
|
||||
|
||||
throw new RpcException(new Status(StatusCode.Unauthenticated, "لطفاً وارد حساب کاربری خود شوید"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,29 @@ using CMSMicroservice.Application.UserWalletCQ.Queries.GetAllUserWalletByFilter;
|
||||
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletChangeLog;
|
||||
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals;
|
||||
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using Grpc.Core;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
public class UserWalletService : UserWalletContract.UserWalletContractBase
|
||||
{
|
||||
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||
private readonly ISender _sender;
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
|
||||
public UserWalletService(IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender)
|
||||
public UserWalletService(
|
||||
IDispatchRequestToCQRS dispatchRequestToCQRS,
|
||||
ISender sender,
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUserService)
|
||||
{
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
_sender = sender;
|
||||
_context = context;
|
||||
_currentUserService = currentUserService;
|
||||
}
|
||||
public override async Task<CreateNewUserWalletResponse> CreateNewUserWallet(CreateNewUserWalletRequest request, ServerCallContext context)
|
||||
{
|
||||
@@ -100,10 +113,38 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
|
||||
|
||||
public override async Task<Google.Protobuf.WellKnownTypes.Empty> CustomerWithdrawBalance(CustomerWithdrawBalanceRequest request, ServerCallContext context)
|
||||
{
|
||||
// Mock implementation - would handle withdrawal
|
||||
var userId = GetCurrentUserId();
|
||||
|
||||
// Find the commission payout record
|
||||
var payout = await _context.UserCommissionPayouts
|
||||
.Where(p => p.Id == request.PayoutId && p.UserId == userId && !p.IsDeleted)
|
||||
.FirstOrDefaultAsync(context.CancellationToken);
|
||||
|
||||
if (payout == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "رکورد پرداخت کمیسیون یافت نشد"));
|
||||
|
||||
// Validate status - can only withdraw if already paid to wallet
|
||||
if (payout.Status != Domain.Enums.CommissionPayoutStatus.Paid)
|
||||
throw new RpcException(new Status(StatusCode.FailedPrecondition,
|
||||
"فقط کمیسیونهای واریز شده به کیف پول قابل برداشت هستند"));
|
||||
|
||||
// Update payout with withdrawal info
|
||||
payout.WithdrawalMethod = (Domain.Enums.WithdrawalMethod)request.WithdrawalMethod;
|
||||
payout.IbanNumber = request.IbanNumber;
|
||||
payout.Status = Domain.Enums.CommissionPayoutStatus.WithdrawRequested;
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
return new Google.Protobuf.WellKnownTypes.Empty();
|
||||
}
|
||||
|
||||
private long GetCurrentUserId()
|
||||
{
|
||||
if (long.TryParse(_currentUserService.UserId, out var userId) && userId > 0)
|
||||
return userId;
|
||||
throw new RpcException(new Status(StatusCode.Unauthenticated, "لطفاً وارد حساب کاربری خود شوید"));
|
||||
}
|
||||
|
||||
public override async Task<GetCustomerWithdrawalsResponse> GetCustomerWithdrawals(GetCustomerWithdrawalsRequest request, ServerCallContext context)
|
||||
{
|
||||
var query = new GetCustomerWithdrawalsQuery
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"UseRealPaymentGateway": false,
|
||||
"FMS": {
|
||||
"Address": "https://dl.afrino.co"
|
||||
},
|
||||
"JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=",
|
||||
"JwtIssuer": "https://localhost",
|
||||
"JwtAudience": "https://localhost",
|
||||
|
||||
Reference in New Issue
Block a user