feat: add Mapster profiles and enable DiscountOrder handlers
All checks were successful
Build and Deploy / build (push) Successful in 2m14s

This commit is contained in:
masoodafar-web
2025-12-08 21:10:21 +03:30
parent dedc0b809f
commit ce3b5db822
57 changed files with 2125 additions and 374 deletions

View File

@@ -7,20 +7,12 @@
<!-- Exclude handlers that need proto fixes -->
<ItemGroup>
<!-- DiscountOrder handlers - proto mismatch with CMS -->
<Compile Remove="DiscountOrderCQ/**/*.cs" />
<!-- DiscountShoppingCart handlers - proto mismatch -->
<!-- DiscountShoppingCart - FrontOffice-only feature, not needed in BackOffice -->
<!-- این feature فقط برای مشتریان FrontOffice است. مدیریت سبد خرید توسط ادمین در آینده اضافه خواهد شد -->
<Compile Remove="DiscountShoppingCartCQ/**/*.cs" />
<!-- ManualPayment handlers - WellKnownTypes conversion issues -->
<Compile Remove="ManualPaymentCQ/**/*.cs" />
<!-- Configuration handlers - StringValue conversion -->
<Compile Remove="ConfigurationCQ/**/*.cs" />
<!-- Commission ProcessWithdrawal - StringValue conversion -->
<Compile Remove="CommissionCQ/Commands/ProcessWithdrawal/**/*.cs" />
<!-- All handlers now ENABLED with BFF Protobuf and Mapster profiles -->
<!-- ProcessWithdrawal, DiscountOrder fixed and enabled -->
</ItemGroup>
<ItemGroup>
@@ -40,6 +32,8 @@
<ProjectReference Include="..\Protobufs\BackOffice.BFF.NetworkMembership.Protobuf\BackOffice.BFF.NetworkMembership.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.ClubMembership.Protobuf\BackOffice.BFF.ClubMembership.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.Configuration.Protobuf\BackOffice.BFF.Configuration.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.ManualPayment.Protobuf\BackOffice.BFF.ManualPayment.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.DiscountOrder.Protobuf\BackOffice.BFF.DiscountOrder.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.Health.Protobuf\BackOffice.BFF.Health.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.PublicMessage.Protobuf\BackOffice.BFF.PublicMessage.Protobuf.csproj" />
</ItemGroup>

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.ClubMembership;
using Foursat.BackOffice.BFF.ClubMembership.Protos;
namespace BackOffice.BFF.Application.ClubMembershipCQ.Commands.ActivateClub;

View File

@@ -1,6 +1,6 @@
using Foursat.BackOffice.BFF.ClubMembership.Protos;
using GetAllClubMembershipsRequest = CMSMicroservice.Protobuf.Protos.ClubMembership.GetAllClubMembershipsRequest;
using Foursat.BackOffice.BFF.ClubMembership.Protos;
namespace BackOffice.BFF.Application.ClubMembershipCQ.Queries.GetAllClubMembers;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.ClubMembership;
using Foursat.BackOffice.BFF.ClubMembership.Protos;
namespace BackOffice.BFF.Application.ClubMembershipCQ.Queries.GetClubStatistics;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.Commission;
using Foursat.BackOffice.BFF.Commission.Protos;
namespace BackOffice.BFF.Application.CommissionCQ.Commands.ApproveWithdrawal;

View File

@@ -7,13 +7,18 @@ public record ProcessWithdrawalCommand : IRequest<ProcessWithdrawalResponseDto>
/// </summary>
public long WithdrawalId { get; init; }
/// <summary>
/// تایید یا رد درخواست
/// </summary>
public bool IsApproved { get; init; }
/// <summary>
/// دلیل رد یا یادداشت مدیر (اختیاری)
/// </summary>
public string? Reason { get; init; }
/// <summary>
/// شناسه تراکنش بانکی (اختیاری)
/// </summary>
public string? TransactionId { get; init; }
/// <summary>
/// یادداشت مدیر (اختیاری)
/// </summary>
public string? AdminNote { get; init; }
}

View File

@@ -17,18 +17,20 @@ public class ProcessWithdrawalCommandHandler : IRequestHandler<ProcessWithdrawal
var grpcRequest = new ProcessWithdrawalRequest
{
PayoutId = request.WithdrawalId,
IsApproved = true,
Reason = request.AdminNote != null
? new StringValue { Value = request.AdminNote }
: null
IsApproved = request.IsApproved,
Reason = request.Reason ?? string.Empty
};
await _context.Commissions.ProcessWithdrawalAsync(grpcRequest, cancellationToken: cancellationToken);
var statusMessage = request.IsApproved
? "درخواست برداشت تایید و برای پرداخت ارسال شد"
: "درخواست برداشت رد شد";
return new ProcessWithdrawalResponseDto
{
Success = true,
Message = "درخواست برداشت برای پرداخت ارسال شد"
Message = statusMessage
};
}
}

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.Commission;
using Foursat.BackOffice.BFF.Commission.Protos;
namespace BackOffice.BFF.Application.CommissionCQ.Commands.RejectWithdrawal;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.Commission;
using Foursat.BackOffice.BFF.Commission.Protos;
namespace BackOffice.BFF.Application.CommissionCQ.Commands.TriggerWeeklyCalculation;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.Commission;
using Foursat.BackOffice.BFF.Commission.Protos;
namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetAllWeeklyPools;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.Commission;
using Foursat.BackOffice.BFF.Commission.Protos;
namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetUserPayouts;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.Commission;
using Foursat.BackOffice.BFF.Commission.Protos;
namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetUserWeeklyBalances;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.Commission;
using Foursat.BackOffice.BFF.Commission.Protos;
namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetWeeklyPool;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.Commission;
using Foursat.BackOffice.BFF.Commission.Protos;
namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetWithdrawalReports;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.Commission;
using Foursat.BackOffice.BFF.Commission.Protos;
namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetWithdrawalRequests;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.Commission;
using Foursat.BackOffice.BFF.Commission.Protos;
namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetWorkerExecutionLogs;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.Commission;
using Foursat.BackOffice.BFF.Commission.Protos;
namespace BackOffice.BFF.Application.CommissionCQ.Queries.GetWorkerStatus;

View File

@@ -13,14 +13,15 @@ using FMSMicroservice.Protobuf.Protos.FileInfo;
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
using CMSMicroservice.Protobuf.Protos.DiscountCategory;
using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart;
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
using BackOffice.BFF.DiscountOrder.Protobuf.Protos.DiscountOrder;
using CMSMicroservice.Protobuf.Protos.Tag;
using CMSMicroservice.Protobuf.Protos.ProductTag;
using CMSMicroservice.Protobuf.Protos;
using CMSMicroservice.Protobuf.Protos.ClubMembership;
using CMSMicroservice.Protobuf.Protos.Commission;
using CMSMicroservice.Protobuf.Protos.Configuration;
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
using Foursat.BackOffice.BFF.ClubMembership.Protos;
using Foursat.BackOffice.BFF.Commission.Protos;
using Foursat.BackOffice.BFF.Configuration.Protos;
using Foursat.BackOffice.BFF.NetworkMembership.Protos;
using BackOffice.BFF.ManualPayment.Protobuf;
namespace BackOffice.BFF.Application.Common.Interfaces;
@@ -61,8 +62,8 @@ public interface IApplicationContractContext
// Public Messages
PublicMessageContract.PublicMessageContractClient PublicMessages { get; }
// Manual Payments (Admin) - CMS gRPC
CMSMicroservice.Protobuf.Protos.ManualPayment.ManualPaymentContract.ManualPaymentContractClient ManualPayments { get; }
// Manual Payments (Admin) - BackOffice BFF gRPC
ManualPaymentContract.ManualPaymentContractClient ManualPayments { get; }
#endregion
}

View File

@@ -27,10 +27,7 @@ public class SetDefaultVatPercentageCommandHandler : IRequestHandler<SetDefaultV
Key = VatConfigurationKey,
Value = request.VatPercentage.ToString(),
Scope = 0, // System scope
Description = new StringValue
{
Value = "درصد پیش‌فرض مالیات بر ارزش افزوده سفارش‌ها"
}
Description = "درصد پیش‌فرض مالیات بر ارزش افزوده سفارش‌ها"
};
await _context.Configurations.CreateOrUpdateConfigurationAsync(grpcRequest, cancellationToken: cancellationToken);

View File

@@ -1,13 +1,13 @@
namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.CompleteOrderPayment;
public record CompleteOrderPaymentCommand : IRequest
public record CompleteOrderPaymentCommand : IRequest<CompleteOrderPaymentResponseDto>
{
/// <summary>شناسه سفارش</summary>
public long OrderId { get; init; }
/// <summary>کد تراکنش پرداخت</summary>
public string PaymentTransactionCode { get; init; }
public string? PaymentTransactionCode { get; init; }
/// <summary>مبلغ پرداخت شده</summary>
public long PaidAmount { get; init; }
/// <summary>وضعیت موفقیت پرداخت</summary>
public bool PaymentSuccess { get; init; }
}

View File

@@ -1,8 +1,9 @@
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
using BackOffice.BFF.DiscountOrder.Protobuf.Protos.DiscountOrder;
using Mapster;
namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.CompleteOrderPayment;
public class CompleteOrderPaymentCommandHandler : IRequestHandler<CompleteOrderPaymentCommand>
public class CompleteOrderPaymentCommandHandler : IRequestHandler<CompleteOrderPaymentCommand, CompleteOrderPaymentResponseDto>
{
private readonly IApplicationContractContext _context;
@@ -11,17 +12,12 @@ public class CompleteOrderPaymentCommandHandler : IRequestHandler<CompleteOrderP
_context = context;
}
public async Task<Unit> Handle(CompleteOrderPaymentCommand request, CancellationToken cancellationToken)
public async Task<CompleteOrderPaymentResponseDto> Handle(CompleteOrderPaymentCommand request, CancellationToken cancellationToken)
{
await _context.DiscountOrders.CompleteOrderPaymentAsync(
new CompleteOrderPaymentRequest
{
OrderId = request.OrderId,
PaymentTransactionCode = request.PaymentTransactionCode,
PaidAmount = request.PaidAmount
},
cancellationToken: cancellationToken);
return Unit.Value;
var grpcRequest = TypeAdapter.Adapt(request, request.GetType(), typeof(CompleteOrderPaymentRequest)) as CompleteOrderPaymentRequest;
var response = await _context.DiscountOrders.CompleteOrderPaymentAsync(grpcRequest, cancellationToken: cancellationToken);
return TypeAdapter.Adapt(response, response.GetType(), typeof(CompleteOrderPaymentResponseDto)) as CompleteOrderPaymentResponseDto;
}
}

View File

@@ -9,8 +9,5 @@ public class CompleteOrderPaymentCommandValidator : AbstractValidator<CompleteOr
RuleFor(x => x.PaymentTransactionCode)
.NotEmpty().WithMessage("کد تراکنش پرداخت الزامی است");
RuleFor(x => x.PaidAmount)
.GreaterThan(0).WithMessage("مبلغ پرداخت شده باید بیشتر از صفر باشد");
}
}

View File

@@ -0,0 +1,7 @@
namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.CompleteOrderPayment;
public class CompleteOrderPaymentResponseDto
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
}

View File

@@ -11,6 +11,6 @@ public record PlaceOrderCommand : IRequest<PlaceOrderResponseDto>
/// <summary>مبلغ پرداخت از موجودی تخفیف</summary>
public long DiscountBalanceAmount { get; init; }
/// <summary>مبلغ پرداخت از درگاه</summary>
public long GatewayAmount { get; init; }
/// <summary>یادداشت سفارش (اختیاری)</summary>
public string? Notes { get; init; }
}

View File

@@ -1,4 +1,5 @@
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
using BackOffice.BFF.DiscountOrder.Protobuf.Protos.DiscountOrder;
using Mapster;
namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.PlaceOrder;
@@ -13,22 +14,10 @@ public class PlaceOrderCommandHandler : IRequestHandler<PlaceOrderCommand, Place
public async Task<PlaceOrderResponseDto> Handle(PlaceOrderCommand request, CancellationToken cancellationToken)
{
var response = await _context.DiscountOrders.PlaceOrderAsync(
new PlaceOrderRequest
{
UserId = request.UserId,
AddressId = request.AddressId,
DiscountBalanceAmount = request.DiscountBalanceAmount,
GatewayAmount = request.GatewayAmount
},
cancellationToken: cancellationToken);
var grpcRequest = TypeAdapter.Adapt(request, request.GetType(), typeof(PlaceOrderRequest)) as PlaceOrderRequest;
return new PlaceOrderResponseDto
{
OrderId = response.OrderId,
TrackingCode = response.TrackingCode,
RequiresGatewayPayment = response.RequiresGatewayPayment,
GatewayPayableAmount = response.GatewayPayableAmount
};
var response = await _context.DiscountOrders.PlaceOrderAsync(grpcRequest, cancellationToken: cancellationToken);
return TypeAdapter.Adapt(response, response.GetType(), typeof(PlaceOrderResponseDto)) as PlaceOrderResponseDto;
}
}

View File

@@ -12,12 +12,5 @@ public class PlaceOrderCommandValidator : AbstractValidator<PlaceOrderCommand>
RuleFor(x => x.DiscountBalanceAmount)
.GreaterThanOrEqualTo(0).WithMessage("مبلغ موجودی تخفیف نمی‌تواند منفی باشد");
RuleFor(x => x.GatewayAmount)
.GreaterThanOrEqualTo(0).WithMessage("مبلغ درگاه نمی‌تواند منفی باشد");
RuleFor(x => x)
.Must(x => x.DiscountBalanceAmount + x.GatewayAmount > 0)
.WithMessage("مجموع مبالغ پرداخت باید بیشتر از صفر باشد");
}
}

View File

@@ -1,13 +1,16 @@
namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.UpdateOrderStatus;
public record UpdateOrderStatusCommand : IRequest
public record UpdateOrderStatusCommand : IRequest<UpdateOrderStatusResponseDto>
{
/// <summary>شناسه سفارش</summary>
public long OrderId { get; init; }
/// <summary>وضعیت جدید</summary>
/// <summary>وضعیت جدید (DeliveryStatus enum)</summary>
public int NewStatus { get; init; }
/// <summary>کد رهگیری (اختیاری)</summary>
public string? TrackingCode { get; init; }
/// <summary>یادداشت مدیر (اختیاری)</summary>
public string AdminNote { get; init; }
public string? AdminNote { get; init; }
}

View File

@@ -1,8 +1,9 @@
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
using BackOffice.BFF.DiscountOrder.Protobuf.Protos.DiscountOrder;
using Mapster;
namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.UpdateOrderStatus;
public class UpdateOrderStatusCommandHandler : IRequestHandler<UpdateOrderStatusCommand>
public class UpdateOrderStatusCommandHandler : IRequestHandler<UpdateOrderStatusCommand, UpdateOrderStatusResponseDto>
{
private readonly IApplicationContractContext _context;
@@ -11,17 +12,12 @@ public class UpdateOrderStatusCommandHandler : IRequestHandler<UpdateOrderStatus
_context = context;
}
public async Task<Unit> Handle(UpdateOrderStatusCommand request, CancellationToken cancellationToken)
public async Task<UpdateOrderStatusResponseDto> Handle(UpdateOrderStatusCommand request, CancellationToken cancellationToken)
{
await _context.DiscountOrders.UpdateOrderStatusAsync(
new UpdateOrderStatusRequest
{
OrderId = request.OrderId,
NewStatus = request.NewStatus,
AdminNote = request.AdminNote ?? string.Empty
},
cancellationToken: cancellationToken);
return Unit.Value;
var grpcRequest = TypeAdapter.Adapt(request, request.GetType(), typeof(UpdateOrderStatusRequest)) as UpdateOrderStatusRequest;
var response = await _context.DiscountOrders.UpdateOrderStatusAsync(grpcRequest, cancellationToken: cancellationToken);
return TypeAdapter.Adapt(response, response.GetType(), typeof(UpdateOrderStatusResponseDto)) as UpdateOrderStatusResponseDto;
}
}

View File

@@ -0,0 +1,7 @@
namespace BackOffice.BFF.Application.DiscountOrderCQ.Commands.UpdateOrderStatus;
public class UpdateOrderStatusResponseDto
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
}

View File

@@ -4,4 +4,7 @@ public record GetOrderByIdQuery : IRequest<GetOrderByIdResponseDto>
{
/// <summary>شناسه سفارش</summary>
public long OrderId { get; init; }
/// <summary>شناسه کاربر (برای بررسی مجوز)</summary>
public long UserId { get; init; }
}

View File

@@ -1,4 +1,5 @@
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
using BackOffice.BFF.DiscountOrder.Protobuf.Protos.DiscountOrder;
using Mapster;
namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetOrderById;
@@ -13,46 +14,14 @@ public class GetOrderByIdQueryHandler : IRequestHandler<GetOrderByIdQuery, GetOr
public async Task<GetOrderByIdResponseDto> Handle(GetOrderByIdQuery request, CancellationToken cancellationToken)
{
var response = await _context.DiscountOrders.GetOrderByIdAsync(
new GetOrderByIdRequest { OrderId = request.OrderId },
cancellationToken: cancellationToken);
return new GetOrderByIdResponseDto
{
Id = response.Id,
UserId = response.UserId,
TrackingCode = response.TrackingCode,
Status = response.Status,
StatusTitle = response.StatusTitle,
TotalPrice = response.TotalPrice,
DiscountBalanceUsed = response.DiscountBalanceUsed,
GatewayPayment = response.GatewayPayment,
FinalPrice = response.FinalPrice,
PaymentTransactionCode = response.PaymentTransactionCode,
AdminNote = response.AdminNote,
CreatedAt = response.CreatedAt.ToDateTime(),
PaidAt = response.HasPaidAt ? response.PaidAt.ToDateTime() : null,
ShippingAddress = new AddressInfoDto
{
Id = response.ShippingAddress.Id,
RecipientName = response.ShippingAddress.RecipientName,
RecipientPhone = response.ShippingAddress.RecipientPhone,
Province = response.ShippingAddress.Province,
City = response.ShippingAddress.City,
PostalCode = response.ShippingAddress.PostalCode,
FullAddress = response.ShippingAddress.FullAddress
},
Items = response.Items.Select(item => new OrderItemDto
{
Id = item.Id,
ProductId = item.ProductId,
ProductTitle = item.ProductTitle,
UnitPrice = item.UnitPrice,
DiscountPercent = item.DiscountPercent,
Quantity = item.Quantity,
TotalPrice = item.TotalPrice,
DiscountedPrice = item.DiscountedPrice
}).ToList()
var grpcRequest = new GetOrderByIdRequest
{
OrderId = request.OrderId,
UserId = request.UserId
};
var response = await _context.DiscountOrders.GetOrderByIdAsync(grpcRequest, cancellationToken: cancellationToken);
return TypeAdapter.Adapt(response, response.GetType(), typeof(GetOrderByIdResponseDto)) as GetOrderByIdResponseDto;
}
}

View File

@@ -1,6 +1,5 @@
using BackOffice.BFF.Application.Common.Models;
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
using Google.Protobuf.WellKnownTypes;
using BackOffice.BFF.DiscountOrder.Protobuf.Protos.DiscountOrder;
using Mapster;
namespace BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetUserOrders;
@@ -23,32 +22,10 @@ public class GetUserOrdersQueryHandler : IRequestHandler<GetUserOrdersQuery, Get
};
if (request.Status.HasValue)
grpcRequest.Status = new Int32Value { Value = request.Status.Value };
grpcRequest.DeliveryStatus = request.Status.Value;
var response = await _context.DiscountOrders.GetUserOrdersAsync(grpcRequest, cancellationToken: cancellationToken);
return new GetUserOrdersResponseDto
{
MetaData = new MetaData
{
CurrentPage = response.MetaData.CurrentPage,
TotalPage = response.MetaData.TotalPages,
PageSize = response.MetaData.PageSize,
TotalCount = response.MetaData.TotalCount,
HasPrevious = response.MetaData.HasPrevious,
HasNext = response.MetaData.HasNext
},
Orders = response.Orders.Select(order => new UserOrderDto
{
Id = order.Id,
TrackingCode = order.TrackingCode,
Status = order.Status,
StatusTitle = order.StatusTitle,
TotalPrice = order.TotalPrice,
FinalPrice = order.FinalPrice,
ItemCount = order.ItemCount,
CreatedAt = order.CreatedAt.ToDateTime()
}).ToList()
};
return TypeAdapter.Adapt(response, response.GetType(), typeof(GetUserOrdersResponseDto)) as GetUserOrdersResponseDto;
}
}

View File

@@ -1,7 +1,7 @@
using CMSMicroservice.Protobuf.Protos.ClubMembership;
using CMSMicroservice.Protobuf.Protos.Commission;
using CMSMicroservice.Protobuf.Protos.Configuration;
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
using Foursat.BackOffice.BFF.ClubMembership.Protos;
using Foursat.BackOffice.BFF.Commission.Protos;
using Foursat.BackOffice.BFF.Configuration.Protos;
using Foursat.BackOffice.BFF.NetworkMembership.Protos;
using Foursat.BackOffice.BFF.Health.Protobuf;
namespace BackOffice.BFF.Application.HealthCQ.Queries.GetSystemHealth;

View File

@@ -1,5 +1,5 @@
using BackOffice.BFF.Application.Common.Interfaces;
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using BackOffice.BFF.ManualPayment.Protobuf;
using Google.Protobuf.WellKnownTypes;
using MediatR;
using Microsoft.Extensions.Logging;
@@ -30,7 +30,7 @@ public class ApproveManualPaymentCommandHandler : IRequestHandler<ApproveManualP
if (!string.IsNullOrWhiteSpace(request.ApprovalNote))
{
grpcRequest.ApprovalNote = new StringValue { Value = request.ApprovalNote };
grpcRequest.ApprovalNote = request.ApprovalNote;
}
await _context.ManualPayments.ApproveManualPaymentAsync(grpcRequest, cancellationToken: cancellationToken);

View File

@@ -1,5 +1,5 @@
using BackOffice.BFF.Application.Common.Interfaces;
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using BackOffice.BFF.ManualPayment.Protobuf;
using Google.Protobuf.WellKnownTypes;
using MediatR;
using Microsoft.Extensions.Logging;
@@ -31,13 +31,13 @@ public class CreateManualPaymentCommandHandler : IRequestHandler<CreateManualPay
{
UserId = request.UserId,
Amount = request.Amount,
Type = (ManualPaymentType)request.Type,
Type = request.Type,
Description = request.Description
};
if (!string.IsNullOrWhiteSpace(request.ReferenceNumber))
{
grpcRequest.ReferenceNumber = new StringValue { Value = request.ReferenceNumber };
grpcRequest.ReferenceNumber = request.ReferenceNumber;
}
var response = await _context.ManualPayments.CreateManualPaymentAsync(grpcRequest, cancellationToken: cancellationToken);

View File

@@ -1,5 +1,5 @@
using BackOffice.BFF.Application.Common.Interfaces;
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using BackOffice.BFF.ManualPayment.Protobuf;
using MediatR;
using Microsoft.Extensions.Logging;

View File

@@ -1,6 +1,6 @@
using BackOffice.BFF.Application.Common.Interfaces;
using BackOffice.BFF.Application.Common.Models;
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using BackOffice.BFF.ManualPayment.Protobuf;
using Google.Protobuf.WellKnownTypes;
using MediatR;
using Microsoft.Extensions.Logging;
@@ -29,35 +29,16 @@ public class GetManualPaymentsQueryHandler : IRequestHandler<GetManualPaymentsQu
request.Status,
request.Type);
var grpcRequest = new GetAllManualPaymentsRequest
var grpcRequest = new GetManualPaymentsRequest
{
PageNumber = request.PageNumber,
PageSize = request.PageSize
PageSize = request.PageSize,
UserId = request.UserId,
Status = request.Status,
Type = request.Type
};
if (request.UserId.HasValue)
{
grpcRequest.UserId = new Int64Value { Value = request.UserId.Value };
}
if (request.Status.HasValue)
{
grpcRequest.Status = new Int32Value { Value = request.Status.Value };
}
if (request.Type.HasValue)
{
grpcRequest.Type = new Int32Value { Value = request.Type.Value };
}
if (request.RequestedBy.HasValue)
{
grpcRequest.RequestedBy = new Int64Value { Value = request.RequestedBy.Value };
}
grpcRequest.OrderByDescending = new BoolValue { Value = request.OrderByDescending };
var response = await _context.ManualPayments.GetAllManualPaymentsAsync(grpcRequest, cancellationToken: cancellationToken);
var response = await _context.ManualPayments.GetManualPaymentsAsync(grpcRequest, cancellationToken: cancellationToken);
var meta = response.MetaData;
@@ -69,20 +50,20 @@ public class GetManualPaymentsQueryHandler : IRequestHandler<GetManualPaymentsQu
UserFullName = m.UserFullName,
UserMobile = m.UserMobile,
Amount = m.Amount,
Type = (int)m.Type,
Type = m.Type,
TypeDisplay = m.TypeDisplay,
Description = m.Description,
ReferenceNumber = m.ReferenceNumber,
Status = (int)m.Status,
Status = m.Status,
StatusDisplay = m.StatusDisplay,
RequestedBy = m.RequestedBy,
RequestedByName = m.RequestedByName,
ApprovedBy = m.ApprovedBy?.Value,
ApprovedBy = m.ApprovedBy,
ApprovedByName = m.ApprovedByName,
ApprovedAt = m.ApprovedAt?.ToDateTime(),
RejectionReason = m.RejectionReason,
TransactionId = m.TransactionId?.Value,
Created = m.Created.ToDateTime()
TransactionId = m.TransactionId,
Created = m.Created?.ToDateTime() ?? DateTime.UtcNow
})
.ToList()
?? new List<ManualPaymentModel>();

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
using Foursat.BackOffice.BFF.NetworkMembership.Protos;
namespace BackOffice.BFF.Application.NetworkMembershipCQ.Queries.GetNetworkHistory;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
using Foursat.BackOffice.BFF.NetworkMembership.Protos;
namespace BackOffice.BFF.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
using Foursat.BackOffice.BFF.NetworkMembership.Protos;
namespace BackOffice.BFF.Application.NetworkMembershipCQ.Queries.GetNetworkTree;

View File

@@ -1,5 +1,5 @@
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
using Foursat.BackOffice.BFF.NetworkMembership.Protos;
namespace BackOffice.BFF.Application.NetworkMembershipCQ.Queries.GetUserNetworkInfo;

View File

@@ -15,15 +15,18 @@ using FMSMicroservice.Protobuf.Protos.FileInfo;
using CMSMicroservice.Protobuf.Protos.DiscountProduct;
using CMSMicroservice.Protobuf.Protos.DiscountCategory;
using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart;
using CMSMicroservice.Protobuf.Protos.DiscountOrder;
using BackOffice.BFF.DiscountOrder.Protobuf.Protos.DiscountOrder;
using CMSMicroservice.Protobuf.Protos.Tag;
using CMSMicroservice.Protobuf.Protos.ProductTag;
using CMSMicroservice.Protobuf.Protos;
using CMSMicroservice.Protobuf.Protos.ClubMembership;
using CMSMicroservice.Protobuf.Protos.Commission;
using CMSMicroservice.Protobuf.Protos.Configuration;
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using CMSMicroservice.Protobuf.Protos.NetworkMembership;
// BFF Protobuf contracts
using Foursat.BackOffice.BFF.ClubMembership.Protos;
using Foursat.BackOffice.BFF.Commission.Protos;
using Foursat.BackOffice.BFF.Configuration.Protos;
using Foursat.BackOffice.BFF.NetworkMembership.Protos;
using BackOffice.BFF.ManualPayment.Protobuf;
using Microsoft.Extensions.DependencyInjection;
namespace BackOffice.BFF.Infrastructure.Services;

View File

@@ -9,13 +9,7 @@
<!-- Exclude services that depend on excluded handlers -->
<ItemGroup>
<Compile Remove="Services/ConfigurationService.cs" />
<Compile Remove="Services/ManualPaymentService.cs" />
<Compile Remove="Services/PublicMessageService.cs" />
<!-- Exclude mappings with proto type mismatches -->
<Compile Remove="Common/Mappings/ProductsProfile.cs" />
<Compile Remove="Common/Mappings/UserOrderProfile.cs" />
<Compile Remove="Common/Mappings/PublicMessageProfile.cs" />
<!-- All services are now enabled with proper Mapster profiles -->
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,59 @@
using BackOffice.BFF.Application.CategoryCQ.Queries.GetAllCategoryByFilter;
using BackOffice.BFF.Application.CategoryCQ.Queries.GetCategory;
using BackOffice.BFF.Application.CategoryCQ.Commands.CreateNewCategory;
using BackOffice.BFF.Category.Protobuf.Protos.Category;
using BackOffice.BFF.Protobuf.Common;
namespace BackOffice.BFF.WebApi.Common.Mappings;
public class CategoryProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
// GetAllCategoryByFilterResponseDto -> GetAllCategoryByFilterResponse
// config.NewConfig<GetAllCategoryByFilterResponseDto, GetAllCategoryByFilterResponse>()
// .MapWith(src => new GetAllCategoryByFilterResponse
// {
// MetaData = new MetaData
// {
// CurrentPage = src.MetaData.PageNumber,
// PageSize = src.MetaData.PageSize,
// TotalCount = src.MetaData.TotalCount,
// TotalPage = (int)Math.Ceiling((double)src.MetaData.TotalCount / src.MetaData.PageSize)
// },
// Models = { (src.Models ?? Enumerable.Empty<GetAllCategoryByFilterResponseModel>())
// .Select(m => new GetAllCategoryByFilterResponseModel
// {
// Id = m.Id,
// Name = m.Name ?? string.Empty,
// Title = m.Title ?? string.Empty,
// Description = m.Description ?? string.Empty,
// ImagePath = m.ImagePath ?? string.Empty,
// ParentId = m.ParentId ?? 0,
// IsActive = m.IsActive,
// SortOrder = m.SortOrder
// }) }
// });
// GetCategoryResponseDto -> GetCategoryResponse
config.NewConfig<GetCategoryResponseDto, GetCategoryResponse>()
.MapWith(src => new GetCategoryResponse
{
Id = src.Id,
Name = src.Name ?? string.Empty,
Title = src.Title ?? string.Empty,
Description = src.Description ?? string.Empty,
ImagePath = src.ImagePath ?? string.Empty,
ParentId = src.ParentId ?? 0,
IsActive = src.IsActive,
SortOrder = src.SortOrder
});
// CreateNewCategoryResponseDto -> CreateNewCategoryResponse
config.NewConfig<CreateNewCategoryResponseDto, CreateNewCategoryResponse>()
.MapWith(src => new CreateNewCategoryResponse
{
Id = src.Id
});
}
}

View File

@@ -0,0 +1,33 @@
using BackOffice.BFF.Application.ClubMembershipCQ.Queries.GetAllClubMembers;
using Foursat.BackOffice.BFF.ClubMembership.Protos;
using Google.Protobuf.WellKnownTypes;
namespace BackOffice.BFF.WebApi.Common.Mappings;
public class ClubMembershipProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
// GetAllClubMembersResponseDto -> GetAllClubMembershipsResponse
// config.NewConfig<GetAllClubMembersResponseDto, GetAllClubMembershipsResponse>()
// .MapWith(src => new GetAllClubMembershipsResponse
// {
// Members = { src.Members.Select(m => new ClubMemberModel
// {
// Id = m.Id,
// UserId = m.UserId,
// UserName = m.UserName ?? string.Empty,
// ActivationDate = Timestamp.FromDateTime(DateTime.SpecifyKind(m.ActivationDate, DateTimeKind.Utc)),
// InitialContribution = m.InitialContribution,
// CurrentBalance = m.CurrentBalance,
// TotalEarnings = m.TotalEarnings,
// IsActive = m.IsActive,
// ClubLevel = m.ClubLevel,
// ClubLevelDisplay = m.ClubLevelDisplay ?? string.Empty
// }) },
// TotalCount = src.TotalCount,
// PageNumber = src.PageNumber,
// PageSize = src.PageSize
// });
}
}

View File

@@ -0,0 +1,172 @@
using BackOffice.BFF.Application.CommissionCQ.Queries.GetWeeklyPool;
using BackOffice.BFF.Application.CommissionCQ.Queries.GetUserPayouts;
using BackOffice.BFF.Application.CommissionCQ.Queries.GetAllWeeklyPools;
using BackOffice.BFF.Application.CommissionCQ.Queries.GetUserWeeklyBalances;
using BackOffice.BFF.Application.CommissionCQ.Queries.GetWithdrawalRequests;
using BackOffice.BFF.Application.CommissionCQ.Queries.GetWithdrawalReports;
using Foursat.BackOffice.BFF.Commission.Protos;
using Google.Protobuf.WellKnownTypes;
using BackOffice.BFF.Protobuf.Common;
namespace BackOffice.BFF.WebApi.Common.Mappings;
public class CommissionProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
// GetWeeklyPoolResponseDto -> GetWeeklyCommissionPoolResponse
config.NewConfig<GetWeeklyPoolResponseDto, GetWeeklyCommissionPoolResponse>()
.MapWith(src => new GetWeeklyCommissionPoolResponse
{
Id = src.Id,
WeekNumber = src.WeekNumber ?? string.Empty,
// TotalPoolValue = src.TotalPoolValue,
// TotalContributions = src.TotalContributions,
// TotalPayouts = src.TotalPayouts,
// LeftBalance = src.LeftBalance,
// ActiveMembersCount = src.ActiveMembersCount,
IsCalculated = src.IsCalculated,
CalculatedAt = src.CalculatedAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.CalculatedAt.Value, DateTimeKind.Utc))
: null,
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(src.CreatedAt, DateTimeKind.Utc))
});
// GetUserPayoutsResponseDto -> GetUserCommissionPayoutsResponse
// config.NewConfig<GetUserPayoutsResponseDto, GetUserCommissionPayoutsResponse>()
// .MapWith(src => new GetUserCommissionPayoutsResponse
// {
// MetaData = new BackOffice.BFF.Protobuf.Common.MetaData
// {
// CurrentPage = src.PageNumber,
// PageSize = src.PageSize,
// TotalCount = src.TotalCount,
// TotalPage = src.TotalPages
// },
// Models = { src.Payouts.Select(m => new UserCommissionPayoutModel
// {
// Id = m.Id,
// UserId = m.UserId,
// UserName = m.UserName ?? string.Empty,
// WeekNumber = m.WeekNumber ?? string.Empty,
// BalanceCount = m.BalanceCount,
// PayoutAmount = m.PayoutAmount,
// Status = m.Status,
// StatusDisplay = m.StatusDisplay ?? string.Empty,
// PayoutDate = m.PayoutDate.HasValue
// ? Timestamp.FromDateTime(DateTime.SpecifyKind(m.PayoutDate.Value, DateTimeKind.Utc))
// : null,
// Created = Timestamp.FromDateTime(DateTime.SpecifyKind(m.Created, DateTimeKind.Utc))
// }) }
// });
// GetAllWeeklyPoolsResponseDto -> GetAllWeeklyPoolsResponse
config.NewConfig<GetAllWeeklyPoolsResponseDto, GetAllWeeklyPoolsResponse>()
.MapWith(src => new GetAllWeeklyPoolsResponse
{
MetaData = new BackOffice.BFF.Protobuf.Common.MetaData
{
CurrentPage = src.MetaData.CurrentPage,
PageSize = src.MetaData.PageSize,
TotalCount = src.MetaData.TotalCount,
TotalPage = src.MetaData.TotalPages
},
Models = { src.Models.Select(m => new WeeklyCommissionPoolModel
{
Id = m.Id,
WeekNumber = m.WeekNumber ?? string.Empty,
TotalPoolAmount = m.TotalPoolAmount,
TotalBalances = m.TotalBalances,
ValuePerBalance = m.ValuePerBalance,
IsCalculated = m.IsCalculated,
CalculatedAt = m.CalculatedAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(m.CalculatedAt.Value, DateTimeKind.Utc))
: null,
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(m.Created, DateTimeKind.Utc))
}) }
});
// GetUserWeeklyBalancesResponseDto -> GetUserWeeklyBalancesResponse
config.NewConfig<GetUserWeeklyBalancesResponseDto, GetUserWeeklyBalancesResponse>()
.MapWith(src => new GetUserWeeklyBalancesResponse
{
MetaData = new BackOffice.BFF.Protobuf.Common.MetaData
{
CurrentPage = src.MetaData.CurrentPage,
PageSize = src.MetaData.PageSize,
TotalCount = src.MetaData.TotalCount,
TotalPage = src.MetaData.TotalPages
},
Models = { src.Models.Select(m => new UserWeeklyBalanceModel
{
Id = m.Id,
UserId = m.UserId,
WeekNumber = m.WeekNumber ?? string.Empty,
LeftLegBalances = m.LeftLegBalances,
RightLegBalances = m.RightLegBalances,
TotalBalances = m.TotalBalances,
WeeklyPoolContribution = m.WeeklyPoolContribution,
CalculatedAt = m.CalculatedAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(m.CalculatedAt.Value, DateTimeKind.Utc))
: null,
IsExpired = m.IsExpired,
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(m.Created, DateTimeKind.Utc))
}) }
});
// GetWithdrawalRequestsResponseDto -> GetWithdrawalRequestsResponse
// config.NewConfig<GetWithdrawalRequestsResponseDto, GetWithdrawalRequestsResponse>()
// .MapWith(src => new GetWithdrawalRequestsResponse
// {
// MetaData = new BackOffice.BFF.Protobuf.Common.MetaData
// {
// CurrentPage = src.MetaData.CurrentPage,
// PageSize = src.MetaData.PageSize,
// TotalCount = src.MetaData.TotalCount,
// TotalPage = src.MetaData.TotalPages
// },
// Models = { src.Models.Select(m => new WithdrawalRequestModel
// {
// Id = m.Id,
// UserId = m.UserId,
// UserName = m.UserName ?? string.Empty,
// Amount = m.Amount,
// Status = m.Status,
// StatusDisplay = m.StatusDisplay ?? string.Empty,
// RequestedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(m.RequestedAt, DateTimeKind.Utc)),
// ProcessedAt = m.ProcessedAt.HasValue
// ? Timestamp.FromDateTime(DateTime.SpecifyKind(m.ProcessedAt.Value, DateTimeKind.Utc))
// : null,
// ProcessedBy = m.ProcessedBy,
// ProcessedByName = m.ProcessedByName ?? string.Empty,
// RejectionReason = m.RejectionReason ?? string.Empty
// }) }
// });
// GetWithdrawalReportsResponseDto -> GetWithdrawalReportsResponse
// config.NewConfig<GetWithdrawalReportsResponseDto, GetWithdrawalReportsResponse>()
// .MapWith(src => new GetWithdrawalReportsResponse
// {
// TotalRequests = src.TotalRequests,
// PendingCount = src.PendingCount,
// ApprovedCount = src.ApprovedCount,
// RejectedCount = src.RejectedCount,
// ProcessedCount = src.ProcessedCount,
// TotalAmount = src.TotalAmount,
// PendingAmount = src.PendingAmount,
// ApprovedAmount = src.ApprovedAmount,
// ProcessedAmount = src.ProcessedAmount,
// Reports = { src.Reports.Select(r => new WithdrawalReportModel
// {
// UserId = r.UserId,
// UserName = r.UserName ?? string.Empty,
// TotalRequests = r.TotalRequests,
// TotalAmount = r.TotalAmount,
// ApprovedAmount = r.ApprovedAmount,
// LastRequestDate = r.LastRequestDate.HasValue
// ? Timestamp.FromDateTime(DateTime.SpecifyKind(r.LastRequestDate.Value, DateTimeKind.Utc))
// : null
// }) }
// });
}
}

View File

@@ -0,0 +1,36 @@
using BackOffice.BFF.Application.ConfigurationCQ.Queries.GetAllConfigurations;
using Foursat.BackOffice.BFF.Configuration.Protos;
using Google.Protobuf.WellKnownTypes;
using BackOffice.BFF.Protobuf.Common;
namespace BackOffice.BFF.WebApi.Common.Mappings;
public class ConfigurationProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
// GetAllConfigurationsResponseDto -> GetAllConfigurationsResponse
config.NewConfig<GetAllConfigurationsResponseDto, GetAllConfigurationsResponse>()
.MapWith(src => new GetAllConfigurationsResponse
{
MetaData = new BackOffice.BFF.Protobuf.Common.MetaData
{
CurrentPage = src.MetaData.CurrentPage,
PageSize = src.MetaData.PageSize,
TotalCount = src.MetaData.TotalCount,
TotalPage = src.MetaData.TotalPages
},
Models = { src.Models.Select(m => new ConfigurationModel
{
Id = m.Id,
Key = m.Key ?? string.Empty,
Value = m.Value ?? string.Empty,
Description = m.Description ?? string.Empty,
Scope = m.Scope,
// ScopeDisplay = m.ScopeDisplay ?? string.Empty,
IsActive = m.IsActive,
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(m.Created, DateTimeKind.Utc))
}) }
});
}
}

View File

@@ -0,0 +1,173 @@
using Mapster;
using BackOffice.BFF.Application.DiscountOrderCQ.Commands.PlaceOrder;
using BackOffice.BFF.Application.DiscountOrderCQ.Commands.CompleteOrderPayment;
using BackOffice.BFF.Application.DiscountOrderCQ.Commands.UpdateOrderStatus;
using BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetOrderById;
using BackOffice.BFF.Application.DiscountOrderCQ.Queries.GetUserOrders;
using BackOffice.BFF.DiscountOrder.Protobuf.Protos.DiscountOrder;
using Google.Protobuf.WellKnownTypes;
namespace BackOffice.BFF.WebApi.Common.Mappings;
public class DiscountOrderProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
// ========== PlaceOrder Mappings ==========
// Command → BFF Proto Request
config.NewConfig<PlaceOrderCommand, PlaceOrderRequest>()
.MapWith(src => new PlaceOrderRequest
{
UserId = src.UserId,
UserAddressId = src.AddressId,
DiscountBalanceToUse = src.DiscountBalanceAmount,
Notes = src.Notes // Protobuf compiler handles string to StringValue conversion
});
// BFF Proto Response → DTO
config.NewConfig<PlaceOrderResponse, PlaceOrderResponseDto>()
.MapWith(src => new PlaceOrderResponseDto
{
OrderId = src.OrderId,
TrackingCode = src.OrderId.ToString(), // Use OrderId as tracking code
RequiresGatewayPayment = src.GatewayAmount > 0,
GatewayPayableAmount = src.GatewayAmount
});
// ========== CompleteOrderPayment Mappings ==========
// Command → BFF Proto Request
config.NewConfig<CompleteOrderPaymentCommand, CompleteOrderPaymentRequest>()
.MapWith(src => new CompleteOrderPaymentRequest
{
OrderId = src.OrderId,
TransactionId = src.PaymentTransactionCode, // Protobuf compiler handles string to StringValue
PaymentSuccess = src.PaymentSuccess
});
// BFF Proto Response → DTO
config.NewConfig<CompleteOrderPaymentResponse, CompleteOrderPaymentResponseDto>()
.MapWith(src => new CompleteOrderPaymentResponseDto
{
Success = src.Success,
Message = src.Message
});
// ========== UpdateOrderStatus Mappings ==========
// Command → BFF Proto Request
config.NewConfig<UpdateOrderStatusCommand, UpdateOrderStatusRequest>()
.MapWith(src => new UpdateOrderStatusRequest
{
OrderId = src.OrderId,
DeliveryStatus = (DeliveryStatus)src.NewStatus,
TrackingCode = src.TrackingCode, // Protobuf compiler handles string to StringValue
AdminNotes = src.AdminNote // Protobuf compiler handles string to StringValue
});
// BFF Proto Response → DTO
config.NewConfig<UpdateOrderStatusResponse, UpdateOrderStatusResponseDto>()
.MapWith(src => new UpdateOrderStatusResponseDto
{
Success = src.Success,
Message = src.Message
});
// ========== GetOrderById Mappings ==========
// BFF Proto Response → DTO
config.NewConfig<GetOrderByIdResponse, GetOrderByIdResponseDto>()
.MapWith(src => new GetOrderByIdResponseDto
{
Id = src.Id,
UserId = src.UserId,
TrackingCode = src.TrackingCode ?? src.OrderNumber,
Status = (int)src.DeliveryStatus,
StatusTitle = GetDeliveryStatusTitle(src.DeliveryStatus),
TotalPrice = src.TotalPrice,
DiscountBalanceUsed = src.DiscountBalanceUsed,
GatewayPayment = src.GatewayAmount,
FinalPrice = src.TotalPrice - src.DiscountBalanceUsed,
PaymentTransactionCode = src.TransactionId ?? string.Empty,
AdminNote = src.AdminNotes ?? string.Empty,
CreatedAt = src.Created != null ? src.Created.ToDateTime() : DateTime.UtcNow,
PaidAt = src.PaymentCompleted && src.Created != null ? src.Created.ToDateTime() : (DateTime?)null,
ShippingAddress = src.Address != null ? new AddressInfoDto
{
Id = src.Address.Id,
RecipientName = src.Address.Title,
RecipientPhone = src.Address.Phone ?? string.Empty,
Province = ExtractProvince(src.Address.Address),
City = ExtractCity(src.Address.Address),
PostalCode = src.Address.PostalCode,
FullAddress = src.Address.Address
} : null,
Items = src.Items.Select(item => new Application.DiscountOrderCQ.Queries.GetOrderById.OrderItemDto
{
Id = item.ProductId, // Use ProductId as Id since proto doesn't have separate Id
ProductId = item.ProductId,
ProductTitle = item.ProductTitle,
UnitPrice = item.UnitPrice,
DiscountPercent = item.MaxDiscountPercent,
Quantity = item.Count,
TotalPrice = item.TotalPrice,
DiscountedPrice = item.FinalPrice
}).ToList()
});
// ========== GetUserOrders Mappings ==========
// BFF Proto Response → DTO
config.NewConfig<GetUserOrdersResponse, GetUserOrdersResponseDto>()
.MapWith(src => new GetUserOrdersResponseDto
{
MetaData = new Application.Common.Models.MetaData
{
CurrentPage = src.MetaData.CurrentPage,
TotalPage = src.MetaData.TotalPage,
PageSize = src.MetaData.PageSize,
TotalCount = src.MetaData.TotalCount,
HasPrevious = src.MetaData.HasPrevious,
HasNext = src.MetaData.HasNext
},
Orders = src.Models.Select(order => new UserOrderDto
{
Id = order.Id,
TrackingCode = order.TrackingCode ?? order.OrderNumber,
Status = (int)order.DeliveryStatus,
StatusTitle = GetDeliveryStatusTitle(order.DeliveryStatus),
TotalPrice = order.TotalPrice,
FinalPrice = order.TotalPrice - order.DiscountBalanceUsed,
ItemCount = order.ItemsCount,
CreatedAt = order.Created != null ? order.Created.ToDateTime() : DateTime.UtcNow
}).ToList()
});
}
// Helper methods
private static string GetDeliveryStatusTitle(DeliveryStatus status) => status switch
{
DeliveryStatus.DeliveryPending => "در انتظار پردازش",
DeliveryStatus.DeliveryProcessing => "در حال پردازش",
DeliveryStatus.DeliveryShipped => "ارسال شده",
DeliveryStatus.DeliveryDelivered => "تحویل داده شده",
DeliveryStatus.DeliveryCancelled => "لغو شده",
_ => "نامشخص"
};
private static string ExtractProvince(string fullAddress)
{
// Simple extraction - assumes format: "Province, City, ..."
var parts = fullAddress?.Split(',');
return parts?.Length > 0 ? parts[0].Trim() : string.Empty;
}
private static string ExtractCity(string fullAddress)
{
// Simple extraction - assumes format: "Province, City, ..."
var parts = fullAddress?.Split(',');
return parts?.Length > 1 ? parts[1].Trim() : string.Empty;
}
}

View File

@@ -0,0 +1,59 @@
using BackOffice.BFF.Application.ManualPaymentCQ.Queries.GetManualPayments;
using BackOffice.BFF.Application.ManualPaymentCQ.Commands.CreateManualPayment;
using BackOffice.BFF.ManualPayment.Protobuf;
using Google.Protobuf.WellKnownTypes;
using BackOffice.BFF.Protobuf.Common;
namespace BackOffice.BFF.WebApi.Common.Mappings;
public class ManualPaymentProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
// GetManualPaymentsResponseDto -> GetManualPaymentsResponse
// config.NewConfig<GetManualPaymentsResponseDto, GetManualPaymentsResponse>()
// .MapWith(src => new GetManualPaymentsResponse
// {
// MetaData = new MetaData
// {
// CurrentPage = src.MetaData.PageNumber,
// PageSize = src.MetaData.PageSize,
// TotalCount = src.MetaData.TotalCount,
// TotalPage = (int)Math.Ceiling((double)src.MetaData.TotalCount / src.MetaData.PageSize)
// },
// Models = { (src.Models ?? Enumerable.Empty<ManualPaymentModel>())
// .Select(m => new ManualPaymentProtoModel
// {
// Id = m.Id,
// UserId = m.UserId,
// UserFullName = m.UserFullName ?? string.Empty,
// UserMobile = m.UserMobile ?? string.Empty,
// Amount = m.Amount,
// Type = m.Type,
// TypeDisplay = m.TypeDisplay ?? string.Empty,
// Description = m.Description ?? string.Empty,
// ReferenceNumber = m.ReferenceNumber ?? string.Empty,
// Status = m.Status,
// StatusDisplay = m.StatusDisplay ?? string.Empty,
// RequestedBy = m.RequestedBy,
// RequestedByName = m.RequestedByName ?? string.Empty,
// ApprovedBy = m.ApprovedBy ?? 0,
// ApprovedByName = m.ApprovedByName ?? string.Empty,
// ApprovedAt = m.ApprovedAt.HasValue
// ? Timestamp.FromDateTime(DateTime.SpecifyKind(m.ApprovedAt.Value, DateTimeKind.Utc))
// : null,
// RejectionReason = m.RejectionReason ?? string.Empty,
// TransactionId = m.TransactionId ?? 0,
// Created = Timestamp.FromDateTime(DateTime.SpecifyKind(m.Created, DateTimeKind.Utc))
// }) }
// });
// CreateManualPaymentResponseDto -> CreateManualPaymentResponse
// config.NewConfig<CreateManualPaymentResponseDto, CreateManualPaymentResponse>()
// .MapWith(src => new CreateManualPaymentResponse
// {
// Id = src.Id,
// TransactionId = src.TransactionId
// });
}
}

View File

@@ -1,6 +1,7 @@
using Foursat.BackOffice.BFF.PublicMessage.Protobuf;
namespace BackOffice.BFF.WebApi.Common.Mappings;
using BackOffice.BFF.PublicMessage.Protobuf;
using BackOffice.BFF.Application.PublicMessageCQ.Queries.GetAllMessages;
using BackOffice.BFF.Application.PublicMessageCQ.Queries.GetActiveMessages;
using Google.Protobuf.WellKnownTypes;
@@ -10,57 +11,57 @@ public class PublicMessageProfile : IRegister
void IRegister.Register(TypeAdapterConfig config)
{
// GetAllMessages mappings
config.NewConfig<GetAllMessagesRequest, GetAllMessagesQuery>()
.Map(dest => dest.Status, src => src.Status != null ? (int?)src.Status.Value : null)
.Map(dest => dest.MessageType, src => src.MessageType != null ? (int?)src.MessageType.Value : null)
.Map(dest => dest.PageNumber, src => src.PageNumber)
.Map(dest => dest.PageSize, src => src.PageSize);
config.NewConfig<GetAllMessagesQuery, GetAllMessagesResponse>()
.Map(dest => dest.Messages, src => src.Messages)
.Map(dest => dest.TotalCount, src => src.TotalCount)
.Map(dest => dest.PageNumber, src => src.PageNumber)
.Map(dest => dest.PageSize, src => src.PageSize);
config.NewConfig<AdminPublicMessageDto, AdminPublicMessageDto>()
.Map(dest => dest.MessageId, src => src.MessageId)
.Map(dest => dest.Title, src => src.Title)
.Map(dest => dest.Content, src => src.Content)
.Map(dest => dest.MessageType, src => src.MessageType)
.Map(dest => dest.MessageTypeName, src => src.MessageTypeName)
.Map(dest => dest.Priority, src => src.Priority)
.Map(dest => dest.PriorityName, src => src.PriorityName)
.Map(dest => dest.Status, src => src.Status)
.Map(dest => dest.StatusName, src => src.StatusName)
.Map(dest => dest.StartsAt, src => src.StartsAt.HasValue ? Timestamp.FromDateTime(src.StartsAt.Value.ToUniversalTime()) : null)
.Map(dest => dest.ExpiresAt, src => src.ExpiresAt.HasValue ? Timestamp.FromDateTime(src.ExpiresAt.Value.ToUniversalTime()) : null)
.Map(dest => dest.PublishedAt, src => src.PublishedAt.HasValue ? Timestamp.FromDateTime(src.PublishedAt.Value.ToUniversalTime()) : null)
.Map(dest => dest.ArchivedAt, src => src.ArchivedAt.HasValue ? Timestamp.FromDateTime(src.ArchivedAt.Value.ToUniversalTime()) : null)
.Map(dest => dest.IsDismissible, src => src.IsDismissible)
.Map(dest => dest.TargetAudience, src => src.TargetAudience)
.Map(dest => dest.Tags, src => src.Tags)
.Map(dest => dest.Created, src => Timestamp.FromDateTime(src.Created.ToUniversalTime()))
.Map(dest => dest.LastModified, src => Timestamp.FromDateTime(src.LastModified.ToUniversalTime()));
// GetActiveMessages mappings
config.NewConfig<GetActiveMessagesRequest, GetActiveMessagesQuery>()
.Map(dest => dest.MessageType, src => src.MessageType != null ? (int?)src.MessageType.Value : null)
.Map(dest => dest.TargetAudience, src => src.TargetAudience);
config.NewConfig<GetActiveMessagesQuery, GetActiveMessagesResponse>()
.Map(dest => dest.Messages, src => src.Messages);
config.NewConfig<PublicMessageDto, PublicMessageDto>()
.Map(dest => dest.MessageId, src => src.MessageId)
.Map(dest => dest.Title, src => src.Title)
.Map(dest => dest.Content, src => src.Content)
.Map(dest => dest.MessageType, src => src.MessageType)
.Map(dest => dest.MessageTypeName, src => src.MessageTypeName)
.Map(dest => dest.Priority, src => src.Priority)
.Map(dest => dest.PriorityName, src => src.PriorityName)
.Map(dest => dest.StartsAt, src => Timestamp.FromDateTime(src.StartsAt.ToUniversalTime()))
.Map(dest => dest.ExpiresAt, src => Timestamp.FromDateTime(src.ExpiresAt.ToUniversalTime()))
.Map(dest => dest.IsDismissible, src => src.IsDismissible)
.Map(dest => dest.Tags, src => src.Tags);
// config.NewConfig<GetAllMessagesRequest, GetAllMessagesQuery>()
// .Map(dest => dest.Status, src => src.Status != null ? (int?)src.Status.Value : null)
// .Map(dest => dest.MessageType, src => src.MessageType != null ? (int?)src.MessageType.Value : null)
// .Map(dest => dest.PageNumber, src => src.PageNumber)
// .Map(dest => dest.PageSize, src => src.PageSize);
//
// config.NewConfig<GetAllMessagesQuery, GetAllMessagesResponse>()
// .Map(dest => dest.Messages, src => src.Messages)
// .Map(dest => dest.TotalCount, src => src.TotalCount)
// .Map(dest => dest.PageNumber, src => src.PageNumber)
// .Map(dest => dest.PageSize, src => src.PageSize);
//
// config.NewConfig<AdminPublicMessageDto, AdminPublicMessageDto>()
// .Map(dest => dest.MessageId, src => src.MessageId)
// .Map(dest => dest.Title, src => src.Title)
// .Map(dest => dest.Content, src => src.Content)
// .Map(dest => dest.MessageType, src => src.MessageType)
// .Map(dest => dest.MessageTypeName, src => src.MessageTypeName)
// .Map(dest => dest.Priority, src => src.Priority)
// .Map(dest => dest.PriorityName, src => src.PriorityName)
// .Map(dest => dest.Status, src => src.Status)
// .Map(dest => dest.StatusName, src => src.StatusName)
// .Map(dest => dest.StartsAt, src => src.StartsAt.HasValue ? Timestamp.FromDateTime(src.StartsAt.Value.ToUniversalTime()) : null)
// .Map(dest => dest.ExpiresAt, src => src.ExpiresAt.HasValue ? Timestamp.FromDateTime(src.ExpiresAt.Value.ToUniversalTime()) : null)
// .Map(dest => dest.PublishedAt, src => src.PublishedAt.HasValue ? Timestamp.FromDateTime(src.PublishedAt.Value.ToUniversalTime()) : null)
// .Map(dest => dest.ArchivedAt, src => src.ArchivedAt.HasValue ? Timestamp.FromDateTime(src.ArchivedAt.Value.ToUniversalTime()) : null)
// .Map(dest => dest.IsDismissible, src => src.IsDismissible)
// .Map(dest => dest.TargetAudience, src => src.TargetAudience)
// .Map(dest => dest.Tags, src => src.Tags)
// .Map(dest => dest.Created, src => Timestamp.FromDateTime(src.Created.ToUniversalTime()))
// .Map(dest => dest.LastModified, src => Timestamp.FromDateTime(src.LastModified.ToUniversalTime()));
//
// // GetActiveMessages mappings
// config.NewConfig<GetActiveMessagesRequest, GetActiveMessagesQuery>()
// .Map(dest => dest.MessageType, src => src.MessageType != null ? (int?)src.MessageType.Value : null)
// .Map(dest => dest.TargetAudience, src => src.TargetAudience);
//
// config.NewConfig<GetActiveMessagesQuery, GetActiveMessagesResponse>()
// .Map(dest => dest.Messages, src => src.Messages);
//
// config.NewConfig<PublicMessageDto, PublicMessageDto>()
// .Map(dest => dest.MessageId, src => src.MessageId)
// .Map(dest => dest.Title, src => src.Title)
// .Map(dest => dest.Content, src => src.Content)
// .Map(dest => dest.MessageType, src => src.MessageType)
// .Map(dest => dest.MessageTypeName, src => src.MessageTypeName)
// .Map(dest => dest.Priority, src => src.Priority)
// .Map(dest => dest.PriorityName, src => src.PriorityName)
// .Map(dest => dest.StartsAt, src => Timestamp.FromDateTime(src.StartsAt.ToUniversalTime()))
// .Map(dest => dest.ExpiresAt, src => Timestamp.FromDateTime(src.ExpiresAt.ToUniversalTime()))
// .Map(dest => dest.IsDismissible, src => src.IsDismissible)
// .Map(dest => dest.Tags, src => src.Tags);
}
}

View File

@@ -12,74 +12,74 @@ public class UserOrderProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
// UpdateOrderStatus mappings
config.NewConfig<UpdateOrderStatusRequest, UpdateOrderStatusCommand>()
.Map(dest => dest.OrderId, src => src.OrderId)
.Map(dest => dest.NewStatus, src => src.NewStatus);
config.NewConfig<UpdateOrderStatusCommand, UpdateOrderStatusResponse>()
.Map(dest => dest.Success, src => src.Success)
.Map(dest => dest.Message, src => src.Message)
.Map(dest => dest.OldStatus, src => src.OldStatus)
.Map(dest => dest.NewStatus, src => src.NewStatus);
// GetOrdersByDateRange mappings
config.NewConfig<GetOrdersByDateRangeRequest, GetOrdersByDateRangeQuery>()
.Map(dest => dest.StartDate, src => src.StartDate.ToDateTime())
.Map(dest => dest.EndDate, src => src.EndDate.ToDateTime())
.Map(dest => dest.Status, src => src.Status != null ? (int?)src.Status.Value : null)
.Map(dest => dest.UserId, src => src.UserId != null ? (long?)src.UserId.Value : null)
.Map(dest => dest.PageNumber, src => src.PageNumber)
.Map(dest => dest.PageSize, src => src.PageSize);
config.NewConfig<GetOrdersByDateRangeQuery, GetOrdersByDateRangeResponse>()
.Map(dest => dest.MetaData, src => src.MetaData)
.Map(dest => dest.Orders, src => src.Orders);
config.NewConfig<OrderSummaryDto, OrderSummaryDto>()
.Map(dest => dest.OrderId, src => src.OrderId)
.Map(dest => dest.OrderNumber, src => src.OrderNumber)
.Map(dest => dest.UserId, src => src.UserId)
.Map(dest => dest.UserFullName, src => src.UserFullName)
.Map(dest => dest.TotalAmount, src => src.TotalAmount)
.Map(dest => dest.Status, src => src.Status)
.Map(dest => dest.StatusName, src => src.StatusName)
.Map(dest => dest.ItemsCount, src => src.ItemsCount)
.Map(dest => dest.CreatedAt, src => Timestamp.FromDateTime(src.CreatedAt.ToUniversalTime()));
// ApplyDiscountToOrder mappings
config.NewConfig<ApplyDiscountToOrderRequest, ApplyDiscountToOrderCommand>()
.Map(dest => dest.OrderId, src => src.OrderId)
.Map(dest => dest.DiscountAmount, src => src.DiscountAmount)
.Map(dest => dest.Reason, src => src.Reason);
config.NewConfig<ApplyDiscountToOrderCommand, ApplyDiscountToOrderResponse>()
.Map(dest => dest.Success, src => src.Success)
.Map(dest => dest.Message, src => src.Message)
.Map(dest => dest.OriginalAmount, src => src.OriginalAmount)
.Map(dest => dest.DiscountAmount, src => src.DiscountAmount)
.Map(dest => dest.FinalAmount, src => src.FinalAmount);
// CalculateOrderPV mappings
config.NewConfig<CalculateOrderPVRequest, CalculateOrderPVQuery>()
.Map(dest => dest.OrderId, src => src.OrderId);
config.NewConfig<CalculateOrderPVQuery, CalculateOrderPVResponse>()
.Map(dest => dest.OrderId, src => src.OrderId)
.Map(dest => dest.TotalPv, src => src.TotalPV)
.Map(dest => dest.Products, src => src.Products);
config.NewConfig<ProductPVDto, ProductPVDto>()
.Map(dest => dest.ProductId, src => src.ProductId)
.Map(dest => dest.ProductTitle, src => src.ProductTitle)
.Map(dest => dest.Quantity, src => src.Quantity)
.Map(dest => dest.UnitPv, src => src.UnitPV)
.Map(dest => dest.TotalPv, src => src.TotalPV);
// CancelOrder mappings
config.NewConfig<CancelOrderRequest, CancelOrderCommand>()
.Map(dest => dest.OrderId, src => src.OrderId)
.Map(dest => dest.CancelReason, src => src.CancelReason)
.Map(dest => dest.RefundPayment, src => src.RefundPayment);
// // UpdateOrderStatus mappings
// config.NewConfig<UpdateOrderStatusRequest, UpdateOrderStatusCommand>()
// .Map(dest => dest.OrderId, src => src.OrderId)
// .Map(dest => dest.NewStatus, src => src.NewStatus);
//
// config.NewConfig<UpdateOrderStatusCommand, UpdateOrderStatusResponse>()
// .Map(dest => dest.Success, src => src.Success)
// .Map(dest => dest.Message, src => src.Message)
// .Map(dest => dest.OldStatus, src => src.OldStatus)
// .Map(dest => dest.NewStatus, src => src.NewStatus);
//
// // GetOrdersByDateRange mappings
// config.NewConfig<GetOrdersByDateRangeRequest, GetOrdersByDateRangeQuery>()
// .Map(dest => dest.StartDate, src => src.StartDate.ToDateTime())
// .Map(dest => dest.EndDate, src => src.EndDate.ToDateTime())
// .Map(dest => dest.Status, src => src.Status != null ? (int?)src.Status.Value : null)
// .Map(dest => dest.UserId, src => src.UserId != null ? (long?)src.UserId.Value : null)
// .Map(dest => dest.PageNumber, src => src.PageNumber)
// .Map(dest => dest.PageSize, src => src.PageSize);
//
// config.NewConfig<GetOrdersByDateRangeQuery, GetOrdersByDateRangeResponse>()
// .Map(dest => dest.MetaData, src => src.MetaData)
// .Map(dest => dest.Orders, src => src.Orders);
//
// config.NewConfig<OrderSummaryDto, OrderSummaryDto>()
// .Map(dest => dest.OrderId, src => src.OrderId)
// .Map(dest => dest.OrderNumber, src => src.OrderNumber)
// .Map(dest => dest.UserId, src => src.UserId)
// .Map(dest => dest.UserFullName, src => src.UserFullName)
// .Map(dest => dest.TotalAmount, src => src.TotalAmount)
// .Map(dest => dest.Status, src => src.Status)
// .Map(dest => dest.StatusName, src => src.StatusName)
// .Map(dest => dest.ItemsCount, src => src.ItemsCount)
// .Map(dest => dest.CreatedAt, src => Timestamp.FromDateTime(src.CreatedAt.ToUniversalTime()));
//
// // ApplyDiscountToOrder mappings
// config.NewConfig<ApplyDiscountToOrderRequest, ApplyDiscountToOrderCommand>()
// .Map(dest => dest.OrderId, src => src.OrderId)
// .Map(dest => dest.DiscountAmount, src => src.DiscountAmount)
// .Map(dest => dest.Reason, src => src.Reason);
//
// config.NewConfig<ApplyDiscountToOrderCommand, ApplyDiscountToOrderResponse>()
// .Map(dest => dest.Success, src => src.Success)
// .Map(dest => dest.Message, src => src.Message)
// .Map(dest => dest.OriginalAmount, src => src.OriginalAmount)
// .Map(dest => dest.DiscountAmount, src => src.DiscountAmount)
// .Map(dest => dest.FinalAmount, src => src.FinalAmount);
//
// // CalculateOrderPV mappings
// config.NewConfig<CalculateOrderPVRequest, CalculateOrderPVQuery>()
// .Map(dest => dest.OrderId, src => src.OrderId);
//
// config.NewConfig<CalculateOrderPVQuery, CalculateOrderPVResponse>()
// .Map(dest => dest.OrderId, src => src.OrderId)
// .Map(dest => dest.TotalPv, src => src.TotalPV)
// .Map(dest => dest.Products, src => src.Products);
//
// config.NewConfig<ProductPVDto, ProductPVDto>()
// .Map(dest => dest.ProductId, src => src.ProductId)
// .Map(dest => dest.ProductTitle, src => src.ProductTitle)
// .Map(dest => dest.Quantity, src => src.Quantity)
// .Map(dest => dest.UnitPv, src => src.UnitPV)
// .Map(dest => dest.TotalPv, src => src.TotalPV);
//
// // CancelOrder mappings
// config.NewConfig<CancelOrderRequest, CancelOrderCommand>()
// .Map(dest => dest.OrderId, src => src.OrderId)
// .Map(dest => dest.CancelReason, src => src.CancelReason)
// .Map(dest => dest.RefundPayment, src => src.RefundPayment);
}
}

View File

@@ -1,4 +1,8 @@
namespace BackOffice.BFF.WebApi.Common.Services;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
public interface IDispatchRequestToCQRS
{
Task<TResponse> Handle<TRequest, TCommand, TResponse>(TRequest request,
@@ -10,10 +14,12 @@ public interface IDispatchRequestToCQRS
public class DispatchRequestToCQRS : IDispatchRequestToCQRS
{
private readonly ISender _sender;
private readonly ILogger<DispatchRequestToCQRS> _logger;
public DispatchRequestToCQRS(ISender sender)
public DispatchRequestToCQRS(ISender sender, ILogger<DispatchRequestToCQRS> logger)
{
_sender = sender;
_logger = logger;
}
public async Task<TResponse> Handle<TRequest, TCommand, TResponse>(TRequest request,
@@ -34,10 +40,43 @@ public class DispatchRequestToCQRS : IDispatchRequestToCQRS
}
var output = await _sender.Send(cqrsInput, context.CancellationToken);
return (output ?? throw new InvalidOperationException()).Adapt<TResponse>();
if (output is null)
{
_logger.LogError("Command/Query {CommandType} returned null output", typeof(TCommand).Name);
throw new InvalidOperationException($"Command/Query {typeof(TCommand).Name} returned null output");
}
try
{
_logger.LogInformation("Mapping from {SourceType} to {TargetType}", output.GetType().Name, typeof(TResponse).Name);
// Try to detect problematic properties before mapping
LogSourceObjectStructure(output);
// Use TypeAdapter.Adapt instead of extension method to ensure MapWith() is used
var result = TypeAdapter.Adapt(output, output.GetType(), typeof(TResponse));
_logger.LogInformation("Mapping successful: {SourceType} -> {TargetType}", output.GetType().Name, typeof(TResponse).Name);
return (TResponse)result;
}
catch (Exception mappingEx)
{
_logger.LogError(mappingEx, "Mapping failed: {SourceType} -> {TargetType}. Full error: {ErrorMessage}",
output.GetType().Name, typeof(TResponse).Name, mappingEx.ToString());
// Log detailed property information
LogDetailedPropertyInfo(output, typeof(TResponse));
throw new InvalidOperationException(
$"Failed to map {output.GetType().Name} to {typeof(TResponse).Name}. Check ProductsProfile configuration.",
mappingEx);
}
}
catch (Exception)
catch (Exception ex)
{
_logger.LogError(ex, "Error in DispatchRequestToCQRS.Handle<{Request},{Command},{Response}>",
typeof(TRequest).Name, typeof(TCommand).Name, typeof(TResponse).Name);
throw;
}
}
@@ -52,10 +91,36 @@ public class DispatchRequestToCQRS : IDispatchRequestToCQRS
}
var output = await _sender.Send(cqrsInput, context.CancellationToken);
return (output ?? throw new InvalidOperationException()).Adapt<TResponse>();
if (output is null)
{
_logger.LogError("Command/Query {CommandType} returned null output", typeof(TCommand).Name);
throw new InvalidOperationException($"Command/Query {typeof(TCommand).Name} returned null output");
}
try
{
_logger.LogInformation("Mapping from {SourceType} to {TargetType}", output.GetType().Name, typeof(TResponse).Name);
// Use TypeAdapter.Adapt instead of extension method to ensure MapWith() is used
var result = TypeAdapter.Adapt(output, output.GetType(), typeof(TResponse));
_logger.LogInformation("Mapping successful: {SourceType} -> {TargetType}", output.GetType().Name, typeof(TResponse).Name);
return (TResponse)result;
}
catch (Exception mappingEx)
{
_logger.LogError(mappingEx, "Mapping failed: {SourceType} -> {TargetType}. Source properties: {@SourceData}",
output.GetType().Name, typeof(TResponse).Name, output);
throw new InvalidOperationException(
$"Failed to map {output.GetType().Name} to {typeof(TResponse).Name}. Check mapping Profile configuration.",
mappingEx);
}
}
catch (Exception)
catch (Exception ex)
{
_logger.LogError(ex, "Error in DispatchRequestToCQRS.Handle<{Command},{Response}>",
typeof(TCommand).Name, typeof(TResponse).Name);
throw;
}
}
@@ -84,4 +149,84 @@ public class DispatchRequestToCQRS : IDispatchRequestToCQRS
throw;
}
}
private void LogSourceObjectStructure(object source)
{
try
{
var properties = source.GetType().GetProperties();
_logger.LogInformation("Source object has {PropertyCount} properties:", properties.Length);
foreach (var prop in properties)
{
var value = prop.GetValue(source);
var valueType = value?.GetType().Name ?? "null";
var isCollection = value != null && value.GetType().IsGenericType &&
value.GetType().GetGenericTypeDefinition() == typeof(List<>);
if (isCollection)
{
var count = ((System.Collections.ICollection)value).Count;
_logger.LogInformation(" - {PropertyName}: {PropertyType} (Collection with {Count} items)",
prop.Name, prop.PropertyType.Name, count);
}
else
{
_logger.LogInformation(" - {PropertyName}: {PropertyType} = {Value}",
prop.Name, prop.PropertyType.Name, value);
}
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not log source object structure");
}
}
private void LogDetailedPropertyInfo(object source, System.Type targetType)
{
try
{
var sourceProps = source.GetType().GetProperties();
var targetProps = targetType.GetProperties();
_logger.LogError("=== DETAILED MAPPING ANALYSIS ===");
_logger.LogError("Source type: {SourceType} ({SourcePropsCount} properties)",
source.GetType().FullName, sourceProps.Length);
_logger.LogError("Target type: {TargetType} ({TargetPropsCount} properties)",
targetType.FullName, targetProps.Length);
foreach (var sourceProp in sourceProps)
{
var targetProp = targetProps.FirstOrDefault(p =>
p.Name.Equals(sourceProp.Name, StringComparison.OrdinalIgnoreCase));
if (targetProp != null)
{
var sourceValue = sourceProp.GetValue(source);
var sourceType = sourceProp.PropertyType;
var targetPropType = targetProp.PropertyType;
var isImmutable = targetPropType.FullName?.Contains("Google.Protobuf") == true ||
targetPropType.Name.Contains("RepeatedField");
_logger.LogError(" Property '{PropertyName}':", sourceProp.Name);
_logger.LogError(" Source: {SourceType} (value: {Value})",
sourceType.Name, sourceValue);
_logger.LogError(" Target: {TargetType} {ImmutableWarning}",
targetPropType.Name,
isImmutable ? "⚠️ IMMUTABLE - needs MapWith()" : "");
}
else
{
_logger.LogWarning(" Property '{PropertyName}' exists in source but NOT in target",
sourceProp.Name);
}
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not log detailed property info");
}
}
}

View File

@@ -7,7 +7,7 @@ using BackOffice.BFF.Application.CommissionCQ.Queries.GetWithdrawalRequests;
using BackOffice.BFF.Application.CommissionCQ.Queries.GetWithdrawalReports;
using BackOffice.BFF.Application.CommissionCQ.Commands.ApproveWithdrawal;
using BackOffice.BFF.Application.CommissionCQ.Commands.RejectWithdrawal;
// using BackOffice.BFF.Application.CommissionCQ.Commands.ProcessWithdrawal; // Excluded - needs proto fix
using BackOffice.BFF.Application.CommissionCQ.Commands.ProcessWithdrawal;
using Foursat.BackOffice.BFF.Commission.Protos;
using Google.Protobuf.WellKnownTypes;
@@ -91,8 +91,10 @@ public class CommissionService : CommissionContract.CommissionContractBase
ProcessWithdrawalRequest request,
ServerCallContext context)
{
// TODO: Implement after ProcessWithdrawalCommand is fixed
throw new RpcException(new Status(StatusCode.Unimplemented, "ProcessWithdrawal is temporarily disabled"));
await _dispatchRequestToCQRS.Handle<ProcessWithdrawalRequest, ProcessWithdrawalCommand, ProcessWithdrawalResponseDto>(
request,
context);
return new Empty();
}
public override async Task<GetWithdrawalReportsResponse> GetWithdrawalReports(

View File

@@ -62,10 +62,10 @@ public class ManualPaymentService : ManualPaymentContract.ManualPaymentContractB
{
PageNumber = request.PageNumber,
PageSize = request.PageSize,
UserId = request.UserId?.Value,
Status = request.Status?.Value,
Type = request.Type?.Value,
ReferenceNumber = request.ReferenceNumber?.Value,
UserId = request.UserId,
Status = request.Status,
Type = request.Type,
ReferenceNumber = request.ReferenceNumber,
// RequestedBy و OrderByDescending در این نسخه از UI ارسال نمی‌شود
};

View File

@@ -1,5 +1,6 @@
using BackOffice.BFF.PublicMessage.Protobuf;
using CMSMicroservice.Protobuf.Protos;
using BackOffice.BFF.WebApi.Common.Services;
using Google.Protobuf.WellKnownTypes;
using BackOffice.BFF.Application.PublicMessageCQ.Queries.GetAllMessages;
using BackOffice.BFF.Application.PublicMessageCQ.Queries.GetActiveMessages;
@@ -21,16 +22,16 @@ public class PublicMessageService : PublicMessageContract.PublicMessageContractB
context);
}
public override async Task<UpdatePublicMessageRequest> UpdatePublicMessage(UpdatePublicMessageRequest request, ServerCallContext context)
public override async Task<Empty> UpdatePublicMessage(UpdatePublicMessageRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<UpdatePublicMessageRequest, UpdatePublicMessageRequest, UpdatePublicMessageRequest>(
return await _dispatchRequestToCQRS.Handle<UpdatePublicMessageRequest, UpdatePublicMessageRequest, Empty>(
request,
context);
}
public override async Task<DeletePublicMessageRequest> DeletePublicMessage(DeletePublicMessageRequest request, ServerCallContext context)
public override async Task<Empty> DeletePublicMessage(DeletePublicMessageRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<DeletePublicMessageRequest, DeletePublicMessageRequest, DeletePublicMessageRequest>(
return await _dispatchRequestToCQRS.Handle<DeletePublicMessageRequest, DeletePublicMessageRequest, Empty>(
request,
context);
}