Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 8m2s

This commit is contained in:
masoodafar-web
2026-08-24 00:10:39 +03:30
15 changed files with 1245 additions and 13 deletions
@@ -1,3 +1,5 @@
using CMSMicroservice.Application.Common;
namespace CMSMicroservice.Application.CommissionCQ.Commands.RequestWithdrawal;
public class RequestWithdrawalCommandHandler : IRequestHandler<RequestWithdrawalCommand, Unit>
@@ -37,7 +39,10 @@ public class RequestWithdrawalCommandHandler : IRequestHandler<RequestWithdrawal
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);
@@ -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;
}
@@ -34,14 +34,39 @@ public class GetAllUserByFilterQueryHandler : IRequestHandler<GetAllUserByFilter
.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.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
{
MetaData = await query.GetMetaData(request.PaginationState, cancellationToken),
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)
};
}
}
@@ -36,4 +36,8 @@ public class GetAllUserByFilterResponseDto
public bool PushNotifications { get; set; }
//تاریخ تولد
public DateTime? BirthDate { get; set; }
//آدرس پیش‌فرض
public string DefaultAddress { get; set; } = string.Empty;
//کدپستی پیش‌فرض
public string DefaultPostalCode { get; set; } = string.Empty;
}
@@ -1,5 +1,6 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System.Collections.Generic;
@@ -13,10 +14,12 @@ namespace CMSMicroservice.Infrastructure.Services;
public class GenerateJwtTokenService : IGenerateJwtToken
{
private readonly IConfiguration _configuration;
private readonly IApplicationDbContext _context;
public GenerateJwtTokenService(IConfiguration configuration)
public GenerateJwtTokenService(IConfiguration configuration, IApplicationDbContext context)
{
_configuration = configuration;
_context = context;
}
public async Task<string> GenerateJwtToken(User user, int? expiryDays = null)
@@ -58,6 +61,10 @@ public class GenerateJwtTokenService : IGenerateJwtToken
claims.Add(new Claim("HasPurchasedPackage",
(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())
{
foreach (var userRole in user.UserRoles)
@@ -3,7 +3,7 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.0.208</Version>
<Version>0.0.209</Version>
<DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
@@ -238,6 +238,8 @@ message GetAllUserByFilterResponseModel
bool sms_notifications = 12;
bool push_notifications = 13;
google.protobuf.Timestamp birth_date = 14;
string default_address = 15;
string default_postal_code = 16;
}
message GetJwtTokenRequest
{
@@ -4,7 +4,10 @@ public class UserProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
{
//config.NewConfig<Source,Destination>()
// .Map(dest => dest.FullName, src => $"{src.Firstname} {src.Lastname}");
config.NewConfig<Application.UserCQ.Queries.GetAllUserByFilter.GetAllUserByFilterResponseModel,
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);
}
}
@@ -18,6 +18,7 @@ using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWalletHistory;
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawals;
using CMSMicroservice.Application.UserWalletCQ.Queries.GetCustomerWithdrawalSettings;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common;
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Domain.Common;
using CMSMicroservice.Domain.Enums;
@@ -175,7 +176,18 @@ public class UserWalletService : UserWalletContract.UserWalletContractBase
// Update payout with withdrawal info
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;
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.';