Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c814cd9118 | |||
| 2d23dbc798 | |||
| 8ec0910cdf | |||
| e882f09955 | |||
| 436e5d7aca | |||
| 6a04a6a270 | |||
| 427d2cce1d | |||
| fb9e3d0109 | |||
| 6c90e4caf0 | |||
| 40da1bd8df |
+2
-2
@@ -163,10 +163,10 @@ public class ActivateClubMembershipCommandHandler : IRequestHandler<ActivateClub
|
|||||||
request.UserId
|
request.UserId
|
||||||
);
|
);
|
||||||
|
|
||||||
// در فعالسازی اجباری، اگر PackagePurchaseMethod تنظیم نشده، مقدار DirectPurchase بگذار
|
// در فعالسازی اجباری ادمین، اگر روش خرید تنظیم نشده، دستی در نظر بگیر
|
||||||
if (user.PackagePurchaseMethod == PackagePurchaseMethod.None)
|
if (user.PackagePurchaseMethod == PackagePurchaseMethod.None)
|
||||||
{
|
{
|
||||||
user.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase;
|
user.PackagePurchaseMethod = PackagePurchaseMethod.Manual;
|
||||||
_context.Users.Update(user);
|
_context.Users.Update(user);
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -1,3 +1,5 @@
|
|||||||
|
using CMSMicroservice.Application.Common;
|
||||||
|
|
||||||
namespace CMSMicroservice.Application.CommissionCQ.Commands.RequestWithdrawal;
|
namespace CMSMicroservice.Application.CommissionCQ.Commands.RequestWithdrawal;
|
||||||
|
|
||||||
public class RequestWithdrawalCommandHandler : IRequestHandler<RequestWithdrawalCommand, Unit>
|
public class RequestWithdrawalCommandHandler : IRequestHandler<RequestWithdrawalCommand, Unit>
|
||||||
@@ -37,7 +39,10 @@ public class RequestWithdrawalCommandHandler : IRequestHandler<RequestWithdrawal
|
|||||||
|
|
||||||
if (request.Method == WithdrawalMethod.Cash)
|
if (request.Method == WithdrawalMethod.Cash)
|
||||||
{
|
{
|
||||||
payout.IbanNumber = request.IbanNumber;
|
var normalizedIban = IbanNormalizer.TryNormalize(request.IbanNumber);
|
||||||
|
if (normalizedIban is null)
|
||||||
|
throw new InvalidOperationException("فرمت شماره شبا معتبر نیست. باید IR و ۲۴ رقم باشد.");
|
||||||
|
payout.IbanNumber = normalizedIban;
|
||||||
}
|
}
|
||||||
|
|
||||||
_context.UserCommissionPayouts.Update(payout);
|
_context.UserCommissionPayouts.Update(payout);
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.Common;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نرمالسازی و اعتبارسنجی شماره شبا ایران (IR + 24 رقم).
|
||||||
|
/// </summary>
|
||||||
|
public static class IbanNormalizer
|
||||||
|
{
|
||||||
|
private static readonly Regex IranianIbanRegex = new(@"^IR\d{24}$", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فاصله و خط تیره را حذف میکند، به حروف بزرگ تبدیل میکند و یک پیشوند IR میگذارد.
|
||||||
|
/// در صورت نامعتبر بودن، null برمیگرداند.
|
||||||
|
/// </summary>
|
||||||
|
public static string? TryNormalize(string? iban)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(iban))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var normalized = iban.Trim().ToUpperInvariant()
|
||||||
|
.Replace(" ", "", StringComparison.Ordinal)
|
||||||
|
.Replace("-", "", StringComparison.Ordinal);
|
||||||
|
|
||||||
|
if (normalized.StartsWith("IR", StringComparison.Ordinal))
|
||||||
|
normalized = normalized[2..];
|
||||||
|
|
||||||
|
normalized = "IR" + normalized;
|
||||||
|
|
||||||
|
return IranianIbanRegex.IsMatch(normalized) ? normalized : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsValid(string? iban) => TryNormalize(iban) is not null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace CMSMicroservice.Application.Common;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نام نمایشی کاربر برای گزارشها؛ اگر نام خالی باشد موبایل یا شناسه.
|
||||||
|
/// </summary>
|
||||||
|
public static class UserDisplayName
|
||||||
|
{
|
||||||
|
public static string From(string? firstName, string? lastName, string? mobile, long userId)
|
||||||
|
{
|
||||||
|
var name = string.Join(" ",
|
||||||
|
new[] { firstName, lastName }.Where(s => !string.IsNullOrWhiteSpace(s)));
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(name))
|
||||||
|
return name;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(mobile))
|
||||||
|
return mobile.Trim();
|
||||||
|
|
||||||
|
return userId > 0 ? $"کاربر {userId}" : "—";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string From(Domain.Entities.User? user)
|
||||||
|
{
|
||||||
|
if (user is null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return From(user.FirstName, user.LastName, user.Mobile, user.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -35,6 +35,11 @@ public class UpdateOrderStatusCommandHandler : IRequestHandler<UpdateOrderStatus
|
|||||||
order.TrackingCode = request.TrackingCode;
|
order.TrackingCode = request.TrackingCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(request.AdminNotes))
|
||||||
|
{
|
||||||
|
order.DeliveryDescription = request.AdminNotes;
|
||||||
|
}
|
||||||
|
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
return new UpdateOrderStatusResponseDto
|
return new UpdateOrderStatusResponseDto
|
||||||
|
|||||||
+36
-12
@@ -1,3 +1,4 @@
|
|||||||
|
using CMSMicroservice.Application.Common;
|
||||||
using CMSMicroservice.Application.Common.Interfaces;
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
using CMSMicroservice.Application.Common.Models;
|
using CMSMicroservice.Application.Common.Models;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
@@ -80,16 +81,40 @@ public class GetAllDiscountOrdersQueryHandler : IRequestHandler<GetAllDiscountOr
|
|||||||
// Apply pagination
|
// Apply pagination
|
||||||
var pagination = request.PaginationQuery ?? new PaginationState { PageNumber = 1, PageSize = 20 };
|
var pagination = request.PaginationQuery ?? new PaginationState { PageNumber = 1, PageSize = 20 };
|
||||||
|
|
||||||
var orders = await query
|
var rows = await query
|
||||||
.OrderByDescending(o => o.Created)
|
.OrderByDescending(o => o.Created)
|
||||||
.Skip((pagination.PageNumber - 1) * pagination.PageSize)
|
.Skip((pagination.PageNumber - 1) * pagination.PageSize)
|
||||||
.Take(pagination.PageSize)
|
.Take(pagination.PageSize)
|
||||||
.Select(o => new AdminOrderDto
|
.Select(o => new
|
||||||
|
{
|
||||||
|
o.Id,
|
||||||
|
o.UserId,
|
||||||
|
FirstName = o.User.FirstName,
|
||||||
|
LastName = o.User.LastName,
|
||||||
|
Mobile = o.User.Mobile,
|
||||||
|
o.TotalAmount,
|
||||||
|
o.DiscountBalanceUsed,
|
||||||
|
o.GatewayAmountPaid,
|
||||||
|
o.VatAmount,
|
||||||
|
o.PaymentStatus,
|
||||||
|
o.PaymentDate,
|
||||||
|
o.DeliveryStatus,
|
||||||
|
ShippingAddress = o.UserAddress.Address,
|
||||||
|
ReceiverName = o.UserAddress.Title,
|
||||||
|
o.TrackingCode,
|
||||||
|
AdminNote = o.DeliveryDescription,
|
||||||
|
o.Created,
|
||||||
|
o.LastModified,
|
||||||
|
ItemsCount = o.OrderDetails.Count
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var orders = rows.Select(o => new AdminOrderDto
|
||||||
{
|
{
|
||||||
Id = o.Id,
|
Id = o.Id,
|
||||||
UserId = o.UserId,
|
UserId = o.UserId,
|
||||||
UserFullName = (o.User.FirstName ?? "") + " " + (o.User.LastName ?? ""),
|
UserFullName = UserDisplayName.From(o.FirstName, o.LastName, o.Mobile, o.UserId),
|
||||||
UserMobile = o.User.Mobile,
|
UserMobile = o.Mobile,
|
||||||
TotalAmount = o.TotalAmount,
|
TotalAmount = o.TotalAmount,
|
||||||
DiscountBalanceUsed = o.DiscountBalanceUsed,
|
DiscountBalanceUsed = o.DiscountBalanceUsed,
|
||||||
GatewayAmountPaid = o.GatewayAmountPaid,
|
GatewayAmountPaid = o.GatewayAmountPaid,
|
||||||
@@ -97,17 +122,16 @@ public class GetAllDiscountOrdersQueryHandler : IRequestHandler<GetAllDiscountOr
|
|||||||
PaymentStatus = o.PaymentStatus,
|
PaymentStatus = o.PaymentStatus,
|
||||||
PaymentDate = o.PaymentDate,
|
PaymentDate = o.PaymentDate,
|
||||||
DeliveryStatus = o.DeliveryStatus,
|
DeliveryStatus = o.DeliveryStatus,
|
||||||
DeliveryDate = null, // TODO: Add DeliveryDate to DiscountOrder if needed
|
DeliveryDate = null,
|
||||||
ShippingAddress = o.UserAddress.Address,
|
ShippingAddress = o.ShippingAddress,
|
||||||
ReceiverName = o.UserAddress.Title,
|
ReceiverName = o.ReceiverName,
|
||||||
ReceiverMobile = o.User.Mobile,
|
ReceiverMobile = o.Mobile,
|
||||||
TrackingCode = o.TrackingCode,
|
TrackingCode = o.TrackingCode,
|
||||||
AdminNote = o.DeliveryDescription,
|
AdminNote = o.AdminNote,
|
||||||
Created = o.Created,
|
Created = o.Created,
|
||||||
LastModified = o.LastModified,
|
LastModified = o.LastModified,
|
||||||
ItemsCount = o.OrderDetails.Count
|
ItemsCount = o.ItemsCount
|
||||||
})
|
}).ToList();
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
|
|
||||||
return new GetAllDiscountOrdersResponseDto
|
return new GetAllDiscountOrdersResponseDto
|
||||||
{
|
{
|
||||||
|
|||||||
+2
@@ -32,6 +32,8 @@ public class UserAddressDto
|
|||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string Address { get; set; }
|
public string Address { get; set; }
|
||||||
public string PostalCode { get; set; }
|
public string PostalCode { get; set; }
|
||||||
|
/// <summary>شماره تماس سفارشدهنده (موبایل کاربر) برای برچسب پست</summary>
|
||||||
|
public string? Phone { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OrderItemDto
|
public class OrderItemDto
|
||||||
|
|||||||
+3
-1
@@ -25,6 +25,7 @@ public class GetOrderByIdQueryHandler : IRequestHandler<GetOrderByIdQuery, Order
|
|||||||
|
|
||||||
var order = await query
|
var order = await query
|
||||||
.Include(o => o.UserAddress)
|
.Include(o => o.UserAddress)
|
||||||
|
.Include(o => o.User)
|
||||||
.Include(o => o.OrderDetails)
|
.Include(o => o.OrderDetails)
|
||||||
.ThenInclude(od => od.Product)
|
.ThenInclude(od => od.Product)
|
||||||
.FirstOrDefaultAsync(cancellationToken);
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
@@ -50,7 +51,8 @@ public class GetOrderByIdQueryHandler : IRequestHandler<GetOrderByIdQuery, Order
|
|||||||
{
|
{
|
||||||
Title = order.UserAddress.Title,
|
Title = order.UserAddress.Title,
|
||||||
Address = order.UserAddress.Address,
|
Address = order.UserAddress.Address,
|
||||||
PostalCode = order.UserAddress.PostalCode
|
PostalCode = order.UserAddress.PostalCode,
|
||||||
|
Phone = order.User?.Mobile
|
||||||
},
|
},
|
||||||
Items = order.OrderDetails.Select(od => new OrderItemDto
|
Items = order.OrderDetails.Select(od => new OrderItemDto
|
||||||
{
|
{
|
||||||
|
|||||||
+12
-2
@@ -150,8 +150,18 @@ public class CreateManualPaymentCommandHandler : IRequestHandler<CreateManualPay
|
|||||||
|
|
||||||
await _context.UserWalletHistories.AddAsync(walletLog, cancellationToken);
|
await _context.UserWalletHistories.AddAsync(walletLog, cancellationToken);
|
||||||
|
|
||||||
// 9. تنظیم روش خرید پکیج
|
// 9. تنظیم روش خرید پکیج + ثبت ledger خرید دستی
|
||||||
user.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase;
|
user.PackagePurchaseMethod = PackagePurchaseMethod.Manual;
|
||||||
|
|
||||||
|
_context.UserPackagePurchases.Add(new UserPackagePurchase
|
||||||
|
{
|
||||||
|
UserId = request.UserId,
|
||||||
|
PackageId = package.Id,
|
||||||
|
PurchaseMethod = PackagePurchaseMethod.Manual,
|
||||||
|
PurchasedAt = DateTime.Now,
|
||||||
|
Amount = balanceAmount,
|
||||||
|
TransactionId = transaction.Id
|
||||||
|
});
|
||||||
|
|
||||||
// 10. ذخیره همه تغییرات
|
// 10. ذخیره همه تغییرات
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|||||||
+1
-1
@@ -118,7 +118,7 @@ public class ProcessManualMembershipPaymentCommandHandler : IRequestHandler<Proc
|
|||||||
};
|
};
|
||||||
await _context.UserWalletHistories.AddAsync(balanceLog, cancellationToken);
|
await _context.UserWalletHistories.AddAsync(balanceLog, cancellationToken);
|
||||||
|
|
||||||
user.PackagePurchaseMethod = PackagePurchaseMethod.DirectPurchase;
|
user.PackagePurchaseMethod = PackagePurchaseMethod.Manual;
|
||||||
// 10. بهروزرسانی ManualPayment با TransactionId
|
// 10. بهروزرسانی ManualPayment با TransactionId
|
||||||
manualPayment.TransactionId = transaction.Id;
|
manualPayment.TransactionId = transaction.Id;
|
||||||
await _context.SaveChangesAsync(cancellationToken);
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
|||||||
+28
-3
@@ -34,14 +34,39 @@ public class GetAllUserByFilterQueryHandler : IRequestHandler<GetAllUserByFilter
|
|||||||
.Where(x => request.Filter.SmsNotifications == null || x.SmsNotifications == request.Filter.SmsNotifications)
|
.Where(x => request.Filter.SmsNotifications == null || x.SmsNotifications == request.Filter.SmsNotifications)
|
||||||
.Where(x => request.Filter.EmailNotifications == null || x.EmailNotifications == request.Filter.EmailNotifications)
|
.Where(x => request.Filter.EmailNotifications == null || x.EmailNotifications == request.Filter.EmailNotifications)
|
||||||
.Where(x => request.Filter.PushNotifications == null || x.PushNotifications == request.Filter.PushNotifications)
|
.Where(x => request.Filter.PushNotifications == null || x.PushNotifications == request.Filter.PushNotifications)
|
||||||
.Where(x => request.Filter.BirthDate == null || x.BirthDate == request.Filter.BirthDate)
|
.Where(x => request.Filter.BirthDate == null || x.BirthDate == request.Filter.BirthDate);
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GetAllUserByFilterResponseDto
|
return new GetAllUserByFilterResponseDto
|
||||||
{
|
{
|
||||||
MetaData = await query.GetMetaData(request.PaginationState, cancellationToken),
|
MetaData = await query.GetMetaData(request.PaginationState, cancellationToken),
|
||||||
Models = await query.PaginatedListAsync(paginationState: request.PaginationState)
|
Models = await query.PaginatedListAsync(paginationState: request.PaginationState)
|
||||||
.ProjectToType<GetAllUserByFilterResponseModel>().ToListAsync(cancellationToken)
|
.Select(x => new GetAllUserByFilterResponseModel
|
||||||
|
{
|
||||||
|
Id = x.Id,
|
||||||
|
FirstName = x.FirstName,
|
||||||
|
LastName = x.LastName,
|
||||||
|
Mobile = x.Mobile,
|
||||||
|
NationalCode = x.NationalCode,
|
||||||
|
AvatarPath = x.AvatarPath,
|
||||||
|
NetworkParentId = x.NetworkParentId,
|
||||||
|
ReferralCode = x.ReferralCode,
|
||||||
|
IsMobileVerified = x.IsMobileVerified,
|
||||||
|
MobileVerifiedAt = x.MobileVerifiedAt,
|
||||||
|
EmailNotifications = x.EmailNotifications,
|
||||||
|
SmsNotifications = x.SmsNotifications,
|
||||||
|
PushNotifications = x.PushNotifications,
|
||||||
|
BirthDate = x.BirthDate,
|
||||||
|
DefaultAddress = x.UserAddresses
|
||||||
|
.Where(a => a.IsDefault && !a.IsDeleted)
|
||||||
|
.Select(a => a.Address)
|
||||||
|
.FirstOrDefault() ?? string.Empty,
|
||||||
|
DefaultPostalCode = x.UserAddresses
|
||||||
|
.Where(a => a.IsDefault && !a.IsDeleted)
|
||||||
|
.Select(a => a.PostalCode)
|
||||||
|
.FirstOrDefault() ?? string.Empty
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -36,4 +36,8 @@ public class GetAllUserByFilterResponseDto
|
|||||||
public bool PushNotifications { get; set; }
|
public bool PushNotifications { get; set; }
|
||||||
//تاریخ تولد
|
//تاریخ تولد
|
||||||
public DateTime? BirthDate { get; set; }
|
public DateTime? BirthDate { get; set; }
|
||||||
|
//آدرس پیشفرض
|
||||||
|
public string DefaultAddress { get; set; } = string.Empty;
|
||||||
|
//کدپستی پیشفرض
|
||||||
|
public string DefaultPostalCode { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -1,3 +1,4 @@
|
|||||||
|
using CMSMicroservice.Application.Common;
|
||||||
using CMSMicroservice.Application.Common.Exceptions;
|
using CMSMicroservice.Application.Common.Exceptions;
|
||||||
using CMSMicroservice.Application.Common.Interfaces;
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
using CMSMicroservice.Domain.Entities;
|
using CMSMicroservice.Domain.Entities;
|
||||||
@@ -50,8 +51,10 @@ public class GetCustomerOrderQueryHandler : IRequestHandler<GetCustomerOrderQuer
|
|||||||
DeliveryStatus = order.DeliveryStatus,
|
DeliveryStatus = order.DeliveryStatus,
|
||||||
TrackingCode = order.TrackingCode ?? "",
|
TrackingCode = order.TrackingCode ?? "",
|
||||||
DeliveryDescription = order.DeliveryDescription ?? "",
|
DeliveryDescription = order.DeliveryDescription ?? "",
|
||||||
UserFullName = $"{order.User?.FirstName ?? ""} {order.User?.LastName ?? ""}".Trim(),
|
UserFullName = UserDisplayName.From(order.User),
|
||||||
UserNationalCode = order.User?.NationalCode ?? "",
|
UserNationalCode = order.User?.NationalCode ?? "",
|
||||||
|
PostalCode = order.UserAddress?.PostalCode ?? "",
|
||||||
|
UserMobile = order.User?.Mobile ?? "",
|
||||||
VatAmount = order.OrderVAT?.VATAmount ?? 0,
|
VatAmount = order.OrderVAT?.VATAmount ?? 0,
|
||||||
VatPercentage = order.OrderVAT != null ? (double)order.OrderVAT.VATRate * 100 : 0,
|
VatPercentage = order.OrderVAT != null ? (double)order.OrderVAT.VATRate * 100 : 0,
|
||||||
FactorDetails = order.FactorDetails?.Select(fd => new FactorDetailDto
|
FactorDetails = order.FactorDetails?.Select(fd => new FactorDetailDto
|
||||||
|
|||||||
+2
@@ -20,6 +20,8 @@ public class GetCustomerOrderResponseDto
|
|||||||
public string DeliveryDescription { get; set; }
|
public string DeliveryDescription { get; set; }
|
||||||
public string UserFullName { get; set; }
|
public string UserFullName { get; set; }
|
||||||
public string UserNationalCode { get; set; }
|
public string UserNationalCode { get; set; }
|
||||||
|
public string PostalCode { get; set; } = string.Empty;
|
||||||
|
public string UserMobile { get; set; } = string.Empty;
|
||||||
public long VatAmount { get; set; }
|
public long VatAmount { get; set; }
|
||||||
public double VatPercentage { get; set; }
|
public double VatPercentage { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -113,6 +113,7 @@ public class GetCustomerOrderHistoryQueryHandler : IRequestHandler<GetCustomerOr
|
|||||||
DeliveryStatus.Delivered => 4,
|
DeliveryStatus.Delivered => 4,
|
||||||
DeliveryStatus.Cancelled => 5,
|
DeliveryStatus.Cancelled => 5,
|
||||||
DeliveryStatus.Returned => 6,
|
DeliveryStatus.Returned => 6,
|
||||||
|
DeliveryStatus.ReadyForOfficePickup => 7,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -127,6 +128,7 @@ public class GetCustomerOrderHistoryQueryHandler : IRequestHandler<GetCustomerOr
|
|||||||
DeliveryStatus.Delivered => "تحویل داده شد",
|
DeliveryStatus.Delivered => "تحویل داده شد",
|
||||||
DeliveryStatus.Cancelled => "لغو شده",
|
DeliveryStatus.Cancelled => "لغو شده",
|
||||||
DeliveryStatus.Returned => "مرجوع شده",
|
DeliveryStatus.Returned => "مرجوع شده",
|
||||||
|
DeliveryStatus.ReadyForOfficePickup => "آماده تحویل در دفتر",
|
||||||
_ => "نامشخص"
|
_ => "نامشخص"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
|
using CMSMicroservice.Application.Common;
|
||||||
using CMSMicroservice.Application.Common.Extensions;
|
using CMSMicroservice.Application.Common.Extensions;
|
||||||
using CMSMicroservice.Application.Common.Interfaces;
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
using CMSMicroservice.Application.Common.Models;
|
using CMSMicroservice.Application.Common.Models;
|
||||||
@@ -72,7 +73,7 @@ public class GetCustomerOrdersQueryHandler : IRequestHandler<GetCustomerOrdersQu
|
|||||||
DeliveryStatus = order.DeliveryStatus,
|
DeliveryStatus = order.DeliveryStatus,
|
||||||
TrackingCode = order.TrackingCode ?? "",
|
TrackingCode = order.TrackingCode ?? "",
|
||||||
DeliveryDescription = order.DeliveryDescription ?? "",
|
DeliveryDescription = order.DeliveryDescription ?? "",
|
||||||
UserFullName = $"{order.User?.FirstName ?? ""} {order.User?.LastName ?? ""}".Trim(),
|
UserFullName = UserDisplayName.From(order.User),
|
||||||
UserNationalCode = order.User?.NationalCode ?? "",
|
UserNationalCode = order.User?.NationalCode ?? "",
|
||||||
VatAmount = order.OrderVAT?.VATAmount ?? 0,
|
VatAmount = order.OrderVAT?.VATAmount ?? 0,
|
||||||
VatPercentage = order.OrderVAT != null ? (double)order.OrderVAT.VATRate * 100 : 0,
|
VatPercentage = order.OrderVAT != null ? (double)order.OrderVAT.VATRate * 100 : 0,
|
||||||
|
|||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetCustomerPackagePurchaseRollup;
|
||||||
|
|
||||||
|
public record GetCustomerPackagePurchaseRollupQuery : IRequest<GetCustomerPackagePurchaseRollupResponseDto>
|
||||||
|
{
|
||||||
|
public PaginationState? PaginationState { get; init; }
|
||||||
|
public GetCustomerPackagePurchaseRollupFilter? Filter { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GetCustomerPackagePurchaseRollupFilter
|
||||||
|
{
|
||||||
|
public long? UserId { get; set; }
|
||||||
|
public PackagePurchaseMethod? PurchaseMethod { get; set; }
|
||||||
|
public DateTime? PurchasedFrom { get; set; }
|
||||||
|
public DateTime? PurchasedTo { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GetCustomerPackagePurchaseRollupResponseDto
|
||||||
|
{
|
||||||
|
public MetaData MetaData { get; set; } = new();
|
||||||
|
public List<CustomerPackagePurchaseRollupModel> Models { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomerPackagePurchaseRollupModel
|
||||||
|
{
|
||||||
|
public long UserId { get; set; }
|
||||||
|
public string UserName { get; set; } = string.Empty;
|
||||||
|
public string UserMobile { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public long FirstPackageId { get; set; }
|
||||||
|
public string FirstPackageName { get; set; } = string.Empty;
|
||||||
|
public long FirstAmount { get; set; }
|
||||||
|
public DateTime FirstPurchasedAt { get; set; }
|
||||||
|
|
||||||
|
public long LastPackageId { get; set; }
|
||||||
|
public string LastPackageName { get; set; } = string.Empty;
|
||||||
|
public long LastAmount { get; set; }
|
||||||
|
public DateTime LastPurchasedAt { get; set; }
|
||||||
|
public PackagePurchaseMethod LastPurchaseMethod { get; set; }
|
||||||
|
|
||||||
|
public int PurchaseCount { get; set; }
|
||||||
|
public long TotalAmount { get; set; }
|
||||||
|
|
||||||
|
public int DayaCount { get; set; }
|
||||||
|
public long DayaAmount { get; set; }
|
||||||
|
public int ManualCount { get; set; }
|
||||||
|
public long ManualAmount { get; set; }
|
||||||
|
public int GatewayCount { get; set; }
|
||||||
|
public long GatewayAmount { get; set; }
|
||||||
|
}
|
||||||
+136
@@ -0,0 +1,136 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Application.Common.Models;
|
||||||
|
using CMSMicroservice.Domain.Enums;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetCustomerPackagePurchaseRollup;
|
||||||
|
|
||||||
|
public class GetCustomerPackagePurchaseRollupQueryHandler
|
||||||
|
: IRequestHandler<GetCustomerPackagePurchaseRollupQuery, GetCustomerPackagePurchaseRollupResponseDto>
|
||||||
|
{
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
|
public GetCustomerPackagePurchaseRollupQueryHandler(IApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<GetCustomerPackagePurchaseRollupResponseDto> Handle(
|
||||||
|
GetCustomerPackagePurchaseRollupQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var filter = request.Filter;
|
||||||
|
var pagination = request.PaginationState ?? new PaginationState { PageNumber = 1, PageSize = 20 };
|
||||||
|
if (pagination.PageNumber < 1) pagination.PageNumber = 1;
|
||||||
|
// PageSize == 0 → خروجی کامل (بدون صفحهبندی)، مثلاً Excel
|
||||||
|
var exportAll = pagination.PageSize == 0;
|
||||||
|
if (!exportAll && pagination.PageSize < 1) pagination.PageSize = 20;
|
||||||
|
|
||||||
|
// خرید معتبر: بدون تراکنش یا تراکنش موفق
|
||||||
|
var purchases = _context.UserPackagePurchases
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(x => x.User)
|
||||||
|
.Include(x => x.Package)
|
||||||
|
.Include(x => x.Transaction)
|
||||||
|
.Where(x => x.TransactionId == null
|
||||||
|
|| (x.Transaction != null && x.Transaction.PaymentStatus == PaymentStatus.Success));
|
||||||
|
|
||||||
|
if (filter?.UserId is > 0)
|
||||||
|
purchases = purchases.Where(x => x.UserId == filter.UserId.Value);
|
||||||
|
|
||||||
|
if (filter?.PurchaseMethod != null)
|
||||||
|
purchases = purchases.Where(x => x.PurchaseMethod == filter.PurchaseMethod.Value);
|
||||||
|
|
||||||
|
if (filter?.PurchasedFrom != null)
|
||||||
|
purchases = purchases.Where(x => x.PurchasedAt >= filter.PurchasedFrom.Value);
|
||||||
|
|
||||||
|
if (filter?.PurchasedTo != null)
|
||||||
|
purchases = purchases.Where(x => x.PurchasedAt <= filter.PurchasedTo.Value);
|
||||||
|
|
||||||
|
var rows = await purchases
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.UserId,
|
||||||
|
FirstName = x.User != null ? x.User.FirstName : null,
|
||||||
|
LastName = x.User != null ? x.User.LastName : null,
|
||||||
|
Mobile = x.User != null ? x.User.Mobile : null,
|
||||||
|
x.PackageId,
|
||||||
|
PackageName = x.Package != null ? x.Package.Title : null,
|
||||||
|
x.Amount,
|
||||||
|
x.PurchasedAt,
|
||||||
|
x.PurchaseMethod
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var grouped = rows
|
||||||
|
.GroupBy(x => x.UserId)
|
||||||
|
.Select(g =>
|
||||||
|
{
|
||||||
|
var ordered = g.OrderBy(x => x.PurchasedAt).ThenBy(x => x.PackageId).ToList();
|
||||||
|
var first = ordered.First();
|
||||||
|
var last = ordered.Last();
|
||||||
|
var userName = string.Join(" ",
|
||||||
|
new[] { first.FirstName, first.LastName }.Where(s => !string.IsNullOrWhiteSpace(s))).Trim();
|
||||||
|
|
||||||
|
return new CustomerPackagePurchaseRollupModel
|
||||||
|
{
|
||||||
|
UserId = g.Key,
|
||||||
|
UserName = string.IsNullOrWhiteSpace(userName)
|
||||||
|
? (!string.IsNullOrWhiteSpace(first.Mobile) ? first.Mobile! : $"کاربر {g.Key}")
|
||||||
|
: userName,
|
||||||
|
UserMobile = first.Mobile ?? string.Empty,
|
||||||
|
|
||||||
|
FirstPackageId = first.PackageId,
|
||||||
|
FirstPackageName = first.PackageName ?? string.Empty,
|
||||||
|
FirstAmount = first.Amount,
|
||||||
|
FirstPurchasedAt = first.PurchasedAt,
|
||||||
|
|
||||||
|
LastPackageId = last.PackageId,
|
||||||
|
LastPackageName = last.PackageName ?? string.Empty,
|
||||||
|
LastAmount = last.Amount,
|
||||||
|
LastPurchasedAt = last.PurchasedAt,
|
||||||
|
LastPurchaseMethod = last.PurchaseMethod,
|
||||||
|
|
||||||
|
PurchaseCount = ordered.Count,
|
||||||
|
TotalAmount = ordered.Sum(x => x.Amount),
|
||||||
|
|
||||||
|
DayaCount = ordered.Count(x => x.PurchaseMethod == PackagePurchaseMethod.DayaLoan),
|
||||||
|
DayaAmount = ordered.Where(x => x.PurchaseMethod == PackagePurchaseMethod.DayaLoan).Sum(x => x.Amount),
|
||||||
|
ManualCount = ordered.Count(x => x.PurchaseMethod == PackagePurchaseMethod.Manual),
|
||||||
|
ManualAmount = ordered.Where(x => x.PurchaseMethod == PackagePurchaseMethod.Manual).Sum(x => x.Amount),
|
||||||
|
GatewayCount = ordered.Count(x => x.PurchaseMethod == PackagePurchaseMethod.DirectPurchase),
|
||||||
|
GatewayAmount = ordered.Where(x => x.PurchaseMethod == PackagePurchaseMethod.DirectPurchase).Sum(x => x.Amount)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.LastPurchasedAt)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var totalCount = grouped.Count;
|
||||||
|
var pageNumber = exportAll ? 1 : pagination.PageNumber;
|
||||||
|
var pageSize = exportAll ? totalCount : pagination.PageSize;
|
||||||
|
var totalPage = exportAll || pageSize <= 0
|
||||||
|
? (totalCount > 0 ? 1 : 0)
|
||||||
|
: (int)Math.Ceiling(totalCount / (double)pageSize);
|
||||||
|
|
||||||
|
var pageModels = exportAll
|
||||||
|
? grouped
|
||||||
|
: grouped
|
||||||
|
.Skip((pageNumber - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new GetCustomerPackagePurchaseRollupResponseDto
|
||||||
|
{
|
||||||
|
MetaData = new MetaData
|
||||||
|
{
|
||||||
|
CurrentPage = pageNumber,
|
||||||
|
PageSize = pageSize,
|
||||||
|
TotalCount = totalCount,
|
||||||
|
TotalPage = totalPage,
|
||||||
|
HasPrevious = !exportAll && pageNumber > 1,
|
||||||
|
HasNext = !exportAll && pageNumber < totalPage
|
||||||
|
},
|
||||||
|
Models = pageModels
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,5 +15,7 @@ public enum DeliveryStatus
|
|||||||
Returned = 4,
|
Returned = 4,
|
||||||
// لغو شده
|
// لغو شده
|
||||||
Cancelled = 5,
|
Cancelled = 5,
|
||||||
|
// آماده تحویل حضوری در دفتر
|
||||||
|
ReadyForOfficePickup = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,5 +18,10 @@ public enum PackagePurchaseMethod
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// از طریق پرداخت مستقیم درگاه بانکی
|
/// از طریق پرداخت مستقیم درگاه بانکی
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DirectPurchase = 2
|
DirectPurchase = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ثبت دستی توسط ادمین
|
||||||
|
/// </summary>
|
||||||
|
Manual = 3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using CMSMicroservice.Application.Common.Interfaces;
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
using CMSMicroservice.Domain.Entities;
|
using CMSMicroservice.Domain.Entities;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -13,10 +14,12 @@ namespace CMSMicroservice.Infrastructure.Services;
|
|||||||
public class GenerateJwtTokenService : IGenerateJwtToken
|
public class GenerateJwtTokenService : IGenerateJwtToken
|
||||||
{
|
{
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly IApplicationDbContext _context;
|
||||||
|
|
||||||
public GenerateJwtTokenService(IConfiguration configuration)
|
public GenerateJwtTokenService(IConfiguration configuration, IApplicationDbContext context)
|
||||||
{
|
{
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GenerateJwtToken(User user, int? expiryDays = null)
|
public async Task<string> GenerateJwtToken(User user, int? expiryDays = null)
|
||||||
@@ -58,6 +61,10 @@ public class GenerateJwtTokenService : IGenerateJwtToken
|
|||||||
claims.Add(new Claim("HasPurchasedPackage",
|
claims.Add(new Claim("HasPurchasedPackage",
|
||||||
(user.PackagePurchaseMethod != PackagePurchaseMethod.None).ToString()));
|
(user.PackagePurchaseMethod != PackagePurchaseMethod.None).ToString()));
|
||||||
|
|
||||||
|
var hasAddress = await _context.UserAddresses
|
||||||
|
.AnyAsync(a => a.UserId == user.Id && !a.IsDeleted);
|
||||||
|
claims.Add(new Claim("HasAddress", hasAddress.ToString()));
|
||||||
|
|
||||||
if (user.UserRoles != null && user.UserRoles.Any())
|
if (user.UserRoles != null && user.UserRoles.Any())
|
||||||
{
|
{
|
||||||
foreach (var userRole in user.UserRoles)
|
foreach (var userRole in user.UserRoles)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>0.0.205</Version>
|
<Version>0.0.209</Version>
|
||||||
<DebugType>None</DebugType>
|
<DebugType>None</DebugType>
|
||||||
<DebugSymbols>False</DebugSymbols>
|
<DebugSymbols>False</DebugSymbols>
|
||||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||||
|
|||||||
@@ -118,6 +118,8 @@ enum DeliveryStatus
|
|||||||
DELIVERY_SHIPPED = 2;
|
DELIVERY_SHIPPED = 2;
|
||||||
DELIVERY_DELIVERED = 3;
|
DELIVERY_DELIVERED = 3;
|
||||||
DELIVERY_CANCELLED = 4;
|
DELIVERY_CANCELLED = 4;
|
||||||
|
DELIVERY_READY_FOR_OFFICE = 5;
|
||||||
|
DELIVERY_RETURNED = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get Order By Id
|
// Get Order By Id
|
||||||
|
|||||||
@@ -102,6 +102,10 @@ enum DeliveryStatus
|
|||||||
DeliveryStatus_Delivered = 3;
|
DeliveryStatus_Delivered = 3;
|
||||||
// مرجوع شده
|
// مرجوع شده
|
||||||
DeliveryStatus_Returned = 4;
|
DeliveryStatus_Returned = 4;
|
||||||
|
// لغو شده
|
||||||
|
DeliveryStatus_Cancelled = 5;
|
||||||
|
// آماده تحویل حضوری در دفتر
|
||||||
|
DeliveryStatus_ReadyForOfficePickup = 6;
|
||||||
}
|
}
|
||||||
enum TransactionType
|
enum TransactionType
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -238,6 +238,8 @@ message GetAllUserByFilterResponseModel
|
|||||||
bool sms_notifications = 12;
|
bool sms_notifications = 12;
|
||||||
bool push_notifications = 13;
|
bool push_notifications = 13;
|
||||||
google.protobuf.Timestamp birth_date = 14;
|
google.protobuf.Timestamp birth_date = 14;
|
||||||
|
string default_address = 15;
|
||||||
|
string default_postal_code = 16;
|
||||||
}
|
}
|
||||||
message GetJwtTokenRequest
|
message GetJwtTokenRequest
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -220,6 +220,9 @@ message GetUserOrderResponse
|
|||||||
google.protobuf.StringValue user_national_code = 16;
|
google.protobuf.StringValue user_national_code = 16;
|
||||||
// اطلاعات مالیات بر ارزش افزوده
|
// اطلاعات مالیات بر ارزش افزوده
|
||||||
OrderVATInfo vat_info = 17;
|
OrderVATInfo vat_info = 17;
|
||||||
|
// اطلاعات پستی برای برچسب کارتن
|
||||||
|
google.protobuf.StringValue postal_code = 18;
|
||||||
|
google.protobuf.StringValue user_mobile = 19;
|
||||||
}
|
}
|
||||||
|
|
||||||
// اطلاعات مالیات بر ارزش افزوده
|
// اطلاعات مالیات بر ارزش افزوده
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ service UserPackagePurchaseContract
|
|||||||
get: "/UserPackagePurchase/GetSummary"
|
get: "/UserPackagePurchase/GetSummary"
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
rpc GetCustomerPackagePurchaseRollup(GetCustomerPackagePurchaseRollupRequest) returns (GetCustomerPackagePurchaseRollupResponse){
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/UserPackagePurchase/GetCustomerRollup"
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetAllUserPackagePurchaseByFilterRequest
|
message GetAllUserPackagePurchaseByFilterRequest
|
||||||
@@ -73,3 +78,51 @@ message GetUserPackagePurchaseSummaryResponse
|
|||||||
int64 total_count = 1;
|
int64 total_count = 1;
|
||||||
int64 total_amount = 2;
|
int64 total_amount = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetCustomerPackagePurchaseRollupRequest
|
||||||
|
{
|
||||||
|
messages.PaginationState pagination_state = 1;
|
||||||
|
GetCustomerPackagePurchaseRollupFilter filter = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetCustomerPackagePurchaseRollupFilter
|
||||||
|
{
|
||||||
|
google.protobuf.Int64Value user_id = 1;
|
||||||
|
google.protobuf.Int32Value purchase_method = 2;
|
||||||
|
google.protobuf.Timestamp purchased_from = 3;
|
||||||
|
google.protobuf.Timestamp purchased_to = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetCustomerPackagePurchaseRollupResponse
|
||||||
|
{
|
||||||
|
messages.MetaData meta_data = 1;
|
||||||
|
repeated CustomerPackagePurchaseRollupModel models = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CustomerPackagePurchaseRollupModel
|
||||||
|
{
|
||||||
|
int64 user_id = 1;
|
||||||
|
string user_name = 2;
|
||||||
|
string user_mobile = 3;
|
||||||
|
|
||||||
|
int64 first_package_id = 4;
|
||||||
|
string first_package_name = 5;
|
||||||
|
int64 first_amount = 6;
|
||||||
|
google.protobuf.Timestamp first_purchased_at = 7;
|
||||||
|
|
||||||
|
int64 last_package_id = 8;
|
||||||
|
string last_package_name = 9;
|
||||||
|
int64 last_amount = 10;
|
||||||
|
google.protobuf.Timestamp last_purchased_at = 11;
|
||||||
|
int32 last_purchase_method = 12;
|
||||||
|
|
||||||
|
int32 purchase_count = 13;
|
||||||
|
int64 total_amount = 14;
|
||||||
|
|
||||||
|
int32 daya_count = 15;
|
||||||
|
int64 daya_amount = 16;
|
||||||
|
int32 manual_count = 17;
|
||||||
|
int64 manual_amount = 18;
|
||||||
|
int32 gateway_count = 19;
|
||||||
|
int64 gateway_amount = 20;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using CMSMicroservice.Domain.Enums;
|
|||||||
using Mapster;
|
using Mapster;
|
||||||
using AppUp = CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetAllUserPackagePurchaseByFilter;
|
using AppUp = CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetAllUserPackagePurchaseByFilter;
|
||||||
using AppUpSummary = CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetUserPackagePurchaseSummary;
|
using AppUpSummary = CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetUserPackagePurchaseSummary;
|
||||||
|
using AppRollup = CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetCustomerPackagePurchaseRollup;
|
||||||
using ProtoUp = CMSMicroservice.Protobuf.Protos.UserPackagePurchase;
|
using ProtoUp = CMSMicroservice.Protobuf.Protos.UserPackagePurchase;
|
||||||
|
|
||||||
namespace CMSMicroservice.WebApi.Common.Mappings;
|
namespace CMSMicroservice.WebApi.Common.Mappings;
|
||||||
@@ -49,5 +50,41 @@ public class UserPackagePurchaseProfile : IRegister
|
|||||||
config.NewConfig<AppUpSummary.GetUserPackagePurchaseSummaryResponseDto, ProtoUp.GetUserPackagePurchaseSummaryResponse>()
|
config.NewConfig<AppUpSummary.GetUserPackagePurchaseSummaryResponseDto, ProtoUp.GetUserPackagePurchaseSummaryResponse>()
|
||||||
.Map(dest => dest.TotalCount, src => src.TotalCount)
|
.Map(dest => dest.TotalCount, src => src.TotalCount)
|
||||||
.Map(dest => dest.TotalAmount, src => src.TotalAmount);
|
.Map(dest => dest.TotalAmount, src => src.TotalAmount);
|
||||||
|
|
||||||
|
config.NewConfig<ProtoUp.GetCustomerPackagePurchaseRollupRequest, AppRollup.GetCustomerPackagePurchaseRollupQuery>()
|
||||||
|
.Map(dest => dest.PaginationState, src => src.PaginationState)
|
||||||
|
.Map(dest => dest.Filter, src => src.Filter);
|
||||||
|
|
||||||
|
config.NewConfig<ProtoUp.GetCustomerPackagePurchaseRollupFilter, AppRollup.GetCustomerPackagePurchaseRollupFilter>()
|
||||||
|
.Map(dest => dest.UserId, src => src.UserId)
|
||||||
|
.Map(dest => dest.PurchaseMethod, src => src.PurchaseMethod == null ? null : (PackagePurchaseMethod?)src.PurchaseMethod)
|
||||||
|
.Map(dest => dest.PurchasedFrom, src => src.PurchasedFrom != null ? src.PurchasedFrom.ToDateTime() : (DateTime?)null)
|
||||||
|
.Map(dest => dest.PurchasedTo, src => src.PurchasedTo != null ? src.PurchasedTo.ToDateTime() : (DateTime?)null);
|
||||||
|
|
||||||
|
config.NewConfig<AppRollup.GetCustomerPackagePurchaseRollupResponseDto, ProtoUp.GetCustomerPackagePurchaseRollupResponse>()
|
||||||
|
.Map(dest => dest.MetaData, src => src.MetaData)
|
||||||
|
.Map(dest => dest.Models, src => src.Models);
|
||||||
|
|
||||||
|
config.NewConfig<AppRollup.CustomerPackagePurchaseRollupModel, ProtoUp.CustomerPackagePurchaseRollupModel>()
|
||||||
|
.Map(dest => dest.UserId, src => src.UserId)
|
||||||
|
.Map(dest => dest.UserName, src => src.UserName)
|
||||||
|
.Map(dest => dest.UserMobile, src => src.UserMobile)
|
||||||
|
.Map(dest => dest.FirstPackageId, src => src.FirstPackageId)
|
||||||
|
.Map(dest => dest.FirstPackageName, src => src.FirstPackageName)
|
||||||
|
.Map(dest => dest.FirstAmount, src => src.FirstAmount)
|
||||||
|
.Map(dest => dest.FirstPurchasedAt, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.FirstPurchasedAt, DateTimeKind.Utc)))
|
||||||
|
.Map(dest => dest.LastPackageId, src => src.LastPackageId)
|
||||||
|
.Map(dest => dest.LastPackageName, src => src.LastPackageName)
|
||||||
|
.Map(dest => dest.LastAmount, src => src.LastAmount)
|
||||||
|
.Map(dest => dest.LastPurchasedAt, src => Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastPurchasedAt, DateTimeKind.Utc)))
|
||||||
|
.Map(dest => dest.LastPurchaseMethod, src => (int)src.LastPurchaseMethod)
|
||||||
|
.Map(dest => dest.PurchaseCount, src => src.PurchaseCount)
|
||||||
|
.Map(dest => dest.TotalAmount, src => src.TotalAmount)
|
||||||
|
.Map(dest => dest.DayaCount, src => src.DayaCount)
|
||||||
|
.Map(dest => dest.DayaAmount, src => src.DayaAmount)
|
||||||
|
.Map(dest => dest.ManualCount, src => src.ManualCount)
|
||||||
|
.Map(dest => dest.ManualAmount, src => src.ManualAmount)
|
||||||
|
.Map(dest => dest.GatewayCount, src => src.GatewayCount)
|
||||||
|
.Map(dest => dest.GatewayAmount, src => src.GatewayAmount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ public class UserProfile : IRegister
|
|||||||
{
|
{
|
||||||
void IRegister.Register(TypeAdapterConfig config)
|
void IRegister.Register(TypeAdapterConfig config)
|
||||||
{
|
{
|
||||||
//config.NewConfig<Source,Destination>()
|
config.NewConfig<Application.UserCQ.Queries.GetAllUserByFilter.GetAllUserByFilterResponseModel,
|
||||||
// .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}");
|
CMSMicroservice.Protobuf.Protos.User.GetAllUserByFilterResponseModel>()
|
||||||
|
.Map(dest => dest.ParentId, src => src.NetworkParentId)
|
||||||
|
.Map(dest => dest.DefaultAddress, src => src.DefaultAddress ?? string.Empty)
|
||||||
|
.Map(dest => dest.DefaultPostalCode, src => src.DefaultPostalCode ?? string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,20 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
|||||||
|
|
||||||
public override async Task<UpdateOrderStatusResponse> UpdateOrderStatus(UpdateOrderStatusRequest request, ServerCallContext context)
|
public override async Task<UpdateOrderStatusResponse> UpdateOrderStatus(UpdateOrderStatusRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
return await _dispatchRequestToCQRS.Handle<UpdateOrderStatusRequest, UpdateOrderStatusCommand, UpdateOrderStatusResponse>(request, context);
|
// Proto DeliveryStatus ≠ Domain DeliveryStatus — نگاشت صریح لازم است
|
||||||
|
var command = new UpdateOrderStatusCommand
|
||||||
|
{
|
||||||
|
OrderId = request.OrderId,
|
||||||
|
DeliveryStatus = MapProtoDeliveryToDomain(request.DeliveryStatus),
|
||||||
|
TrackingCode = request.TrackingCode,
|
||||||
|
AdminNotes = request.AdminNotes
|
||||||
|
};
|
||||||
|
var result = await _sender.Send(command, context.CancellationToken);
|
||||||
|
return new UpdateOrderStatusResponse
|
||||||
|
{
|
||||||
|
Success = result.Success,
|
||||||
|
Message = result.Message ?? string.Empty
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GetOrderByIdResponse> GetOrderById(GetOrderByIdRequest request, ServerCallContext context)
|
public override async Task<GetOrderByIdResponse> GetOrderById(GetOrderByIdRequest request, ServerCallContext context)
|
||||||
@@ -141,6 +154,8 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
|||||||
Address = result.Address.Address ?? "",
|
Address = result.Address.Address ?? "",
|
||||||
PostalCode = result.Address.PostalCode ?? ""
|
PostalCode = result.Address.PostalCode ?? ""
|
||||||
};
|
};
|
||||||
|
if (!string.IsNullOrWhiteSpace(result.Address.Phone))
|
||||||
|
response.Address.Phone = result.Address.Phone;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in result.Items)
|
foreach (var item in result.Items)
|
||||||
@@ -197,9 +212,98 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GetAllDiscountOrdersResponse> GetAllDiscountOrders(GetAllDiscountOrdersRequest request, ServerCallContext context)
|
public override async Task<GetAllDiscountOrdersResponse> GetAllDiscountOrders(
|
||||||
|
GetAllDiscountOrdersRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
return await _dispatchRequestToCQRS.Handle<GetAllDiscountOrdersRequest, GetAllDiscountOrdersQuery, GetAllDiscountOrdersResponse>(request, context);
|
// Proto PaymentStatus ≠ Domain PaymentStatus (مقادیر int متفاوت) — Adapt خام باعث باگ لیست/فیلتر میشود
|
||||||
|
if (request.PaymentStatus is 3) // PAYMENT_REFUNDED — در دامنه وجود ندارد
|
||||||
|
{
|
||||||
|
return new GetAllDiscountOrdersResponse
|
||||||
|
{
|
||||||
|
MetaData = new CMSMicroservice.Protobuf.Protos.MetaData
|
||||||
|
{
|
||||||
|
TotalCount = 0,
|
||||||
|
PageSize = request.PageSize > 0 ? request.PageSize : 20,
|
||||||
|
CurrentPage = request.PageNumber > 0 ? request.PageNumber : 1,
|
||||||
|
TotalPage = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = new GetAllDiscountOrdersQuery
|
||||||
|
{
|
||||||
|
PaginationQuery = new Application.Common.Models.PaginationState
|
||||||
|
{
|
||||||
|
PageNumber = request.PageNumber > 0 ? request.PageNumber : 1,
|
||||||
|
PageSize = request.PageSize > 0 ? request.PageSize : 20
|
||||||
|
},
|
||||||
|
UserId = request.UserId,
|
||||||
|
UserMobile = request.UserMobile,
|
||||||
|
TrackingCode = request.TrackingCode,
|
||||||
|
FromDate = request.FromDate?.ToDateTime(),
|
||||||
|
ToDate = request.ToDate?.ToDateTime(),
|
||||||
|
MinAmount = request.MinAmount,
|
||||||
|
MaxAmount = request.MaxAmount
|
||||||
|
};
|
||||||
|
|
||||||
|
if (request.PaymentStatus is int paymentStatus)
|
||||||
|
query.PaymentStatus = MapProtoPaymentFilterToDomain(paymentStatus);
|
||||||
|
|
||||||
|
if (request.DeliveryStatus is int deliveryStatus)
|
||||||
|
query.DeliveryStatus = MapProtoDeliveryFilterToDomain(deliveryStatus);
|
||||||
|
|
||||||
|
var result = await _sender.Send(query, context.CancellationToken);
|
||||||
|
|
||||||
|
var response = new GetAllDiscountOrdersResponse
|
||||||
|
{
|
||||||
|
MetaData = new CMSMicroservice.Protobuf.Protos.MetaData
|
||||||
|
{
|
||||||
|
TotalCount = result.MetaData.TotalCount,
|
||||||
|
PageSize = result.MetaData.PageSize,
|
||||||
|
CurrentPage = result.MetaData.CurrentPage,
|
||||||
|
TotalPage = result.MetaData.TotalPage,
|
||||||
|
HasPrevious = result.MetaData.HasPrevious,
|
||||||
|
HasNext = result.MetaData.HasNext
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var o in result.Models)
|
||||||
|
{
|
||||||
|
var model = new CMSMicroservice.Protobuf.Protos.DiscountOrder.AdminOrderDto
|
||||||
|
{
|
||||||
|
Id = o.Id,
|
||||||
|
UserId = o.UserId,
|
||||||
|
UserFullName = o.UserFullName ?? string.Empty,
|
||||||
|
UserMobile = o.UserMobile ?? string.Empty,
|
||||||
|
TotalAmount = o.TotalAmount,
|
||||||
|
DiscountBalanceUsed = o.DiscountBalanceUsed,
|
||||||
|
GatewayAmountPaid = o.GatewayAmountPaid,
|
||||||
|
VatAmount = o.VatAmount,
|
||||||
|
PaymentStatus = MapPaymentStatus(o.PaymentStatus),
|
||||||
|
DeliveryStatus = MapDeliveryStatus(o.DeliveryStatus),
|
||||||
|
ItemsCount = o.ItemsCount,
|
||||||
|
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(o.Created, DateTimeKind.Utc))
|
||||||
|
};
|
||||||
|
|
||||||
|
if (o.PaymentDate.HasValue)
|
||||||
|
model.PaymentDate = Timestamp.FromDateTime(DateTime.SpecifyKind(o.PaymentDate.Value, DateTimeKind.Utc));
|
||||||
|
if (o.LastModified.HasValue)
|
||||||
|
model.LastModified = Timestamp.FromDateTime(DateTime.SpecifyKind(o.LastModified.Value, DateTimeKind.Utc));
|
||||||
|
if (!string.IsNullOrWhiteSpace(o.ShippingAddress))
|
||||||
|
model.ShippingAddress = o.ShippingAddress;
|
||||||
|
if (!string.IsNullOrWhiteSpace(o.ReceiverName))
|
||||||
|
model.ReceiverName = o.ReceiverName;
|
||||||
|
if (!string.IsNullOrWhiteSpace(o.ReceiverMobile))
|
||||||
|
model.ReceiverMobile = o.ReceiverMobile;
|
||||||
|
if (!string.IsNullOrWhiteSpace(o.TrackingCode))
|
||||||
|
model.TrackingCode = o.TrackingCode;
|
||||||
|
if (!string.IsNullOrWhiteSpace(o.AdminNote))
|
||||||
|
model.AdminNote = o.AdminNote;
|
||||||
|
|
||||||
|
response.Models.Add(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GetDiscountSalesReportResponse> GetDiscountSalesReport(GetDiscountSalesReportRequest request, ServerCallContext context)
|
public override async Task<GetDiscountSalesReportResponse> GetDiscountSalesReport(GetDiscountSalesReportRequest request, ServerCallContext context)
|
||||||
@@ -359,14 +463,50 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB
|
|||||||
_ => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentPending
|
_ => CMSMicroservice.Protobuf.Protos.DiscountOrder.PaymentStatus.PaymentPending
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فیلتر UI/proto → دامنه: PENDING=0, COMPLETED=1, FAILED=2
|
||||||
|
/// </summary>
|
||||||
|
private static DomainEnums.PaymentStatus MapProtoPaymentFilterToDomain(int protoPaymentStatus) => protoPaymentStatus switch
|
||||||
|
{
|
||||||
|
1 => DomainEnums.PaymentStatus.Success, // PAYMENT_COMPLETED
|
||||||
|
2 => DomainEnums.PaymentStatus.Reject, // PAYMENT_FAILED
|
||||||
|
_ => DomainEnums.PaymentStatus.Pending // PAYMENT_PENDING (0) و پیشفرض
|
||||||
|
};
|
||||||
|
|
||||||
private static DeliveryStatus MapDeliveryStatus(DomainEnums.DeliveryStatus status) => status switch
|
private static DeliveryStatus MapDeliveryStatus(DomainEnums.DeliveryStatus status) => status switch
|
||||||
{
|
{
|
||||||
DomainEnums.DeliveryStatus.None => DeliveryStatus.DeliveryPending,
|
DomainEnums.DeliveryStatus.None => DeliveryStatus.DeliveryPending,
|
||||||
DomainEnums.DeliveryStatus.Pending => DeliveryStatus.DeliveryProcessing,
|
DomainEnums.DeliveryStatus.Pending => DeliveryStatus.DeliveryProcessing,
|
||||||
DomainEnums.DeliveryStatus.InTransit => DeliveryStatus.DeliveryShipped,
|
DomainEnums.DeliveryStatus.InTransit => DeliveryStatus.DeliveryShipped,
|
||||||
DomainEnums.DeliveryStatus.Delivered => DeliveryStatus.DeliveryDelivered,
|
DomainEnums.DeliveryStatus.Delivered => DeliveryStatus.DeliveryDelivered,
|
||||||
DomainEnums.DeliveryStatus.Returned => DeliveryStatus.DeliveryCancelled,
|
|
||||||
DomainEnums.DeliveryStatus.Cancelled => DeliveryStatus.DeliveryCancelled,
|
DomainEnums.DeliveryStatus.Cancelled => DeliveryStatus.DeliveryCancelled,
|
||||||
|
DomainEnums.DeliveryStatus.ReadyForOfficePickup => DeliveryStatus.DeliveryReadyForOffice,
|
||||||
|
DomainEnums.DeliveryStatus.Returned => DeliveryStatus.DeliveryReturned,
|
||||||
_ => DeliveryStatus.DeliveryPending
|
_ => DeliveryStatus.DeliveryPending
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فیلتر UI/proto → دامنه DeliveryStatus
|
||||||
|
/// </summary>
|
||||||
|
private static DomainEnums.DeliveryStatus MapProtoDeliveryFilterToDomain(int protoDeliveryStatus) => protoDeliveryStatus switch
|
||||||
|
{
|
||||||
|
1 => DomainEnums.DeliveryStatus.Pending, // DELIVERY_PROCESSING
|
||||||
|
2 => DomainEnums.DeliveryStatus.InTransit, // DELIVERY_SHIPPED
|
||||||
|
3 => DomainEnums.DeliveryStatus.Delivered, // DELIVERY_DELIVERED
|
||||||
|
4 => DomainEnums.DeliveryStatus.Cancelled, // DELIVERY_CANCELLED
|
||||||
|
5 => DomainEnums.DeliveryStatus.ReadyForOfficePickup, // DELIVERY_READY_FOR_OFFICE
|
||||||
|
6 => DomainEnums.DeliveryStatus.Returned, // DELIVERY_RETURNED
|
||||||
|
_ => DomainEnums.DeliveryStatus.None // DELIVERY_PENDING (0)
|
||||||
|
};
|
||||||
|
|
||||||
|
private static DomainEnums.DeliveryStatus MapProtoDeliveryToDomain(DeliveryStatus status) => status switch
|
||||||
|
{
|
||||||
|
DeliveryStatus.DeliveryProcessing => DomainEnums.DeliveryStatus.Pending,
|
||||||
|
DeliveryStatus.DeliveryShipped => DomainEnums.DeliveryStatus.InTransit,
|
||||||
|
DeliveryStatus.DeliveryDelivered => DomainEnums.DeliveryStatus.Delivered,
|
||||||
|
DeliveryStatus.DeliveryCancelled => DomainEnums.DeliveryStatus.Cancelled,
|
||||||
|
DeliveryStatus.DeliveryReadyForOffice => DomainEnums.DeliveryStatus.ReadyForOfficePickup,
|
||||||
|
DeliveryStatus.DeliveryReturned => DomainEnums.DeliveryStatus.Returned,
|
||||||
|
_ => DomainEnums.DeliveryStatus.None
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,9 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
|||||||
TrackingCode = result.TrackingCode,
|
TrackingCode = result.TrackingCode,
|
||||||
DeliveryDescription = result.DeliveryDescription,
|
DeliveryDescription = result.DeliveryDescription,
|
||||||
UserFullName = result.UserFullName,
|
UserFullName = result.UserFullName,
|
||||||
UserNationalCode = result.UserNationalCode
|
UserNationalCode = result.UserNationalCode,
|
||||||
|
PostalCode = result.PostalCode,
|
||||||
|
UserMobile = result.UserMobile
|
||||||
};
|
};
|
||||||
|
|
||||||
// VAT Info
|
// VAT Info
|
||||||
@@ -789,7 +791,9 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
|||||||
TrackingCode = result.TrackingCode,
|
TrackingCode = result.TrackingCode,
|
||||||
DeliveryDescription = result.DeliveryDescription,
|
DeliveryDescription = result.DeliveryDescription,
|
||||||
UserFullName = result.UserFullName,
|
UserFullName = result.UserFullName,
|
||||||
UserNationalCode = result.UserNationalCode
|
UserNationalCode = result.UserNationalCode,
|
||||||
|
PostalCode = result.PostalCode,
|
||||||
|
UserMobile = result.UserMobile
|
||||||
};
|
};
|
||||||
|
|
||||||
// VAT Info
|
// VAT Info
|
||||||
@@ -1054,6 +1058,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
|||||||
Domain.Enums.DeliveryStatus.Delivered => "تحویل داده شده",
|
Domain.Enums.DeliveryStatus.Delivered => "تحویل داده شده",
|
||||||
Domain.Enums.DeliveryStatus.Returned => "مرجوع شده",
|
Domain.Enums.DeliveryStatus.Returned => "مرجوع شده",
|
||||||
Domain.Enums.DeliveryStatus.Cancelled => "لغو شده",
|
Domain.Enums.DeliveryStatus.Cancelled => "لغو شده",
|
||||||
|
Domain.Enums.DeliveryStatus.ReadyForOfficePickup => "آماده تحویل در دفتر",
|
||||||
_ => status.ToString()
|
_ => status.ToString()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using CMSMicroservice.Protobuf.Protos.UserPackagePurchase;
|
|||||||
using CMSMicroservice.WebApi.Common.Services;
|
using CMSMicroservice.WebApi.Common.Services;
|
||||||
using CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetAllUserPackagePurchaseByFilter;
|
using CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetAllUserPackagePurchaseByFilter;
|
||||||
using CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetUserPackagePurchaseSummary;
|
using CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetUserPackagePurchaseSummary;
|
||||||
|
using CMSMicroservice.Application.UserPackagePurchaseCQ.Queries.GetCustomerPackagePurchaseRollup;
|
||||||
|
|
||||||
namespace CMSMicroservice.WebApi.Services;
|
namespace CMSMicroservice.WebApi.Services;
|
||||||
|
|
||||||
@@ -18,4 +19,8 @@ public class UserPackagePurchaseService : UserPackagePurchaseContract.UserPackag
|
|||||||
public override Task<GetUserPackagePurchaseSummaryResponse> GetUserPackagePurchaseSummary(
|
public override Task<GetUserPackagePurchaseSummaryResponse> GetUserPackagePurchaseSummary(
|
||||||
GetUserPackagePurchaseSummaryRequest request, ServerCallContext context) =>
|
GetUserPackagePurchaseSummaryRequest request, ServerCallContext context) =>
|
||||||
_dispatch.Handle<GetUserPackagePurchaseSummaryRequest, GetUserPackagePurchaseSummaryQuery, GetUserPackagePurchaseSummaryResponse>(request, context);
|
_dispatch.Handle<GetUserPackagePurchaseSummaryRequest, GetUserPackagePurchaseSummaryQuery, GetUserPackagePurchaseSummaryResponse>(request, context);
|
||||||
|
|
||||||
|
public override Task<GetCustomerPackagePurchaseRollupResponse> GetCustomerPackagePurchaseRollup(
|
||||||
|
GetCustomerPackagePurchaseRollupRequest request, ServerCallContext context) =>
|
||||||
|
_dispatch.Handle<GetCustomerPackagePurchaseRollupRequest, GetCustomerPackagePurchaseRollupQuery, GetCustomerPackagePurchaseRollupResponse>(request, context);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory;
|
|||||||
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals;
|
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals;
|
||||||
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings;
|
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings;
|
||||||
using CMSMicroservice.Application.Common.Interfaces;
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Application.Common;
|
||||||
using CMSMicroservice.Application.Common.Exceptions;
|
using CMSMicroservice.Application.Common.Exceptions;
|
||||||
using CMSMicroservice.Domain.Common;
|
using CMSMicroservice.Domain.Common;
|
||||||
using CMSMicroservice.Domain.Enums;
|
using CMSMicroservice.Domain.Enums;
|
||||||
@@ -175,7 +176,18 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
|
|||||||
|
|
||||||
// Update payout with withdrawal info
|
// Update payout with withdrawal info
|
||||||
payout.WithdrawalMethod = (Domain.Enums.WithdrawalMethod)request.WithdrawalMethod;
|
payout.WithdrawalMethod = (Domain.Enums.WithdrawalMethod)request.WithdrawalMethod;
|
||||||
payout.IbanNumber = request.IbanNumber;
|
if (payout.WithdrawalMethod == Domain.Enums.WithdrawalMethod.Cash)
|
||||||
|
{
|
||||||
|
var normalizedIban = IbanNormalizer.TryNormalize(request.IbanNumber);
|
||||||
|
if (normalizedIban is null)
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument,
|
||||||
|
"فرمت شماره شبا معتبر نیست. باید IR و ۲۴ رقم باشد."));
|
||||||
|
payout.IbanNumber = normalizedIban;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
payout.IbanNumber = null;
|
||||||
|
}
|
||||||
payout.Status = Domain.Enums.CommissionPayoutStatus.WithdrawRequested;
|
payout.Status = Domain.Enums.CommissionPayoutStatus.WithdrawRequested;
|
||||||
|
|
||||||
await _context.SaveChangesAsync(context.CancellationToken);
|
await _context.SaveChangesAsync(context.CancellationToken);
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
/*
|
||||||
|
One-time cleanup: ClubMemberships without a successful UserPackagePurchases row.
|
||||||
|
|
||||||
|
Goal after this script:
|
||||||
|
every non-deleted ClubMembership user has >= 1 successful UPP
|
||||||
|
(TransactionId IS NULL OR Transaction.PaymentStatus = 0 / Success)
|
||||||
|
|
||||||
|
Priority for missing rows:
|
||||||
|
1) ManualPayments Approved + successful Transaction -> reuse same TransactionId
|
||||||
|
PurchaseMethod = Manual (3)
|
||||||
|
2) else ClubMembershipCycles / membership dates -> create synthetic Success Transaction + UPP
|
||||||
|
PurchaseMethod priority:
|
||||||
|
Cycle.PurchaseMethod (if not None)
|
||||||
|
-> ClubMembership.PurchaseMethod (if not None)
|
||||||
|
-> Users.PackagePurchaseMethod (if not None)
|
||||||
|
-> Manual (3) as last resort
|
||||||
|
|
||||||
|
Safe to re-run (skips users who already have a successful UPP).
|
||||||
|
|
||||||
|
Run against CMS database once, then verify Orphans left = 0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SET XACT_ABORT ON;
|
||||||
|
|
||||||
|
BEGIN TRAN;
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- 1) From ManualPayments (reuse existing Success transaction)
|
||||||
|
------------------------------------------------------------
|
||||||
|
;WITH OrphanClubUsers AS (
|
||||||
|
SELECT m.UserId,
|
||||||
|
COALESCE(NULLIF(m.LastPackageId, 0), NULLIF(m.FirstPackageId, 0)) AS PackageIdHint
|
||||||
|
FROM CMS.ClubMemberships m
|
||||||
|
WHERE m.IsDeleted = 0
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM CMS.UserPackagePurchases upp
|
||||||
|
LEFT JOIN CMS.Transactions t ON t.Id = upp.TransactionId AND t.IsDeleted = 0
|
||||||
|
WHERE upp.UserId = m.UserId
|
||||||
|
AND upp.IsDeleted = 0
|
||||||
|
AND (upp.TransactionId IS NULL OR t.PaymentStatus = 0)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
BestManual AS (
|
||||||
|
SELECT mp.UserId,
|
||||||
|
mp.Id AS ManualPaymentId,
|
||||||
|
mp.Amount,
|
||||||
|
mp.TransactionId,
|
||||||
|
COALESCE(mp.ApprovedAt, mp.Created) AS PurchasedAt,
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY mp.UserId
|
||||||
|
ORDER BY COALESCE(mp.ApprovedAt, mp.Created) DESC, mp.Id DESC
|
||||||
|
) AS rn
|
||||||
|
FROM CMS.ManualPayments mp
|
||||||
|
INNER JOIN CMS.Transactions t ON t.Id = mp.TransactionId AND t.IsDeleted = 0 AND t.PaymentStatus = 0
|
||||||
|
WHERE mp.IsDeleted = 0
|
||||||
|
AND mp.Status = 1 -- Approved
|
||||||
|
AND mp.TransactionId IS NOT NULL
|
||||||
|
),
|
||||||
|
BasePkg AS (
|
||||||
|
SELECT TOP (1) Id AS PackageId, Price
|
||||||
|
FROM CMS.Packages
|
||||||
|
WHERE IsDeleted = 0 AND IsBasePackage = 1
|
||||||
|
ORDER BY Id
|
||||||
|
)
|
||||||
|
INSERT INTO CMS.UserPackagePurchases
|
||||||
|
(
|
||||||
|
UserId, PackageId, PurchaseMethod, PurchasedAt, Amount,
|
||||||
|
OrderId, TransactionId, Created, CreatedBy, LastModified, LastModifiedBy, IsDeleted
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
o.UserId,
|
||||||
|
COALESCE(o.PackageIdHint, b.PackageId),
|
||||||
|
3, -- Manual
|
||||||
|
bm.PurchasedAt,
|
||||||
|
bm.Amount,
|
||||||
|
NULL,
|
||||||
|
bm.TransactionId,
|
||||||
|
SYSUTCDATETIME(),
|
||||||
|
NULL,
|
||||||
|
SYSUTCDATETIME(),
|
||||||
|
NULL,
|
||||||
|
0
|
||||||
|
FROM OrphanClubUsers o
|
||||||
|
INNER JOIN BestManual bm ON bm.UserId = o.UserId AND bm.rn = 1
|
||||||
|
CROSS JOIN BasePkg b
|
||||||
|
WHERE COALESCE(o.PackageIdHint, b.PackageId) IS NOT NULL;
|
||||||
|
|
||||||
|
DECLARE @FromManual INT = @@ROWCOUNT;
|
||||||
|
PRINT CONCAT('Inserted from ManualPayments: ', @FromManual);
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- 2) Remaining orphans from Cycle / membership dates
|
||||||
|
------------------------------------------------------------
|
||||||
|
;WITH OrphanClubUsers AS (
|
||||||
|
SELECT
|
||||||
|
m.UserId,
|
||||||
|
m.PurchaseMethod,
|
||||||
|
u.PackagePurchaseMethod AS UserPurchaseMethod,
|
||||||
|
COALESCE(NULLIF(m.LastPackageId, 0), NULLIF(m.FirstPackageId, 0)) AS MembPackageId,
|
||||||
|
COALESCE(m.FirstActivationDate, m.LastActivationDate, m.ActivatedAt, m.Created) AS MembPurchasedAt
|
||||||
|
FROM CMS.ClubMemberships m
|
||||||
|
INNER JOIN CMS.Users u ON u.Id = m.UserId AND u.IsDeleted = 0
|
||||||
|
WHERE m.IsDeleted = 0
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM CMS.UserPackagePurchases upp
|
||||||
|
LEFT JOIN CMS.Transactions t ON t.Id = upp.TransactionId AND t.IsDeleted = 0
|
||||||
|
WHERE upp.UserId = m.UserId
|
||||||
|
AND upp.IsDeleted = 0
|
||||||
|
AND (upp.TransactionId IS NULL OR t.PaymentStatus = 0)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
BestCycle AS (
|
||||||
|
SELECT
|
||||||
|
c.UserId,
|
||||||
|
c.PackageId,
|
||||||
|
c.PackageAmount,
|
||||||
|
c.PurchaseMethod,
|
||||||
|
c.PackagePurchasedAt,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY c.UserId ORDER BY c.CycleNumber ASC, c.Id ASC) AS rn
|
||||||
|
FROM CMS.ClubMembershipCycles c
|
||||||
|
WHERE c.IsDeleted = 0
|
||||||
|
),
|
||||||
|
BasePkg AS (
|
||||||
|
SELECT TOP (1) Id AS PackageId, Price
|
||||||
|
FROM CMS.Packages
|
||||||
|
WHERE IsDeleted = 0 AND IsBasePackage = 1
|
||||||
|
ORDER BY Id
|
||||||
|
),
|
||||||
|
ToInsert AS (
|
||||||
|
SELECT
|
||||||
|
o.UserId,
|
||||||
|
COALESCE(NULLIF(bc.PackageId, 0), o.MembPackageId, b.PackageId) AS PackageId,
|
||||||
|
CASE
|
||||||
|
WHEN bc.PurchaseMethod IS NOT NULL AND bc.PurchaseMethod <> 0 THEN bc.PurchaseMethod
|
||||||
|
WHEN o.PurchaseMethod IS NOT NULL AND o.PurchaseMethod <> 0 THEN o.PurchaseMethod
|
||||||
|
WHEN o.UserPurchaseMethod IS NOT NULL AND o.UserPurchaseMethod <> 0 THEN o.UserPurchaseMethod
|
||||||
|
ELSE 3 -- Manual (last resort)
|
||||||
|
END AS PurchaseMethod,
|
||||||
|
COALESCE(bc.PackagePurchasedAt, o.MembPurchasedAt, SYSUTCDATETIME()) AS PurchasedAt,
|
||||||
|
CASE
|
||||||
|
WHEN bc.PackageAmount IS NOT NULL AND bc.PackageAmount > 0 THEN bc.PackageAmount
|
||||||
|
ELSE COALESCE(p.Price, b.Price, 0)
|
||||||
|
END AS Amount
|
||||||
|
FROM OrphanClubUsers o
|
||||||
|
LEFT JOIN BestCycle bc ON bc.UserId = o.UserId AND bc.rn = 1
|
||||||
|
CROSS JOIN BasePkg b
|
||||||
|
LEFT JOIN CMS.Packages p ON p.Id = COALESCE(NULLIF(bc.PackageId, 0), o.MembPackageId, b.PackageId) AND p.IsDeleted = 0
|
||||||
|
)
|
||||||
|
SELECT *
|
||||||
|
INTO #BackfillCandidates
|
||||||
|
FROM ToInsert
|
||||||
|
WHERE PackageId IS NOT NULL AND Amount > 0;
|
||||||
|
|
||||||
|
DECLARE @Remaining INT = (SELECT COUNT(*) FROM #BackfillCandidates);
|
||||||
|
PRINT CONCAT('Remaining orphans to synthetic backfill: ', @Remaining);
|
||||||
|
|
||||||
|
DECLARE @UserId BIGINT, @PackageId BIGINT, @PurchaseMethod INT, @PurchasedAt DATETIME2, @Amount BIGINT;
|
||||||
|
DECLARE @TxId BIGINT;
|
||||||
|
|
||||||
|
DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
|
||||||
|
SELECT UserId, PackageId, PurchaseMethod, PurchasedAt, Amount
|
||||||
|
FROM #BackfillCandidates;
|
||||||
|
|
||||||
|
OPEN cur;
|
||||||
|
FETCH NEXT FROM cur INTO @UserId, @PackageId, @PurchaseMethod, @PurchasedAt, @Amount;
|
||||||
|
|
||||||
|
DECLARE @Synthetic INT = 0;
|
||||||
|
|
||||||
|
WHILE @@FETCH_STATUS = 0
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO CMS.Transactions
|
||||||
|
(
|
||||||
|
Amount, Description, PaymentStatus, PaymentDate, RefId, Type,
|
||||||
|
Created, CreatedBy, LastModified, LastModifiedBy, IsDeleted
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@Amount,
|
||||||
|
CONCAT(N'بکفیل عضویت باشگاه — UserId ', @UserId),
|
||||||
|
0, -- Success
|
||||||
|
@PurchasedAt,
|
||||||
|
CONCAT('UPP-BACKFILL-', @UserId, '-', @PackageId),
|
||||||
|
2, -- DepositExternal1
|
||||||
|
SYSUTCDATETIME(), NULL, SYSUTCDATETIME(), NULL, 0
|
||||||
|
);
|
||||||
|
|
||||||
|
SET @TxId = SCOPE_IDENTITY();
|
||||||
|
|
||||||
|
INSERT INTO CMS.UserPackagePurchases
|
||||||
|
(
|
||||||
|
UserId, PackageId, PurchaseMethod, PurchasedAt, Amount,
|
||||||
|
OrderId, TransactionId, Created, CreatedBy, LastModified, LastModifiedBy, IsDeleted
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@UserId, @PackageId, @PurchaseMethod, @PurchasedAt, @Amount,
|
||||||
|
NULL, @TxId, SYSUTCDATETIME(), NULL, SYSUTCDATETIME(), NULL, 0
|
||||||
|
);
|
||||||
|
|
||||||
|
SET @Synthetic += 1;
|
||||||
|
FETCH NEXT FROM cur INTO @UserId, @PackageId, @PurchaseMethod, @PurchasedAt, @Amount;
|
||||||
|
END
|
||||||
|
|
||||||
|
CLOSE cur;
|
||||||
|
DEALLOCATE cur;
|
||||||
|
|
||||||
|
PRINT CONCAT('Inserted synthetic UPP rows: ', @Synthetic);
|
||||||
|
|
||||||
|
DROP TABLE #BackfillCandidates;
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- Verify invariant
|
||||||
|
------------------------------------------------------------
|
||||||
|
DECLARE @ClubCount INT =
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM CMS.ClubMemberships WHERE IsDeleted = 0
|
||||||
|
);
|
||||||
|
|
||||||
|
DECLARE @OrphansLeft INT =
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM CMS.ClubMemberships m
|
||||||
|
WHERE m.IsDeleted = 0
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM CMS.UserPackagePurchases upp
|
||||||
|
LEFT JOIN CMS.Transactions t ON t.Id = upp.TransactionId AND t.IsDeleted = 0
|
||||||
|
WHERE upp.UserId = m.UserId
|
||||||
|
AND upp.IsDeleted = 0
|
||||||
|
AND (upp.TransactionId IS NULL OR t.PaymentStatus = 0)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DECLARE @SuccessfulBuyers INT =
|
||||||
|
(
|
||||||
|
SELECT COUNT(DISTINCT upp.UserId)
|
||||||
|
FROM CMS.UserPackagePurchases upp
|
||||||
|
LEFT JOIN CMS.Transactions t ON t.Id = upp.TransactionId AND t.IsDeleted = 0
|
||||||
|
WHERE upp.IsDeleted = 0
|
||||||
|
AND (upp.TransactionId IS NULL OR t.PaymentStatus = 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
PRINT CONCAT('ClubMemberships: ', @ClubCount);
|
||||||
|
PRINT CONCAT('Distinct successful UPP users: ', @SuccessfulBuyers);
|
||||||
|
PRINT CONCAT('Orphans left (must be 0): ', @OrphansLeft);
|
||||||
|
|
||||||
|
IF @OrphansLeft > 0
|
||||||
|
BEGIN
|
||||||
|
ROLLBACK;
|
||||||
|
THROW 50001, 'Backfill incomplete — orphans remain; transaction rolled back.', 1;
|
||||||
|
END
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
PRINT 'Backfill committed successfully.';
|
||||||
@@ -0,0 +1,465 @@
|
|||||||
|
/*
|
||||||
|
Staging fix: complete post-Daya processing for Excel rows with status «فعال شده»
|
||||||
|
that are NOT_PROCESSED or PARTIAL.
|
||||||
|
|
||||||
|
Classification:
|
||||||
|
DONE — credit + processed + Daya UPP
|
||||||
|
PARTIAL — HasReceivedDayaCredit OR IsProcessed OR existing «اعتبار دایا» Tx in wallet
|
||||||
|
NOT_PROCESSED — none of the above (will create Tx + charge)
|
||||||
|
|
||||||
|
If ExistingDayaTxId found (wallet RefrenceId → Tx Description LIKE '%اعتبار دایا%'
|
||||||
|
OR Tx.RefId = ContractNumber): REUSE that Tx — do NOT create Transaction / do NOT charge wallet.
|
||||||
|
|
||||||
|
Special UserId mapping (shared NationalCode):
|
||||||
|
C4_D8815963 / 2490371436 → UserId 50
|
||||||
|
C4_G8945289 / 2490371436 → UserId 51
|
||||||
|
|
||||||
|
@Commit = 0 → dry-run (ROLLBACK)
|
||||||
|
@Commit = 1 → apply
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SET XACT_ABORT ON;
|
||||||
|
|
||||||
|
DECLARE @Commit BIT = 0; -- << set 1 to apply
|
||||||
|
|
||||||
|
DECLARE @Now DATETIME2 = SYSUTCDATETIME();
|
||||||
|
DECLARE @PackageId BIGINT;
|
||||||
|
DECLARE @PackagePrice BIGINT;
|
||||||
|
DECLARE @DiscountMultiplier DECIMAL(18,4);
|
||||||
|
DECLARE @DiscountAmount BIGINT;
|
||||||
|
|
||||||
|
SELECT TOP (1)
|
||||||
|
@PackageId = Id,
|
||||||
|
@PackagePrice = Price,
|
||||||
|
@DiscountMultiplier = DiscountMultiplier
|
||||||
|
FROM CMS.Packages
|
||||||
|
WHERE IsDeleted = 0 AND IsBasePackage = 1
|
||||||
|
ORDER BY Id;
|
||||||
|
|
||||||
|
IF @PackageId IS NULL
|
||||||
|
THROW 50001, 'Base package not found.', 1;
|
||||||
|
|
||||||
|
SET @DiscountAmount = CAST(@PackagePrice * @DiscountMultiplier AS BIGINT);
|
||||||
|
|
||||||
|
PRINT CONCAT('PackageId=', @PackageId, ' Price=', @PackagePrice, ' Discount=', @DiscountAmount);
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- Targets: Excel «فعال شده»
|
||||||
|
------------------------------------------------------------
|
||||||
|
DECLARE @Targets TABLE (
|
||||||
|
ContractNumber NVARCHAR(50) NOT NULL,
|
||||||
|
NationalCode NVARCHAR(20) NOT NULL,
|
||||||
|
UserIdHint BIGINT NULL -- explicit override when NC is shared
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO @Targets (ContractNumber, NationalCode, UserIdHint) VALUES
|
||||||
|
(N'C4_H10706299', N'4060667961', NULL),
|
||||||
|
(N'C4_P9320383', N'2572729636', NULL),
|
||||||
|
(N'C4_Q9465813', N'2480017796', NULL),
|
||||||
|
(N'C4_B9348378', N'0452314488', NULL),
|
||||||
|
(N'C4_X9630520', N'0680164286', NULL),
|
||||||
|
(N'C4_B9813553', N'0055680364', NULL), -- may match DayaLoanContracts.NationalCode if Users.NC differs
|
||||||
|
(N'C4_M9994735', N'0793851572', NULL),
|
||||||
|
(N'C4_S9301452', N'0082242925', NULL),
|
||||||
|
(N'C4_T9961063', N'0312342993', NULL),
|
||||||
|
(N'C4_L9524515', N'0024757489', NULL),
|
||||||
|
(N'C4_Q9947535', N'2480259668', NULL),
|
||||||
|
(N'C4_T9892089', N'2451477016', NULL),
|
||||||
|
(N'C4_W8785358', N'0322922720', NULL),
|
||||||
|
(N'C4_D8815963', N'2490371436', 50), -- earlier activation → user 50
|
||||||
|
(N'C4_G8945289', N'2490371436', 51), -- later activation → user 51
|
||||||
|
(N'C4_J8388494', N'2002637903', NULL);
|
||||||
|
|
||||||
|
DECLARE @Work TABLE (
|
||||||
|
ContractNumber NVARCHAR(50),
|
||||||
|
NationalCode NVARCHAR(20),
|
||||||
|
UserId BIGINT,
|
||||||
|
DayaContractId BIGINT NULL,
|
||||||
|
HasCredit BIT,
|
||||||
|
IsProcessed BIT,
|
||||||
|
HasDayaUpp BIT,
|
||||||
|
ExistingDayaTxId BIGINT NULL, -- Tx از قبل با شرح «اعتبار دایا» / RefId قرارداد (شارژ شده)
|
||||||
|
Verdict NVARCHAR(20)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO @Work (ContractNumber, NationalCode, UserId, DayaContractId, HasCredit, IsProcessed, HasDayaUpp, ExistingDayaTxId, Verdict)
|
||||||
|
SELECT
|
||||||
|
t.ContractNumber,
|
||||||
|
t.NationalCode,
|
||||||
|
u.Id,
|
||||||
|
d.Id,
|
||||||
|
CAST(ISNULL(u.HasReceivedDayaCredit, 0) AS BIT),
|
||||||
|
CAST(ISNULL(d.IsProcessed, 0) AS BIT),
|
||||||
|
CAST(CASE WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM CMS.UserPackagePurchases upp
|
||||||
|
LEFT JOIN CMS.Transactions tx ON tx.Id = upp.TransactionId AND tx.IsDeleted = 0
|
||||||
|
WHERE upp.UserId = u.Id
|
||||||
|
AND upp.IsDeleted = 0
|
||||||
|
AND upp.PurchaseMethod = 1
|
||||||
|
AND (upp.TransactionId IS NULL OR tx.PaymentStatus = 0)
|
||||||
|
) THEN 1 ELSE 0 END AS BIT),
|
||||||
|
etx.TxId,
|
||||||
|
CASE
|
||||||
|
WHEN u.Id IS NULL THEN N'NO_USER'
|
||||||
|
WHEN ISNULL(u.HasReceivedDayaCredit, 0) = 1
|
||||||
|
AND ISNULL(d.IsProcessed, 0) = 1
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM CMS.UserPackagePurchases upp
|
||||||
|
LEFT JOIN CMS.Transactions tx ON tx.Id = upp.TransactionId AND tx.IsDeleted = 0
|
||||||
|
WHERE upp.UserId = u.Id
|
||||||
|
AND upp.IsDeleted = 0
|
||||||
|
AND upp.PurchaseMethod = 1
|
||||||
|
AND (upp.TransactionId IS NULL OR tx.PaymentStatus = 0)
|
||||||
|
) THEN N'DONE'
|
||||||
|
-- قبلاً شارژ دایا در کیف/تراکنش هست ولی فلگ/UPP/قرارداد کامل نیست
|
||||||
|
WHEN etx.TxId IS NOT NULL THEN N'PARTIAL'
|
||||||
|
WHEN ISNULL(u.HasReceivedDayaCredit, 0) = 1
|
||||||
|
OR ISNULL(d.IsProcessed, 0) = 1 THEN N'PARTIAL'
|
||||||
|
ELSE N'NOT_PROCESSED'
|
||||||
|
END
|
||||||
|
FROM @Targets t
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT TOP (1) u0.*
|
||||||
|
FROM CMS.Users u0
|
||||||
|
WHERE u0.IsDeleted = 0
|
||||||
|
AND (
|
||||||
|
(t.UserIdHint IS NOT NULL AND u0.Id = t.UserIdHint)
|
||||||
|
OR (t.UserIdHint IS NULL AND u0.NationalCode = t.NationalCode)
|
||||||
|
OR (t.UserIdHint IS NULL AND EXISTS (
|
||||||
|
SELECT 1 FROM CMS.DayaLoanContracts dx
|
||||||
|
WHERE dx.UserId = u0.Id
|
||||||
|
AND dx.NationalCode = t.NationalCode
|
||||||
|
AND dx.IsDeleted = 0
|
||||||
|
))
|
||||||
|
)
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN t.UserIdHint IS NOT NULL AND u0.Id = t.UserIdHint THEN 0 ELSE 1 END,
|
||||||
|
CASE WHEN u0.NationalCode = t.NationalCode THEN 0 ELSE 1 END,
|
||||||
|
u0.Id
|
||||||
|
) u
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT TOP (1) d0.*
|
||||||
|
FROM CMS.DayaLoanContracts d0
|
||||||
|
WHERE d0.UserId = u.Id
|
||||||
|
AND (
|
||||||
|
d0.ContractNumber = t.ContractNumber
|
||||||
|
OR d0.NationalCode = t.NationalCode
|
||||||
|
OR d0.IsDeleted IN (0, 1)
|
||||||
|
)
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN d0.ContractNumber = t.ContractNumber THEN 0 ELSE 1 END,
|
||||||
|
CASE WHEN d0.IsDeleted = 0 THEN 0 ELSE 1 END,
|
||||||
|
d0.Id DESC
|
||||||
|
) d
|
||||||
|
OUTER APPLY (
|
||||||
|
-- تراکنش دایا که از قبل شارژ شده (مرجع کیف پول یا RefId قرارداد)
|
||||||
|
SELECT TOP (1) t0.Id AS TxId
|
||||||
|
FROM CMS.Transactions t0
|
||||||
|
WHERE t0.IsDeleted = 0
|
||||||
|
AND t0.PaymentStatus = 0
|
||||||
|
AND (
|
||||||
|
t0.RefId = t.ContractNumber
|
||||||
|
OR (
|
||||||
|
t0.Description LIKE N'%اعتبار دایا%'
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM CMS.UserWallets w
|
||||||
|
INNER JOIN CMS.UserWalletHistories h ON h.WalletId = w.Id
|
||||||
|
WHERE w.UserId = u.Id
|
||||||
|
AND h.RefrenceId = t0.Id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN t0.RefId = t.ContractNumber THEN 0 ELSE 1 END,
|
||||||
|
t0.Id DESC
|
||||||
|
) etx;
|
||||||
|
|
||||||
|
PRINT '=== Before ===';
|
||||||
|
SELECT Verdict, COUNT(*) AS Cnt FROM @Work GROUP BY Verdict;
|
||||||
|
SELECT ContractNumber, NationalCode, UserId, DayaContractId, HasCredit, IsProcessed, HasDayaUpp,
|
||||||
|
ExistingDayaTxId, Verdict
|
||||||
|
FROM @Work
|
||||||
|
ORDER BY Verdict, ContractNumber;
|
||||||
|
|
||||||
|
BEGIN TRAN;
|
||||||
|
|
||||||
|
DECLARE @ContractNumber NVARCHAR(50), @NationalCode NVARCHAR(20), @UserId BIGINT;
|
||||||
|
DECLARE @DayaContractId BIGINT, @HasCredit BIT, @IsProcessed BIT, @HasDayaUpp BIT, @Verdict NVARCHAR(20);
|
||||||
|
DECLARE @ExistingDayaTxId BIGINT;
|
||||||
|
DECLARE @TxId BIGINT, @WalletId BIGINT, @Action NVARCHAR(200);
|
||||||
|
|
||||||
|
DECLARE @Log TABLE (
|
||||||
|
ContractNumber NVARCHAR(50),
|
||||||
|
UserId BIGINT,
|
||||||
|
VerdictBefore NVARCHAR(20),
|
||||||
|
ActionTaken NVARCHAR(200),
|
||||||
|
TxId BIGINT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
|
||||||
|
SELECT ContractNumber, NationalCode, UserId, DayaContractId, HasCredit, IsProcessed, HasDayaUpp, ExistingDayaTxId, Verdict
|
||||||
|
FROM @Work
|
||||||
|
WHERE Verdict IN (N'NOT_PROCESSED', N'PARTIAL');
|
||||||
|
|
||||||
|
OPEN cur;
|
||||||
|
FETCH NEXT FROM cur INTO @ContractNumber, @NationalCode, @UserId, @DayaContractId, @HasCredit, @IsProcessed, @HasDayaUpp, @ExistingDayaTxId, @Verdict;
|
||||||
|
|
||||||
|
WHILE @@FETCH_STATUS = 0
|
||||||
|
BEGIN
|
||||||
|
SET @TxId = NULL;
|
||||||
|
SET @Action = N'';
|
||||||
|
|
||||||
|
IF @UserId IS NULL
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO @Log VALUES (@ContractNumber, NULL, @Verdict, N'SKIP NO_USER', NULL);
|
||||||
|
GOTO NextRow;
|
||||||
|
END
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- Ensure / revive DayaLoanContract
|
||||||
|
------------------------------------------------------------
|
||||||
|
IF @DayaContractId IS NULL
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO CMS.DayaLoanContracts
|
||||||
|
(
|
||||||
|
UserId, NationalCode, ContractNumber, Status, IsProcessed,
|
||||||
|
LastCheckDate, ProcessedDate, TransactionId,
|
||||||
|
Created, CreatedBy, LastModified, LastModifiedBy, IsDeleted
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@UserId, @NationalCode, @ContractNumber, 2, 0,
|
||||||
|
@Now, NULL, NULL,
|
||||||
|
@Now, NULL, @Now, NULL, 0
|
||||||
|
);
|
||||||
|
SET @DayaContractId = SCOPE_IDENTITY();
|
||||||
|
SET @Action = @Action + N'INSERT_CONTRACT;';
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE CMS.DayaLoanContracts
|
||||||
|
SET
|
||||||
|
IsDeleted = 0,
|
||||||
|
NationalCode = @NationalCode,
|
||||||
|
ContractNumber = @ContractNumber,
|
||||||
|
Status = 2, -- Received («فعال شده»)
|
||||||
|
LastCheckDate = @Now,
|
||||||
|
LastModified = @Now
|
||||||
|
WHERE Id = @DayaContractId;
|
||||||
|
SET @Action = @Action + N'UPDATE_CONTRACT;';
|
||||||
|
END
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- اگر تراکنش/شارژ «اعتبار دایا» از قبل هست → همان Tx را بگیر؛ شارژ نکن
|
||||||
|
------------------------------------------------------------
|
||||||
|
IF @ExistingDayaTxId IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
SET @TxId = @ExistingDayaTxId;
|
||||||
|
SET @Action = @Action + CONCAT(N'REUSE_EXISTING_DAYA_TX=', @TxId, N';');
|
||||||
|
|
||||||
|
UPDATE CMS.Users
|
||||||
|
SET
|
||||||
|
PackagePurchaseMethod = 1,
|
||||||
|
HasReceivedDayaCredit = 1,
|
||||||
|
DayaCreditReceivedAt = COALESCE(DayaCreditReceivedAt, @Now),
|
||||||
|
LastModified = @Now
|
||||||
|
WHERE Id = @UserId;
|
||||||
|
|
||||||
|
SET @HasCredit = 1;
|
||||||
|
SET @Action = @Action + N'SET_USER_FLAGS(no charge);';
|
||||||
|
END
|
||||||
|
ELSE IF @HasCredit = 0
|
||||||
|
BEGIN
|
||||||
|
-- Full wallet charge only if never credited and no existing Daya tx
|
||||||
|
INSERT INTO CMS.Transactions
|
||||||
|
(
|
||||||
|
Amount, Description, PaymentStatus, PaymentDate, RefId, Type,
|
||||||
|
Created, CreatedBy, LastModified, LastModifiedBy, IsDeleted
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@PackagePrice,
|
||||||
|
CONCAT(N'دریافت اعتبار دایا - قرارداد ', @ContractNumber),
|
||||||
|
0, -- Success
|
||||||
|
@Now,
|
||||||
|
@ContractNumber,
|
||||||
|
2, -- DepositExternal1
|
||||||
|
@Now, NULL, @Now, NULL, 0
|
||||||
|
);
|
||||||
|
SET @TxId = SCOPE_IDENTITY();
|
||||||
|
|
||||||
|
SELECT @WalletId = Id FROM CMS.UserWallets WHERE UserId = @UserId;
|
||||||
|
IF @WalletId IS NULL
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO CMS.UserWallets (UserId, Balance, NetworkBalance, DiscountBalance, WalletMode, Created, LastModified, IsDeleted)
|
||||||
|
VALUES (@UserId, 0, 0, 0, 0, @Now, @Now, 0);
|
||||||
|
SET @WalletId = SCOPE_IDENTITY();
|
||||||
|
END
|
||||||
|
|
||||||
|
UPDATE CMS.UserWallets
|
||||||
|
SET
|
||||||
|
Balance = Balance + @PackagePrice,
|
||||||
|
DiscountBalance = DiscountBalance + @DiscountAmount,
|
||||||
|
LastModified = @Now
|
||||||
|
WHERE Id = @WalletId;
|
||||||
|
|
||||||
|
INSERT INTO CMS.UserWalletHistories
|
||||||
|
(
|
||||||
|
WalletId, CurrentBalance, ChangeValue, CurrentNetworkBalance, ChangeNerworkValue,
|
||||||
|
CurrentDiscountBalance, ChangeDiscountValue, IsIncrease, RefrenceId, PackageId,
|
||||||
|
Created, LastModified, IsDeleted
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@WalletId, 0, @PackagePrice, 0, 0,
|
||||||
|
0, 0, 1, @TxId, @PackageId,
|
||||||
|
@Now, @Now, 0
|
||||||
|
),
|
||||||
|
(
|
||||||
|
@WalletId, 0, 0, 0, 0,
|
||||||
|
0, @DiscountAmount, 1, @TxId, @PackageId,
|
||||||
|
@Now, @Now, 0
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE CMS.Users
|
||||||
|
SET
|
||||||
|
HasReceivedDayaCredit = 1,
|
||||||
|
DayaCreditReceivedAt = @Now,
|
||||||
|
PackagePurchaseMethod = 1, -- DayaLoan
|
||||||
|
LastModified = @Now
|
||||||
|
WHERE Id = @UserId;
|
||||||
|
|
||||||
|
SET @HasCredit = 1;
|
||||||
|
SET @Action = @Action + N'CHARGE_WALLET+USER_FLAGS;';
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
-- HasReceivedDayaCredit=1 ولی ExistingDayaTx پیدا نشد
|
||||||
|
UPDATE CMS.Users
|
||||||
|
SET
|
||||||
|
PackagePurchaseMethod = 1,
|
||||||
|
HasReceivedDayaCredit = 1,
|
||||||
|
DayaCreditReceivedAt = COALESCE(DayaCreditReceivedAt, @Now),
|
||||||
|
LastModified = @Now
|
||||||
|
WHERE Id = @UserId;
|
||||||
|
SET @Action = @Action + N'SKIP_CHARGE(HasReceivedDayaCredit);';
|
||||||
|
END
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- Ensure successful Daya UPP (no duplicate if already exists)
|
||||||
|
------------------------------------------------------------
|
||||||
|
IF @HasDayaUpp = 0
|
||||||
|
BEGIN
|
||||||
|
IF @TxId IS NULL
|
||||||
|
BEGIN
|
||||||
|
-- فقط لجر؛ بدون شارژ کیف (نباید برای کسی که Tx دایا دارد به اینجا برسد)
|
||||||
|
INSERT INTO CMS.Transactions
|
||||||
|
(
|
||||||
|
Amount, Description, PaymentStatus, PaymentDate, RefId, Type,
|
||||||
|
Created, CreatedBy, LastModified, LastModifiedBy, IsDeleted
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@PackagePrice,
|
||||||
|
CONCAT(N'لجر UPP دایا (بدون شارژ مجدد) - قرارداد ', @ContractNumber),
|
||||||
|
0,
|
||||||
|
@Now,
|
||||||
|
CONCAT(N'UPP-DAYA-FIX-', @ContractNumber),
|
||||||
|
2,
|
||||||
|
@Now, NULL, @Now, NULL, 0
|
||||||
|
);
|
||||||
|
SET @TxId = SCOPE_IDENTITY();
|
||||||
|
SET @Action = @Action + N'LEDGER_TX;';
|
||||||
|
END
|
||||||
|
|
||||||
|
INSERT INTO CMS.UserPackagePurchases
|
||||||
|
(
|
||||||
|
UserId, PackageId, PurchaseMethod, PurchasedAt, Amount,
|
||||||
|
OrderId, TransactionId, Created, CreatedBy, LastModified, LastModifiedBy, IsDeleted
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@UserId, @PackageId, 1, @Now, @PackagePrice,
|
||||||
|
NULL, @TxId, @Now, NULL, @Now, NULL, 0
|
||||||
|
);
|
||||||
|
SET @Action = @Action + N'INSERT_UPP;';
|
||||||
|
END
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- Mark contract processed
|
||||||
|
------------------------------------------------------------
|
||||||
|
UPDATE CMS.DayaLoanContracts
|
||||||
|
SET
|
||||||
|
IsProcessed = 1,
|
||||||
|
ProcessedDate = COALESCE(ProcessedDate, @Now),
|
||||||
|
TransactionId = COALESCE(TransactionId, @TxId),
|
||||||
|
Status = 2,
|
||||||
|
ContractNumber = @ContractNumber,
|
||||||
|
IsDeleted = 0,
|
||||||
|
LastModified = @Now
|
||||||
|
WHERE Id = @DayaContractId;
|
||||||
|
|
||||||
|
SET @Action = @Action + N'MARK_PROCESSED;';
|
||||||
|
|
||||||
|
INSERT INTO @Log VALUES (@ContractNumber, @UserId, @Verdict, @Action, @TxId);
|
||||||
|
|
||||||
|
NextRow:
|
||||||
|
FETCH NEXT FROM cur INTO @ContractNumber, @NationalCode, @UserId, @DayaContractId, @HasCredit, @IsProcessed, @HasDayaUpp, @ExistingDayaTxId, @Verdict;
|
||||||
|
END
|
||||||
|
|
||||||
|
CLOSE cur;
|
||||||
|
DEALLOCATE cur;
|
||||||
|
|
||||||
|
PRINT '=== Actions ===';
|
||||||
|
SELECT * FROM @Log ORDER BY VerdictBefore, ContractNumber;
|
||||||
|
|
||||||
|
-- Re-score after changes
|
||||||
|
PRINT '=== After (same targets) ===';
|
||||||
|
SELECT
|
||||||
|
t.ContractNumber,
|
||||||
|
t.NationalCode,
|
||||||
|
u.Id AS UserId,
|
||||||
|
u.HasReceivedDayaCredit,
|
||||||
|
d.IsProcessed,
|
||||||
|
d.ContractNumber AS DbContractNumber,
|
||||||
|
d.Status,
|
||||||
|
CASE WHEN EXISTS (
|
||||||
|
SELECT 1 FROM CMS.UserPackagePurchases upp
|
||||||
|
LEFT JOIN CMS.Transactions tx ON tx.Id = upp.TransactionId AND tx.IsDeleted = 0
|
||||||
|
WHERE upp.UserId = u.Id AND upp.IsDeleted = 0 AND upp.PurchaseMethod = 1
|
||||||
|
AND (upp.TransactionId IS NULL OR tx.PaymentStatus = 0)
|
||||||
|
) THEN 1 ELSE 0 END AS HasDayaUpp,
|
||||||
|
CASE
|
||||||
|
WHEN u.Id IS NULL THEN N'NO_USER'
|
||||||
|
WHEN u.HasReceivedDayaCredit = 1 AND d.IsProcessed = 1
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1 FROM CMS.UserPackagePurchases upp
|
||||||
|
LEFT JOIN CMS.Transactions tx ON tx.Id = upp.TransactionId AND tx.IsDeleted = 0
|
||||||
|
WHERE upp.UserId = u.Id AND upp.IsDeleted = 0 AND upp.PurchaseMethod = 1
|
||||||
|
AND (upp.TransactionId IS NULL OR tx.PaymentStatus = 0)
|
||||||
|
) THEN N'DONE'
|
||||||
|
WHEN u.HasReceivedDayaCredit = 1 OR d.IsProcessed = 1 THEN N'PARTIAL'
|
||||||
|
ELSE N'NOT_PROCESSED'
|
||||||
|
END AS Verdict
|
||||||
|
FROM @Targets t
|
||||||
|
LEFT JOIN CMS.Users u ON u.IsDeleted = 0 AND (
|
||||||
|
(t.UserIdHint IS NOT NULL AND u.Id = t.UserIdHint) OR (t.UserIdHint IS NULL AND u.NationalCode = t.NationalCode)
|
||||||
|
)
|
||||||
|
LEFT JOIN CMS.DayaLoanContracts d ON d.IsDeleted = 0 AND d.UserId = u.Id
|
||||||
|
AND (d.ContractNumber = t.ContractNumber OR d.NationalCode = t.NationalCode)
|
||||||
|
ORDER BY Verdict, t.ContractNumber;
|
||||||
|
|
||||||
|
IF @Commit = 1
|
||||||
|
BEGIN
|
||||||
|
COMMIT;
|
||||||
|
PRINT 'COMMITTED.';
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
ROLLBACK;
|
||||||
|
PRINT 'Dry-run ROLLBACK. Set @Commit = 1 to apply.';
|
||||||
|
END
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
Correct users wrongly marked as DayaLoan — should be Manual.
|
||||||
|
|
||||||
|
Affects:
|
||||||
|
- CMS.Users.PackagePurchaseMethod (1 → 3)
|
||||||
|
- CMS.ClubMemberships.PurchaseMethod (1 or 0 → 3)
|
||||||
|
- CMS.ClubMembershipCycles.PurchaseMethod (1 or 0 → 3)
|
||||||
|
- CMS.UserPackagePurchases.PurchaseMethod (1 → 3)
|
||||||
|
- CMS.Users.HasReceivedDayaCredit / DayaCreditReceivedAt clear
|
||||||
|
- CMS.DayaLoanContracts soft-delete stub rows
|
||||||
|
(IsProcessed=0, no TransactionId, no ProcessedDate)
|
||||||
|
for these UserIds only
|
||||||
|
|
||||||
|
PackagePurchaseMethod: None=0, DayaLoan=1, DirectPurchase=2, Manual=3
|
||||||
|
|
||||||
|
NOTE: UserId=43 shares NationalCode 5289780335 with UserId=42 (real Excel Daya).
|
||||||
|
This script only touches UserId=43's own rows — not UserId=42's contract.
|
||||||
|
|
||||||
|
Defaults to ROLLBACK. Uncomment COMMIT after verifying PRINT counts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SET XACT_ABORT ON;
|
||||||
|
|
||||||
|
BEGIN TRAN;
|
||||||
|
|
||||||
|
DECLARE @FixUsers TABLE (UserId BIGINT PRIMARY KEY);
|
||||||
|
INSERT INTO @FixUsers (UserId) VALUES
|
||||||
|
(2),(7),(8),(9),(10),(11),(12),(13),(40),(41),(43),(45),(47),
|
||||||
|
(52),(58),(69),(71),(72),(74),(75),(87),(88),(89),(90),(91),(93),
|
||||||
|
(96),(99),(103),(105),(106),(111),(113),(115),(116),(119),(121),
|
||||||
|
(125),(130),(142),(162),(169),(170),(171),(172),(175),(176);
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- 1) Users
|
||||||
|
------------------------------------------------------------
|
||||||
|
UPDATE u
|
||||||
|
SET
|
||||||
|
u.PackagePurchaseMethod = 3, -- Manual
|
||||||
|
u.HasReceivedDayaCredit = 0,
|
||||||
|
u.DayaCreditReceivedAt = NULL,
|
||||||
|
u.LastModified = SYSUTCDATETIME()
|
||||||
|
FROM CMS.Users u
|
||||||
|
INNER JOIN @FixUsers f ON f.UserId = u.Id
|
||||||
|
WHERE u.IsDeleted = 0
|
||||||
|
AND (
|
||||||
|
u.PackagePurchaseMethod = 1 -- DayaLoan
|
||||||
|
OR u.HasReceivedDayaCredit = 1
|
||||||
|
OR u.DayaCreditReceivedAt IS NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
PRINT CONCAT('Users updated: ', @@ROWCOUNT);
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- 2) ClubMemberships
|
||||||
|
------------------------------------------------------------
|
||||||
|
UPDATE m
|
||||||
|
SET
|
||||||
|
m.PurchaseMethod = 3, -- Manual
|
||||||
|
m.LastModified = SYSUTCDATETIME()
|
||||||
|
FROM CMS.ClubMemberships m
|
||||||
|
INNER JOIN @FixUsers f ON f.UserId = m.UserId
|
||||||
|
WHERE m.IsDeleted = 0
|
||||||
|
AND m.PurchaseMethod IN (0, 1); -- None or DayaLoan
|
||||||
|
|
||||||
|
PRINT CONCAT('ClubMemberships updated: ', @@ROWCOUNT);
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- 3) ClubMembershipCycles
|
||||||
|
------------------------------------------------------------
|
||||||
|
UPDATE c
|
||||||
|
SET
|
||||||
|
c.PurchaseMethod = 3, -- Manual
|
||||||
|
c.LastModified = SYSUTCDATETIME()
|
||||||
|
FROM CMS.ClubMembershipCycles c
|
||||||
|
INNER JOIN @FixUsers f ON f.UserId = c.UserId
|
||||||
|
WHERE c.IsDeleted = 0
|
||||||
|
AND c.PurchaseMethod IN (0, 1);
|
||||||
|
|
||||||
|
PRINT CONCAT('ClubMembershipCycles updated: ', @@ROWCOUNT);
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- 4) UserPackagePurchases (incl. backfill rows)
|
||||||
|
------------------------------------------------------------
|
||||||
|
UPDATE upp
|
||||||
|
SET
|
||||||
|
upp.PurchaseMethod = 3, -- Manual
|
||||||
|
upp.LastModified = SYSUTCDATETIME()
|
||||||
|
FROM CMS.UserPackagePurchases upp
|
||||||
|
INNER JOIN @FixUsers f ON f.UserId = upp.UserId
|
||||||
|
WHERE upp.IsDeleted = 0
|
||||||
|
AND upp.PurchaseMethod = 1; -- only Daya → Manual (keep Direct=2 if any)
|
||||||
|
|
||||||
|
PRINT CONCAT('UserPackagePurchases updated: ', @@ROWCOUNT);
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- 5) Soft-delete stub DayaLoanContracts (never processed)
|
||||||
|
------------------------------------------------------------
|
||||||
|
UPDATE d
|
||||||
|
SET
|
||||||
|
d.IsDeleted = 1,
|
||||||
|
d.LastModified = SYSUTCDATETIME()
|
||||||
|
FROM CMS.DayaLoanContracts d
|
||||||
|
INNER JOIN @FixUsers f ON f.UserId = d.UserId
|
||||||
|
WHERE d.IsDeleted = 0
|
||||||
|
AND d.IsProcessed = 0
|
||||||
|
AND d.TransactionId IS NULL
|
||||||
|
AND d.ProcessedDate IS NULL;
|
||||||
|
|
||||||
|
PRINT CONCAT('DayaLoanContracts soft-deleted (stubs): ', @@ROWCOUNT);
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- Verify
|
||||||
|
------------------------------------------------------------
|
||||||
|
SELECT 'Users still Daya' AS CheckName, COUNT(*) AS Cnt
|
||||||
|
FROM CMS.Users u
|
||||||
|
INNER JOIN @FixUsers f ON f.UserId = u.Id
|
||||||
|
WHERE u.IsDeleted = 0 AND u.PackagePurchaseMethod = 1
|
||||||
|
UNION ALL
|
||||||
|
SELECT 'Club still Daya/None', COUNT(*)
|
||||||
|
FROM CMS.ClubMemberships m
|
||||||
|
INNER JOIN @FixUsers f ON f.UserId = m.UserId
|
||||||
|
WHERE m.IsDeleted = 0 AND m.PurchaseMethod IN (0, 1)
|
||||||
|
UNION ALL
|
||||||
|
SELECT 'Cycle still Daya/None', COUNT(*)
|
||||||
|
FROM CMS.ClubMembershipCycles c
|
||||||
|
INNER JOIN @FixUsers f ON f.UserId = c.UserId
|
||||||
|
WHERE c.IsDeleted = 0 AND c.PurchaseMethod IN (0, 1)
|
||||||
|
UNION ALL
|
||||||
|
SELECT 'UPP still Daya', COUNT(*)
|
||||||
|
FROM CMS.UserPackagePurchases upp
|
||||||
|
INNER JOIN @FixUsers f ON f.UserId = upp.UserId
|
||||||
|
WHERE upp.IsDeleted = 0 AND upp.PurchaseMethod = 1
|
||||||
|
UNION ALL
|
||||||
|
SELECT 'Active stub Daya contracts', COUNT(*)
|
||||||
|
FROM CMS.DayaLoanContracts d
|
||||||
|
INNER JOIN @FixUsers f ON f.UserId = d.UserId
|
||||||
|
WHERE d.IsDeleted = 0
|
||||||
|
AND d.IsProcessed = 0
|
||||||
|
AND d.TransactionId IS NULL
|
||||||
|
AND d.ProcessedDate IS NULL;
|
||||||
|
|
||||||
|
-- Sample after
|
||||||
|
SELECT u.Id AS UserId,
|
||||||
|
u.FirstName, u.LastName, u.Mobile,
|
||||||
|
u.PackagePurchaseMethod,
|
||||||
|
m.PurchaseMethod AS ClubPurchaseMethod
|
||||||
|
FROM CMS.Users u
|
||||||
|
INNER JOIN @FixUsers f ON f.UserId = u.Id
|
||||||
|
LEFT JOIN CMS.ClubMemberships m ON m.UserId = u.Id AND m.IsDeleted = 0
|
||||||
|
WHERE u.IsDeleted = 0
|
||||||
|
ORDER BY u.Id;
|
||||||
|
|
||||||
|
-- Uncomment to apply:
|
||||||
|
-- COMMIT;
|
||||||
|
ROLLBACK;
|
||||||
|
PRINT 'Rolled back (safety). Uncomment COMMIT and remove ROLLBACK to apply.';
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
Rollback for Backfill_UserPackagePurchases_FromClubMemberships.sql
|
||||||
|
|
||||||
|
What it undoes:
|
||||||
|
A) 77 synthetic rows: Tx with RefId LIKE 'UPP-BACKFILL-%' + their UPP
|
||||||
|
B) 24 ManualPayment-linked UPP rows inserted by backfill
|
||||||
|
(same TransactionId as Approved ManualPayments — NOT deleting those Transactions)
|
||||||
|
|
||||||
|
BEFORE RUN:
|
||||||
|
Set @BackfillStartedAt to a timestamp slightly BEFORE you ran the backfill
|
||||||
|
(e.g. if you ran at 06:40, use '2026-08-14 06:00:00').
|
||||||
|
Part B only deletes UPP with Created >= that time so later real CreateManualPayment rows stay.
|
||||||
|
|
||||||
|
Safe to preview: leave @Commit = 0 (default) to only PRINT counts inside a rolled-back tran.
|
||||||
|
Set @Commit = 1 to actually apply.
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SET XACT_ABORT ON;
|
||||||
|
|
||||||
|
DECLARE @BackfillStartedAt DATETIME2 = '2026-08-14 00:00:00'; -- << adjust
|
||||||
|
DECLARE @Commit BIT = 0; -- 0 = dry-run (rollback), 1 = commit deletes
|
||||||
|
|
||||||
|
BEGIN TRAN;
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- A) Synthetic Tx + UPP (RefId marker)
|
||||||
|
------------------------------------------------------------
|
||||||
|
DECLARE @SyntheticUpp TABLE (Id BIGINT PRIMARY KEY, TransactionId BIGINT);
|
||||||
|
|
||||||
|
INSERT INTO @SyntheticUpp (Id, TransactionId)
|
||||||
|
SELECT upp.Id, upp.TransactionId
|
||||||
|
FROM CMS.UserPackagePurchases upp
|
||||||
|
INNER JOIN CMS.Transactions t ON t.Id = upp.TransactionId
|
||||||
|
WHERE t.RefId LIKE N'UPP-BACKFILL-%';
|
||||||
|
|
||||||
|
DECLARE @SynthUppCount INT = (SELECT COUNT(*) FROM @SyntheticUpp);
|
||||||
|
DECLARE @SynthTxCount INT =
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM CMS.Transactions t
|
||||||
|
WHERE t.RefId LIKE N'UPP-BACKFILL-%'
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE upp
|
||||||
|
FROM CMS.UserPackagePurchases upp
|
||||||
|
INNER JOIN @SyntheticUpp s ON s.Id = upp.Id;
|
||||||
|
|
||||||
|
DELETE t
|
||||||
|
FROM CMS.Transactions t
|
||||||
|
WHERE t.RefId LIKE N'UPP-BACKFILL-%';
|
||||||
|
|
||||||
|
PRINT CONCAT('Deleted synthetic UPP: ', @SynthUppCount);
|
||||||
|
PRINT CONCAT('Deleted synthetic Transactions: ', @SynthTxCount);
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- B) UPP created from ManualPayments reuse (no new Tx)
|
||||||
|
------------------------------------------------------------
|
||||||
|
DECLARE @ManualUpp TABLE (Id BIGINT PRIMARY KEY);
|
||||||
|
|
||||||
|
INSERT INTO @ManualUpp (Id)
|
||||||
|
SELECT upp.Id
|
||||||
|
FROM CMS.UserPackagePurchases upp
|
||||||
|
INNER JOIN CMS.ManualPayments mp
|
||||||
|
ON mp.TransactionId = upp.TransactionId
|
||||||
|
AND mp.IsDeleted = 0
|
||||||
|
AND mp.Status = 1 -- Approved
|
||||||
|
WHERE upp.IsDeleted = 0
|
||||||
|
AND upp.PurchaseMethod = 3 -- Manual
|
||||||
|
AND upp.Created >= @BackfillStartedAt
|
||||||
|
-- exclude anything already covered as synthetic (shouldn't overlap)
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM CMS.Transactions t
|
||||||
|
WHERE t.Id = upp.TransactionId AND t.RefId LIKE N'UPP-BACKFILL-%'
|
||||||
|
);
|
||||||
|
|
||||||
|
DECLARE @ManualUppCount INT = (SELECT COUNT(*) FROM @ManualUpp);
|
||||||
|
|
||||||
|
DELETE upp
|
||||||
|
FROM CMS.UserPackagePurchases upp
|
||||||
|
INNER JOIN @ManualUpp m ON m.Id = upp.Id;
|
||||||
|
|
||||||
|
PRINT CONCAT('Deleted ManualPayment-linked UPP (Created >= @BackfillStartedAt): ', @ManualUppCount);
|
||||||
|
PRINT CONCAT('@BackfillStartedAt used: ', CONVERT(varchar(30), @BackfillStartedAt, 126));
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- Optional sanity after rollback
|
||||||
|
------------------------------------------------------------
|
||||||
|
DECLARE @Orphans INT =
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM CMS.ClubMemberships m
|
||||||
|
WHERE m.IsDeleted = 0
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM CMS.UserPackagePurchases upp
|
||||||
|
LEFT JOIN CMS.Transactions t ON t.Id = upp.TransactionId AND t.IsDeleted = 0
|
||||||
|
WHERE upp.UserId = m.UserId
|
||||||
|
AND upp.IsDeleted = 0
|
||||||
|
AND (upp.TransactionId IS NULL OR t.PaymentStatus = 0)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
PRINT CONCAT('Club members without successful UPP after rollback: ', @Orphans);
|
||||||
|
PRINT '(Expect around 101 if dump-era state, if no new purchases since.)';
|
||||||
|
|
||||||
|
IF @Commit = 1
|
||||||
|
BEGIN
|
||||||
|
COMMIT;
|
||||||
|
PRINT 'Rollback COMMITTED.';
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
ROLLBACK;
|
||||||
|
PRINT 'Dry-run only — ROLLBACK. Set @Commit = 1 after verifying counts.';
|
||||||
|
END
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
Update DayaLoanContracts.ContractNumber from MerchantCreditList.
|
||||||
|
|
||||||
|
Match: NationalCode (unique cases).
|
||||||
|
Special case — same NationalCode 2490371436 on TWO users (two mobiles):
|
||||||
|
UserId=50 (09207316560, Created 2025-11-11) → C4_D8815963 (فعالسازی 1404/8/24 = 2025-11-15)
|
||||||
|
UserId=51 (09366426797, Created 2025-11-12) → C4_G8945289 (فعالسازی 1404/8/30 = 2025-11-21)
|
||||||
|
User 51 may have no DayaLoanContracts row yet → INSERT.
|
||||||
|
|
||||||
|
Script defaults to ROLLBACK; uncomment COMMIT after verifying.
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SET XACT_ABORT ON;
|
||||||
|
|
||||||
|
BEGIN TRAN;
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- 1) Unique NationalCodes → UPDATE ContractNumber
|
||||||
|
------------------------------------------------------------
|
||||||
|
;WITH Src AS (
|
||||||
|
SELECT *
|
||||||
|
FROM (VALUES
|
||||||
|
(N'C4_C11231802', N'0312980973'),
|
||||||
|
(N'C4_H10706299', N'4060667961'),
|
||||||
|
(N'C4_E10490523', N'4072306754'),
|
||||||
|
(N'C4_C10337736', N'0054140161'),
|
||||||
|
(N'C4_Q10727431', N'5279791253'),
|
||||||
|
(N'C4_W10568862', N'1960144804'),
|
||||||
|
(N'C4_V10338780', N'4072240567'),
|
||||||
|
(N'C4_G10538762', N'6639906902'),
|
||||||
|
(N'C4_N10921338', N'0943060761'),
|
||||||
|
(N'C4_P9320383', N'2572729636'),
|
||||||
|
(N'C4_Y9276146', N'0012011061'),
|
||||||
|
(N'C4_Q9465813', N'2480017796'),
|
||||||
|
(N'C4_A9107753', N'0450314103'),
|
||||||
|
(N'C4_B9348378', N'0452314488'),
|
||||||
|
(N'C4_U9595222', N'2062876815'),
|
||||||
|
(N'C4_X9630520', N'0680164286'),
|
||||||
|
(N'C4_J9199624', N'0681699401'),
|
||||||
|
(N'C4_B9813553', N'0055680364'),
|
||||||
|
(N'C4_M9994735', N'0793851572'),
|
||||||
|
(N'C4_S9301452', N'0082242925'),
|
||||||
|
(N'C4_V9257168', N'4459676451'),
|
||||||
|
(N'C4_T9961063', N'0312342993'),
|
||||||
|
(N'C4_L9524515', N'0024757489'),
|
||||||
|
(N'C4_Q9947535', N'2480259668'),
|
||||||
|
(N'C4_T9892089', N'2451477016'),
|
||||||
|
(N'C4_N9945163', N'1289603669'),
|
||||||
|
(N'C4_W8785358', N'0322922720'),
|
||||||
|
(N'C4_J8388494', N'2002637903'),
|
||||||
|
(N'C4_V8165679', N'0081093829'),
|
||||||
|
(N'C4_T8579002', N'5289780335')
|
||||||
|
-- 2490371436 handled separately by UserId below
|
||||||
|
) AS v(ContractNumber, NationalCode)
|
||||||
|
)
|
||||||
|
UPDATE d
|
||||||
|
SET
|
||||||
|
d.ContractNumber = s.ContractNumber,
|
||||||
|
d.LastModified = SYSUTCDATETIME()
|
||||||
|
FROM CMS.DayaLoanContracts d
|
||||||
|
INNER JOIN Src s ON s.NationalCode = d.NationalCode
|
||||||
|
WHERE d.IsDeleted = 0;
|
||||||
|
|
||||||
|
PRINT CONCAT('Updated by NationalCode (unique): ', @@ROWCOUNT);
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- 2) Shared NC 2490371436 — match by UserId + Excel activation date
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- UserId 50 ← earlier activation 1404/8/24 → C4_D8815963
|
||||||
|
UPDATE CMS.DayaLoanContracts
|
||||||
|
SET
|
||||||
|
ContractNumber = N'C4_D8815963',
|
||||||
|
LastModified = SYSUTCDATETIME()
|
||||||
|
WHERE IsDeleted = 0
|
||||||
|
AND UserId = 50
|
||||||
|
AND NationalCode = N'2490371436';
|
||||||
|
|
||||||
|
PRINT CONCAT('Updated UserId=50 (C4_D8815963): ', @@ROWCOUNT);
|
||||||
|
|
||||||
|
-- UserId 51 ← later activation 1404/8/30 → C4_G8945289
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM CMS.DayaLoanContracts
|
||||||
|
WHERE IsDeleted = 0 AND UserId = 51 AND NationalCode = N'2490371436'
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
UPDATE CMS.DayaLoanContracts
|
||||||
|
SET
|
||||||
|
ContractNumber = N'C4_G8945289',
|
||||||
|
LastModified = SYSUTCDATETIME()
|
||||||
|
WHERE IsDeleted = 0
|
||||||
|
AND UserId = 51
|
||||||
|
AND NationalCode = N'2490371436';
|
||||||
|
|
||||||
|
PRINT CONCAT('Updated UserId=51 (C4_G8945289): ', @@ROWCOUNT);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO CMS.DayaLoanContracts
|
||||||
|
(
|
||||||
|
UserId, NationalCode, ContractNumber, Status, IsProcessed,
|
||||||
|
LastCheckDate, ProcessedDate, TransactionId,
|
||||||
|
Created, CreatedBy, LastModified, LastModifiedBy, IsDeleted
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
51,
|
||||||
|
N'2490371436',
|
||||||
|
N'C4_G8945289',
|
||||||
|
0, -- NotRequested (same as sibling row for UserId=50 in dump; adjust if needed)
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
SYSUTCDATETIME(),
|
||||||
|
NULL,
|
||||||
|
SYSUTCDATETIME(),
|
||||||
|
NULL,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
PRINT 'Inserted DayaLoanContracts for UserId=51 (C4_G8945289).';
|
||||||
|
END
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- Verify
|
||||||
|
------------------------------------------------------------
|
||||||
|
SELECT
|
||||||
|
d.Id,
|
||||||
|
d.UserId,
|
||||||
|
u.Mobile,
|
||||||
|
d.NationalCode,
|
||||||
|
d.ContractNumber,
|
||||||
|
d.Status,
|
||||||
|
d.IsProcessed
|
||||||
|
FROM CMS.DayaLoanContracts d
|
||||||
|
INNER JOIN CMS.Users u ON u.Id = d.UserId
|
||||||
|
WHERE d.IsDeleted = 0
|
||||||
|
AND d.NationalCode = N'2490371436'
|
||||||
|
ORDER BY d.UserId;
|
||||||
|
|
||||||
|
-- Uncomment to apply:
|
||||||
|
-- COMMIT;
|
||||||
|
ROLLBACK;
|
||||||
|
PRINT 'Rolled back (safety). Uncomment COMMIT and remove ROLLBACK to apply.';
|
||||||
Reference in New Issue
Block a user