fix(network): update network statistics and tree documentation
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 10m15s

- Updated README to reflect the new documentation location.
- Refactored GetNetworkStatisticsQueryHandler to improve method naming and logic for loading package holder counts.
- Enhanced GetNetworkStatisticsResponseDto and NetworkTreeDto to clarify the purpose of leg total properties.
- Updated Protobuf definitions to align with the new response structure for network statistics.
- Removed obsolete SQL scripts related to user package purchases and DayaLoan contracts cleanup.
This commit is contained in:
masoodafar-web
2026-08-28 18:55:48 +03:30
parent 87d61abc5b
commit 612d6fd141
24 changed files with 4036 additions and 256 deletions
@@ -0,0 +1,431 @@
/*
DIAGNOSTIC ONLY — no data changes (read-only)
Trace WrongDaya→Manual FixUsers ledger via UserWalletHistory:
WalletHistory → CreatedBy/LastModifiedBy (mobile) → User → Wallet
→ Transaction (RefrenceId / CreatedBy-as-TxId / Tx.CreatedBy mobile)
→ UPP / ManualPayment / PurchaseMethod
Known IDs from manual CSV exports (UserWalletHistories*.csv, Transactions.csv):
WH: 102-108,122,138-142,292-295,118-121,124,201
Tx: 23-31,42-46,56-59,68,107-110
RefrenceId=0 rule (ChargeUserWallets.sql):
RefId = 'manualy' + original ChangeLogId. After id remap, WH.RefrenceId may be 0.
For Ref=0 rows, assume Tx exists ONLY if CreatedBy is numeric AND that Tx has RefId manualy%.
Do NOT infer Tx from mobile alone or from manualy{N} suffix alone.
See also: 10-Diagnose_Manualy_Transaction_Map.sql
Run on staging, review Verdict + RecommendedAction per user before any fix script.
*/
SET NOCOUNT ON;
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);
-- Known WalletHistory / Transaction ids from CSV exports (for cross-check)
DECLARE @KnownWh TABLE (WalletHistoryId BIGINT PRIMARY KEY);
INSERT INTO @KnownWh (WalletHistoryId) VALUES
(102),(103),(104),(105),(106),(107),(108),(122),
(138),(139),(140),(141),(142),
(292),(293),(294),(295),
(118),(119),(120),(121),(124),(201);
DECLARE @KnownTx TABLE (TransactionId BIGINT PRIMARY KEY);
INSERT INTO @KnownTx (TransactionId) VALUES
(23),(24),(25),(26),(28),(29),(30),(31),
(42),(43),(44),(45),(46),
(56),(57),(58),(59),(68),
(107),(108),(109),(110);
DECLARE @TargetUserCount INT = (SELECT COUNT(*) FROM @FixUsers);
PRINT CONCAT('FixUsers scope: ', @TargetUserCount);
------------------------------------------------------------
-- 1) WalletHistories found BY mobile in CreatedBy / LastModifiedBy
-- (starts from WH — user request)
------------------------------------------------------------
PRINT '=== 1) WalletHistory → User via CreatedBy/LastModifiedBy mobile ===';
SELECT
h.Id AS WalletHistoryId,
h.WalletId,
h.RefrenceId AS WhRefrenceId,
h.ChangeValue,
h.ChangeDiscountValue,
h.Created AS WhCreated,
h.CreatedBy AS WhCreatedBy,
h.LastModifiedBy AS WhLastModifiedBy,
u.Id AS UserId,
u.Mobile,
u.FirstName,
u.LastName,
u.PackagePurchaseMethod AS UserMethod,
CASE WHEN kwh.WalletHistoryId IS NOT NULL THEN 1 ELSE 0 END AS InKnownWhCsv,
CASE
WHEN u.Id IS NULL THEN N'NO_USER_FOR_MOBILE'
WHEN f.UserId IS NULL THEN N'USER_NOT_IN_FIX_SCOPE'
ELSE N'IN_FIX_SCOPE'
END AS ScopeStatus
FROM CMS.UserWalletHistories h
LEFT JOIN CMS.Users u ON u.IsDeleted = 0
AND u.Mobile IN (
NULLIF(LTRIM(RTRIM(h.CreatedBy)), N''),
NULLIF(LTRIM(RTRIM(h.LastModifiedBy)), N'')
)
LEFT JOIN @FixUsers f ON f.UserId = u.Id
LEFT JOIN @KnownWh kwh ON kwh.WalletHistoryId = h.Id
WHERE h.IsDeleted = 0
AND (
(h.CreatedBy LIKE N'09%' AND LEN(h.CreatedBy) >= 10)
OR (h.LastModifiedBy LIKE N'09%' AND LEN(h.LastModifiedBy) >= 10)
)
ORDER BY h.Id;
------------------------------------------------------------
-- 2) All WalletHistories for FixUsers (via Wallet → User)
------------------------------------------------------------
PRINT '=== 2) All WalletHistory rows for FixUsers ===';
SELECT
f.UserId,
u.Mobile,
u.FirstName,
u.LastName,
u.PackagePurchaseMethod AS UserMethod,
w.Id AS WalletId,
h.Id AS WalletHistoryId,
h.RefrenceId AS WhRefrenceId,
h.ChangeValue,
h.ChangeDiscountValue,
h.Created AS WhCreated,
h.CreatedBy AS WhCreatedBy,
h.LastModifiedBy AS WhLastModifiedBy,
CASE WHEN kwh.WalletHistoryId IS NOT NULL THEN 1 ELSE 0 END AS InKnownWhCsv,
CASE WHEN h.RefrenceId IS NULL OR h.RefrenceId = 0 THEN N'WH_NO_REF' ELSE N'WH_HAS_REF' END AS WhRefStatus
FROM @FixUsers f
INNER JOIN CMS.Users u ON u.Id = f.UserId AND u.IsDeleted = 0
INNER JOIN CMS.UserWallets w ON w.UserId = f.UserId AND w.IsDeleted = 0
INNER JOIN CMS.UserWalletHistories h ON h.WalletId = w.Id AND h.IsDeleted = 0
LEFT JOIN @KnownWh kwh ON kwh.WalletHistoryId = h.Id
ORDER BY f.UserId, h.Id;
------------------------------------------------------------
-- 3) Per-user ledger verdict (WH + Tx + UPP)
------------------------------------------------------------
PRINT '=== 3) Per-user ledger verdict ===';
;WITH UserWh AS (
SELECT
f.UserId,
COUNT(*) AS WalletHistoryCount,
SUM(CASE WHEN h.RefrenceId IS NULL OR h.RefrenceId = 0 THEN 1 ELSE 0 END) AS WhMissingRefCount,
SUM(CASE WHEN kwh.WalletHistoryId IS NOT NULL THEN 1 ELSE 0 END) AS KnownCsvWhCount,
MAX(h.Id) AS LatestWhId,
MAX(h.RefrenceId) AS LatestWhRefId
FROM @FixUsers f
INNER JOIN CMS.UserWallets w ON w.UserId = f.UserId AND w.IsDeleted = 0
LEFT JOIN CMS.UserWalletHistories h ON h.WalletId = w.Id AND h.IsDeleted = 0
LEFT JOIN @KnownWh kwh ON kwh.WalletHistoryId = h.Id
GROUP BY f.UserId
),
UserTxViaWh AS (
SELECT
uw.UserId,
COUNT(DISTINCT t.Id) AS TxViaWhRefCount,
MAX(t.Id) AS SampleTxViaWhRef
FROM UserWh uw
INNER JOIN CMS.UserWallets w ON w.UserId = uw.UserId AND w.IsDeleted = 0
INNER JOIN CMS.UserWalletHistories h ON h.WalletId = w.Id AND h.IsDeleted = 0
INNER JOIN CMS.Transactions t ON t.Id = h.RefrenceId AND t.IsDeleted = 0
GROUP BY uw.UserId
),
UserTxViaMobile AS (
SELECT
f.UserId,
COUNT(DISTINCT t.Id) AS TxViaMobileCount,
MAX(t.Id) AS SampleTxViaMobile
FROM @FixUsers f
INNER JOIN CMS.Users u ON u.Id = f.UserId AND u.IsDeleted = 0
INNER JOIN CMS.Transactions t ON t.IsDeleted = 0
AND (t.CreatedBy = u.Mobile OR t.LastModifiedBy = u.Mobile)
GROUP BY f.UserId
),
UserTxViaCreatedByHint AS (
-- WH.CreatedBy stores Tx Id when RefrenceId=0 failed to update (UserWalletHistories2.csv).
-- Only count when hinted Tx exists AND is manualy% (may still be wrong user — verify mobile).
SELECT
f.UserId,
COUNT(DISTINCT t.Id) AS TxViaWhCreatedByHintCount,
MAX(t.Id) AS SampleTxViaHint
FROM @FixUsers f
INNER JOIN CMS.UserWallets w ON w.UserId = f.UserId AND w.IsDeleted = 0
INNER JOIN CMS.UserWalletHistories h ON h.WalletId = w.Id AND h.IsDeleted = 0
INNER JOIN CMS.Transactions t ON t.Id = TRY_CAST(h.CreatedBy AS BIGINT)
AND t.IsDeleted = 0 AND t.RefId LIKE N'manualy%'
WHERE (h.RefrenceId IS NULL OR h.RefrenceId = 0)
AND TRY_CAST(h.CreatedBy AS BIGINT) > 0
GROUP BY f.UserId
),
UserTxViaManualyMobile AS (
-- Informational only — NOT used alone for Ref=0 WH linking
SELECT
f.UserId,
COUNT(DISTINCT t.Id) AS TxViaManualyMobileCount,
MAX(t.Id) AS SampleManualyTxViaMobile
FROM @FixUsers f
INNER JOIN CMS.Users u ON u.Id = f.UserId AND u.IsDeleted = 0
INNER JOIN CMS.Transactions t ON t.IsDeleted = 0
AND t.RefId LIKE N'manualy%'
AND u.Mobile IN (
NULLIF(LTRIM(RTRIM(t.CreatedBy)), N''),
NULLIF(LTRIM(RTRIM(t.LastModifiedBy)), N'')
)
GROUP BY f.UserId
),
UserUpp AS (
SELECT
f.UserId,
COUNT(*) AS UppCount,
SUM(CASE WHEN upp.PurchaseMethod = 1 THEN 1 ELSE 0 END) AS UppDayaCount,
SUM(CASE WHEN upp.PurchaseMethod = 3 THEN 1 ELSE 0 END) AS UppManualCount,
MAX(upp.TransactionId) AS LatestUppTxId
FROM @FixUsers f
LEFT JOIN CMS.UserPackagePurchases upp
ON upp.UserId = f.UserId AND upp.IsDeleted = 0
GROUP BY f.UserId
),
UserMp AS (
SELECT
f.UserId,
COUNT(*) AS ManualPaymentCount,
MAX(mp.TransactionId) AS LatestMpTxId
FROM @FixUsers f
LEFT JOIN CMS.ManualPayments mp
ON mp.UserId = f.UserId AND mp.IsDeleted = 0
GROUP BY f.UserId
)
SELECT *
FROM (
SELECT
f.UserId,
u.Mobile,
u.FirstName,
u.LastName,
u.PackagePurchaseMethod AS UserMethod,
ISNULL(uwh.WalletHistoryCount, 0) AS WalletHistoryCount,
ISNULL(uwh.WhMissingRefCount, 0) AS WhMissingRefCount,
ISNULL(uwh.KnownCsvWhCount, 0) AS KnownCsvWhCount,
uwh.LatestWhId,
uwh.LatestWhRefId,
ISNULL(utw.TxViaWhRefCount, 0) AS TxViaWhRefCount,
utw.SampleTxViaWhRef,
ISNULL(utm.TxViaMobileCount, 0) AS TxViaMobileCount,
utm.SampleTxViaMobile,
ISNULL(uth.TxViaWhCreatedByHintCount, 0) AS TxViaWhCreatedByHintCount,
uth.SampleTxViaHint,
ISNULL(utmm.TxViaManualyMobileCount, 0) AS TxViaManualyMobileCount,
utmm.SampleManualyTxViaMobile,
ISNULL(uu.UppCount, 0) AS UppCount,
ISNULL(uu.UppDayaCount, 0) AS UppDayaCount,
uu.LatestUppTxId,
ISNULL(ump.ManualPaymentCount, 0) AS ManualPaymentCount,
ump.LatestMpTxId,
CASE
WHEN ISNULL(uwh.WalletHistoryCount, 0) = 0 THEN N'NO_WALLET_HISTORY'
WHEN ISNULL(uwh.WhMissingRefCount, 0) > 0
AND ISNULL(uth.TxViaWhCreatedByHintCount, 0) > 0 THEN N'WH_REF_BROKEN_FIX_VIA_CREATEDBY_HINT'
WHEN ISNULL(uwh.WhMissingRefCount, 0) > 0
AND ISNULL(utmm.TxViaManualyMobileCount, 0) > 0 THEN N'WH_REF_BROKEN_MANUALY_TX_BY_MOBILE_REVIEW'
WHEN ISNULL(uwh.WhMissingRefCount, 0) > 0 THEN N'WH_REF_BROKEN_NO_TX_HINT'
WHEN ISNULL(utw.TxViaWhRefCount, 0) > 0
AND ISNULL(uu.UppCount, 0) = 0 THEN N'HAS_WH+TX_NEED_UPP'
WHEN ISNULL(utw.TxViaWhRefCount, 0) > 0
AND ISNULL(uu.UppDayaCount, 0) > 0 THEN N'HAS_WH+TX_UPP_DAYA_FIX_METHOD'
WHEN ISNULL(utw.TxViaWhRefCount, 0) > 0 THEN N'OK_WH+TX'
WHEN ISNULL(utm.TxViaMobileCount, 0) > 0 THEN N'TX_BY_MOBILE_WH_LINK_UNCLEAR'
ELSE N'NEEDS_MANUAL_REVIEW'
END AS Verdict,
CASE
WHEN ISNULL(uwh.WalletHistoryCount, 0) = 0 THEN N'Create WH+Tx+UPP (or find hidden WH by mobile)'
WHEN ISNULL(uwh.WhMissingRefCount, 0) > 0
AND ISNULL(uth.TxViaWhCreatedByHintCount, 0) > 0 THEN N'Link WH.RefrenceId to manualy Tx from CreatedBy hint only'
WHEN ISNULL(uwh.WhMissingRefCount, 0) > 0
AND ISNULL(utmm.TxViaManualyMobileCount, 0) > 0 THEN N'manualy Tx exists by mobile but WH hint missing — manual review'
WHEN ISNULL(uwh.WhMissingRefCount, 0) > 0 THEN N'No Tx hint — do NOT assume Tx exists; review charge log'
WHEN ISNULL(utw.TxViaWhRefCount, 0) > 0 AND ISNULL(uu.UppCount, 0) = 0 THEN N'Create UPP Manual(3) reusing Tx'
WHEN ISNULL(uu.UppDayaCount, 0) > 0 OR u.PackagePurchaseMethod = 1 THEN N'Fix PurchaseMethod 1→3 only'
ELSE N'Likely OK — verify PurchaseMethod'
END AS RecommendedAction
FROM @FixUsers f
INNER JOIN CMS.Users u ON u.Id = f.UserId AND u.IsDeleted = 0
LEFT JOIN UserWh uwh ON uwh.UserId = f.UserId
LEFT JOIN UserTxViaWh utw ON utw.UserId = f.UserId
LEFT JOIN UserTxViaMobile utm ON utm.UserId = f.UserId
LEFT JOIN UserTxViaCreatedByHint uth ON uth.UserId = f.UserId
LEFT JOIN UserTxViaManualyMobile utmm ON utmm.UserId = f.UserId
LEFT JOIN UserUpp uu ON uu.UserId = f.UserId
LEFT JOIN UserMp ump ON ump.UserId = f.UserId
) q
ORDER BY
CASE q.Verdict
WHEN N'OK_WH+TX' THEN 9
WHEN N'HAS_WH+TX_UPP_DAYA_FIX_METHOD' THEN 8
ELSE 1
END,
q.UserId;
------------------------------------------------------------
-- 4) Summary counts by Verdict
------------------------------------------------------------
PRINT '=== 4) Summary by Verdict ===';
;WITH PerUser AS (
SELECT
f.UserId,
u.PackagePurchaseMethod AS UserMethod,
ISNULL(uwh.Cnt, 0) AS WhCnt,
ISNULL(uwh.BadRefCnt, 0) AS WhBadRefCnt,
ISNULL(utw.Cnt, 0) AS TxViaWhCnt,
ISNULL(utm.Cnt, 0) AS TxViaMobileCnt,
ISNULL(uth.Cnt, 0) AS TxViaHintCnt,
ISNULL(uu.Cnt, 0) AS UppCnt
FROM @FixUsers f
INNER JOIN CMS.Users u ON u.Id = f.UserId AND u.IsDeleted = 0
OUTER APPLY (
SELECT COUNT(*) AS Cnt,
SUM(CASE WHEN h.RefrenceId IS NULL OR h.RefrenceId = 0 THEN 1 ELSE 0 END) AS BadRefCnt
FROM CMS.UserWallets w
INNER JOIN CMS.UserWalletHistories h ON h.WalletId = w.Id AND h.IsDeleted = 0
WHERE w.UserId = f.UserId AND w.IsDeleted = 0
) uwh
OUTER APPLY (
SELECT COUNT(DISTINCT t.Id) AS Cnt
FROM CMS.UserWallets w
INNER JOIN CMS.UserWalletHistories h ON h.WalletId = w.Id AND h.IsDeleted = 0
INNER JOIN CMS.Transactions t ON t.Id = h.RefrenceId AND t.IsDeleted = 0
WHERE w.UserId = f.UserId AND w.IsDeleted = 0
) utw
OUTER APPLY (
SELECT COUNT(DISTINCT t.Id) AS Cnt
FROM CMS.Transactions t
WHERE t.IsDeleted = 0
AND (t.CreatedBy = u.Mobile OR t.LastModifiedBy = u.Mobile)
) utm
OUTER APPLY (
SELECT COUNT(DISTINCT t.Id) AS Cnt
FROM CMS.UserWallets w
INNER JOIN CMS.UserWalletHistories h ON h.WalletId = w.Id AND h.IsDeleted = 0
INNER JOIN CMS.Transactions t ON t.Id = TRY_CAST(h.CreatedBy AS BIGINT)
AND t.IsDeleted = 0 AND t.RefId LIKE N'manualy%'
WHERE w.UserId = f.UserId AND w.IsDeleted = 0
AND (h.RefrenceId IS NULL OR h.RefrenceId = 0)
AND TRY_CAST(h.CreatedBy AS BIGINT) > 0
) uth
OUTER APPLY (
SELECT COUNT(*) AS Cnt
FROM CMS.UserPackagePurchases upp
WHERE upp.UserId = f.UserId AND upp.IsDeleted = 0
) uu
),
Verdicts AS (
SELECT
UserId,
CASE
WHEN WhCnt = 0 THEN N'NO_WALLET_HISTORY'
WHEN WhBadRefCnt > 0 AND TxViaHintCnt > 0 THEN N'WH_REF_BROKEN_FIX_VIA_CREATEDBY_HINT'
WHEN WhBadRefCnt > 0 THEN N'WH_REF_BROKEN_NO_TX_HINT'
WHEN TxViaWhCnt > 0 AND UppCnt = 0 THEN N'HAS_WH+TX_NEED_UPP'
WHEN TxViaWhCnt > 0 THEN N'OK_WH+TX'
WHEN TxViaMobileCnt > 0 THEN N'TX_BY_MOBILE_WH_LINK_UNCLEAR'
ELSE N'NEEDS_MANUAL_REVIEW'
END AS Verdict
FROM PerUser
)
SELECT Verdict, COUNT(*) AS UserCount
FROM Verdicts
GROUP BY Verdict
ORDER BY UserCount DESC;
------------------------------------------------------------
-- 5) Known CSV Tx ids — exist in DB? linked to WH/UPP?
------------------------------------------------------------
PRINT '=== 5) Known Transaction IDs from CSV ===';
SELECT
kt.TransactionId,
CASE WHEN t.Id IS NULL THEN N'MISSING_IN_DB' ELSE N'EXISTS' END AS TxStatus,
t.Amount,
t.Description,
t.RefId,
t.CreatedBy AS TxCreatedBy,
t.LastModifiedBy AS TxLastModifiedBy,
u.Id AS UserByMobile,
u.Mobile,
h.Id AS LinkedWalletHistoryId,
h.WalletId,
h.RefrenceId AS WhRefrenceId,
upp.UserId AS UppUserId,
upp.PurchaseMethod AS UppMethod
FROM @KnownTx kt
LEFT JOIN CMS.Transactions t ON t.Id = kt.TransactionId
LEFT JOIN CMS.Users u ON u.IsDeleted = 0
AND u.Mobile IN (NULLIF(t.CreatedBy, N''), NULLIF(t.LastModifiedBy, N''))
LEFT JOIN CMS.UserWalletHistories h ON h.IsDeleted = 0 AND h.RefrenceId = kt.TransactionId
LEFT JOIN CMS.UserPackagePurchases upp ON upp.IsDeleted = 0 AND upp.TransactionId = kt.TransactionId
ORDER BY kt.TransactionId;
------------------------------------------------------------
-- 6) Known CSV WH ids — RefrenceId vs CreatedBy hint vs mobile
------------------------------------------------------------
PRINT '=== 6) Known WalletHistory IDs from CSV ===';
SELECT
kw.WalletHistoryId,
h.WalletId,
h.RefrenceId AS WhRefrenceId,
h.ChangeValue,
h.CreatedBy AS WhCreatedBy,
h.LastModifiedBy AS WhLastModifiedBy,
w.UserId AS WalletUserId,
u.Mobile AS WalletUserMobile,
u_by_mobile.Id AS UserByWhMobile,
u_by_mobile.Mobile AS MatchedMobile,
t_ref.Id AS TxByRefrenceId,
t_ref.Description AS TxByRefDescription,
t_hint.Id AS TxByCreatedByHint,
t_hint.RefId AS TxByHintRefId,
t_hint.Description AS TxByHintDescription,
TRY_CAST(
SUBSTRING(t_hint.RefId, LEN(N'manualy') + 1, 20)
AS BIGINT
) AS ManualyOriginalChangeLogId,
CASE
WHEN h.Id IS NULL THEN N'WH_MISSING_IN_DB'
WHEN t_ref.Id IS NOT NULL AND t_ref.RefId LIKE N'manualy%' THEN N'WH_LINKED_TO_MANUALY_TX'
WHEN t_ref.Id IS NOT NULL THEN N'WH_LINKED_TO_OTHER_TX'
WHEN t_hint.Id IS NOT NULL AND t_hint.RefId LIKE N'manualy%' THEN N'WH_REF_BROKEN_USE_CREATEDBY_HINT'
WHEN TRY_CAST(h.CreatedBy AS BIGINT) > 0 AND t_hint.Id IS NULL THEN N'WH_HINT_TX_MISSING'
WHEN u_by_mobile.Id IS NOT NULL THEN N'WH_MOBILE_ONLY_NO_AUTO_LINK'
ELSE N'WH_ORPHAN_REVIEW'
END AS WhLinkStatus
FROM @KnownWh kw
LEFT JOIN CMS.UserWalletHistories h ON h.Id = kw.WalletHistoryId AND h.IsDeleted = 0
LEFT JOIN CMS.UserWallets w ON w.Id = h.WalletId AND w.IsDeleted = 0
LEFT JOIN CMS.Users u ON u.Id = w.UserId AND u.IsDeleted = 0
LEFT JOIN CMS.Users u_by_mobile ON u_by_mobile.IsDeleted = 0
AND u_by_mobile.Mobile IN (
NULLIF(LTRIM(RTRIM(h.CreatedBy)), N''),
NULLIF(LTRIM(RTRIM(h.LastModifiedBy)), N'')
)
LEFT JOIN CMS.Transactions t_ref ON t_ref.Id = h.RefrenceId AND t_ref.IsDeleted = 0
LEFT JOIN CMS.Transactions t_hint ON t_hint.Id = TRY_CAST(h.CreatedBy AS BIGINT) AND t_hint.IsDeleted = 0
ORDER BY kw.WalletHistoryId;
PRINT 'Done (diagnostic only — no changes).';