fix mandehesab, bazdid, sms

This commit is contained in:
javad committed 2026-10-16 17:54:07 +03:30
1 parent 7e5c83bea2
commit 1fd27d0445
22 files changed
+16462 -5918

No files matched your search

+29 -4
View File
@@ -1,11 +1,11 @@
Project Root: khatibi Project Root: khatibi
Generated At: 2026-10-15T06:59:41+00:00 Generated At: 2026-10-16T12:37:31+00:00
Python Files: 175 Python Files: 179
Django Files: 12 Django Files: 12
JavaScript/TypeScript Files: 14 JavaScript/TypeScript Files: 14
HTML Files: 58 HTML Files: 58
Total Files: 689 Total Files: 713
Total Directories: 76 Total Directories: 77
khatibi khatibi
├── .cursor ├── .cursor
@@ -41,6 +41,7 @@ khatibi
│ │ │ ├── fix_customer_names.py │ │ │ ├── fix_customer_names.py
│ │ │ ├── fix_customer_phones.py │ │ │ ├── fix_customer_phones.py
│ │ │ ├── gt_esh.py │ │ │ ├── gt_esh.py
│ │ │ ├── normalize_customer_sms_phones.py
│ │ │ ├── recalc_last_bazdid.py │ │ │ ├── recalc_last_bazdid.py
│ │ │ ├── send_daily_report.py │ │ │ ├── send_daily_report.py
│ │ │ ├── sms_delivery.py │ │ │ ├── sms_delivery.py
@@ -121,6 +122,8 @@ khatibi
│ │ ├── 0070_sms_types_seven.py │ │ ├── 0070_sms_types_seven.py
│ │ ├── 0071_sms_settings_permission.py │ │ ├── 0071_sms_settings_permission.py
│ │ ├── 0072_customernamefixjob.py │ │ ├── 0072_customernamefixjob.py
│ │ ├── 0073_alter_action_actiontype_choices.py
│ │ ├── 0074_alter_action_status_choices.py
│ │ └── __init__.py │ │ └── __init__.py
│ ├── models │ ├── models
│ │ ├── __init__.py │ │ ├── __init__.py
@@ -338,6 +341,13 @@ khatibi
│ │ ├── graph.json │ │ ├── graph.json
│ │ ├── GRAPH_REPORT.md │ │ ├── GRAPH_REPORT.md
│ │ └── manifest.json │ │ └── manifest.json
│ ├── 2026-10-16
│ │ ├── .graphify_analysis.json
│ │ ├── .graphify_labels.json
│ │ ├── .graphify_semantic_marker
│ │ ├── graph.json
│ │ ├── GRAPH_REPORT.md
│ │ └── manifest.json
│ ├── cache │ ├── cache
│ │ ├── ast │ │ ├── ast
│ │ │ └── v0.9.82-s7 │ │ │ └── v0.9.82-s7
@@ -368,8 +378,10 @@ khatibi
│ │ │ ├── 183467d5c1ee7daac78dfb428bd335c17e17272bd4510358f38fc5924140c816.json │ │ │ ├── 183467d5c1ee7daac78dfb428bd335c17e17272bd4510358f38fc5924140c816.json
│ │ │ ├── 1893253c5245ae073f9de9168d537384ab764b79d7b1631c785cd7335aab791b.json │ │ │ ├── 1893253c5245ae073f9de9168d537384ab764b79d7b1631c785cd7335aab791b.json
│ │ │ ├── 1d5a29a9dabb365c079039c788988636b66c3a4bd2dbe66b249df001c2b5396f.json │ │ │ ├── 1d5a29a9dabb365c079039c788988636b66c3a4bd2dbe66b249df001c2b5396f.json
│ │ │ ├── 1d7a28f73bb52bbc018674a0c01405b7f395bcc5f340ecabe332e2fe440bcae8.json
│ │ │ ├── 1d9f15f04104af07c60349f21bcd9f6f2c0e65c915e3e9c69040a10197b0fa6f.json │ │ │ ├── 1d9f15f04104af07c60349f21bcd9f6f2c0e65c915e3e9c69040a10197b0fa6f.json
│ │ │ ├── 1dd0c6958a307a1e4e59c24f20225292277585dd8652b35537ede94fc04807ee.json │ │ │ ├── 1dd0c6958a307a1e4e59c24f20225292277585dd8652b35537ede94fc04807ee.json
│ │ │ ├── 1f7001563689c4ba77d24f98418fd10725419d45385094fd18d89f20573d6453.json
│ │ │ ├── 20652a8d348bf514ac266f30a6400f923674dae819d18046d008066943377804.json │ │ │ ├── 20652a8d348bf514ac266f30a6400f923674dae819d18046d008066943377804.json
│ │ │ ├── 21af53aa7322868a25b47458e59f339a8f6e58b7914fbe8e3389ab86e0b0b643.json │ │ │ ├── 21af53aa7322868a25b47458e59f339a8f6e58b7914fbe8e3389ab86e0b0b643.json
│ │ │ ├── 22310d4d49f2beee611c83915933ea06e80ec562fd92c170345c556cd3772561.json │ │ │ ├── 22310d4d49f2beee611c83915933ea06e80ec562fd92c170345c556cd3772561.json
@@ -399,10 +411,12 @@ khatibi
│ │ │ ├── 4162977f2c2251e7a8b331d7229bd43dff56df31356191b19886b5bf5d56377d.json │ │ │ ├── 4162977f2c2251e7a8b331d7229bd43dff56df31356191b19886b5bf5d56377d.json
│ │ │ ├── 41952e7c846da15fe658603ff001dafdb0e12d3372a7f0306adfa3a71de8bee7.json │ │ │ ├── 41952e7c846da15fe658603ff001dafdb0e12d3372a7f0306adfa3a71de8bee7.json
│ │ │ ├── 4204828f3cbb3465c469e28345e0fc4d7ff9a84e30e3dd9f00ab4dbe27014e0d.json │ │ │ ├── 4204828f3cbb3465c469e28345e0fc4d7ff9a84e30e3dd9f00ab4dbe27014e0d.json
│ │ │ ├── 435b8e433cd2f09f7b81717214592f9bfadd8edd8177adcabd4338ce803855d2.json
│ │ │ ├── 458c1b9a3ce257e789f75305939ab2e79029c557657102eebf2082e50a47a0e5.json │ │ │ ├── 458c1b9a3ce257e789f75305939ab2e79029c557657102eebf2082e50a47a0e5.json
│ │ │ ├── 45f7e59ef3f291171180b6763e8da408d8b7579bb7cb96dd4ca859e48aa5ce14.json │ │ │ ├── 45f7e59ef3f291171180b6763e8da408d8b7579bb7cb96dd4ca859e48aa5ce14.json
│ │ │ ├── 46616763804b2b938d73c75f7651158e94803e0b86b3a759ecdd59375759eea3.json │ │ │ ├── 46616763804b2b938d73c75f7651158e94803e0b86b3a759ecdd59375759eea3.json
│ │ │ ├── 46bde576f6f1e69457e4ad33238fdbd602d2a7f027644a84cb81068215fbd5e1.json │ │ │ ├── 46bde576f6f1e69457e4ad33238fdbd602d2a7f027644a84cb81068215fbd5e1.json
│ │ │ ├── 477b0c06bcadb94314490ac76899f499d45a942aadef1e65623d303342157463.json
│ │ │ ├── 4da7df08a0ef803551d404438fe24d6c8cc11d9a2c3cc8d85e37d5228ba3a166.json │ │ │ ├── 4da7df08a0ef803551d404438fe24d6c8cc11d9a2c3cc8d85e37d5228ba3a166.json
│ │ │ ├── 4e24d52f79dfa76aa2406de05583fa49ce966a28fb653fa284fb8e8204797385.json │ │ │ ├── 4e24d52f79dfa76aa2406de05583fa49ce966a28fb653fa284fb8e8204797385.json
│ │ │ ├── 4e604dd03a70463ae25f6615ea3ce4fe8e98fab15d6b11ddafd51d601062e84d.json │ │ │ ├── 4e604dd03a70463ae25f6615ea3ce4fe8e98fab15d6b11ddafd51d601062e84d.json
@@ -440,6 +454,7 @@ khatibi
│ │ │ ├── 6603c5eb7403f7426c429341d43018fdffcb3430f22972c426ac43956d229b33.json │ │ │ ├── 6603c5eb7403f7426c429341d43018fdffcb3430f22972c426ac43956d229b33.json
│ │ │ ├── 6648bc2b25d0bd25846d48f406d3e9a6f89677542600e9f3f57533747ed9b08c.json │ │ │ ├── 6648bc2b25d0bd25846d48f406d3e9a6f89677542600e9f3f57533747ed9b08c.json
│ │ │ ├── 6691dea01045e5561ac1c72f902f96ba84564c221498829443f793fee8784021.json │ │ │ ├── 6691dea01045e5561ac1c72f902f96ba84564c221498829443f793fee8784021.json
│ │ │ ├── 67b4e119f02b3ed94553e526bbd33cddff90053edb27438e472409b2ca5ad9a0.json
│ │ │ ├── 6805914035b171d32a1b80948179da15f78c5741e5a80b216d51a6586185c0c3.json │ │ │ ├── 6805914035b171d32a1b80948179da15f78c5741e5a80b216d51a6586185c0c3.json
│ │ │ ├── 6876f75a9cb4a5f5857c47e3b0d6b38e51941aff76bc6d92cb1b4eef1c51071e.json │ │ │ ├── 6876f75a9cb4a5f5857c47e3b0d6b38e51941aff76bc6d92cb1b4eef1c51071e.json
│ │ │ ├── 6a88f82e5b9f9da12b50f8531eac30c10e1c2a0cf39601d275fd90ee3e28da55.json │ │ │ ├── 6a88f82e5b9f9da12b50f8531eac30c10e1c2a0cf39601d275fd90ee3e28da55.json
@@ -451,6 +466,7 @@ khatibi
│ │ │ ├── 6e262456570c174e207055c7c964fe85431addb0f59298b1e4d912500dfb23a3.json │ │ │ ├── 6e262456570c174e207055c7c964fe85431addb0f59298b1e4d912500dfb23a3.json
│ │ │ ├── 6e2ed39911a83c75e432ffd0dab22fd095ce8c5bb186d6ab22d594375f3b32bc.json │ │ │ ├── 6e2ed39911a83c75e432ffd0dab22fd095ce8c5bb186d6ab22d594375f3b32bc.json
│ │ │ ├── 6ed362dbf43e46eeb440246d52fdc4e0783c895114249ea33f3de7ddf6898a3a.json │ │ │ ├── 6ed362dbf43e46eeb440246d52fdc4e0783c895114249ea33f3de7ddf6898a3a.json
│ │ │ ├── 6ee8ac22fd8b7d0152fe85c630c98fd9c7f1f74312b5f923dbebb3ff5367a71e.json
│ │ │ ├── 71a07328d56403d55714d76cefc05db3e68c6498719e629e4e76889a1940efc2.json │ │ │ ├── 71a07328d56403d55714d76cefc05db3e68c6498719e629e4e76889a1940efc2.json
│ │ │ ├── 72577fde1eae8cf079aff518cecce910ac310cefcc038fbbcb0a5d11f20fa20d.json │ │ │ ├── 72577fde1eae8cf079aff518cecce910ac310cefcc038fbbcb0a5d11f20fa20d.json
│ │ │ ├── 73a129efd08cc38274f3c56503c234eef17f58faf74dadbdbbe475efcbfc2777.json │ │ │ ├── 73a129efd08cc38274f3c56503c234eef17f58faf74dadbdbbe475efcbfc2777.json
@@ -505,9 +521,11 @@ khatibi
│ │ │ ├── a823c9c28027c8d525714e6571bc3f3ca38f3bd0420aaf4f4921067cf0672475.json │ │ │ ├── a823c9c28027c8d525714e6571bc3f3ca38f3bd0420aaf4f4921067cf0672475.json
│ │ │ ├── a8311f6e51027ea4606e35bf14d50edc1795e71b2d63cddeae8c7bb7ece215b8.json │ │ │ ├── a8311f6e51027ea4606e35bf14d50edc1795e71b2d63cddeae8c7bb7ece215b8.json
│ │ │ ├── aabf6dbb2202699bb1b06fd582c88465b9cf2b8cc060bda2e2bc0c67fc493bed.json │ │ │ ├── aabf6dbb2202699bb1b06fd582c88465b9cf2b8cc060bda2e2bc0c67fc493bed.json
│ │ │ ├── ab5d0682afc0714c24b9ac5fda36b243c638d272ea973f46a5c18e783132c861.json
│ │ │ ├── add004e78a62164d7565c102eb825debea20811f7fe76afa0567a1b6b289f264.json │ │ │ ├── add004e78a62164d7565c102eb825debea20811f7fe76afa0567a1b6b289f264.json
│ │ │ ├── aeaae7b60339b1283cab517b4ab42806c64933c272d007a68431988ba8e0edb5.json │ │ │ ├── aeaae7b60339b1283cab517b4ab42806c64933c272d007a68431988ba8e0edb5.json
│ │ │ ├── af128ca7fc8d02ae40fff36925a5b96034862ce0d581e22a8a0c88f41040e526.json │ │ │ ├── af128ca7fc8d02ae40fff36925a5b96034862ce0d581e22a8a0c88f41040e526.json
│ │ │ ├── b24556e20d540fd63de3dc04501ffd021836b930c4ebc3ff309f7950ef17a335.json
│ │ │ ├── b26c22033963df1727407c1fa6c79d8241bfc16056e7a8d3c4383fb2d050d3cd.json │ │ │ ├── b26c22033963df1727407c1fa6c79d8241bfc16056e7a8d3c4383fb2d050d3cd.json
│ │ │ ├── b26e251bde84a040dbb2d1a4f700ebcd761f51a124a98a104704e82537e320eb.json │ │ │ ├── b26e251bde84a040dbb2d1a4f700ebcd761f51a124a98a104704e82537e320eb.json
│ │ │ ├── b34cb0a1a28e753a900b3d23f01d3810f9c06635b0650d34be4ec9d98daa8fbb.json │ │ │ ├── b34cb0a1a28e753a900b3d23f01d3810f9c06635b0650d34be4ec9d98daa8fbb.json
@@ -526,11 +544,13 @@ khatibi
│ │ │ ├── be72e639c12e860a2120c3ecf96b94c86e5c69b2f1f0715cd2804d0475409cd6.json │ │ │ ├── be72e639c12e860a2120c3ecf96b94c86e5c69b2f1f0715cd2804d0475409cd6.json
│ │ │ ├── c12baf851ec40542280cb62d2f878d5e7383357365281938cfb55a64c08a9a10.json │ │ │ ├── c12baf851ec40542280cb62d2f878d5e7383357365281938cfb55a64c08a9a10.json
│ │ │ ├── c36b696b57c1deb51e47a057b32d1dd75e3f1560e83931aab1f8ba86abcc1543.json │ │ │ ├── c36b696b57c1deb51e47a057b32d1dd75e3f1560e83931aab1f8ba86abcc1543.json
│ │ │ ├── c3c6651e6b7dd802c31efb56ac20c1f1ba3c4c0108599f1480c6a143b2e688ef.json
│ │ │ ├── c46ad3fc8fd3db1315ab2e40add760b44e8f98c24c79e334c342c6a411fc671c.json │ │ │ ├── c46ad3fc8fd3db1315ab2e40add760b44e8f98c24c79e334c342c6a411fc671c.json
│ │ │ ├── c52300dce4cf464158f8db56df5030359a501a81bbe4a5f36e50c8c38a5ac48c.json │ │ │ ├── c52300dce4cf464158f8db56df5030359a501a81bbe4a5f36e50c8c38a5ac48c.json
│ │ │ ├── c535c8a7523b768d28903b84f4cfbce7d62181a2a05f3db08ec3a7c8cbf86ce8.json │ │ │ ├── c535c8a7523b768d28903b84f4cfbce7d62181a2a05f3db08ec3a7c8cbf86ce8.json
│ │ │ ├── c56beb43cf063fd1cc92950ca14695b543b3971bf7e7dbd4a676242057258533.json │ │ │ ├── c56beb43cf063fd1cc92950ca14695b543b3971bf7e7dbd4a676242057258533.json
│ │ │ ├── c5c93ccc7715c31fc7538bcd9327c8fb7c1e495bd17d6444b384ec426b7a231f.json │ │ │ ├── c5c93ccc7715c31fc7538bcd9327c8fb7c1e495bd17d6444b384ec426b7a231f.json
│ │ │ ├── c718a8f0cdfb66217ab4484dca103c7029d43976f7f0889bfb11f6cab3bb2510.json
│ │ │ ├── c72226544540cc75217ba02f1d09d989eb810d879409ac086c76245f27220cfa.json │ │ │ ├── c72226544540cc75217ba02f1d09d989eb810d879409ac086c76245f27220cfa.json
│ │ │ ├── c795f43bd1f10cf666ff987d18af538b2164c1683629ac689f0a54cb875118be.json │ │ │ ├── c795f43bd1f10cf666ff987d18af538b2164c1683629ac689f0a54cb875118be.json
│ │ │ ├── c7eb97fa16701c6d9ee531fa032479a7c771292f3e9d8921d3ea12a4897fd43a.json │ │ │ ├── c7eb97fa16701c6d9ee531fa032479a7c771292f3e9d8921d3ea12a4897fd43a.json
@@ -545,6 +565,7 @@ khatibi
│ │ │ ├── d1289d5406d360e3ff617e213db4e262cd43a32711a61aba97bdcd0a906cbcb5.json │ │ │ ├── d1289d5406d360e3ff617e213db4e262cd43a32711a61aba97bdcd0a906cbcb5.json
│ │ │ ├── d12cd2b31145213be1b4d89e7be64f3aecc122841cdeac7660eceae3dd871f01.json │ │ │ ├── d12cd2b31145213be1b4d89e7be64f3aecc122841cdeac7660eceae3dd871f01.json
│ │ │ ├── d12dccf07d58d7c0a2b5ea5b6aa821aa419fdc8b34a48939ebf9c565bc439551.json │ │ │ ├── d12dccf07d58d7c0a2b5ea5b6aa821aa419fdc8b34a48939ebf9c565bc439551.json
│ │ │ ├── d1392fc7270aa978a815d8dbe99ea56de021ee699664635d53b8df5b2988c835.json
│ │ │ ├── d1f2090208e1aab3b0e377278b02e9fc1007e5e1724c9000fb69d050365c8660.json │ │ │ ├── d1f2090208e1aab3b0e377278b02e9fc1007e5e1724c9000fb69d050365c8660.json
│ │ │ ├── d3456cd690135f9630e6b0bb79fef5d205804c7acabffc61cbd5ac4032b17bc7.json │ │ │ ├── d3456cd690135f9630e6b0bb79fef5d205804c7acabffc61cbd5ac4032b17bc7.json
│ │ │ ├── d51b2eb414ee4ea432987cd45d6b623013e994a6080c89cde57b69004b576e9d.json │ │ │ ├── d51b2eb414ee4ea432987cd45d6b623013e994a6080c89cde57b69004b576e9d.json
@@ -565,6 +586,7 @@ khatibi
│ │ │ ├── e2c62f6012dcae176d19340f6db8db3e94547ebbc4a21577b63caf35cc72ecfb.json │ │ │ ├── e2c62f6012dcae176d19340f6db8db3e94547ebbc4a21577b63caf35cc72ecfb.json
│ │ │ ├── e3ee8d9942c74a74be83ca00d5438e4a1e1cb5b23f55d95d308a42954e4757f0.json │ │ │ ├── e3ee8d9942c74a74be83ca00d5438e4a1e1cb5b23f55d95d308a42954e4757f0.json
│ │ │ ├── e86dc6bb7703d4329623bd33ccbc5e5e6c6daebf8d36d84fd415d6f868c779b0.json │ │ │ ├── e86dc6bb7703d4329623bd33ccbc5e5e6c6daebf8d36d84fd415d6f868c779b0.json
│ │ │ ├── e8738fdd0a67e92abd0ce636990ee001a2fc7586e9eda224915f33cb80d3b2c6.json
│ │ │ ├── e8e380264b41f789dddd25b7af5d0e3c517b957a14fe8ba2ce7bb8a529e005cf.json │ │ │ ├── e8e380264b41f789dddd25b7af5d0e3c517b957a14fe8ba2ce7bb8a529e005cf.json
│ │ │ ├── e9589ded446a5befb285a0a2e93a182ca9431c76661e68c9210ed252580dd7f0.json │ │ │ ├── e9589ded446a5befb285a0a2e93a182ca9431c76661e68c9210ed252580dd7f0.json
│ │ │ ├── e9e14d581ed59799b2d83c67402e8555b5b59e7d49425950ec490422bd0307d9.json │ │ │ ├── e9e14d581ed59799b2d83c67402e8555b5b59e7d49425950ec490422bd0307d9.json
@@ -577,6 +599,7 @@ khatibi
│ │ │ ├── efd113025c3fb3c0becaecd8090e8494b13d92ff3c49220d54665b3a8af586af.json │ │ │ ├── efd113025c3fb3c0becaecd8090e8494b13d92ff3c49220d54665b3a8af586af.json
│ │ │ ├── f081ba494afddbf02c2599aedee9fc9d38ccdb159ade7f07ce3bdad3323a001e.json │ │ │ ├── f081ba494afddbf02c2599aedee9fc9d38ccdb159ade7f07ce3bdad3323a001e.json
│ │ │ ├── f1af209a02f2ec5c5a344e3fa7dd8ad7d443e0479e55f5465449c0a2eed314a1.json │ │ │ ├── f1af209a02f2ec5c5a344e3fa7dd8ad7d443e0479e55f5465449c0a2eed314a1.json
│ │ │ ├── f2eacc54ffb4b9802045711c0fb0ac7210c95f21ed6ae78011fe0496951aa086.json
│ │ │ ├── f3a6285dcf9eb39df500bc0b76b03b5941103e8b90c06ffaeab1b3129f18fedc.json │ │ │ ├── f3a6285dcf9eb39df500bc0b76b03b5941103e8b90c06ffaeab1b3129f18fedc.json
│ │ │ ├── f3fe81220df98d0410b1505fa995df65427035dd552c85f1d80e35bb75a9aba1.json │ │ │ ├── f3fe81220df98d0410b1505fa995df65427035dd552c85f1d80e35bb75a9aba1.json
│ │ │ ├── f48decaf1220666fbda1a5328b963dbd999caab9ebbb1d84459d4c556a6e6ba7.json │ │ │ ├── f48decaf1220666fbda1a5328b963dbd999caab9ebbb1d84459d4c556a6e6ba7.json
@@ -740,6 +763,7 @@ khatibi
│ ├── test_inventory.py │ ├── test_inventory.py
│ ├── test_models_contract.py │ ├── test_models_contract.py
│ ├── test_sms.py │ ├── test_sms.py
│ ├── test_sms_phone_normalize.py
│ ├── test_urls_contract.py │ ├── test_urls_contract.py
│ └── test_view_smoke.py │ └── test_view_smoke.py
├── .clinerules ├── .clinerules
@@ -763,6 +787,7 @@ khatibi
├── khatibi.zip ├── khatibi.zip
├── local.env ├── local.env
├── manage.py ├── manage.py
├── normalize_customer_sms_phones.md
├── production.env ├── production.env
├── project_tree.py ├── project_tree.py
├── recalc_last_bazdid.md ├── recalc_last_bazdid.md
@@ -0,0 +1,37 @@
# Generated by Django 3.2.25 on 2026-10-16 10:49
from django.db import migrations, models
class Migration(migrations.Migration):
"""افزودن ``choices`` به ``Action.actionType``.
این migration فقط وضعیت مدل را به‌روز می‌کند: Django ``choices`` را در
``deconstruct()`` فیلد نگه می‌دارد، پس بدون این migration دستور
``makemigrations --check`` شکست می‌خورد. تعریف ستون در SQLite هیچ تغییری
نمی‌کند (``choices`` در DDL بازتاب نمی‌یابد) و هیچ داده‌ای بازنویسی نمی‌شود.
"""
dependencies = [
('admin_panel', '0072_customernamefixjob'),
]
operations = [
migrations.AlterField(
model_name='action',
name='actionType',
field=models.CharField(
blank=True,
choices=[
('bazdid', 'بازدید'),
('nasb', 'نصب'),
('vosol', 'وصول مطالبات'),
('hamahangi', 'هماهنگی'),
('inCall', 'تماس ورودی'),
('outCall', 'تماس خروجی'),
],
max_length=50,
null=True,
),
),
]
@@ -0,0 +1,37 @@
# Generated by Django 3.2.25 on 2026-10-16 10:49
from django.db import migrations, models
class Migration(migrations.Migration):
"""افزودن ``choices`` به ``Action.status``.
این migration فقط وضعیت مدل را به‌روز می‌کند: Django ``choices`` را در
``deconstruct()`` فیلد نگه می‌دارد، پس بدون این migration دستور
``makemigrations --check`` شکست می‌خورد. تعریف ستون در SQLite هیچ تغییری
نمی‌کند (``choices`` در DDL بازتاب نمی‌یابد) و هیچ داده‌ای بازنویسی نمی‌شود.
املای مقادیر عیناً همان چیزی است که در دیتابیس و کد موجود به‌کار رفته:
``'waite'`` (نه ``'wait'``)، ``'finish'`` و ``'cancell'``.
"""
dependencies = [
('admin_panel', '0073_alter_action_actiontype_choices'),
]
operations = [
migrations.AlterField(
model_name='action',
name='status',
field=models.CharField(
blank=True,
choices=[
('waite', 'در انتظار (باز)'),
('finish', 'انجام شده'),
('cancell', 'لغو شده'),
],
max_length=50,
null=True,
),
),
]
@@ -0,0 +1,49 @@
"""یادآوری تأمین موجودی چک: تغییر جهت از «پس از سررسید» به «پیش از سررسید».
هدف این migration **فقط** هم‌راستا‌کردن مقدار عددی ``offset_days`` قالب
``cheque_registration`` با معنای جدید است (تعداد روز **قبل** از سررسید).
آنچه این migration عمداً **نمی‌کند**:
* ``text`` (متن پیامک) را تغییر نمی‌دهد — متنی که مدیر در پنل تنظیم کرده دست‌نخورده
می‌ماند. اگر متن فعلی از زمان گذشته حرف می‌زند («سررسید شده است»)، مدیر می‌تواند
خودش از صفحهٔ «قالب‌های پیامک» ویرایشش کند.
* ``title``، ``is_enabled`` و ``auto_send`` را تغییر نمی‌دهد.
* هیچ نوع/قالب دیگری را لمس نمی‌کند.
مقدار ``offset_days`` روی پیش‌فرض معنای جدید (۱ روز قبل از سررسید) تنظیم می‌شود تا
نصب‌های تازه و نصب‌های موجود یکسان رفتار کنند؛ اما اگر مدیر قبلاً عددی غیر از مقدار
قدیمی (``1``) گذاشته باشد، به آن دست نمی‌زنیم تا انتخابش از بین نرود.
idempotent است؛ اجرای دوباره بی‌خطر است.
"""
from django.db import migrations
SMS_TYPE = "cheque_registration"
# مقدار پیش‌فرضِ منطق قدیم («۱ روز پس از سررسید») که با معنای جدید هم‌عددی است؛
# فقط رکوردهایی که هنوز روی همین مقدار هستند هم‌راستا می‌شوند.
OLD_DEFAULT = 1
NEW_DEFAULT = 1
def align_cheque_offset(apps, schema_editor):
"""``offset_days`` قالب چک را روی پیش‌فرض جدید بگذارد (بدون لمس متن)."""
SMSTemplate = apps.get_model("admin_panel", "SMSTemplate")
SMSTemplate.objects.filter(
sms_type=SMS_TYPE, offset_days=OLD_DEFAULT,
).update(offset_days=NEW_DEFAULT)
def noop(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
("admin_panel", "0074_alter_action_status_choices"),
]
operations = [
migrations.RunPython(align_cheque_offset, noop),
]
@@ -0,0 +1,36 @@
"""افزودن متغیر ``{sayyad_id}`` به راهنمای ``help_text`` متن قالب پیامک.
هدف: راهنمای متغیرها در پنل، «شناسهٔ صیاد چک» را هم نشان دهد.
آنچه این migration عمداً **نمی‌کند**:
* ``text`` (متن واقعی قالب‌ها) را تغییر نمی‌دهد — فقط ``help_text`` فیلد عوض شده
است که در ``deconstruct()`` می‌آید و state مدل را جلو می‌برد.
* هیچ رکورد ``SMSTemplate`` را لمس نمی‌کند.
idempotent است؛ اجرای دوباره بی‌خطر است.
"""
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("admin_panel", "0075_cheque_funding_before_due"),
]
operations = [
migrations.AlterField(
model_name="smstemplate",
name="text",
field=models.TextField(
help_text=(
"متغیرهای قابل استفاده: {name} نام مشتری، {amount} مبلغ، "
"{amount_sep} مبلغ با جداکننده، {date} تاریخ جلالی، "
"{days} تعداد روز، {check_row} شماره ردیف چک، "
"{sayyad_id} شناسه صیاد چک، {owner_name} نام مالک چک."
),
verbose_name="متن پیامک",
),
),
]
@@ -0,0 +1,39 @@
"""افزودن متغیرهای ``{balance}`` / ``{balance_sep}`` به راهنمای متن قالب پیامک.
هدف: راهنمای متغیرها در پنل، «مانده حساب مشتری» را هم نشان دهد و تفاوتش را با
مبلغ سررسیدشده روشن کند.
آنچه این migration عمداً **نمی‌کند**:
* ``text`` (متن واقعی قالب‌ها) را تغییر نمی‌دهد — فقط ``help_text`` فیلد عوض شده
است که در ``deconstruct()`` می‌آید و state مدل را جلو می‌برد.
* هیچ رکورد ``SMSTemplate`` را لمس نمی‌کند.
idempotent است؛ اجرای دوباره بی‌خطر است.
"""
from django.db import migrations, models
HELP_TEXT = (
"متغیرهای قابل استفاده: {name} نام مشتری، {amount} مبلغ، "
"{amount_sep} مبلغ با جداکننده، {date} تاریخ جلالی، {days} تعداد روز، "
"{check_row} شماره ردیف چک، {sayyad_id} شناسه صیاد چک، "
"{owner_name} نام مالک چک، {balance} مانده حساب مشتری، "
"{balance_sep} مانده حساب با جداکننده. "
"(توجه: {balance} مانده کل حساب است و با مبلغ سررسیدشدهٔ چک/نسیه "
"که در {amount} می‌آید تفاوت دارد.)"
)
class Migration(migrations.Migration):
dependencies = [
("admin_panel", "0076_alter_smstemplate_text_help"),
]
operations = [
migrations.AlterField(
model_name="smstemplate",
name="text",
field=models.TextField(help_text=HELP_TEXT, verbose_name="متن پیامک"),
),
]
+32 -2
View File
@@ -22,7 +22,35 @@ class VisitQueue(models.Model):
class Action(models.Model): class Action(models.Model):
actionType = models.CharField(max_length=50, null=True, blank=True) """اقدام/وظیفهٔ CRM مرتبط با مشتری.
``actionType`` فقط این شش مقدار را می‌پذیرد؛ همین شش مقدار در فرم‌های پنل
(``customerActionInc.html``) و در تمام مقایسه‌های کد (``== 'bazdid'``،
``exclude(actionType='vosol')`` و …) استفاده می‌شوند.
``status`` نیز همان سه مقدارِ به‌کاررفته در کد و قالب‌ها را می‌پذیرد.
املای ``'waite'`` عمداً حفظ شده است: ۲٫۷۱۹ رکورد موجودِ دیتابیس و تمام
مقایسه‌های کد/قالب (``== 'waite'``، ``if filter_status == 'waite'``) با
همین املا کار می‌کنند و تغییرش داده را از موتور جست‌وجو جدا می‌کند.
"""
class ActionType(models.TextChoices):
BAZDID = 'bazdid', 'بازدید'
NASB = 'nasb', 'نصب'
VOSOL = 'vosol', 'وصول مطالبات'
HAMAHANGI = 'hamahangi', 'هماهنگی'
IN_CALL = 'inCall', 'تماس ورودی'
OUT_CALL = 'outCall', 'تماس خروجی'
class Status(models.TextChoices):
# املا عیناً مطابق داده و کد فعلی است (نه 'wait').
WAITE = 'waite', 'در انتظار (باز)'
FINISH = 'finish', 'انجام شده'
CANCELL = 'cancell', 'لغو شده'
actionType = models.CharField(
max_length=50, null=True, blank=True, choices=ActionType.choices,
)
forUser = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) forUser = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
customer = models.ForeignKey('Customer', related_name='actionrlt', on_delete=models.CASCADE, null=True, blank=True) customer = models.ForeignKey('Customer', related_name='actionrlt', on_delete=models.CASCADE, null=True, blank=True)
createdDT = models.DateTimeField(auto_now_add=True) createdDT = models.DateTimeField(auto_now_add=True)
@@ -31,7 +59,9 @@ class Action(models.Model):
finishDateTime = models.DateTimeField(null=True, blank=True) finishDateTime = models.DateTimeField(null=True, blank=True)
desc = models.CharField(max_length=500, null=True, blank=True) desc = models.CharField(max_length=500, null=True, blank=True)
result = models.CharField(max_length=500, null=True, blank=True) result = models.CharField(max_length=500, null=True, blank=True)
status = models.CharField(max_length=50, null=True, blank=True) status = models.CharField(
max_length=50, null=True, blank=True, choices=Status.choices,
)
nazar = models.CharField(max_length=50, null=True, blank=True, default=None) nazar = models.CharField(max_length=50, null=True, blank=True, default=None)
nextNazarDT = models.DateTimeField(null=True, blank=True, default=None) nextNazarDT = models.DateTimeField(null=True, blank=True, default=None)
nazarDT = models.DateTimeField(null=True, blank=True, default=None) nazarDT = models.DateTimeField(null=True, blank=True, default=None)
+5 -1
View File
@@ -154,7 +154,11 @@ class SMSTemplate(models.Model):
verbose_name="متن پیامک", verbose_name="متن پیامک",
help_text="متغیرهای قابل استفاده: {name} نام مشتری، {amount} مبلغ، " help_text="متغیرهای قابل استفاده: {name} نام مشتری، {amount} مبلغ، "
"{amount_sep} مبلغ با جداکننده، {date} تاریخ جلالی، {days} تعداد روز، " "{amount_sep} مبلغ با جداکننده، {date} تاریخ جلالی، {days} تعداد روز، "
"{check_row} شماره ردیف چک، {owner_name} نام مالک چک.", "{check_row} شماره ردیف چک، {sayyad_id} شناسه صیاد چک، "
"{owner_name} نام مالک چک، {balance} مانده حساب مشتری، "
"{balance_sep} مانده حساب با جداکننده. "
"(توجه: {balance} مانده کل حساب است و با مبلغ سررسیدشدهٔ چک/نسیه "
"که در {amount} می‌آید تفاوت دارد.)",
) )
is_enabled = models.BooleanField(default=True, verbose_name="فعال") is_enabled = models.BooleanField(default=True, verbose_name="فعال")
auto_send = models.BooleanField( auto_send = models.BooleanField(
+44
View File
@@ -0,0 +1,44 @@
"""مانده حساب مشتری برای استفاده در قالب‌های پیامک.
تعریف (همان قاعدهٔ صفحهٔ حسابداری مشتری و گزارش مالی)::
مانده حساب = مجموع بدهکار (debit) − مجموع بستانکار (credit)
نکتهٔ مهم — تفاوت با «مانده سررسیدشده»:
* **مانده حساب** (این ماژول): تراز کل همهٔ اسناد حسابداری مشتری (``Article``).
منبع: ``admin_panel/models/finance.py::Article``.
* **مانده/مبلغ سررسیدشده**: مبلغ چک‌ها و نسیه‌های پرداخت‌نشده در جدول
``Sarresid`` (``payStatus='false'``). بسیار متفاوت از مانده حساب است و
جای دیگری (``views/finance.py``) محاسبه می‌شود.
به همین دلیل این مقدار با کلید مستقل ``{balance}`` در قالب‌ها منتشر می‌شود و
هرگز با مبلغ سررسید (``{amount}`` در پیامک چک/نسیه) قاطی نمی‌شود.
"""
import logging
logger = logging.getLogger(__name__)
def customer_balance(customer):
"""مانده حساب مشتری (``debit − credit``) به‌صورت عدد صحیح.
اگر مشتری یا اسنادش وجود نداشته باشد ``0`` برمی‌گردد. هیچ استثنایی پرتاب
نمی‌کند تا مسیر ساخت پیامک هرگز مختل نشود.
"""
if customer is None or getattr(customer, "pk", None) is None:
return 0
try:
from django.db.models import Sum
from ..models import Article
agg = Article.objects.filter(customer=customer).aggregate(
d=Sum("debit"), c=Sum("credit"),
)
debit = agg["d"] or 0
credit = agg["c"] or 0
return debit - credit
except Exception:
logger.exception("محاسبهٔ مانده حساب مشتری %s شکست خورد", customer.pk)
return 0
+10 -1
View File
@@ -5,6 +5,7 @@ from uuid import uuid4
from django.db import IntegrityError from django.db import IntegrityError
from ..models import SMSDedup, SMSQueue, SMSTemplate, SMSType from ..models import SMSDedup, SMSQueue, SMSTemplate, SMSType
from .balance import customer_balance
from .recipients import resolve_recipients from .recipients import resolve_recipients
from .render import render_template from .render import render_template
from .rules import is_sms_enabled from .rules import is_sms_enabled
@@ -22,6 +23,11 @@ def enqueue_sms(*, customer=None, sms_type, context=None, dedup_key=None,
یا آزمایشی)، و ``SMSTemplate.auto_send`` که مدیر در صفحهٔ قالب‌ها برای هر نوع یا آزمایشی)، و ``SMSTemplate.auto_send`` که مدیر در صفحهٔ قالب‌ها برای هر نوع
پیامک روشن می‌کند تا پیامک‌های خودکارِ اسکنر بدون صف تأیید ارسال شوند. پیامک روشن می‌کند تا پیامک‌های خودکارِ اسکنر بدون صف تأیید ارسال شوند.
متغیرهای مشترکِ همهٔ قالب‌ها (مثل ``{balance}`` = مانده حساب مشتری) همین‌جا و
یک‌بار به ``context`` افزوده می‌شوند تا هر قالبی بتواند از آن‌ها استفاده کند؛
فراخوان‌ها لازم نیست هر بار آن‌ها را بسازند. اگر فراخوان خودش مقداری داده باشد
(``context``) همان اولویت دارد.
هیچ استثنایی به فراخوان پرتاب نمی‌شود؛ خطاها فقط لاگ می‌شوند تا مسیر اصلی هیچ استثنایی به فراخوان پرتاب نمی‌شود؛ خطاها فقط لاگ می‌شوند تا مسیر اصلی
(مثلاً ایجاد مشتری یا اسکنر) هرگز به‌خاطر پیامک مختل نشود. (مثلاً ایجاد مشتری یا اسکنر) هرگز به‌خاطر پیامک مختل نشود.
""" """
@@ -30,7 +36,10 @@ def enqueue_sms(*, customer=None, sms_type, context=None, dedup_key=None,
template = SMSTemplate.objects.filter(sms_type=sms_type, is_enabled=True).first() template = SMSTemplate.objects.filter(sms_type=sms_type, is_enabled=True).first()
if template is None: if template is None:
return 0 return 0
message = render_template(template.text, context) ctx = dict(context or {})
# متغیرهای سراسری قالب‌ها؛ هرگز مقدار فراخوان را بازنویسی نمی‌کنند.
ctx.setdefault("balance", customer_balance(customer))
message = render_template(template.text, ctx)
# «ارسال خودکار» هر نوع پیامک در صفحهٔ قالب‌ها تعیین می‌شود؛ خاموش = بازبینی مدیر. # «ارسال خودکار» هر نوع پیامک در صفحهٔ قالب‌ها تعیین می‌شود؛ خاموش = بازبینی مدیر.
approved = approved or template.auto_send approved = approved or template.auto_send
+15 -1
View File
@@ -3,11 +3,21 @@ from persiantools.jdatetime import JalaliDate, JalaliDateTime
class _SafeDict(dict): class _SafeDict(dict):
"""دیکشنری که برای کلیدهای ناشناخته رشته خالی برمی‌گرداند (بدون خطا).""" """دیکشنری امن برای ``str.format_map``.
* کلیدِ ناشناخته ⇒ رشتهٔ خالی (بدون ``KeyError``).
* کلیدِ شناخته‌شده با مقدار ``None`` ⇒ رشتهٔ خالی. فیلدهای اختیاری مدل
(مثل ``Sarresid.check_sayyad_id`` یا ``check_row_number``) ممکن است ``NULL``
باشند؛ بدون این تبدیل، ``None`` عیناً در متن پیامک چاپ می‌شد.
"""
def __missing__(self, key): def __missing__(self, key):
return "" return ""
def __getitem__(self, key):
value = super().__getitem__(key) if key in self else ""
return "" if value is None else value
def format_amount(value): def format_amount(value):
try: try:
@@ -35,6 +45,10 @@ def render_template(text, context=None):
ctx = dict(context or {}) ctx = dict(context or {})
if "amount" in ctx: if "amount" in ctx:
ctx.setdefault("amount_sep", format_amount(ctx["amount"])) ctx.setdefault("amount_sep", format_amount(ctx["amount"]))
if "balance" in ctx:
# مثل «مبلغ»، نسخهٔ جداکننده‌دار هم آماده می‌شود تا در قالب‌ها
# ``{balance_sep}`` در دسترس باشد (۳٬۵۰۰٬۰۰۰ → "3,500,000").
ctx.setdefault("balance_sep", format_amount(ctx["balance"]))
if "date" in ctx: if "date" in ctx:
ctx.setdefault("date_raw", ctx["date"]) ctx.setdefault("date_raw", ctx["date"])
ctx["date"] = format_jalali(ctx["date"]) ctx["date"] = format_jalali(ctx["date"])
+23 -10
View File
@@ -81,36 +81,49 @@ class _Budget:
def scan_cheque_funding(today): def scan_cheque_funding(today):
"""یادآوری «تأمین موجودی چک» چند روز پس از سررسید چکِ پرداخت‌نشده. """یادآوری «تأمین موجودی چک» چند روز **پیش از** سررسید چکِ پرداخت‌نشده.
چند روزِ پس از سررسید از قالب همین نوع (قابل تنظیم از پنل) خوانده می‌شود و مبلغ تعداد روزِ *قبل* از سررسید از قالب همین نوع (``offset_days`` در دیتابیس،
چک در متن قرار می‌گیرد. اگر چک پرداخت شود (``payStatus=true``) دیگر یادآوری قابل ویرایش از پنل) خوانده می‌شود؛ مثلاً ``offset_days = 1`` یعنی یک روز پیش
نمی‌گیرد؛ ``dedup_key`` نیز مانع ارسال تکراری در اجراهای بعدی می‌شود. اگر اسکنر از سررسید پیامک می‌رود تا مشتری فرصت تأمین موجودی داشته باشد.
چند روز از دست برود، چک‌های «معوق‌تر» هم واجد شرط می‌شوند تا یادآوری از دست نرود.
پنجرهٔ یادآوری از «سررسید − offset روز» باز می‌شود و تا خودِ سررسید ادامه
دارد؛ پس اگر اسکنر چند روز از دست برود، یادآوری از دست نمی‌رود (چک‌هایی که
سررسیدشان رسیده هم واجد شرط می‌مانند). اگر چک پرداخت شود
(``payStatus=true``) دیگر یادآوری نمی‌گیرد؛ ``dedup_key`` نیز مانع ارسال
تکراری در اجراهای بعدی می‌شود.
""" """
template = _template(SMSType.CHEQUE_REGISTRATION) template = _template(SMSType.CHEQUE_REGISTRATION)
if template is None: if template is None:
return 0 return 0
budget = _Budget(SMSProviderConfig.load()) budget = _Budget(SMSProviderConfig.load())
offset = template.offset_days or 0 offset = template.offset_days or 0
target = today - timedelta(days=offset) # پنجرهٔ یادآوری: از «سررسید − offset» تا «سررسید». یعنی فقط سررسیدهای
# امروز تا offset روز آینده واجد شرط‌اند و مقادیر دورتر هنوز پیامکی نمی‌گیرند.
# (سررسیدهای گذشته هم داخل شرط می‌مانند تا یک یادآوری از دست نرود.)
window_end = today + timedelta(days=max(offset, 0))
qs = Sarresid.objects.filter( qs = Sarresid.objects.filter(
type="check", payStatus="false", sarresidDT__date__lte=target, type="check", payStatus="false", sarresidDT__date__lte=window_end,
).select_related("customer").order_by("sarresidDT") ).select_related("customer").order_by("sarresidDT")
count = 0 count = 0
for s in qs: for s in qs:
if not s.customer or not s.sarresidDT: if not s.customer or not s.sarresidDT:
continue continue
# لحظهٔ باز شدن پنجرهٔ یادآوری = سررسید + offset روز. due = s.sarresidDT.date()
trigger = _aware(s.sarresidDT) + timedelta(days=offset) # لحظهٔ باز شدن پنجرهٔ یادآوری = سررسید − offset روز.
trigger = _aware(s.sarresidDT) - timedelta(days=offset)
if not budget.can_send(trigger): if not budget.can_send(trigger):
continue continue
days = (today - s.sarresidDT.date()).days # روزهای باقی‌مانده تا سررسید؛ اگر سررسید دورتر از پنجره باشد پیامک نمی‌رود.
days = (due - today).days
if days > offset:
continue
made = enqueue_sms( made = enqueue_sms(
customer=s.customer, sms_type=SMSType.CHEQUE_REGISTRATION, customer=s.customer, sms_type=SMSType.CHEQUE_REGISTRATION,
context={ context={
"name": s.customer.name_family, "amount": s.price, "name": s.customer.name_family, "amount": s.price,
"date": s.sarresidDT, "days": days, "check_row": s.check_row_number, "date": s.sarresidDT, "days": days, "check_row": s.check_row_number,
"sayyad_id": s.check_sayyad_id, "owner_name": s.check_owner_name,
}, },
dedup_key=f"cheque_fund:{s.id}", dedup_key=f"cheque_fund:{s.id}",
) )
+3 -3
View File
@@ -27,11 +27,11 @@ TIMING = {
"note": "بی‌درنگ پس از ثبت فاکتور ارسال می‌شود؛ تنظیم زمانی ندارد.", "note": "بی‌درنگ پس از ثبت فاکتور ارسال می‌شود؛ تنظیم زمانی ندارد.",
}, },
SMSType.CHEQUE_REGISTRATION: { SMSType.CHEQUE_REGISTRATION: {
"kind": AFTER_DUE, "kind": BEFORE_DUE,
"field": "offset_days", "field": "offset_days",
"label": "چند روز پس از سررسید چک", "label": "چند روز قبل از سررسید چک",
"default": 1, "default": 1,
"note": "این تعداد روز پس از سررسید چکِ پرداخت‌نشده، پیامک یادآوری " "note": "این تعداد روز پیش از سررسید چکِ پرداخت‌نشده، پیامک یادآوری "
"تأمین موجودی با مبلغ چک ارسال می‌شود.", "تأمین موجودی با مبلغ چک ارسال می‌شود.",
}, },
SMSType.VISIT: { SMSType.VISIT: {
+5 -5
View File
@@ -12,6 +12,7 @@ from persiantools.jdatetime import JalaliDate
from ..models import (Customer, PhoneNumber, ProductCategory, Product, Factor, from ..models import (Customer, PhoneNumber, ProductCategory, Product, Factor,
FactorIteam, Article, Sarresid, Action, CacheTable) FactorIteam, Article, Sarresid, Action, CacheTable)
from ..sms.balance import customer_balance
from ..sms.phones import (normalize_primary_values, from ..sms.phones import (normalize_primary_values,
save_primary_normalized) save_primary_normalized)
from ..sms.triggers import notify_payment_thanks, notify_welcome from ..sms.triggers import notify_payment_thanks, notify_welcome
@@ -437,14 +438,13 @@ def customerDel(request, c_id):
@login_required(login_url='/panel/p_login/') @login_required(login_url='/panel/p_login/')
def customerHesabdari(request ,c_id): def customerHesabdari(request ,c_id):
customer = Customer.objects.get(id=c_id) customer = Customer.objects.get(id=c_id)
# مانده حساب از همان تابع مشترکی می‌آید که قالب‌های پیامک استفاده می‌کنند تا
# عددِ این صفحه و پیامک هرگز واگرا نشوند (منبع حقیقت: sms/balance.py).
mandehesabCs = customer_balance(customer)
try: try:
debit = Article.objects.filter(customer=customer).aggregate(Sum('debit'))['debit__sum']
credit = Article.objects.filter(customer=customer).aggregate(Sum('credit'))['credit__sum']
mandehesabCs = debit - credit
articls = Article.objects.filter(customer=customer).order_by('-createdDT') articls = Article.objects.filter(customer=customer).order_by('-createdDT')
except: except:
articls =None articls = None
mandehesabCs = 0
dateNow = datetime.datetime.now().date dateNow = datetime.datetime.now().date
data = {'customer': customer, 'mandehesabCs': mandehesabCs, 'articls': articls} data = {'customer': customer, 'mandehesabCs': mandehesabCs, 'articls': articls}
+7 -1
View File
@@ -64,14 +64,17 @@ TEMPLATE_VARIABLES = [
("{date}", "تاریخ جلالی"), ("{date}", "تاریخ جلالی"),
("{days}", "تعداد روز"), ("{days}", "تعداد روز"),
("{check_row}", "شمارهٔ ردیف چک"), ("{check_row}", "شمارهٔ ردیف چک"),
("{sayyad_id}", "شناسهٔ چک صیاد (۱۶ رقم)"),
("{owner_name}", "نام مالک چک"), ("{owner_name}", "نام مالک چک"),
("{balance}", "مانده حساب مشتری (بدهکار − بستانکار)"),
("{balance_sep}", "مانده حساب با جداکنندهٔ هزارگان"),
] ]
# فقط برای نمایش: آیکن و توضیح کوتاه «چه زمانی ارسال می‌شود» هر نوع (لایهٔ UI، نه دامنه). # فقط برای نمایش: آیکن و توضیح کوتاه «چه زمانی ارسال می‌شود» هر نوع (لایهٔ UI، نه دامنه).
SMS_TYPE_UI = { SMS_TYPE_UI = {
SMSType.WELCOME: {"icon": "👋", "when": "بی‌درنگ پس از ثبت مشتری"}, SMSType.WELCOME: {"icon": "👋", "when": "بی‌درنگ پس از ثبت مشتری"},
SMSType.SURVEY: {"icon": "⭐", "when": "بی‌درنگ پس از ثبت فاکتور"}, SMSType.SURVEY: {"icon": "⭐", "when": "بی‌درنگ پس از ثبت فاکتور"},
SMSType.CHEQUE_REGISTRATION: {"icon": "🧾", "when": "چند روز پس از سررسید چک"}, SMSType.CHEQUE_REGISTRATION: {"icon": "🧾", "when": "چند روز پیش از سررسید چک"},
SMSType.VISIT: {"icon": "🔧", "when": "پس از گذشت دورهٔ بازدید دستگاه"}, SMSType.VISIT: {"icon": "🔧", "when": "پس از گذشت دورهٔ بازدید دستگاه"},
SMSType.VISIT_CREATED: {"icon": "📅", "when": "پس از ثبت اقدام بازدید"}, SMSType.VISIT_CREATED: {"icon": "📅", "when": "پس از ثبت اقدام بازدید"},
SMSType.PAYMENT_THANKS: {"icon": "🙏", "when": "پس از ثبت پرداخت"}, SMSType.PAYMENT_THANKS: {"icon": "🙏", "when": "پس از ثبت پرداخت"},
@@ -86,7 +89,10 @@ SAMPLE_CONTEXT = {
"date": None, # در زمان ساخت ویو با تاریخ امروز پر می‌شود "date": None, # در زمان ساخت ویو با تاریخ امروز پر می‌شود
"days": 2, "days": 2,
"check_row": "1234", "check_row": "1234",
"sayyad_id": "1234567890123456",
"owner_name": "نام مالک", "owner_name": "نام مالک",
"balance": 3500000,
"balance_sep": "3,500,000",
} }
+63 -50
View File
@@ -2,117 +2,117 @@
"0": "views/sms.py", "0": "views/sms.py",
"1": "run_all", "1": "run_all",
"2": "django_db", "2": "django_db",
"3": "views/store.py", "3": "create_transfer_request",
"4": "ModelsPackageContractTests", "4": "ModelsPackageContractTests",
"5": "PhoneCleanupTests", "5": "PhoneCleanupTests",
"6": ".load", "6": ".load",
"7": "models/sms.py", "7": "models/sms.py",
"8": "logging", "8": "models/__init__.py",
"9": "views/customers.py", "9": "views/customers.py",
"10": "django_db_models_deletion", "10": "django_conf",
"11": "make_customer", "11": "make_customer",
"12": "customer_phone_entries", "12": "resolve_recipients",
"13": "EventTriggerTests", "13": "EventTriggerTests",
"14": "views/finance.py", "14": "views/finance.py",
"15": "SMSResult", "15": "views/store.py",
"16": "sms0098ws.py", "16": "sms0098ws.py",
"17": "Admin Panel Customization", "17": "Admin Panel Customization",
"18": "User", "18": "User",
"19": "django_conf", "19": "timing_for",
"20": "test_customers.py", "20": "Customer",
"21": "phones.py", "21": "phones.py",
"22": "Login Template Forms", "22": "Login Template Forms",
"23": "cheque_inquiry.py", "23": "cheque_inquiry.py",
"24": "factories.py", "24": "factories.py",
"25": "Rip'n Fast. Fewer Tokens. Better Code.", "25": "Rip'n Fast. Fewer Tokens. Better Code.",
"26": "views/factors.py", "26": "views/factors.py",
"27": "to_local", "27": "sms0098.py",
"28": "make_user", "28": "make_user",
"29": "QueueReviewTests", "29": "QueueReviewTests",
"30": "NoticeMechanismTests", "30": "NoticeMechanismTests",
"31": "enqueue.py", "31": "SmsReportTests",
"32": "enqueue_sms", "32": "scanner.py",
"33": "PanelTestCase", "33": "ZeroZeroNinetyEightWebServiceTests",
"34": "ProductInStore", "34": "ProductInStore",
"35": "admin_panel/templates/admin_panel/baseHesabdari.html", "35": "admin_panel/templates/admin_panel/baseHesabdari.html",
"36": "Reference guide", "36": "Reference guide",
"37": "Factor Creation UI", "37": "Factor Creation UI",
"38": "admin_panel/templates/admin_panel/bedehi.html", "38": "hesabdariResidNashode.html",
"39": "project_tree.py", "39": "requests",
"40": "CustomerSmsHistoryTests", "40": "CustomerSmsHistoryTests",
"41": "TemplatesPageUITests", "41": "TemplatesPageUITests",
"42": "SMS Integration Docs", "42": "SMS Integration Docs",
"43": "timing_for", "43": "enqueue.py",
"44": "django_shortcuts", "44": "django_shortcuts",
"45": "stock_in", "45": "stock_in",
"46": "admin_panel/templates/admin_panel/customerAction.html", "46": "admin_panel/templates/admin_panel/customerAction.html",
"47": "parts_count", "47": "parts_count",
"48": "write_tree", "48": "write_tree",
"49": "CustomerInfoCardUITests", "49": "CustomerInfoCardUITests",
"50": "Customer", "50": "test_sms.py",
"51": "Phone Cleanup Guide", "51": "Phone Cleanup Guide",
"52": "SmsReportTests", "52": ".run_command",
"53": "manage.py", "53": "project_tree.py",
"54": "Agent Operating Guidelines", "54": "Agent Operating Guidelines",
"55": "SMSDeliveryCommandTests", "55": "Command",
"56": "BehinSMSProvider", "56": "BehinSMSProvider",
"57": "test_sms.py", "57": "normalize_mobile",
"58": "Financial Data Objects", "58": "Financial Data Objects",
"59": "Hybrid Migration Command", "59": "Hybrid Migration Command",
"60": "بازمحاسبهٔ «آخرین بازدید» مشتریان", "60": ".run_cmd",
"61": "Resource Cleanup Handler", "61": "Resource Cleanup Handler",
"62": "SMS Timing Migration", "62": "SMS Timing Migration",
"63": "Articl Model", "63": "admin_panel/templates/admin_panel/bedehi.html",
"64": "customerIDFactorBackup.html", "64": "customerIDFactorBackup.html",
"65": "test_factors.py", "65": "bale/providers.py",
"66": "os", "66": "asgi.py",
"67": "Graphify Tooling Docs", "67": "Graphify Tooling Docs",
"68": "django_urls", "68": "Command",
"69": "0063_alter_smsqueue_status.py", "69": "ZeroZeroNinetyEightLinkTests",
"70": "SMS Template Seeding", "70": "SMS Template Seeding",
"71": "admin_panel/entezar.html", "71": "admin_panel/entezar.html",
"72": "customerNewBackup.html", "72": "customerNewBackup.html",
"73": "hesabdariResidNashode.html", "73": "SMSResult",
"74": "0070_sms_types_seven.py", "74": "0070_sms_types_seven.py",
"75": "Endpoint Smoke Tests", "75": "Endpoint Smoke Tests",
"76": "Timezone Migration History", "76": "Timezone Migration History",
"77": "models/__init__.py", "77": "Command",
"78": "UrlContractTests", "78": "django_urls",
"79": "Brand Scrubbing Migration", "79": "Brand Scrubbing Migration",
"80": "SMS Dedup Migration", "80": "SMS Dedup Migration",
"81": "Installation Guide", "81": "Installation Guide",
"82": "Project Honesty Contract", "82": "Project Honesty Contract",
"83": "Flow Control Migration", "83": "Flow Control Migration",
"84": "Permission Label Migration", "84": "Permission Label Migration",
"85": "stop.sh", "85": "IntegrationAndEfficiencyTests",
"86": "Output Format Specs", "86": "Output Format Specs",
"87": "Performance Benchmarks", "87": "Performance Benchmarks",
"88": "SMS Integration Overview", "88": "SMS Integration Overview",
"89": "System Cache Table", "89": "CustomerNameFixJob",
"90": "Customer Modal UI", "90": "Customer Modal UI",
"91": "SentQueueProtectionTests", "91": "PanelTestCase",
"92": "Infrastructure Services", "92": "Infrastructure Services",
"93": "Agent Setup Guide", "93": "Agent Setup Guide",
"94": "First Use Guide", "94": "First Use Guide",
"95": "0056_auto_20260731_1824.py", "95": "ConsoleSMSProvider",
"96": "0009_remove_storeroom_user.py", "96": "PhoneNumber",
"97": "PhoneEntry", "97": "تجمیع شماره‌های پیامک مشتریان",
"98": "reconciliation_tool.py", "98": "Command",
"99": "announcements.py", "99": "Base Template",
"100": "pathlib", "100": "test_customer_name_fix.py",
"101": "Command", "101": "Command",
"102": "0064_sms_webservice_delivery.py", "102": "customer_phone_entries",
"103": "0040_customer_laghv_bazdid.py", "103": "manage.py",
"104": "0068_auto_20261010_1305.py", "104": "افزودن پیشوند «آقای/خانم» به نام مشتریان",
"105": "0069_smstemplate_auto_send.py", "105": "بازمحاسبهٔ «آخرین بازدید» مشتریان",
"106": "0047_alter_productinstore_movement_type.py", "106": "QueueStatsTests",
"107": "Command", "107": "admin_panel/apps.py",
"108": "SMS Domain Logic", "108": "SMS Domain Logic",
"109": "Customer Communication Partials", "109": "Customer Communication Partials",
"110": "SMS Template Management", "110": "SMS Template Management",
"111": "Documentation and Graphing", "111": "Documentation and Graphing",
"112": "Admin Migration Package", "112": "Admin Migration Package",
"113": "bale/providers.py", "113": "primary_phones",
"114": "Customer Action Templates", "114": "Customer Action Templates",
"115": "Customer Feedback Components", "115": "Customer Feedback Components",
"116": "Customer Invoice Details", "116": "Customer Invoice Details",
@@ -152,6 +152,7 @@
"150": "Transfer Processing Endpoint", "150": "Transfer Processing Endpoint",
"151": "Satisfaction Edit Endpoint", "151": "Satisfaction Edit Endpoint",
"152": "Customer Deletion Endpoint", "152": "Customer Deletion Endpoint",
"153": "HttpProviderRobustnessTests",
"154": "Django Permissions System", "154": "Django Permissions System",
"155": "Static Asset Management", "155": "Static Asset Management",
"156": "Data Cleanup Documentation", "156": "Data Cleanup Documentation",
@@ -161,13 +162,25 @@
"160": "Khatibi Custom Module", "160": "Khatibi Custom Module",
"161": "Project Dependencies", "161": "Project Dependencies",
"162": "Test Suite Package", "162": "Test Suite Package",
"164": "QueueStatsTests", "163": "Customer",
"166": "sms_settings", "164": "SMSQueue",
"165": "signals.py",
"166": "توقف و ادامه (Resume / Checkpoint)",
"167": "Command", "167": "Command",
"168": "0071_sms_settings_permission.py", "168": "0071_sms_settings_permission.py",
"169": "index", "169": "index",
"172": "0017_factoriteam_garantyesh.py", "170": "الگوریتم تصمیم (به همین ترتیب)",
"173": "0028_action_grade.py", "171": "سریع‌ترین مسیر روی Production (خلاصهٔ اجرایی)",
"174": "0034_alter_article_sarresid.py", "172": "models/customers.py",
"175": "0039_customer_last_bazdid_dt.py" "173": "stop.sh",
"174": "SMSDedup",
"175": "rules.py",
"176": "اجرای محلی (توسعه)",
"177": "Factor",
"178": "اجرا روی سرور Production",
"179": "اجرای محلی (توسعه)",
"180": "ادامهٔ job پس از crash / قطع برق",
"181": "اجرا روی سرور Production",
"182": "wsgi.py",
"189": "django_contrib_auth_models"
} }
+1 -1
View File
@@ -1 +1 @@
{"0": "41d4c54b2ed9be31", "1": "b3e9db47e46c8950", "2": "c23f6f22b3532a57", "3": "44c42f7cbafe0d72", "4": "a480b4d6afdb4d1b", "5": "a8d6a179fdf0eb6f", "6": "3bd1354c22863397", "7": "88c19438cd72b3dd", "8": "15ca8060fedfc22d", "9": "274a49fd42c1a00e", "10": "58991daa10bb40c9", "11": "892dbef1deee47a9", "12": "f30060e6c2359aa4", "13": "96ec15fbcfe2d27a", "14": "05fc19ac0daa7b6c", "15": "7fe7625f0e064fdb", "16": "60b1fcd46cba604e", "17": "9eec9953ee3ca67f", "18": "dd5464972fce7e01", "19": "1f155e80fa7aa135", "20": "5c5477821216359a", "21": "13d087c92ddf57ef", "22": "9701f1591d8fda6e", "23": "7cd46ce4c8fb3ca4", "24": "d0b49ec6ae8ee7a4", "25": "75866dc8db907971", "26": "c578cf70cb6202fa", "27": "5c3fd3713dd960e8", "28": "1f2e16b5709d3936", "29": "1e9c1fa6fabde0d3", "30": "627dae19704f40f5", "31": "a5bd616f03cdbec3", "32": "ba49af147f38b69e", "33": "509eeabd8a23e031", "34": "f5248661c2005b5a", "35": "e091859c108d6145", "36": "4e9567703b727de7", "37": "d36df3c85181f806", "38": "b9fa892721272dc1", "39": "219b01f43ec5f76d", "40": "89444f5d51189c99", "41": "8c2e4ed6cd161bf3", "42": "bbd8a9066e48dada", "43": "781d0380d9314204", "44": "159d7e82897e35fc", "45": "0827f8a8bc343f88", "46": "a2f2be073f6f5bb9", "47": "8fd5a6f3cd5970b9", "48": "9d46bbe4641a8543", "49": "7bc001888a685a2a", "50": "dc8e4b85d8dd63ea", "51": "afc6d855b9fc1b76", "52": "11d769da5f6dd93e", "53": "3ec6277e63307488", "54": "34401beb9f60ebfd", "55": "054037b18f80b80c", "56": "330644cc56a0e06c", "57": "a3022e2d8c68315c", "58": "90146abe85d9419f", "59": "88f880da5a61f9dc", "60": "69bb788e4cabbf39", "61": "dbfbf8557361f5ea", "62": "6e90b41fbd82c265", "63": "bf2566d91998d2e4", "64": "8ff365aee1c1a767", "65": "120dc3cc659bbc4a", "66": "41c8f6c33d904f01", "67": "0f632fc57e9feeb0", "68": "2db67510f080898c", "69": "84208a7b8507da66", "70": "df7ea8ce3fa52be3", "71": "7db54a3d7ae0017c", "72": "aea8fc3ff351253f", "73": "af89b22ae0662d6d", "74": "a534e43d03671a7c", "75": "5290f7a7558a9c3e", "76": "a19c211702401a5b", "77": "7f4cbca34cc36f61", "78": "0f4a56d55e3925dc", "79": "da683f642f8c61ac", "80": "40313433c93b7054", "81": "81af0a28b68936d9", "82": "25287261c3d1c44b", "83": "bda9be183e0f933e", "84": "63526d01888c4f22", "85": "827420b645a55301", "86": "694a38d63caf7217", "87": "f220eaa4fac26e3e", "88": "d78c8e21a45613e9", "89": "c5743041b2101132", "90": "03f8896e9b60ca96", "91": "a06651e191a4aa22", "92": "4bfa64a1cd8c6a47", "93": "057eae5a523d2004", "94": "5de74655cbc56e06", "95": "cfbcf8cef78c5757", "96": "a1a1f3c1cdff72cf", "97": "72fca8ce97e3eeee", "98": "2ae07d5b9790c230", "99": "4793efd518dcb021", "100": "1b94d53a14e88aa3", "101": "35aa0fc2a0626f70", "102": "cb48fd8d430ac66e", "103": "ef8221c5fdeee7f8", "104": "9144923ab31b52a3", "105": "a2eea51406c7d4fc", "106": "b5ce3fce9192f8b8", "107": "7508d3e84c588617", "108": "b738c5aefe939cff", "109": "d2001d7d283134ac", "110": "6b8e6100893a6b6a", "111": "66ed76491a412a72", "112": "e67668b62ee6c62c", "113": "7b932cfef527d130", "114": "68aee39cd24db354", "115": "054401d27ffaa4b2", "116": "dace81c1995fb321", "117": "19e301aeace95bb2", "118": "d6fd0cec4634a0b0", "119": "0230d355a6dabb54", "120": "5cebbb5528321795", "121": "536fe28d24c361f5", "122": "b08bdc2712cb0f12", "123": "6ce8444f504022cc", "124": "8021f52559633383", "125": "d8e34a3b3eb43ee0", "126": "171c34c74a95481b", "127": "73e9eca6f2ca1c9b", "128": "dcc2230ae751c29a", "129": "2a617b932f5d92b2", "130": "d55cb638532fdfeb", "131": "06cae4389208269f", "132": "e9c0a7be255e9b26", "133": "455b4ca39e55ecb0", "134": "8105c55538c34081", "135": "74304670b54c018f", "136": "a27f876eabb044b2", "137": "73f74dd7a40776d6", "138": "29d433d164b99f89", "139": "3f44aa48aa05d597", "140": "ca8a7efba0a3e579", "141": "5cc770af9d184d77", "142": "2d31fa723128b164", "143": "be720fc0071f0c3d", "144": "9d814c2520329846", "145": "f97d1a5c3a066598", "146": "14f84a6e4cd311d6", "147": "3a592d27b1f4ccc9", "148": "0bf6f9d0aeab4fd8", "149": "8b491bb3ff0649dd", "150": "638812cee15d1b9b", "151": "3dd16d68928a479c", "152": "03a0ee43e18f7934", "154": "435331945d6aad17", "155": "968e1a135dc062b7", "156": "d1a987a548e55a76", "157": "a9bf69b3d550abef", "158": "b039e1c69c8fb7b9", "159": "60029bf0948fc02a", "160": "72dad91d66d471ce", "161": "353a9d09eaae47ea", "162": "43bbf35b96423d46", "164": "825ba1cf2cfa5500", "166": "9fee6d982ee873a6", "167": "ce80aac5f4c439da", "168": "1abe2e7b0bcbeca9", "169": "ce5f237abed01c9e", "172": "a800ecffc01fc9d5", "173": "edb619bf5ac0fc49", "174": "c02bc9595eaedd36", "175": "52502a4c1ef75ce6"} {"0": "038518c7d9194bab", "1": "06dae62b458fc666", "2": "f61ab1111a89fc50", "3": "e61c3927bdba862c", "4": "11012e19786f67ca", "5": "22b97ba625f8378b", "6": "65060bb181af0bf2", "7": "3ce7acc47ea68c9f", "8": "fabc1e3307709f1a", "9": "123d258529fa9882", "10": "237c411f20dd22bb", "11": "fee454d62d24fd21", "12": "a62dcf8197187c85", "13": "96ec15fbcfe2d27a", "14": "9547a4a3dbac41ac", "15": "499026e81d8e6a05", "16": "1b80b390c6d35082", "17": "9eec9953ee3ca67f", "18": "f04ceeae6fdfe7c6", "19": "781d0380d9314204", "20": "895beea340408433", "21": "1e32348234be9dd4", "22": "9701f1591d8fda6e", "23": "8f0f51ff611eb425", "24": "102a0c450b7cb6b3", "25": "75866dc8db907971", "26": "b6a24d75ed17d59c", "27": "9b6b55a8d8dcb4c4", "28": "1f2e16b5709d3936", "29": "1e9c1fa6fabde0d3", "30": "627dae19704f40f5", "31": "11d769da5f6dd93e", "32": "9f3159f2dae4ab4f", "33": "8794530c7ccb06cb", "34": "4864a4651499f51b", "35": "0928f3aa4e597117", "36": "4e9567703b727de7", "37": "d36df3c85181f806", "38": "bc695fcee2050806", "39": "3557c20b0ad29e99", "40": "89444f5d51189c99", "41": "8c2e4ed6cd161bf3", "42": "bbd8a9066e48dada", "43": "6378ca56250cfe26", "44": "00d4d9fc79267971", "45": "a7e9b25451da94c4", "46": "f38c1f1dc0ead50f", "47": "8fd5a6f3cd5970b9", "48": "9d46bbe4641a8543", "49": "7bc001888a685a2a", "50": "4c34de7757ef1e08", "51": "afc6d855b9fc1b76", "52": "e9a7c031e64eeadb", "53": "afa933e731f6344a", "54": "34401beb9f60ebfd", "55": "05213eed8eed2d6f", "56": "330644cc56a0e06c", "57": "20956521d16ce674", "58": "90146abe85d9419f", "59": "88f880da5a61f9dc", "60": "f723d0b8f5c4997b", "61": "dbfbf8557361f5ea", "62": "6e90b41fbd82c265", "63": "6af26e59b0e8c354", "64": "8ae1cb991df55ba5", "65": "7b932cfef527d130", "66": "e0d0013fbf063aa4", "67": "0f632fc57e9feeb0", "68": "92d7e4efe25770db", "69": "7650c66736fc59da", "70": "df7ea8ce3fa52be3", "71": "fd47ef3914adf11e", "72": "9b3249f4a4e6fbfe", "73": "e00f9e12dd9fb95f", "74": "a534e43d03671a7c", "75": "5290f7a7558a9c3e", "76": "a19c211702401a5b", "77": "35aa0fc2a0626f70", "78": "3100956d3076c59f", "79": "da683f642f8c61ac", "80": "40313433c93b7054", "81": "81af0a28b68936d9", "82": "25287261c3d1c44b", "83": "bda9be183e0f933e", "84": "63526d01888c4f22", "85": "e5577b2d90500462", "86": "694a38d63caf7217", "87": "f220eaa4fac26e3e", "88": "d78c8e21a45613e9", "89": "8cb58c63b0d606b1", "90": "03f8896e9b60ca96", "91": "50363e32255931f4", "92": "4bfa64a1cd8c6a47", "93": "057eae5a523d2004", "94": "5de74655cbc56e06", "95": "b8ea4a512b424113", "96": "e0d3010c0e1a9d7a", "97": "de6816239e70e6e8", "98": "7508d3e84c588617", "99": "15f6a40009e539bb", "100": "ed5eb60fa9b1adb6", "101": "4b7f81f0e7e4c044", "102": "24252b994a79d741", "103": "85ecb40ed558ed43", "104": "756a405f723988ce", "105": "e618d1b582eac9a5", "106": "825ba1cf2cfa5500", "107": "6c05d0c0a89d1816", "108": "b738c5aefe939cff", "109": "d2001d7d283134ac", "110": "6b8e6100893a6b6a", "111": "66ed76491a412a72", "112": "e67668b62ee6c62c", "113": "800f5b95a2eac8d1", "114": "68aee39cd24db354", "115": "054401d27ffaa4b2", "116": "dace81c1995fb321", "117": "19e301aeace95bb2", "118": "d6fd0cec4634a0b0", "119": "0230d355a6dabb54", "120": "5cebbb5528321795", "121": "536fe28d24c361f5", "122": "b08bdc2712cb0f12", "123": "6ce8444f504022cc", "124": "8021f52559633383", "125": "d8e34a3b3eb43ee0", "126": "171c34c74a95481b", "127": "73e9eca6f2ca1c9b", "128": "dcc2230ae751c29a", "129": "2a617b932f5d92b2", "130": "d55cb638532fdfeb", "131": "06cae4389208269f", "132": "e9c0a7be255e9b26", "133": "455b4ca39e55ecb0", "134": "8105c55538c34081", "135": "74304670b54c018f", "136": "a27f876eabb044b2", "137": "73f74dd7a40776d6", "138": "29d433d164b99f89", "139": "3f44aa48aa05d597", "140": "ca8a7efba0a3e579", "141": "5cc770af9d184d77", "142": "2d31fa723128b164", "143": "be720fc0071f0c3d", "144": "9d814c2520329846", "145": "f97d1a5c3a066598", "146": "14f84a6e4cd311d6", "147": "3a592d27b1f4ccc9", "148": "0bf6f9d0aeab4fd8", "149": "8b491bb3ff0649dd", "150": "638812cee15d1b9b", "151": "3dd16d68928a479c", "152": "03a0ee43e18f7934", "153": "60e9520e0a9facae", "154": "435331945d6aad17", "155": "968e1a135dc062b7", "156": "d1a987a548e55a76", "157": "a9bf69b3d550abef", "158": "b039e1c69c8fb7b9", "159": "60029bf0948fc02a", "160": "72dad91d66d471ce", "161": "353a9d09eaae47ea", "162": "43bbf35b96423d46", "163": "742c12886e57d4fa", "164": "6b4bf464232edaa0", "165": "d0c2310f7d93caf6", "166": "bca6f6760a942d10", "167": "ce80aac5f4c439da", "168": "1abe2e7b0bcbeca9", "169": "ce5f237abed01c9e", "170": "c9f25e20d3a7d5d8", "171": "7e818e8715c414bd", "172": "22f3057d405a4f06", "173": "827420b645a55301", "174": "adfc2fda7e4fc28a", "175": "59ba9ce54759cbf2", "176": "adac855b05ea55ee", "177": "6ba88c862239632d", "178": "c67bd11d1973d6ac", "179": "63c999649e523e73", "180": "56f7f3573c34f651", "181": "f8f5346df7d0a239", "182": "9c5614d4fee3efc0", "189": "36b8b06281e0571e"}
+298 -201
View File
@@ -1,17 +1,17 @@
# Graph Report - khatibi (2026-10-11) # Graph Report - khatibi (2026-10-16)
## Corpus Check ## Corpus Check
- 182 files · ~145,325 words - 193 files · ~286,477 words
- Verdict: corpus is large enough that graph structure adds value. - Verdict: corpus is large enough that graph structure adds value.
- Unclassified: 8 file(s) not represented in the graph (top: (none) 5, .env 2, .csv 1) - Unclassified: 8 file(s) not represented in the graph (top: (none) 5, .env 2, .csv 1)
## Summary ## Summary
- 1684 nodes · 3008 edges · 171 communities (74 shown, 97 thin omitted) - 1960 nodes · 3584 edges · 184 communities (95 shown, 89 thin omitted)
- Extraction: 95% EXTRACTED · 5% INFERRED · 0% AMBIGUOUS · INFERRED: 151 edges (avg confidence: 0.89) - Extraction: 95% EXTRACTED · 5% INFERRED · 0% AMBIGUOUS · INFERRED: 188 edges (avg confidence: 0.89)
- Token cost: 0 input · 0 output - Token cost: 0 input · 0 output
## Graph Freshness ## Graph Freshness
- Built from commit: `1dea3509` - Built from commit: `7e5c83be`
- Run `git rev-parse HEAD` and compare to check if the graph is stale. - Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost). - Run `graphify update .` after code changes (no API cost).
@@ -19,113 +19,114 @@
- views/sms.py - views/sms.py
- run_all - run_all
- django_db - django_db
- views/store.py - create_transfer_request
- ModelsPackageContractTests - ModelsPackageContractTests
- PhoneCleanupTests - PhoneCleanupTests
- .load - .load
- models/sms.py - models/sms.py
- logging - models/__init__.py
- views/customers.py - views/customers.py
- django_db_models_deletion - django_conf
- make_customer - make_customer
- customer_phone_entries - resolve_recipients
- EventTriggerTests - EventTriggerTests
- views/finance.py - views/finance.py
- SMSResult - views/store.py
- sms0098ws.py - sms0098ws.py
- Admin Panel Customization - Admin Panel Customization
- User - User
- django_conf - timing_for
- test_customers.py - Customer
- phones.py - phones.py
- Login Template Forms - Login Template Forms
- cheque_inquiry.py - cheque_inquiry.py
- factories.py - factories.py
- Rip'n Fast. Fewer Tokens. Better Code. - Rip'n Fast. Fewer Tokens. Better Code.
- views/factors.py - views/factors.py
- to_local - sms0098.py
- make_user - make_user
- QueueReviewTests - QueueReviewTests
- NoticeMechanismTests - NoticeMechanismTests
- enqueue.py - SmsReportTests
- enqueue_sms - scanner.py
- PanelTestCase - ZeroZeroNinetyEightWebServiceTests
- ProductInStore - ProductInStore
- admin_panel/templates/admin_panel/baseHesabdari.html - admin_panel/templates/admin_panel/baseHesabdari.html
- Reference guide - Reference guide
- Factor Creation UI - Factor Creation UI
- admin_panel/templates/admin_panel/bedehi.html - hesabdariResidNashode.html
- project_tree.py - requests
- CustomerSmsHistoryTests - CustomerSmsHistoryTests
- TemplatesPageUITests - TemplatesPageUITests
- SMS Integration Docs - SMS Integration Docs
- timing_for - enqueue.py
- django_shortcuts - django_shortcuts
- stock_in - stock_in
- admin_panel/templates/admin_panel/customerAction.html - admin_panel/templates/admin_panel/customerAction.html
- parts_count - parts_count
- write_tree - write_tree
- CustomerInfoCardUITests - CustomerInfoCardUITests
- Customer
- Phone Cleanup Guide
- SmsReportTests
- manage.py
- Agent Operating Guidelines
- SMSDeliveryCommandTests
- BehinSMSProvider
- test_sms.py - test_sms.py
- Phone Cleanup Guide
- .run_command
- project_tree.py
- Agent Operating Guidelines
- Command
- BehinSMSProvider
- normalize_mobile
- Financial Data Objects - Financial Data Objects
- Hybrid Migration Command - Hybrid Migration Command
- بازمحاسبهٔ «آخرین بازدید» مشتریان - .run_cmd
- Resource Cleanup Handler - Resource Cleanup Handler
- SMS Timing Migration - SMS Timing Migration
- Articl Model - admin_panel/templates/admin_panel/bedehi.html
- customerIDFactorBackup.html - customerIDFactorBackup.html
- test_factors.py - bale/providers.py
- os
- Graphify Tooling Docs - Graphify Tooling Docs
- 0063_alter_smsqueue_status.py - Command
- ZeroZeroNinetyEightLinkTests
- SMS Template Seeding - SMS Template Seeding
- admin_panel/entezar.html - admin_panel/entezar.html
- customerNewBackup.html - customerNewBackup.html
- hesabdariResidNashode.html - SMSResult
- 0070_sms_types_seven.py - 0070_sms_types_seven.py
- Endpoint Smoke Tests - Endpoint Smoke Tests
- Timezone Migration History - Timezone Migration History
- models/__init__.py - Command
- UrlContractTests - django_urls
- Brand Scrubbing Migration - Brand Scrubbing Migration
- SMS Dedup Migration - SMS Dedup Migration
- Installation Guide - Installation Guide
- Project Honesty Contract - Project Honesty Contract
- Flow Control Migration - Flow Control Migration
- Permission Label Migration - Permission Label Migration
- stop.sh - IntegrationAndEfficiencyTests
- Output Format Specs - Output Format Specs
- Performance Benchmarks - Performance Benchmarks
- SMS Integration Overview - SMS Integration Overview
- System Cache Table - CustomerNameFixJob
- Customer Modal UI - Customer Modal UI
- SentQueueProtectionTests - PanelTestCase
- Infrastructure Services - Infrastructure Services
- Agent Setup Guide - Agent Setup Guide
- First Use Guide - First Use Guide
- 0056_auto_20260731_1824.py - ConsoleSMSProvider
- 0009_remove_storeroom_user.py - PhoneNumber
- PhoneEntry - تجمیع شماره‌های پیامک مشتریان
- reconciliation_tool.py
- announcements.py
- Command - Command
- 0064_sms_webservice_delivery.py - Base Template
- 0040_customer_laghv_bazdid.py - test_customer_name_fix.py
- 0068_auto_20261010_1305.py
- 0069_smstemplate_auto_send.py
- 0047_alter_productinstore_movement_type.py
- Command - Command
- customer_phone_entries
- manage.py
- افزودن پیشوند «آقای/خانم» به نام مشتریان
- بازمحاسبهٔ «آخرین بازدید» مشتریان
- QueueStatsTests
- admin_panel/apps.py
- Customer Communication Partials - Customer Communication Partials
- SMS Template Management - SMS Template Management
- Documentation and Graphing - Documentation and Graphing
- bale/providers.py - primary_phones
- Customer Action Templates - Customer Action Templates
- Customer Feedback Components - Customer Feedback Components
- Customer Invoice Details - Customer Invoice Details
@@ -155,41 +156,53 @@
- Store Transfer Interface - Store Transfer Interface
- Store Directory Template - Store Directory Template
- Currency Formatting Utility - Currency Formatting Utility
- HttpProviderRobustnessTests
- Data Cleanup Documentation - Data Cleanup Documentation
- Project Dependencies - Project Dependencies
- QueueStatsTests - Customer
- sms_settings - SMSQueue
- signals.py
- توقف و ادامه (Resume / Checkpoint)
- Command - Command
- 0071_sms_settings_permission.py - 0071_sms_settings_permission.py
- index - index
- 0017_factoriteam_garantyesh.py - الگوریتم تصمیم (به همین ترتیب)
- 0028_action_grade.py - سریع‌ترین مسیر روی Production (خلاصهٔ اجرایی)
- 0034_alter_article_sarresid.py - models/customers.py
- 0039_customer_last_bazdid_dt.py - stop.sh
- SMSDedup
- rules.py
- اجرای محلی (توسعه)
- Factor
- اجرا روی سرور Production
- اجرای محلی (توسعه)
- ادامهٔ job پس از crash / قطع برق
- اجرا روی سرور Production
- django_contrib_auth_models
## God Nodes (most connected - your core abstractions) ## God Nodes (most connected - your core abstractions)
1. `make_customer()` - 70 edges 1. `make_customer()` - 89 edges
2. `make_user()` - 28 edges 2. `make_user()` - 28 edges
3. `run_all()` - 24 edges 3. `normalize_mobile()` - 27 edges
4. `enqueue_sms()` - 23 edges 4. `Customer` - 24 edges
5. `SMSResult` - 23 edges 5. `PhoneNumber` - 24 edges
6. `Rip'n Fast. Fewer Tokens. Better Code.` - 23 edges 6. `Command` - 24 edges
7. `User` - 22 edges 7. `run_all()` - 24 edges
8. `create_transfer_request()` - 22 edges 8. `enqueue_sms()` - 23 edges
9. `normalize_mobile()` - 22 edges 9. `SMSResult` - 23 edges
10. `SMSProvider` - 21 edges 10. `Rip'n Fast. Fewer Tokens. Better Code.` - 23 edges
## Surprising Connections (you probably didn't know these) ## Surprising Connections (you probably didn't know these)
- `نکته‌های مهم` --references--> `Action` [INFERRED] - `نکته‌های مهم` --references--> `Action` [INFERRED]
recalc_last_bazdid.md → admin_panel/models/actions.py recalc_last_bazdid.md → admin_panel/models/actions.py
- `SMS Templates UI` --semantically_similar_to--> `SMS Templates UI Screenshot` [EXTRACTED] [semantically similar] - `مدل وضعیت job` --references--> `CustomerNameFixJob` [INFERRED]
admin_panel/templates/admin_panel/sms_templates.html → screencapture-localhost-8000-panel-sms-templates-2026-10-09-23_01_18.png fix_customer_names.md → admin_panel/models/system.py
- `check_financial_discrepancy()` --uses--> `Customer` [INFERRED] - `چرا؟` --references--> `CustomerNameFixJob` [INFERRED]
check_finance.py → admin/models.py fix_customer_names.md → admin_panel/models/system.py
- `run_360_audit()` --uses--> `Customer` [INFERRED] - `سریع‌ترین مسیر روی Production (اگر همین یک دستور را می‌خواهی)` --references--> `CustomerNameFixJob` [INFERRED]
ghost_hunter.py → admin/models.py normalize_customer_sms_phones.md → admin_panel/models/system.py
- ``nest=` is a max, so it cannot tell a long function from a tangled one` --references--> `index()` [INFERRED] - `تست‌ها` --references--> `resolve_recipients()` [INFERRED]
ripwire - README.md → admin/views.py normalize_customer_sms_phones.md → admin_panel/sms/recipients.py
## Import Cycles ## Import Cycles
- None detected. - None detected.
@@ -222,102 +235,98 @@
- **New Factor Creation Flow** — admin_panel_templates_admin_panel_customeridbackup_html, django_url_admin_panel_productsjson, api_endpoint_panel_productmojodi, admin_panel_templates_admin_panel_customeridbackup_html_orderlist, admin_panel_templates_admin_panel_customeridbackup_html_buildorderlist, django_url_admin_panel_newfactor [INFERRED] - **New Factor Creation Flow** — admin_panel_templates_admin_panel_customeridbackup_html, django_url_admin_panel_productsjson, api_endpoint_panel_productmojodi, admin_panel_templates_admin_panel_customeridbackup_html_orderlist, admin_panel_templates_admin_panel_customeridbackup_html_buildorderlist, django_url_admin_panel_newfactor [INFERRED]
- **Store and Inventory Management Flow** — admin_panel_templates_admin_panel_basestore, concept_url_admin_panel_stores, concept_url_admin_panel_productadd, concept_url_admin_panel_operations, concept_url_admin_panel_store_transfers [INFERRED] - **Store and Inventory Management Flow** — admin_panel_templates_admin_panel_basestore, concept_url_admin_panel_stores, concept_url_admin_panel_productadd, concept_url_admin_panel_operations, concept_url_admin_panel_store_transfers [INFERRED]
## Communities (171 total, 97 thin omitted) ## Communities (184 total, 89 thin omitted)
### Community 0 - "views/sms.py" ### Community 0 - "views/sms.py"
Cohesion: 0.14 Cohesion: 0.12
Nodes (14): _approve(), customer_sms_history(), _customer_url(), _notice(), sms_customer_toggle(), sms_primary_toggle(), sms_queue(), sms_queue_approve() (+6 more) Nodes (18): _approve(), customer_sms_history(), _customer_url(), _notice(), parse_jalali_date(), parse_scan_since(), resolve_recipients_test(), sms_customer_toggle() (+10 more)
### Community 1 - "run_all" ### Community 1 - "run_all"
Cohesion: 0.06 Cohesion: 0.05
Nodes (9): Command, run_all(), apply_timing(), activate_sms(), AutoSendTests, ColdStartTests, ConsoleEncodingTests, ScannerTests (+1 more) Nodes (9): Command, run_all(), apply_timing(), activate_sms(), AutoSendTests, ColdStartTests, ScannerTests, keys() (+1 more)
### Community 2 - "django_db" ### Community 2 - "django_db"
Cohesion: 0.04 Cohesion: 0.03
Nodes (24): Migration, Migration, Migration, Migration, Migration, Migration, Migration, Migration (+16 more) Nodes (38): Migration, Migration, Migration, Migration, Migration, Migration, Migration, Migration (+30 more)
### Community 3 - "views/store.py" ### Community 3 - "create_transfer_request"
Cohesion: 0.07 Cohesion: 0.10
Nodes (26): Product, Meta, StoreTransferRequest, Meta, Status, approve_transfer_request(), cancel_transfer_request(), _check_source_store_permission() (+18 more) Nodes (12): StoreTransferRequest, Meta, Status, approve_transfer_request(), cancel_transfer_request(), _check_source_store_permission(), _check_target_store_permission(), create_transfer_request() (+4 more)
### Community 4 - "ModelsPackageContractTests"
Cohesion: 0.07
Nodes (5): AdminConfig, AdminPanelConfig, make_std_streams_unicode_safe(), configure_sqlite_connection(), ModelsPackageContractTests
### Community 5 - "PhoneCleanupTests" ### Community 5 - "PhoneCleanupTests"
Cohesion: 0.09 Cohesion: 0.08
Nodes (9): Command, Migration, scrub_legacy_phones(), _append_note(), clean_all(), clean_customer(), clean_legacy_cell(), _numeric_tokens() (+1 more) Nodes (9): Command, Migration, scrub_legacy_phones(), _append_note(), clean_all(), clean_customer(), clean_legacy_cell(), _numeric_tokens() (+1 more)
### Community 6 - ".load" ### Community 6 - ".load"
Cohesion: 0.06 Cohesion: 0.12
Nodes (9): 12. Supported languages and formats, Languages, HttpProviderRobustnessTests, RuleTests, SettingsPanelTests, TestMessageSettingsTests, WorkerTests, ZeroZeroNinetyEightLinkTests (+1 more) Nodes (6): SMSProviderConfig, Meta, 12. Supported languages and formats, Languages, SettingsPanelTests, TestMessageSettingsTests
### Community 7 - "models/sms.py" ### Community 7 - "models/sms.py"
Cohesion: 0.06 Cohesion: 0.12
Nodes (15): CustomerSMSSetting, Meta, SMSDedup, Meta, SMSLog, Meta, SMSProviderConfig, Meta (+7 more) Nodes (7): CustomerSMSSetting, Meta, SMSLog, Meta, SMSTemplate, Meta, SMSType
### Community 8 - "logging" ### Community 8 - "models/__init__.py"
Cohesion: 0.09 Cohesion: 0.10
Nodes (3): get_provider_class(), get_sms_provider(), reset_sms_provider() Nodes (4): remove_query_param(), check_financial_discrepancy(), run_360_audit(), run_audit()
### Community 9 - "views/customers.py" ### Community 9 - "views/customers.py"
Cohesion: 0.09 Cohesion: 0.16
Nodes (18): normalize_primary_values(), customer_toggle_map(), notify_welcome(), customer(), customerDel(), customerEdit(), customerFactor(), customerFactors() (+10 more) Nodes (11): customer(), customerDel(), customerFactor(), customerFactors(), customerHesabdari(), customerNew(), customerNew2(), customers() (+3 more)
### Community 10 - "django_db_models_deletion" ### Community 10 - "django_conf"
Cohesion: 0.08 Cohesion: 0.05
Nodes (12): Migration, Migration, Migration, Migration, Migration, Migration, Migration, Migration (+4 more) Nodes (23): Migration, Migration, Migration, Migration, Migration, Migration, Migration, Migration (+15 more)
### Community 11 - "make_customer" ### Community 11 - "make_customer"
Cohesion: 0.14 Cohesion: 0.15
Nodes (3): make_customer(), EmptyQueueTests, PrimaryNumberPanelTests Nodes (3): make_customer(), EmptyQueueTests, PrimaryNumberPanelTests
### Community 12 - "customer_phone_entries" ### Community 12 - "resolve_recipients"
Cohesion: 0.13 Cohesion: 0.15
Nodes (5): customer_phone_entries(), _iter_sources(), resolve_recipients(), mark_primary(), RecipientTests Nodes (4): resolve_recipients(), تست‌ها, mark_primary(), RecipientTests
### Community 14 - "views/finance.py" ### Community 14 - "views/finance.py"
Cohesion: 0.18 Cohesion: 0.17
Nodes (19): Article, Meta, notify_payment_thanks(), addSarresid(), bedehi(), bedehiKhadamat(), bedehiNasb(), financial_dashboard() (+11 more) Nodes (20): Article, Meta, notify_payment_thanks(), customerHesabdariAdd(), addSarresid(), bedehi(), bedehiKhadamat(), bedehiNasb() (+12 more)
### Community 15 - "SMSResult" ### Community 15 - "views/store.py"
Cohesion: 0.13 Cohesion: 0.14
Nodes (7): SMSProvider, SMSResult, webotp_message(), console_write(), ConsoleSMSProvider, HTTPSMSProvider, _FailingProvider Nodes (16): Product, Meta, StoreRoom, Meta, newOperation(), newStore(), operation(), operationProduct() (+8 more)
### Community 16 - "sms0098ws.py" ### Community 16 - "sms0098ws.py"
Cohesion: 0.10 Cohesion: 0.27
Nodes (7): classify_delivery(), DeliveryCode, describe_send_code(), _as_text(), ZeroZeroNinetyEightWebServiceProvider, place_voip_call(), VoipCallResult Nodes (3): classify_delivery(), DeliveryCode, describe_send_code()
### Community 17 - "Admin Panel Customization" ### Community 17 - "Admin Panel Customization"
Cohesion: 0.12 Cohesion: 0.12
Nodes (10): CustomerSMSSettingAdmin, SMSLogAdmin, SMSProviderConfigAdmin, SMSProviderConfigForm, Meta, SMSQueueAdmin, SMSTemplateAdmin, StoreRoomAdmin (+2 more) Nodes (10): CustomerSMSSettingAdmin, SMSLogAdmin, SMSProviderConfigAdmin, SMSProviderConfigForm, Meta, SMSQueueAdmin, SMSTemplateAdmin, StoreRoomAdmin (+2 more)
### Community 18 - "User" ### Community 18 - "User"
Cohesion: 0.16 Cohesion: 0.17
Nodes (17): User, Action, VisitQueue, UserProfile, notify_visit_scheduled(), ActionList(), ActionListAll(), ActionListToday() (+9 more) Nodes (18): User, Action, ActionType, Status, notify_visit_scheduled(), ActionList(), ActionListAll(), ActionListToday() (+10 more)
### Community 19 - "django_conf" ### Community 19 - "timing_for"
Cohesion: 0.09 Cohesion: 0.12
Nodes (11): Migration, Migration, Migration, Migration, Migration, Migration, Migration, Migration (+3 more) Nodes (6): current_timing_value(), timing_for(), sms_template_edit(), sms_templates(), _templates_redirect(), TimingsTests
### Community 20 - "test_customers.py" ### Community 20 - "Customer"
Cohesion: 0.11 Cohesion: 0.12
Nodes (4): login_superuser(), CustomerCrudTests, CustomerDeleteGuardTests, InactiveCustomerHiddenTests Nodes (5): Customer, login_superuser(), CustomerCrudTests, CustomerDeleteGuardTests, InactiveCustomerHiddenTests
### Community 21 - "phones.py" ### Community 21 - "phones.py"
Cohesion: 0.18 Cohesion: 0.19
Nodes (6): primary_phones(), resolved_recipients(), save_primary_normalized(), stored_primary_normalized(), usable_entries(), primary_phones() Nodes (7): normalize_primary_values(), resolved_recipients(), save_primary_normalized(), stored_primary_normalized(), usable_entries(), قالبِ ذخیره‌شده: `+989xxxxxxxxx`, قواعد اعتبارسنجی و قالب شماره
### Community 22 - "Login Template Forms" ### Community 22 - "Login Template Forms"
Cohesion: 0.11 Cohesion: 0.11
Nodes (17): Django CSRF Token, Django Custom Tags, Django Static Files, Django URL: admin_panel:newStore, Django URL: admin_panel:operations, Django URL: admin_panel:p_login, Django URL: admin_panel:store, Form Field: password (+9 more) Nodes (17): Django CSRF Token, Django Custom Tags, Django Static Files, Django URL: admin_panel:newStore, Django URL: admin_panel:operations, Django URL: admin_panel:p_login, Django URL: admin_panel:store, Form Field: password (+9 more)
### Community 23 - "cheque_inquiry.py" ### Community 23 - "cheque_inquiry.py"
Cohesion: 0.19 Cohesion: 0.17
Nodes (6): api_check_sabt_inquiry(), api_check_status_inquiry(), parse_cheque_result_html(), run_bpi_cheque_inquiry_logic(), run_cbi_sayad_inquiry_logic(), solve_base64_captcha() Nodes (6): api_check_sabt_inquiry(), api_check_status_inquiry(), parse_cheque_result_html(), run_bpi_cheque_inquiry_logic(), run_cbi_sayad_inquiry_logic(), solve_base64_captcha()
### Community 24 - "factories.py" ### Community 24 - "factories.py"
Cohesion: 0.27 Cohesion: 0.18
Nodes (5): ProductCategory, make_category(), make_product(), make_store(), _next() Nodes (5): ProductCategory, make_category(), make_product(), make_store(), _next()
### Community 25 - "Rip'n Fast. Fewer Tokens. Better Code." ### Community 25 - "Rip'n Fast. Fewer Tokens. Better Code."
@@ -325,36 +334,32 @@ Cohesion: 0.11
Nodes (18): Against the leading graph-database code-context MCP server, Better Code: It automates the review judgments nobody has time to make — every lens from published research, Documentation, Graph-Ranked Retrieval: It finds the right files more often than the alternatives, If it works on your codebase, tell us what it got wrong, Improve it with your agent, Licence, Quickstart (+10 more) Nodes (18): Against the leading graph-database code-context MCP server, Better Code: It automates the review judgments nobody has time to make — every lens from published research, Documentation, Graph-Ranked Retrieval: It finds the right files more often than the alternatives, If it works on your codebase, tell us what it got wrong, Improve it with your agent, Licence, Quickstart (+10 more)
### Community 26 - "views/factors.py" ### Community 26 - "views/factors.py"
Cohesion: 0.19 Cohesion: 0.14
Nodes (6): notify_factor_survey(), newFactor2(), InventoryException, _parse_jalali_date(), _parse_timestamp(), _safe_int() Nodes (9): FactorIteam, notify_factor_survey(), FactorDel(), newFactor(), newFactor2(), InventoryException, _parse_jalali_date(), _parse_timestamp() (+1 more)
### Community 27 - "to_local" ### Community 27 - "sms0098.py"
Cohesion: 0.17 Cohesion: 0.20
Nodes (4): to_local(), _configured_codes(), _response_code(), ZeroZeroNinetyEightSMSProvider Nodes (3): _configured_codes(), _response_code(), ZeroZeroNinetyEightSMSProvider
### Community 30 - "NoticeMechanismTests" ### Community 30 - "NoticeMechanismTests"
Cohesion: 0.17 Cohesion: 0.17
Nodes (3): dashBoxData(), resolve(), NoticeMechanismTests Nodes (3): dashBoxData(), resolve(), NoticeMechanismTests
### Community 31 - "enqueue.py" ### Community 32 - "scanner.py"
Cohesion: 0.15 Cohesion: 0.23
Nodes (6): format_amount(), format_jalali(), render_template(), _SafeDict, is_sms_enabled(), _sample_context() Nodes (6): _aware(), _Budget, scan_cheque_funding(), scan_nesiye_due(), scan_visit(), _template()
### Community 32 - "enqueue_sms" ### Community 33 - "ZeroZeroNinetyEightWebServiceTests"
Cohesion: 0.21 Cohesion: 0.11
Nodes (7): enqueue_sms(), _aware(), _Budget, scan_cheque_funding(), scan_nesiye_due(), scan_visit(), _template() Nodes (5): SMSDeliveryResult, _as_text(), ZeroZeroNinetyEightWebServiceProvider, SMSDeliveryCommandTests, ZeroZeroNinetyEightWebServiceTests
### Community 33 - "PanelTestCase"
Cohesion: 0.17
Nodes (5): PhoneNumber, DeletedStaysDeletedTests, EnqueueIdempotencyTests, PanelTestCase, WelcomeTriggerTests
### Community 34 - "ProductInStore" ### Community 34 - "ProductInStore"
Cohesion: 0.14 Cohesion: 0.33
Nodes (8): Factor, Meta, FactorIteam, ProductInStore, Meta, MovementType, FactorDel(), newFactor() Nodes (3): ProductInStore, Meta, MovementType
### Community 35 - "admin_panel/templates/admin_panel/baseHesabdari.html" ### Community 35 - "admin_panel/templates/admin_panel/baseHesabdari.html"
Cohesion: 0.07 Cohesion: 0.13
Nodes (27): Base Template, Customer Base Template, New Customer Base Template, Create Factor View, Customer List View, Django Custom Tags: custom_tags, URL: admin_panel:ActionListUserToday, URL: admin_panel:bedehi (+19 more) Nodes (14): URL: admin_panel:ActionListUserToday, URL: admin_panel:bedehi, URL: admin_panel:entezar, URL: admin_panel:hesabdari, URL: admin_panel:hesabdari_residnashone, URL: admin_panel:index, URL: admin_panel:p_logout, URL: admin_panel:rezayat (+6 more)
### Community 36 - "Reference guide" ### Community 36 - "Reference guide"
Cohesion: 0.14 Cohesion: 0.14
@@ -364,13 +369,13 @@ Nodes (14): 10. Quality evaluation, 11. Agent integration, 13. Performance, 14.
Cohesion: 0.15 Cohesion: 0.15
Nodes (4): Customer ID Backup/New Factor Template, all_products (JS variable), buildOrderList (JS function), orderList (JS variable) Nodes (4): Customer ID Backup/New Factor Template, all_products (JS variable), buildOrderList (JS function), orderList (JS variable)
### Community 38 - "admin_panel/templates/admin_panel/bedehi.html" ### Community 38 - "hesabdariResidNashode.html"
Cohesion: 0.16 Cohesion: 0.19
Nodes (10): admin_panel:pardakhtNesiye, /panel/passCheck/, all_sarresid, mandehesabKP, admin_panel.ViewReportMali, toJalali, admin_panel:bedehi, admin_panel:bedehiKhadamat (+2 more) Nodes (11): admin_panel:customerNew, admin_panel:pardakhtNesiye, /panel/passCheck/, all_sarresid, mandehesabKP, admin_panel.ViewReportMali, jQuery, pDatepicker JS Library (+3 more)
### Community 39 - "project_tree.py" ### Community 39 - "requests"
Cohesion: 0.21 Cohesion: 0.21
Nodes (6): is_excluded_file(), main(), non_negative_integer(), parse_args(), sorted_entries(), warn() Nodes (4): AnnouncementRejected, place_announcement_call(), place_voip_call(), VoipCallResult
### Community 40 - "CustomerSmsHistoryTests" ### Community 40 - "CustomerSmsHistoryTests"
Cohesion: 0.15 Cohesion: 0.15
@@ -380,17 +385,21 @@ Nodes (3): customer_phones(), toJalaliDT(), CustomerSmsHistoryTests
Cohesion: 0.15 Cohesion: 0.15
Nodes (12): **جدول کدهای خطای لینک ارتباطی**, **جدول کدهای خطای وب‌سرویس و وضعیت‌های دلیوری**, **راهنمای استفاده از Web Service**, **راهنمای استفاده از لینک ارتباطی (URL GET)**, **راهنمای استفاده از لینک ارتباطی و وب‌سرویس سامانه پیامک (0098vip.com و 0098sms.com)**, **فهرست مطالب**, **مقدمات اتصال**, **نمونه کدهای پیاده‌سازی** (+4 more) Nodes (12): **جدول کدهای خطای لینک ارتباطی**, **جدول کدهای خطای وب‌سرویس و وضعیت‌های دلیوری**, **راهنمای استفاده از Web Service**, **راهنمای استفاده از لینک ارتباطی (URL GET)**, **راهنمای استفاده از لینک ارتباطی و وب‌سرویس سامانه پیامک (0098vip.com و 0098sms.com)**, **فهرست مطالب**, **مقدمات اتصال**, **نمونه کدهای پیاده‌سازی** (+4 more)
### Community 43 - "timing_for" ### Community 43 - "enqueue.py"
Cohesion: 0.12 Cohesion: 0.19
Nodes (6): current_timing_value(), timing_for(), sms_template_edit(), sms_templates(), _templates_redirect(), TimingsTests Nodes (5): format_amount(), format_jalali(), render_template(), _SafeDict, _sample_context()
### Community 44 - "django_shortcuts"
Cohesion: 0.13
Nodes (3): Migration, ArticleType, Sarresid
### Community 45 - "stock_in" ### Community 45 - "stock_in"
Cohesion: 0.25 Cohesion: 0.12
Nodes (6): Product, ProductInStore, StoreRoom, current_stock(), stock_in(), ProductInStoreMathTests Nodes (11): Factor, FactorIteam, Product, ProductInStore, StoreRoom, VisitQueue, current_stock(), stock_in() (+3 more)
### Community 46 - "admin_panel/templates/admin_panel/customerAction.html" ### Community 46 - "admin_panel/templates/admin_panel/customerAction.html"
Cohesion: 0.16 Cohesion: 0.18
Nodes (10): admin_panel/templates/admin_panel/customerActionInc.html, admin_panel:customerNew, admin_panel:customerNewAction URL, Action Data Model, DataTables Library, jQuery, Django 'url' template tag, Bulma CSS (+2 more) Nodes (9): admin_panel/templates/admin_panel/customerActionInc.html, admin_panel:customerNewAction URL, Action Data Model, DataTables Library, jQuery, Django 'url' template tag, Bulma CSS, DataTables.js (+1 more)
### Community 47 - "parts_count" ### Community 47 - "parts_count"
Cohesion: 0.16 Cohesion: 0.16
@@ -400,70 +409,78 @@ Nodes (6): describe(), detect_encoding(), encoded_length(), is_gsm7(), parts_cou
Cohesion: 0.21 Cohesion: 0.21
Nodes (6): Counts, format_header(), generate_snapshot(), normalized_path(), write_tree(), visit() Nodes (6): Counts, format_header(), generate_snapshot(), normalized_path(), write_tree(), visit()
### Community 50 - "Customer" ### Community 50 - "test_sms.py"
Cohesion: 0.16 Cohesion: 0.21
Nodes (6): Customer, Factor, FactorIteam, VisitQueue, Command, NewFactor2ViewTests Nodes (4): enqueue_sms(), is_sms_enabled(), EnqueueIdempotencyTests, RuleTests
### Community 51 - "Phone Cleanup Guide" ### Community 51 - "Phone Cleanup Guide"
Cohesion: 0.17 Cohesion: 0.17
Nodes (11): اجرا روی سرور اصلی (production), اجرای دستور, اصلاح دستی شماره‌های قدیمی مشتریان, راه جایگزین: مهاجرت (migration), قاعدهٔ پاک‌سازی, مشاهدهٔ نتیجه, نکته‌ها, چرا؟ (+3 more) Nodes (11): اجرا روی سرور اصلی (production), اجرای دستور, اصلاح دستی شماره‌های قدیمی مشتریان, راه جایگزین: مهاجرت (migration), قاعدهٔ پاک‌سازی, مشاهدهٔ نتیجه, نکته‌ها, چرا؟ (+3 more)
### Community 53 - "manage.py" ### Community 52 - ".run_command"
Cohesion: 0.22 Cohesion: 0.13
Nodes (5): entrypoint.sh script, main(), run.sh script, start_service(), usage() Nodes (6): ApplyTests, CommandTestMixin, DryRunTests, ResilienceTests, flaky(), sha256_of()
### Community 53 - "project_tree.py"
Cohesion: 0.23
Nodes (6): is_excluded_file(), main(), non_negative_integer(), parse_args(), sorted_entries(), warn()
### Community 54 - "Agent Operating Guidelines" ### Community 54 - "Agent Operating Guidelines"
Cohesion: 0.18 Cohesion: 0.18
Nodes (8): 1. Codebase Navigation & Analysis (Ripwire vs. Graphify), 2. Dynamic Help, Self-Correction & Staleness, 3. Git Operations & Safety, 4. Development Server Lifecycle, 5. Environment, Frontend & PWA Safety, 6. End-to-End Browser Tests (Playwright), Project Operating & Agent Guidelines, Routing Gate & Pipeline Nodes (8): 1. Codebase Navigation & Analysis (Ripwire vs. Graphify), 2. Dynamic Help, Self-Correction & Staleness, 3. Git Operations & Safety, 4. Development Server Lifecycle, 5. Environment, Frontend & PWA Safety, 6. End-to-End Browser Tests (Playwright), Project Operating & Agent Guidelines, Routing Gate & Pipeline
### Community 57 - "test_sms.py" ### Community 55 - "Command"
Cohesion: 0.17 Cohesion: 0.05
Nodes (4): to_display(), _clean(), normalize_mobile(), NormalizeTests Nodes (12): Command, build_name_index(), DatasetError, decide_prefix(), Decision, load_name_index(), NameIndex, normalize_name_key() (+4 more)
### Community 57 - "normalize_mobile"
Cohesion: 0.18
Nodes (5): to_display(), _clean(), normalize_mobile(), to_local(), NormalizeTests
### Community 58 - "Financial Data Objects" ### Community 58 - "Financial Data Objects"
Cohesion: 0.20 Cohesion: 0.20
Nodes (9): Customer Data Object, Factor Data Object, Sarresid Data Object, admin_panel:customerNew URL, admin_panel:pardakhtNesiye URL, /panel/passCheck/ URL, admin_panel.ViewReportMali Permission, jQuery JS Library (+1 more) Nodes (9): Customer Data Object, Factor Data Object, Sarresid Data Object, admin_panel:customerNew URL, admin_panel:pardakhtNesiye URL, /panel/passCheck/ URL, admin_panel.ViewReportMali Permission, jQuery JS Library (+1 more)
### Community 60 - "بازمحاسبهٔ «آخرین بازدید» مشتریان" ### Community 60 - ".run_cmd"
Cohesion: 0.18 Cohesion: 0.11
Nodes (10): اجرا روی production, بازمحاسبهٔ «آخرین بازدید» مشتریان, بررسی صحت داده (SQL، پیش از اجرا), دستور, دقیقاً چه می‌کند؟, نمونهٔ خروجی, نکته‌های مهم, گزینه‌ها (+2 more) Nodes (5): ApplyTests, BatchAndCheckpointTests, flaky(), CommandTestCase, OverflowTests
### Community 62 - "SMS Timing Migration" ### Community 62 - "SMS Timing Migration"
Cohesion: 0.22 Cohesion: 0.22
Nodes (4): backfill_primary_phones(), Migration, _normalize(), normalize_template_timing() Nodes (4): backfill_primary_phones(), Migration, _normalize(), normalize_template_timing()
### Community 63 - "Articl Model" ### Community 63 - "admin_panel/templates/admin_panel/bedehi.html"
Cohesion: 0.67 Cohesion: 0.20
Nodes (3): Articl Model, Customer Model, articls Nodes (8): Articl Model, Customer Model, articls, toJalali, admin_panel:bedehi, admin_panel:bedehiKhadamat, admin_panel:bedehiNasb, admin_panel:customer
### Community 64 - "customerIDFactorBackup.html" ### Community 64 - "customerIDFactorBackup.html"
Cohesion: 0.18 Cohesion: 0.20
Nodes (8): Client-side script in customerIDFactorBackup.html, URL: admin_panel:customerEdit, Customer Data, Factor Data Model, Product Data Model, Product Category Data Model, User Data Model, URL: /panel/productMojodi/ Nodes (7): Client-side script in customerIDFactorBackup.html, URL: admin_panel:customerEdit, Factor Data Model, Product Data Model, Product Category Data Model, User Data Model, URL: /panel/productMojodi/
### Community 65 - "bale/providers.py"
Cohesion: 0.36
Nodes (4): BaleBotProvider, ConsoleBaleProvider, get_bale_provider(), NotificationResult
### Community 67 - "Graphify Tooling Docs" ### Community 67 - "Graphify Tooling Docs"
Cohesion: 0.22 Cohesion: 0.22
Nodes (8): Copilot Chat, graphify, graphify-out/graph.json, graphify-out/GRAPH_REPORT.md, graphify-out/wiki/index.md, graphify explain, graphify path, graphify query Nodes (8): Copilot Chat, graphify, graphify-out/graph.json, graphify-out/GRAPH_REPORT.md, graphify-out/wiki/index.md, graphify explain, graphify path, graphify query
### Community 71 - "admin_panel/entezar.html" ### Community 71 - "admin_panel/entezar.html"
Cohesion: 0.40 Cohesion: 0.25
Nodes (4): Customer Data Structure, kamaDatepicker Library, custom_tags, toJalali filter Nodes (6): Customer Data Structure, kamaDatepicker Library, URL: admin_panel:customers, URL: /panel/customer/{id}/factor/{id}/del, custom_tags, toJalali filter
### Community 72 - "customerNewBackup.html" ### Community 72 - "customerNewBackup.html"
Cohesion: 0.29 Cohesion: 0.25
Nodes (6): admin_panel:newFactor URL, Category Data, Client-side Form Logic, CSRF Token, custom_tags, static Nodes (7): admin_panel:newFactor URL, Category Data, Client-side Form Logic, Customer Data, CSRF Token, custom_tags, static
### Community 73 - "hesabdariResidNashode.html" ### Community 73 - "SMSResult"
Cohesion: 0.47 Cohesion: 0.11
Nodes (5): jQuery, pDatepicker JS Library, custom_tags, toCurrency, toJalali Nodes (8): SMSProvider, SMSResult, webotp_message(), get_provider_class(), get_sms_provider(), reset_sms_provider(), HTTPSMSProvider, _FailingProvider
### Community 76 - "Timezone Migration History" ### Community 76 - "Timezone Migration History"
Cohesion: 0.29 Cohesion: 0.29
Nodes (3): Migration, Migration, Migration Nodes (3): Migration, Migration, Migration
### Community 77 - "models/__init__.py"
Cohesion: 0.08
Nodes (9): Customer, Meta, PhoneNumber, ProductCategory, ProductStockHistory, Meta, StoreRoom, Meta (+1 more)
### Community 81 - "Installation Guide" ### Community 81 - "Installation Guide"
Cohesion: 0.40 Cohesion: 0.40
Nodes (5): 3.1 Install a prebuilt binary, 3.2 Build from source, 3.3 Install the agent skills, 3.4 Register the MCP server (optional), 3. Installation Nodes (5): 3.1 Install a prebuilt binary, 3.2 Build from source, 3.3 Install the agent skills, 3.4 Register the MCP server (optional), 3. Installation
@@ -472,10 +489,6 @@ Nodes (5): 3.1 Install a prebuilt binary, 3.2 Build from source, 3.3 Install the
Cohesion: 0.40 Cohesion: 0.40
Nodes (5): In the numbers, In the output, In the tests, The honesty contract, What it misses, and what to run next Nodes (5): In the numbers, In the output, In the tests, The honesty contract, What it misses, and what to run next
### Community 85 - "stop.sh"
Cohesion: 0.73
Nodes (5): is_our_process(), say(), stop.sh script, stop_pid(), windows_cleanup()
### Community 86 - "Output Format Specs" ### Community 86 - "Output Format Specs"
Cohesion: 0.50 Cohesion: 0.50
Nodes (4): 6.1 Output example, 6.2 Exit codes, 6.3 JSON output, 6. Output format Nodes (4): 6.1 Output example, 6.2 Exit codes, 6.3 JSON output, 6. Output format
@@ -488,10 +501,18 @@ Nodes (4): Against other tools, Measured, What it saves you, in tokens, Where it
Cohesion: 0.67 Cohesion: 0.67
Nodes (3): SMS Gateway Integration Guide, SMS Settings UI, Python Dependencies Nodes (3): SMS Gateway Integration Guide, SMS Settings UI, Python Dependencies
### Community 89 - "CustomerNameFixJob"
Cohesion: 0.25
Nodes (6): CustomerNameFixJob, Meta, Status, مدل وضعیت job, چرا؟, سریع‌ترین مسیر روی Production (اگر همین یک دستور را می‌خواهی)
### Community 90 - "Customer Modal UI" ### Community 90 - "Customer Modal UI"
Cohesion: 0.67 Cohesion: 0.67
Nodes (3): createCS click handler, openRow click handler, Customer Modals UI Nodes (3): createCS click handler, openRow click handler, Customer Modals UI
### Community 91 - "PanelTestCase"
Cohesion: 0.24
Nodes (3): DeletedStaysDeletedTests, PanelTestCase, SentQueueProtectionTests
### Community 92 - "Infrastructure Services" ### Community 92 - "Infrastructure Services"
Cohesion: 0.67 Cohesion: 0.67
Nodes (3): Docker Infrastructure, SMS Delivery Tracker, SMS Worker Service Nodes (3): Docker Infrastructure, SMS Delivery Tracker, SMS Worker Service
@@ -504,46 +525,122 @@ Nodes (3): 1. (Optional) Register the MCP server, 2. Install the skills, Set it
Cohesion: 0.67 Cohesion: 0.67
Nodes (3): 4.1 Make the first map, 4.2 Use the common commands, 4. First use Nodes (3): 4.1 Make the first map, 4.2 Use the common commands, 4. First use
### Community 98 - "reconciliation_tool.py" ### Community 95 - "ConsoleSMSProvider"
Cohesion: 0.21
Nodes (3): console_write(), ConsoleSMSProvider, ConsoleEncodingTests
### Community 96 - "PhoneNumber"
Cohesion: 0.22
Nodes (4): PhoneNumber, DryRunTests, make_phone(), WelcomeTriggerTests
### Community 97 - "تجمیع شماره‌های پیامک مشتریان"
Cohesion: 0.14
Nodes (13): الگوریتم (برای هر مشتری), بازگردانی (اگر نتیجه را نخواستی), بررسی صحت داده (SQL), تجمیع شماره‌های پیامک مشتریان, تعریف کلیدهای گزارش, خطاها و اقدام درست, عملکرد و بار روی دیتابیس, نمونه‌ها (+5 more)
### Community 99 - "Base Template"
Cohesion: 0.15
Nodes (11): Base Template, Customer Base Template, New Customer Base Template, Create Factor View, Customer List View, Django Custom Tags: custom_tags, URL: admin_panel:newFactor, URL: admin_panel:operations (+3 more)
### Community 100 - "test_customer_name_fix.py"
Cohesion: 0.09
Nodes (3): digit_string(), is_landline(), PureHelperTests
### Community 102 - "customer_phone_entries"
Cohesion: 0.20
Nodes (3): customer_phone_entries(), _iter_sources(), PhoneEntry
### Community 103 - "manage.py"
Cohesion: 0.24 Cohesion: 0.24
Nodes (3): remove_query_param(), run_360_audit(), run_audit() Nodes (5): entrypoint.sh script, main(), run.sh script, start_service(), usage()
### Community 113 - "bale/providers.py" ### Community 104 - "افزودن پیشوند «آقای/خانم» به نام مشتریان"
Cohesion: 0.36 Cohesion: 0.15
Nodes (4): BaleBotProvider, ConsoleBaleProvider, get_bale_provider(), NotificationResult Nodes (12): SQLite، قفل دیتابیس و بار روی سایت, افزودن پیشوند «آقای/خانم» به نام مشتریان, بررسی صحت داده (SQL، پیش از/پس از اجرا), بکاپ/بازیابی خودکار نیست, بکاپ پیش از اجرای واقعی, تست‌ها, تعریف کلیدهای آماری, خطاهای رایج و اقدام درست (+4 more)
### Community 166 - "sms_settings" ### Community 105 - "بازمحاسبهٔ «آخرین بازدید» مشتریان"
Cohesion: 0.33 Cohesion: 0.20
Nodes (4): parse_jalali_date(), parse_scan_since(), resolve_recipients_test(), sms_settings() Nodes (9): اجرا روی production, بازمحاسبهٔ «آخرین بازدید» مشتریان, بررسی صحت داده (SQL، پیش از اجرا), دستور, دقیقاً چه می‌کند؟, نمونهٔ خروجی, گزینه‌ها, ۱) اول «شبیه‌سازی» — بدون هیچ تغییری (+1 more)
### Community 113 - "primary_phones"
Cohesion: 0.25
Nodes (5): primary_phones(), primary_phones(), customerEdit(), _parse_phone_rows(), ⚠️ هشدار معنای داده (مهم‌ترین نکتهٔ این سند)
### Community 163 - "Customer"
Cohesion: 0.29
Nodes (4): Customer, Meta, notify_welcome(), customersAdd()
### Community 164 - "SMSQueue"
Cohesion: 0.29
Nodes (4): SMSQueue, DeliveryStatus, Meta, Status
### Community 166 - "توقف و ادامه (Resume / Checkpoint)"
Cohesion: 0.40
Nodes (5): اتمیک بودن, توقف و ادامه (Resume / Checkpoint), سلسله‌مراتب تصمیم نقطهٔ شروع, فایل وضعیت, گزینه‌های ادامه
### Community 169 - "index" ### Community 169 - "index"
Cohesion: 0.67 Cohesion: 0.67
Nodes (3): index(), `nest=` is a max, so it cannot tell a long function from a tangled one, The quality panel Nodes (3): index(), `nest=` is a max, so it cannot tell a long function from a tangled one, The quality panel
### Community 170 - "الگوریتم تصمیم (به همین ترتیب)"
Cohesion: 0.50
Nodes (4): الگوریتم تصمیم (به همین ترتیب), برخورد با نام‌های تکراری و متناقض, نرمال‌سازی (فقط برای کلید جست‌وجو), نمونه‌های رفتاری
### Community 171 - "سریع‌ترین مسیر روی Production (خلاصهٔ اجرایی)"
Cohesion: 0.50
Nodes (4): اگر وسط کار قطع شد, بررسی نتیجه, سریع‌ترین مسیر روی Production (خلاصهٔ اجرایی), ✅ فایل JSON داخل ایمیج کپی می‌شود (تضمین‌شده)
### Community 173 - "stop.sh"
Cohesion: 0.73
Nodes (5): is_our_process(), say(), stop.sh script, stop_pid(), windows_cleanup()
### Community 176 - "اجرای محلی (توسعه)"
Cohesion: 0.50
Nodes (4): اجرای محلی (توسعه), ۱) شبیه‌سازی — همیشه قدم اول, ۲) تنظیم سرعت batch, ۳) اجرای واقعی
### Community 178 - "اجرا روی سرور Production"
Cohesion: 0.67
Nodes (3): اجرا روی سرور Production, اطمینان از انتخاب دیتابیس درست, ترتیب دقیق اجرا
### Community 179 - "اجرای محلی (توسعه)"
Cohesion: 0.67
Nodes (3): اجرای محلی (توسعه), ۱) اول همیشه شبیه‌سازی, ۲) اجرای واقعی (فقط روی مقصد دلخواه)
### Community 180 - "ادامهٔ job پس از crash / قطع برق"
Cohesion: 0.67
Nodes (3): ادامهٔ job پس از crash / قطع برق, دستورهای دقیق ادامه / شروع مجدد, گزینه‌های ادامه
### Community 181 - "اجرا روی سرور Production"
Cohesion: 0.67
Nodes (3): اجرا روی سرور Production, ترتیب دقیق, نکته‌ها برای Production
### Community 189 - "django_contrib_auth_models"
Cohesion: 0.12
Nodes (6): VisitQueue, ProductCategory, ProductStockHistory, Meta, StoreRoomOwner, UserProfile
## Ambiguous Edges - Review These ## Ambiguous Edges - Review These
- `admin_panel/templates/admin_panel/baseHesabdari.html` → `Base Template` [AMBIGUOUS] - `admin_panel/templates/admin_panel/baseHesabdari.html` → `Base Template` [AMBIGUOUS]
admin_panel/templates/admin_panel/baseHesabdari.html · relation: conceptually_related_to admin_panel/templates/admin_panel/baseHesabdari.html · relation: conceptually_related_to
## Knowledge Gaps ## Knowledge Gaps
- **118 isolated node(s):** `base.html`, `toCurrency`, `DataTables Library`, `Customer Data Object`, `Factor Data Object` (+113 more) - **118 isolated node(s):** `base.html`, `toCurrency`, `DataTables Library`, `Customer Data Object`, `Factor Data Object` (+113 more)
These have ≤1 connection - possible missing edges or undocumented components. (Counts symbols only; 738 node(s) total have ≤1 connection when file, concept and rationale nodes are included.) These have ≤1 connection - possible missing edges or undocumented components. (Counts symbols only; 841 node(s) total have ≤1 connection when file, concept and rationale nodes are included.)
- **97 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes. - **89 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions ## Suggested Questions
_Questions this graph is uniquely positioned to answer:_ _Questions this graph is uniquely positioned to answer:_
- **Why does `Rip'n Fast. Fewer Tokens. Better Code.` connect `Rip'n Fast. Fewer Tokens. Better Code.` to `Reference guide`, `.load`, `index`, `Project Honesty Contract`, `Agent Operating Guidelines`, `Performance Benchmarks`, `Agent Setup Guide`?** - **Why does `make_customer()` connect `make_customer` to `run_all`, `PhoneCleanupTests`, `resolve_recipients`, `EventTriggerTests`, `Customer`, `factories.py`, `make_user`, `QueueReviewTests`, `NoticeMechanismTests`, `CustomerSmsHistoryTests`, `stock_in`, `CustomerInfoCardUITests`, `test_sms.py`, `Command`, `.run_cmd`, `IntegrationAndEfficiencyTests`, `PanelTestCase`, `ConsoleSMSProvider`, `PhoneNumber`, `test_customer_name_fix.py`, `customer_phone_entries`, `QueueStatsTests`?**
_High betweenness centrality (0.046) - this node is a cross-community bridge._ _High betweenness centrality (0.075) - this node is a cross-community bridge._
- **Are the 2 inferred relationships involving `make_customer()` (e.g. with `Customer` and `PhoneNumber`) actually correct?** - **Are the 2 inferred relationships involving `make_customer()` (e.g. with `Customer` and `PhoneNumber`) actually correct?**
_`make_customer()` has 2 INFERRED edges - model-reasoned connections that need verification._ _`make_customer()` has 2 INFERRED edges - model-reasoned connections that need verification._
- **What connects `base.html`, `toCurrency`, `DataTables Library` to the rest of the system?** - **What connects `base.html`, `toCurrency`, `DataTables Library` to the rest of the system?**
_118 weakly-connected nodes found - possible documentation gaps or missing edges._ _118 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `views/sms.py` be split into smaller, more focused modules?** - **Should `views/sms.py` be split into smaller, more focused modules?**
_Cohesion score 0.1425287356321839 - nodes in this community are weakly interconnected._ _Cohesion score 0.1226890756302521 - nodes in this community are weakly interconnected._
- **Why does `Languages` connect `.load` to `Rip'n Fast. Fewer Tokens. Better Code.`?** - **Why does `Reference guide` connect `Reference guide` to `.load`, `Installation Guide`, `Output Format Specs`, `Rip'n Fast. Fewer Tokens. Better Code.`, `First Use Guide`?**
_High betweenness centrality (0.044) - this node is a cross-community bridge._ _High betweenness centrality (0.038) - this node is a cross-community bridge._
- **Should `run_all` be split into smaller, more focused modules?** - **Are the 2 inferred relationships involving `normalize_mobile()` (e.g. with `الگوریتم (برای هر مشتری)` and `قالبِ ذخیره‌شده: `+989xxxxxxxxx``) actually correct?**
_Cohesion score 0.05552617662612375 - nodes in this community are weakly interconnected._ _`normalize_mobile()` has 2 INFERRED edges - model-reasoned connections that need verification._
### Low-confidence Hints ### Low-confidence Hints
_AMBIGUOUS edges — the extractor was unsure. Verify before acting on these._ _AMBIGUOUS edges — the extractor was unsure. Verify before acting on these._
+4 -4
View File
@@ -63,12 +63,12 @@
</div> </div>
<div id="legend"></div> <div id="legend"></div>
</div> </div>
<div id="stats">1684 nodes &middot; 3008 edges &middot; 171 communities</div> <div id="stats">1960 nodes &middot; 3584 edges &middot; 184 communities</div>
</div> </div>
<script> <script>
const RAW_NODES = [{"id": "admin_panel_management_commands_sms_scan_command", "label": "Command", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 12.4, "font": {"size": 0, "color": "#ffffff"}, "title": "Command", "community": 1, "community_name": "run_all", "source_file": "admin_panel/management/commands/sms_scan.py", "file_type": "code", "degree": 7}, {"id": "tests_test_sms_autosendtests", "label": "AutoSendTests", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 14.0, "font": {"size": 0, "color": "#ffffff"}, "title": "AutoSendTests", "community": 1, "community_name": "run_all", "source_file": "tests/test_sms.py", "file_type": "code", "degree": 12}, {"id": "tests_test_sms_coldstarttests", "label": "ColdStartTests", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 14.0, "font": {"size": 0, "color": "#ffffff"}, "title": "ColdStartTests", "community": 1, "community_name": "run_all", "source_file": "tests/test_sms.py", "file_type": "code", "degree": 12}, {"id": "tests_test_sms_consoleencodingtests", "label": "ConsoleEncodingTests", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 13.4, "font": {"size": 0, "color": "#ffffff"}, "title": "ConsoleEncodingTests", "community": 1, "community_name": "run_all", "source_file": "tests/test_sms.py", "file_type": "code", "degree": 10}, {"id": "tests_test_sms_scannertests", "label": "ScannerTests", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 15.1, "font": {"size": 12, "color": "#ffffff"}, "title": "ScannerTests", "community": 1, "community_name": "run_all", "source_file": "tests/test_sms.py", "file_type": "code", "degree": 15}, {"id": "admin_panel_migrations_0001_initial_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_db_models_deletion", "source_file": "admin_panel/migrations/0001_initial.py", "file_type": "code", "degree": 1}, {"id": "admin_panel_migrations_0007_productinstore_factor_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_db_models_deletion", "source_file": "admin_panel/migrations/0007_productinstore_factor.py", "file_type": "code", "degree": 1}, {"id": "admin_panel_migrations_0008_storeroom_user_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_db_models_deletion", "source_file": "admin_panel/migrations/0008_storeroom_user.py", "file_type": "code", "degree": 1}, {"id": "admin_panel_migrations_0018_alter_factoriteam_product_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_db_models_deletion", "source_file": "admin_panel/migrations/0018_alter_factoriteam_product.py", "file_type": "code", "degree": 1}, {"id": "admin_panel_migrations_0019_article_articletype_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_db_models_deletion", "source_file": "admin_panel/migrations/0019_article_articletype.py", "file_type": "code", "degree": 1}, {"id": "admin_panel_migrations_0036_sarresid_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_db_models_deletion", "source_file": "admin_panel/migrations/0036_sarresid.py", "file_type": "code", "degree": 1}, {"id": "admin_panel_migrations_0038_auto_20240213_1735_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_db_models_deletLine truncated const RAW_NODES = [{"id": "admin_panel_management_commands_sms_scan_command", "label": "Command", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 12.2, "font": {"size": 0, "color": "#ffffff"}, "title": "Command", "community": 1, "community_name": "run_all", "source_file": "admin_panel/management/commands/sms_scan.py", "file_type": "code", "degree": 7}, {"id": "tests_test_sms_autosendtests", "label": "AutoSendTests", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 13.8, "font": {"size": 0, "color": "#ffffff"}, "title": "AutoSendTests", "community": 1, "community_name": "run_all", "source_file": "tests/test_sms.py", "file_type": "code", "degree": 12}, {"id": "tests_test_sms_coldstarttests", "label": "ColdStartTests", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 13.8, "font": {"size": 0, "color": "#ffffff"}, "title": "ColdStartTests", "community": 1, "community_name": "run_all", "source_file": "tests/test_sms.py", "file_type": "code", "degree": 12}, {"id": "tests_test_sms_scannertests", "label": "ScannerTests", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 14.7, "font": {"size": 12, "color": "#ffffff"}, "title": "ScannerTests", "community": 1, "community_name": "run_all", "source_file": "tests/test_sms.py", "file_type": "code", "degree": 15}, {"id": "tests_test_sms_workertests", "label": "WorkerTests", "color": {"background": "#F28E2B", "border": "#F28E2B", "highlight": {"background": "#ffffff", "border": "#F28E2B"}}, "size": 12.8, "font": {"size": 0, "color": "#ffffff"}, "title": "WorkerTests", "community": 1, "community_name": "run_all", "source_file": "tests/test_sms.py", "file_type": "code", "degree": 9}, {"id": "admin_panel_migrations_0001_initial_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_conf", "source_file": "admin_panel/migrations/0001_initial.py", "file_type": "code", "degree": 1}, {"id": "admin_panel_migrations_0007_productinstore_factor_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_conf", "source_file": "admin_panel/migrations/0007_productinstore_factor.py", "file_type": "code", "degree": 1}, {"id": "admin_panel_migrations_0008_storeroom_user_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_conf", "source_file": "admin_panel/migrations/0008_storeroom_user.py", "file_type": "code", "degree": 1}, {"id": "admin_panel_migrations_0011_storeroom_user_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_conf", "source_file": "admin_panel/migrations/0011_storeroom_user.py", "file_type": "code", "degree": 1}, {"id": "admin_panel_migrations_0012_visitqueue_user_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_conf", "source_file": "admin_panel/migrations/0012_visitqueue_user.py", "file_type": "code", "degree": 1}, {"id": "admin_panel_migrations_0015_auto_20220124_0858_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_conf", "source_file": "admin_panel/migrations/0015_auto_20220124_0858.py", "file_type": "code", "degree": 1}, {"id": "admin_panel_migrations_0016_factor_user_migration", "label": "Migration", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "Migration", "community": 10, "community_name": "django_conf", "source_file": "admin_panel/migrations/0016_factor_user.py", "file_type": "code", "degree": 1}, {"id": "admin_panel_migrations_0018_alterLine truncated
const RAW_EDGES = [{"from": "admin_panel_management_commands_sms_scan", "to": "admin_panel_management_commands_sms_scan_command", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "admin_panel_management_commands_sms_scan_command", "to": "admin_panel_management_commands_sms_scan_py_basecommand", "label": "inherits", "title": "inherits [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "admin_panel_management_commands_sms_scan_command", "to": "admin_panel_management_commands_sms_scan_command_init", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "admin_panel_management_commands_sms_scan_command", "to": "admin_panel_management_commands_sms_scan_command_add_arguments", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "admin_panel_management_commands_sms_scan_command", "to": "admin_panel_management_commands_sms_scan_command_handle", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "admin_panel_management_commands_sms_scan_command", "to": "admin_panel_management_commands_sms_scan_command_handle_signal", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "admin_panel_management_commands_sms_scan_command", "to": "admin_panel_management_commands_sms_scan_command_emit", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms", "to": "tests_test_sms_autosendtests", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_paneltestcase", "label": "inherits", "title": "inherits [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_overdue_visit_customer", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_test_default_is_pending_so_manager_must_approve", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_test_auto_send_enqueues_approved_without_manager", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_test_auto_send_does_not_bypass_opt_out_or_disabled", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_test_welcome_auto_send_skips_approval_on_customer_creation", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_test_template_edit_persists_auto_send", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_test_templates_page_shows_auto_send_switch", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_rationale_1069", "to": "tests_test_sms_autosendtests", "label": "rationale_for", "title": "rationale_for [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "admin_models_phonenumber", "label": "uses", "title": "uses [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "tests_test_sms_autosendtests", "to": "admin_models_customer", "label": "uses", "title": "uses [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "tests_test_sms", "to": "tests_test_sms_coldstarttests", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_coldstarttests", "to": "tests_test_sms_py_testcase", "label": "inherits", "Line truncated const RAW_EDGES = [{"from": "admin_panel_management_commands_sms_scan", "to": "admin_panel_management_commands_sms_scan_command", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "admin_panel_management_commands_sms_scan_command", "to": "admin_panel_management_commands_sms_scan_py_basecommand", "label": "inherits", "title": "inherits [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "admin_panel_management_commands_sms_scan_command", "to": "admin_panel_management_commands_sms_scan_command_init", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "admin_panel_management_commands_sms_scan_command", "to": "admin_panel_management_commands_sms_scan_command_add_arguments", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "admin_panel_management_commands_sms_scan_command", "to": "admin_panel_management_commands_sms_scan_command_handle", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "admin_panel_management_commands_sms_scan_command", "to": "admin_panel_management_commands_sms_scan_command_handle_signal", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "admin_panel_management_commands_sms_scan_command", "to": "admin_panel_management_commands_sms_scan_command_emit", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms", "to": "tests_test_sms_autosendtests", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_paneltestcase", "label": "inherits", "title": "inherits [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_overdue_visit_customer", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_test_default_is_pending_so_manager_must_approve", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_test_auto_send_enqueues_approved_without_manager", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_test_auto_send_does_not_bypass_opt_out_or_disabled", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_test_welcome_auto_send_skips_approval_on_customer_creation", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_test_template_edit_persists_auto_send", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "tests_test_sms_autosendtests_test_templates_page_shows_auto_send_switch", "label": "method", "title": "method [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_rationale_1069", "to": "tests_test_sms_autosendtests", "label": "rationale_for", "title": "rationale_for [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_autosendtests", "to": "admin_models_phonenumber", "label": "uses", "title": "uses [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "tests_test_sms_autosendtests", "to": "admin_models_customer", "label": "uses", "title": "uses [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "tests_test_sms", "to": "tests_test_sms_coldstarttests", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tests_test_sms_coldstarttests", "to": "tests_test_sms_py_testcase", "label": "inherits", "Line truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "views/sms.py", "count": 30}, {"cid": 1, "color": "#F28E2B", "label": "run_all", "count": 62}, {"cid": 2, "color": "#E15759", "label": "django_db", "count": 49}, {"cid": 3, "color": "#76B7B2", "label": "views/store.py", "count": 63}, {"cid": 4, "color": "#59A14F", "label": "ModelsPackageContractTests", "count": 28}, {"cid": 5, "color": "#EDC948", "label": "PhoneCleanupTests", "count": 33}, {"cid": 6, "color": "#B07AA1", "label": ".load", "count": 61}, {"cid": 7, "color": "#FF9DA7", "label": "models/sms.py", "count": 32}, {"cid": 8, "color": "#9C755F", "label": "logging", "count": 34}, {"cid": 9, "color": "#BAB0AC", "label": "views/customers.py", "count": 34}, {"cid": 10, "color": "#4E79A7", "label": "django_db_models_deletion", "count": 25}, {"cid": 11, "color": "#F28E2B", "label": "make_customer", "count": 20}, {"cid": 12, "color": "#E15759", "label": "customer_phone_entries", "count": 23}, {"cid": 13, "color": "#76B7B2", "label": "EventTriggerTests", "count": 22}, {"cid": 14, "color": "#59A14F", "label": "views/finance.py", "count": 26}, {"cid": 15, "color": "#EDC948", "label": "SMSResult", "count": 31}, {"cid": 16, "color": "#B07AA1", "label": "sms0098ws.py", "count": 29}, {"cid": 17, "color": "#FF9DA7", "label": "Admin Panel Customization", "count": 22}, {"cid": 18, "color": "#9C755F", "label": "User", "count": 25}, {"cid": 19, "color": "#BAB0AC", "label": "django_conf", "count": 23}, {"cid": 20, "color": "#4E79A7", "label": "test_customers.py", "count": 21}, {"cid": 21, "color": "#F28E2B", "label": "phones.py", "count": 14}, {"cid": 22, "color": "#E15759", "label": "Login Template Forms", "count": 19}, {"cid": 23, "color": "#76B7B2", "label": "cheque_inquiry.py", "count": 19}, {"cid": 24, "color": "#59A14F", "label": "factories.py", "count": 14}, {"cid": 25, "color": "#EDC948", "label": "Rip&#x27;n Fast. Fewer Tokens. Better Code.", "count": 18}, {"cid": 26, "color": "#B07AA1", "label": "views/factors.py", "count": 20}, {"cid": 27, "color": "#FF9DA7", "label": "to_local", "count": 16}, {"cid": 28, "color": "#9C755F", "label": "make_user", "count": 23}, {"cid": 29, "color": "#BAB0AC", "label": "QueueReviewTests", "count": 9}, {"cid": 30, "color": "#4E79A7", "label": "NoticeMechanismTests", "count": 16}, {"cid": 31, "color": "#F28E2B", "label": "enqueue.py", "count": 17}, {"cid": 32, "color": "#E15759", "label": "enqueue_sms", "count": 18}, {"cid": 33, "color": "#76B7B2", "label": "PanelTestCase", "count": 13}, {"cid": 34, "color": "#59A14F", "label": "ProductInStore", "count": 16}, {"cid": 35, "color": "#EDC948", "label": "admin_panel/templates/admin_panel/baseHesabdari.html", "count": 31}, {"cid": 36, "color": "#B07AA1", "label": "Reference guide", "count": 14}, {"cid": 37, "color": "#FF9DA7", "label": "Factor Creation UI", "count": 14}, {"cid": 38, "color": "#9C755F", "label": "admin_panel/templates/admin_panel/bedehi.html", "count": 14}, {"cid": 39, "color": "#BAB0AC", "label": "project_tree.py", "count": 14}, {"cid": 40, "color": "#4E79A7", "label": "CustomerSmsHistoryTests", "count": 24}, {"cid": 41, "color": "#F28E2B", "label": "TemplatesPageUITests", "count": 14}, {"cid": 42, "color": "#E15759", "label": "SMS Integration Docs", "count": 13}, {"cid": 43, "color": "#76B7B2", "label": "timing_for", "count": 20}, {"cid": 44, "color": "#59A14F", "label": "django_shortcuts", "count": 12}, {"cid": 45, "color": "#EDC948", "label": "stock_in", "count": 14}, {"cid": 46, "color": "#B07AA1", "label": "admin_panel/templates/admin_panel/customerAction.html", "count": 14}, {"cid": 47, "color": "#FF9DA7", "label": "parts_count", "count": 22}, {"cid": 48, "color": "#9C755F", "label": "write_tree", "count": 14}, {"cid": 49, "color": "#BAB0AC", "label": "CustomerInfoCardUITests", "count": 12}, {"cid": 50, "color": "#4E79A7", "label": "Customer", "count": 14}, {"cid": 51, "color": "#F28E2B", "label": "Phone Cleanup Guide", "count": 12}, {"cid": 52, "color": "#E15759", "label": "SmsReportTests", "count": 17}, {"cid": 53, "color": "#76B7B2", "label": "manage.py", "count": 11}, {"cid": 54, "color": "#59A14F", "label": "Agent Operating Guidelines", "count": 11}, {"cid": 55, "color": "#EDC948", "label": "SMSDeliveryCommandTests", "count": 10}, {"cid": 56, "color": "#B07AA1", "label": "BehinSMSProvider", "count": 8}, {"cid": 57, "color": "#FF9DA7", "label": "test_sms.py", "count": 16}, {"cid": 58, "color": "#9C755F", "label": "Financial Data Objects", "count": 10}, {"cid": 59, "color": "#BAB0AC", "label": "Hybrid Migration Command", "count": 10}, {"cid": 60, "color": "#4E79A7", "label": "\u0628\u0627\u0632\u0645\u062d\u0627\u0633\u0628\u0647\u0654 \u00ab\u0622\u062e\u0631\u06cc\u0646 \u0628\u0627\u0632\u062f\u06cc\u062f\u00bb \u0645\u0634\u062a\u0631\u06cc\u0627\u0646", "count": 11}, {"cid": 61, "color": "#F28E2B", "label": "Resource Cleanup Handler", "count": 10}, {"cid": 62, "color": "#E15759", "label": "SMS Timing Migration", "count": 10}, {"cid": 63, "color": "#Line truncated const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "views/sms.py", "count": 35}, {"cid": 1, "color": "#F28E2B", "label": "run_all", "count": 65}, {"cid": 2, "color": "#E15759", "label": "django_db", "count": 79}, {"cid": 3, "color": "#76B7B2", "label": "create_transfer_request", "count": 42}, {"cid": 4, "color": "#59A14F", "label": "ModelsPackageContractTests", "count": 14}, {"cid": 5, "color": "#EDC948", "label": "PhoneCleanupTests", "count": 33}, {"cid": 6, "color": "#B07AA1", "label": ".load", "count": 19}, {"cid": 7, "color": "#FF9DA7", "label": "models/sms.py", "count": 16}, {"cid": 8, "color": "#9C755F", "label": "models/__init__.py", "count": 39}, {"cid": 9, "color": "#BAB0AC", "label": "views/customers.py", "count": 19}, {"cid": 10, "color": "#4E79A7", "label": "django_conf", "count": 50}, {"cid": 11, "color": "#F28E2B", "label": "make_customer", "count": 20}, {"cid": 12, "color": "#E15759", "label": "resolve_recipients", "count": 19}, {"cid": 13, "color": "#76B7B2", "label": "EventTriggerTests", "count": 22}, {"cid": 14, "color": "#59A14F", "label": "views/finance.py", "count": 27}, {"cid": 15, "color": "#EDC948", "label": "views/store.py", "count": 26}, {"cid": 16, "color": "#B07AA1", "label": "sms0098ws.py", "count": 10}, {"cid": 17, "color": "#FF9DA7", "label": "Admin Panel Customization", "count": 22}, {"cid": 18, "color": "#9C755F", "label": "User", "count": 24}, {"cid": 19, "color": "#BAB0AC", "label": "timing_for", "count": 20}, {"cid": 20, "color": "#4E79A7", "label": "Customer", "count": 22}, {"cid": 21, "color": "#F28E2B", "label": "phones.py", "count": 14}, {"cid": 22, "color": "#E15759", "label": "Login Template Forms", "count": 19}, {"cid": 23, "color": "#76B7B2", "label": "cheque_inquiry.py", "count": 20}, {"cid": 24, "color": "#59A14F", "label": "factories.py", "count": 21}, {"cid": 25, "color": "#EDC948", "label": "Rip&#x27;n Fast. Fewer Tokens. Better Code.", "count": 18}, {"cid": 26, "color": "#B07AA1", "label": "views/factors.py", "count": 25}, {"cid": 27, "color": "#FF9DA7", "label": "sms0098.py", "count": 14}, {"cid": 28, "color": "#9C755F", "label": "make_user", "count": 23}, {"cid": 29, "color": "#BAB0AC", "label": "QueueReviewTests", "count": 9}, {"cid": 30, "color": "#4E79A7", "label": "NoticeMechanismTests", "count": 16}, {"cid": 31, "color": "#F28E2B", "label": "SmsReportTests", "count": 17}, {"cid": 32, "color": "#E15759", "label": "scanner.py", "count": 16}, {"cid": 33, "color": "#76B7B2", "label": "ZeroZeroNinetyEightWebServiceTests", "count": 38}, {"cid": 34, "color": "#59A14F", "label": "ProductInStore", "count": 6}, {"cid": 35, "color": "#EDC948", "label": "admin_panel/templates/admin_panel/baseHesabdari.html", "count": 15}, {"cid": 36, "color": "#B07AA1", "label": "Reference guide", "count": 14}, {"cid": 37, "color": "#FF9DA7", "label": "Factor Creation UI", "count": 14}, {"cid": 38, "color": "#9C755F", "label": "hesabdariResidNashode.html", "count": 14}, {"cid": 39, "color": "#BAB0AC", "label": "requests", "count": 13}, {"cid": 40, "color": "#4E79A7", "label": "CustomerSmsHistoryTests", "count": 24}, {"cid": 41, "color": "#F28E2B", "label": "TemplatesPageUITests", "count": 14}, {"cid": 42, "color": "#E15759", "label": "SMS Integration Docs", "count": 13}, {"cid": 43, "color": "#76B7B2", "label": "enqueue.py", "count": 14}, {"cid": 44, "color": "#59A14F", "label": "django_shortcuts", "count": 15}, {"cid": 45, "color": "#EDC948", "label": "stock_in", "count": 27}, {"cid": 46, "color": "#B07AA1", "label": "admin_panel/templates/admin_panel/customerAction.html", "count": 13}, {"cid": 47, "color": "#FF9DA7", "label": "parts_count", "count": 22}, {"cid": 48, "color": "#9C755F", "label": "write_tree", "count": 14}, {"cid": 49, "color": "#BAB0AC", "label": "CustomerInfoCardUITests", "count": 12}, {"cid": 50, "color": "#4E79A7", "label": "test_sms.py", "count": 14}, {"cid": 51, "color": "#F28E2B", "label": "Phone Cleanup Guide", "count": 12}, {"cid": 52, "color": "#E15759", "label": ".run_command", "count": 40}, {"cid": 53, "color": "#76B7B2", "label": "project_tree.py", "count": 14}, {"cid": 54, "color": "#59A14F", "label": "Agent Operating Guidelines", "count": 11}, {"cid": 55, "color": "#EDC948", "label": "Command", "count": 72}, {"cid": 56, "color": "#B07AA1", "label": "BehinSMSProvider", "count": 8}, {"cid": 57, "color": "#FF9DA7", "label": "normalize_mobile", "count": 17}, {"cid": 58, "color": "#9C755F", "label": "Financial Data Objects", "count": 10}, {"cid": 59, "color": "#BAB0AC", "label": "Hybrid Migration Command", "count": 10}, {"cid": 60, "color": "#4E79A7", "label": ".run_cmd", "count": 34}, {"cid": 61, "color": "#F28E2B", "label": "Resource Cleanup Handler", "count": 10}, {"cid": 62, "color": "#E15759", "label": "SMS Timing Migration", "count": 10}, {"cid": 63, "color": "#76B7B2", "label": "admin_panel/templates/admin_panel/bedehi.html", "count": 10}, {"cid": 64, "color": "#59A14F", "label": "customerIDFactorBackup.html", "count": 10}, {"cid": 65, "colLine truncated
// HTML-escape helper — prevents XSS when injecting graph data into innerHTML // HTML-escape helper — prevents XSS when injecting graph data into innerHTML
function esc(s) { function esc(s) {
+15519 -5602
View File
File diff suppressed because it is too large. Load diff
+68 -8
View File
@@ -882,10 +882,10 @@
"semantic_hash": "7cd6b8258d07f9d1a4f668a95b1c9c58" "semantic_hash": "7cd6b8258d07f9d1a4f668a95b1c9c58"
}, },
"admin_panel/models/actions.py": { "admin_panel/models/actions.py": {
"mtime": 1791557666.0745652, "mtime": 1792153042.089988,
"seen": 1791560671.8564537, "seen": 1792154233.652624,
"ast_hash": "8213389e796cad243cdb758fc78076a5", "ast_hash": "5b305ef4bef23a1e8b518282ef1b318e",
"semantic_hash": "8213389e796cad243cdb758fc78076a5" "semantic_hash": ""
}, },
"admin_panel/models/customers.py": { "admin_panel/models/customers.py": {
"mtime": 1791621708.4566169, "mtime": 1791621708.4566169,
@@ -918,10 +918,10 @@
"semantic_hash": "eb5af656ba8114816e5ccabf297fe73c" "semantic_hash": "eb5af656ba8114816e5ccabf297fe73c"
}, },
"admin_panel/models/system.py": { "admin_panel/models/system.py": {
"mtime": 1791557640.9975393, "mtime": 1792044277.0221417,
"seen": 1791560671.8564537, "seen": 1792143069.509325,
"ast_hash": "519f162ee90768141c9d3a8bc66f899b", "ast_hash": "c10d997d1e07f8068c6fc1058f175272",
"semantic_hash": "519f162ee90768141c9d3a8bc66f899b" "semantic_hash": ""
}, },
"admin_panel/models/users.py": { "admin_panel/models/users.py": {
"mtime": 1791557633.8912268, "mtime": 1791557633.8912268,
@@ -1456,5 +1456,65 @@
"seen": 1791720362.5829337, "seen": 1791720362.5829337,
"ast_hash": "29e04949da66478a11268a7672440f2b", "ast_hash": "29e04949da66478a11268a7672440f2b",
"semantic_hash": "" "semantic_hash": ""
},
"admin_panel/management/commands/fix_customer_names.py": {
"mtime": 1792044603.4607384,
"seen": 1792143069.4936357,
"ast_hash": "5a49fa8cbc87b9f6a953d057fa57d99a",
"semantic_hash": ""
},
"admin_panel/management/commands/normalize_customer_sms_phones.py": {
"mtime": 1792047955.413919,
"seen": 1792143069.4936357,
"ast_hash": "133a138ccdd4893c1f143ae1c02624fc",
"semantic_hash": ""
},
"admin_panel/migrations/0072_customernamefixjob.py": {
"mtime": 1792044430.7213922,
"seen": 1792143069.5073185,
"ast_hash": "bb36754a8212524a8139a1902c5d7342",
"semantic_hash": ""
},
"admin_panel/name_fix.py": {
"mtime": 1792044299.406774,
"seen": 1792143069.509325,
"ast_hash": "dcd494dfe70787d8f08fc7931cb672e1",
"semantic_hash": ""
},
"tests/test_customer_name_fix.py": {
"mtime": 1792044607.9337437,
"seen": 1792143069.5209367,
"ast_hash": "9be8b6950f0393905fece6a5ec39d3fe",
"semantic_hash": ""
},
"tests/test_sms_phone_normalize.py": {
"mtime": 1792047960.6255968,
"seen": 1792143069.5209367,
"ast_hash": "136471d0abdbcf7954655a016227debe",
"semantic_hash": ""
},
"fix_customer_names.md": {
"mtime": 1792050620.3585155,
"seen": 1792143069.5341449,
"ast_hash": "219b8dae11524c783147745a3dcf95b2",
"semantic_hash": ""
},
"normalize_customer_sms_phones.md": {
"mtime": 1792050085.597962,
"seen": 1792143069.5341449,
"ast_hash": "eb58b7f94cbb566de62503c61f011ac9",
"semantic_hash": ""
},
"admin_panel/migrations/0073_alter_action_actiontype_choices.py": {
"mtime": 1792147840.0279932,
"seen": 1792152215.2757294,
"ast_hash": "c7edfada8a3fc49fc89515b8d6e38aab",
"semantic_hash": ""
},
"admin_panel/migrations/0074_alter_action_status_choices.py": {
"mtime": 1792153129.8287945,
"seen": 1792154233.652624,
"ast_hash": "315d8f435e2837683f3c4b797f7928b9",
"semantic_hash": ""
} }
} }
+138 -24
View File
@@ -18,6 +18,7 @@ from admin_panel.sms.recipients import resolve_recipients
from admin_panel.sms.rules import is_sms_enabled from admin_panel.sms.rules import is_sms_enabled
from admin_panel.sms.scanner import run_all from admin_panel.sms.scanner import run_all
from admin_panel.sms.timing import apply_timing, current_timing_value, timing_for from admin_panel.sms.timing import apply_timing, current_timing_value, timing_for
from admin_panel.sms.triggers import notify_welcome
from integrations.sms.base import SMSProvider, SMSResult from integrations.sms.base import SMSProvider, SMSResult
from integrations.sms.normalize import normalize_mobile, to_local from integrations.sms.normalize import normalize_mobile, to_local
@@ -193,13 +194,13 @@ class ScannerTests(TestCase):
customer=self.customer, phone_number='09120000001', customer=self.customer, phone_number='09120000001',
) )
def test_cheque_funding_fires_after_due_and_is_idempotent(self): def test_cheque_funding_fires_before_due_and_is_idempotent(self):
"""یادآوری تأمین موجودی، چند روز پس از سررسید و فقط یک‌بار.""" """یادآوری تأمین موجودی، پیش از سررسید و فقط یک‌بار."""
today = timezone.localdate() today = timezone.localdate()
# سررسید ۳ روز پیش، offset پیش‌فرض = ۱ ⇒ واجد شرط. # سررسید فردا، offset پیش‌فرض = ۱ ⇒ همین امروز واجد شرط است.
Sarresid.objects.create( Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check', customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() - timedelta(days=3), sarresidDT=timezone.now() + timedelta(days=1),
) )
first = run_all(today) first = run_all(today)
second = run_all(today) second = run_all(today)
@@ -209,8 +210,8 @@ class ScannerTests(TestCase):
SMSQueue.objects.filter(sms_type=SMSType.CHEQUE_REGISTRATION).count(), 1, SMSQueue.objects.filter(sms_type=SMSType.CHEQUE_REGISTRATION).count(), 1,
) )
def test_cheque_funding_waits_until_after_due(self): def test_cheque_funding_waits_when_due_is_far_ahead(self):
"""پیش از سررسید هیچ یادآوری‌ای ساخته نمی‌شود.""" """سررسیدِ دورتر از پنجرهٔ یادآوری هیچ پیامکی نمی‌سازد."""
today = timezone.localdate() today = timezone.localdate()
Sarresid.objects.create( Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check', customer=self.customer, price=5000, payStatus='false', type='check',
@@ -223,29 +224,138 @@ class ScannerTests(TestCase):
today = timezone.localdate() today = timezone.localdate()
Sarresid.objects.create( Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='true', type='check', customer=self.customer, price=5000, payStatus='true', type='check',
sarresidDT=timezone.now() - timedelta(days=3), sarresidDT=timezone.now() + timedelta(days=1),
) )
self.assertEqual(run_all(today)['cheque_registration'], 0) self.assertEqual(run_all(today)['cheque_registration'], 0)
def test_cheque_funding_offset_is_configurable(self): def test_cheque_funding_offset_is_configurable(self):
"""تعداد روز پس از سررسید از قالب (پنل) قابل تنظیم است.""" """تعداد روز پیش از سررسید از قالب (پنل) قابل تنظیم است."""
template = SMSTemplate.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION) template = SMSTemplate.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION)
apply_timing(template, 10) apply_timing(template, 10)
template.save() template.save()
today = timezone.localdate() today = timezone.localdate()
# سررسید ۳ روز پیش ولی offset=۱۰ ⇒ هنوز واجد شرط نیست. # سررسید فردا ولی offset=۱۰ ⇒ داخل پنجره ⇒ پیامک می‌رود.
Sarresid.objects.create( Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check', customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() - timedelta(days=3), sarresidDT=timezone.now() + timedelta(days=1),
)
self.assertEqual(run_all(today)['cheque_registration'], 0)
# سررسید ۱۲ روز پیش ⇒ واجد شرط.
Sarresid.objects.all().delete()
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() - timedelta(days=12),
) )
self.assertEqual(run_all(today)['cheque_registration'], 1) self.assertEqual(run_all(today)['cheque_registration'], 1)
# با offset کوچک‌تر (۱)، سررسید ۱۲ روز آینده بیرون پنجره است ⇒ ۰.
Sarresid.objects.all().delete()
SMSDedup.objects.all().delete()
SMSQueue.objects.all().delete()
apply_timing(template, 1)
template.save()
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() + timedelta(days=12),
)
self.assertEqual(run_all(today)['cheque_registration'], 0)
# همان چک با offset=۱۲ داخل پنجره می‌شود ⇒ ۱.
apply_timing(template, 12)
template.save()
self.assertEqual(run_all(today)['cheque_registration'], 1)
def test_cheque_funding_days_is_days_remaining(self):
"""«{days}» تعداد روز باقی‌مانده تا سررسید است (مثبت = پیش از سررسید)."""
today = timezone.localdate()
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() + timedelta(days=1),
)
# قالبِ دیتابیس ممکن است {days} نداشته باشد؛ متن را برای این تست می‌گذاریم
# تا معنای روزِ باقی‌مانده سنجیده شود (متنِ خودِ رکورد DB دست‌نخورده می‌ماند).
SMSTemplate.objects.filter(
sms_type=SMSType.CHEQUE_REGISTRATION,
).update(text='{days}')
self.assertEqual(run_all(today)['cheque_registration'], 1)
item = SMSQueue.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION)
# سررسید فردا ⇒ روز باقی‌مانده = ۱
self.assertEqual(item.message, "1")
def test_cheque_funding_exposes_sayyad_id(self):
"""«{sayyad_id}» شناسهٔ صیاد همان چک را نشان می‌دهد."""
today = timezone.localdate()
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() + timedelta(days=1),
check_row_number='4321', check_sayyad_id='1234567890123456',
)
# متن با هر دو متغیر چک، برای سنجش مقدار واقعی (DB دست‌نخورده می‌ماند).
SMSTemplate.objects.filter(
sms_type=SMSType.CHEQUE_REGISTRATION,
).update(text='{check_row}|{sayyad_id}')
self.assertEqual(run_all(today)['cheque_registration'], 1)
item = SMSQueue.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION)
self.assertEqual(item.message, "4321|1234567890123456")
def test_cheque_funding_sayyad_id_empty_when_not_set(self):
"""اگر شناسهٔ صیاد ثبت نشده باشد، متغیر خالی رندر می‌شود (نه خطا)."""
today = timezone.localdate()
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() + timedelta(days=1),
check_row_number='9999',
)
SMSTemplate.objects.filter(
sms_type=SMSType.CHEQUE_REGISTRATION,
).update(text='[{sayyad_id}]')
self.assertEqual(run_all(today)['cheque_registration'], 1)
item = SMSQueue.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION)
self.assertEqual(item.message, "[]")
def test_balance_is_available_in_every_template(self):
"""«{balance}» در همهٔ قالب‌ها (نه فقط چک) قابل استفاده است."""
from admin_panel.models import Article, SMSType as T
Article.objects.create(customer=self.customer, debit=500000, credit=0)
Article.objects.create(customer=self.customer, debit=0, credit=125000)
# ۵۰۰٬۰۰۰ − ۱۲۵٬۰۰۰ = ۳۷۵٬۰۰۰
for sms_type in T.values:
SMSTemplate.objects.filter(sms_type=sms_type).update(
text='{balance}|{balance_sep}',
)
# رویدادی: خوش‌آمدگویی
notify_welcome(self.customer)
item = SMSQueue.objects.get(sms_type=T.WELCOME)
self.assertEqual(item.message, "375000|375,000")
def test_balance_reflects_real_articles(self):
"""مانده حساب = مجموع بدهکار − مجموع بستانکار اسناد مشتری."""
from admin_panel.models import Article
from admin_panel.sms.balance import customer_balance
self.assertEqual(customer_balance(self.customer), 0)
Article.objects.create(customer=self.customer, debit=900000, credit=0)
Article.objects.create(customer=self.customer, debit=0, credit=400000)
self.assertEqual(customer_balance(self.customer), 500000)
# مانده منفی هم باید درست برگردد (بستانکار بیشتر از بدهکار).
Article.objects.create(customer=self.customer, debit=0, credit=750000)
self.assertEqual(customer_balance(self.customer), -250000)
def test_balance_zero_when_customer_is_none(self):
"""بدون مشتری، مانده صفر است و خطایی رخ نمی‌دهد."""
from admin_panel.sms.balance import customer_balance
self.assertEqual(customer_balance(None), 0)
def test_balance_does_not_touch_due_amount(self):
"""{balance} (مانده کل حساب) با {amount} (مبلغ چک) قاطی نمی‌شود."""
from admin_panel.models import Article
today = timezone.localdate()
Article.objects.create(customer=self.customer, debit=8000000, credit=0)
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() + timedelta(days=1),
)
SMSTemplate.objects.filter(
sms_type=SMSType.CHEQUE_REGISTRATION,
).update(text='amount={amount_sep} balance={balance_sep}')
self.assertEqual(run_all(today)['cheque_registration'], 1)
item = SMSQueue.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION)
# مبلغ چک = ۵٬۰۰۰ (سررسیدشده) ولی مانده حساب = ۸٬۰۰۰٬۰۰۰
self.assertEqual(item.message, "amount=5,000 balance=8,000,000")
def test_nesiye_due_fires_on_due_date_and_ignores_checks(self): def test_nesiye_due_fires_on_due_date_and_ignores_checks(self):
"""یادآوری سررسید فقط برای نسیه است، نه چک.""" """یادآوری سررسید فقط برای نسیه است، نه چک."""
@@ -356,7 +466,7 @@ class ScannerTests(TestCase):
today = timezone.localdate() today = timezone.localdate()
Sarresid.objects.create( Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check', customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() - timedelta(days=3), sarresidDT=timezone.now() + timedelta(days=1),
) )
SMSTemplate.objects.filter( SMSTemplate.objects.filter(
sms_type=SMSType.CHEQUE_REGISTRATION, sms_type=SMSType.CHEQUE_REGISTRATION,
@@ -374,16 +484,20 @@ class ColdStartTests(TestCase):
) )
self.today = timezone.localdate() self.today = timezone.localdate()
def _make_due_cheque(self): def _make_cheque(self, days_from_today):
# سررسید در گذشته تا یادآوری «تأمین موجودی» واجد شرط شود. """چکِ پرداخت‌نشده با سررسیدِ ``days_from_today`` نسبت به امروز."""
return Sarresid.objects.create( return Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check', customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() - timedelta(days=3), sarresidDT=timezone.now() + timedelta(days=days_from_today),
) )
def _make_due_cheque(self):
# سررسید فردا تا یادآوری «تأمین موجودی» (offset=۱) واجد شرط شود.
return self._make_cheque(1)
def test_first_run_sends_nothing_for_existing_records(self): def test_first_run_sends_nothing_for_existing_records(self):
# مورد پیش از فعال‌سازی وجود داشته است (خط شروع = همین حالا). # مورد قدیمی: لحظهٔ واجد‌شرط‌شدنش (سررسید − offset) پیش از «خط شروع» است.
self._make_due_cheque() self._make_cheque(-3)
activate_sms(since=timezone.now()) activate_sms(since=timezone.now())
self.assertEqual(run_all(self.today)['cheque_registration'], 0) self.assertEqual(run_all(self.today)['cheque_registration'], 0)
self.assertEqual(SMSQueue.objects.count(), 0) self.assertEqual(SMSQueue.objects.count(), 0)
@@ -1696,7 +1810,7 @@ class DeletedStaysDeletedTests(PanelTestCase):
mark_primary(customer, '09120000000') mark_primary(customer, '09120000000')
Sarresid.objects.create( Sarresid.objects.create(
customer=customer, price=5000, payStatus='false', type='check', customer=customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() - timedelta(days=3), sarresidDT=timezone.now() + timedelta(days=1),
) )
activate_sms() activate_sms()
template = SMSTemplate.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION) template = SMSTemplate.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION)