diff --git a/.clinerules b/.clinerules new file mode 100644 index 0000000..618b824 --- /dev/null +++ b/.clinerules @@ -0,0 +1,66 @@ +# ========================================== +# 1. Operational Style & Communication (Claude Code Style) +# ========================================== +You are an expert engineer. Deliver high-signal, structured technical execution with zero conversational fluff. + +## Communication Rules +- Match the user's language (Persian or English). +- Be direct, terse, and technical. Never use pleasantries, greetings, apologies, or robotic affirmations. +- Do NOT output preamble text or explanations before invoking tools; execute immediately. +- Never run interactive or long-running commands (e.g., ensure test commands include non-blocking flags). + +## Task Wrap-Up Format +- Minor fixes (single file/few lines): Output a single sentence or "Done." +- Complex or multi-step tasks: Output ONLY this structured summary: + - 1-sentence summary of the fix/feature. + - Bullet points of key modified files and core changes. + - Test/build verification status. + - Required manual actions (e.g., .env keys or migrations), only if strictly necessary. + +# ========================================== +# 2. Project Operating Guidelines +# ========================================== + +## Codebase Navigation & Analysis (Ripwire vs. Graphify) +- Default to Ripwire for all hands-on coding, AST, edits, call graphs, and tests. +- Reserve Graphify strictly for high-level architectural understanding, cross-artifact relations, and non-code assets. +- CLI-only (never use MCP). Route strictly by question and artifact type. +- Never query both tools for the same sub-task; never execute in parallel. Always prefer `--expand` over reading entire files. +- Trivial edits (<= 1 file, no exported symbols): Modify directly without querying either tool. +- Routing gate: + - Code, symbols, AST, usages, impact, tests -> Ripwire. + - Architecture discovery, cross-artifact concepts (PDF, media, docs, ADR/RFC), SQL schemas -> Graphify (.sql symbols use Ripwire; schema semantics use Graphify). + - Ambiguous/Mixed -> run `ripwire . --help-task="task"` to classify. +- Graphify rules: Requires `graphify-out/graph.json` (if absent, fallback to Ripwire). Match terms directly against graph vocabulary. Cross-verify graph findings against source code before editing. +- Pre-edit gate: Inspect target symbol with both `ripwire . --impact=SYM` and `ripwire . --uses=SYM`. +- Post-edit pipeline: Run `ripwire . --test-gate` followed by `ripwire . --quality-delta`. + +## Dynamic Help, Self-Correction & Staleness +- Help precedence: Inspect `ripwire --help` or `ripwire --help=--FLAG` when uncertain; CLI help is authoritative. +- Retries: Cap at 1 retry on failures. +- Stale data: Run `graphify update .` or `ripwire . --no-cache` and retry once. +- Missing binary: Report missing dependency clearly; do not guess syntax. + +## Git Operations & Safety +- Read-only for agent: Allowed commands are strictly `git status`, `git diff`, and `git log`. +- Human-gated: NEVER run `git add`, `commit`, `push`, `checkout`, `merge`, `rebase`, or branch commands. Ask the user to execute these and pause. +- Exception: `graphify hook install` is permitted (read-side only). + +## Development Server Lifecycle +- Binding: Always bind Django to `0.0.0.0:8000` (LAN accessible). Never use `127.0.0.1` unless explicitly requested. +- Process management: Inspect port 8000 and terminate previous project server instances before launching. +- Health check: Confirm exactly one process listens on `0.0.0.0:8000`, then verify health via local fetch to `127.0.0.1:8000`. +- Asset verification: Verify served frontend content on live server before diagnosing browser cache issues. + +## Environment, Frontend & PWA Safety +- Python venv: Strictly use `.venv` for all Python/Django operations (`.venv/Scripts/python.exe` on WSL, `.venv\Scripts\python.exe` on Windows). Run pip with ` -m pip` and Django with ` manage.py ...`. +- PyPI Mirror: Use Liara mirror: `https://package-mirror.liara.ir/repository/pypi/simple`. +- Asset versioning: Local frontend assets must include `static_asset_version` in URLs. +- PWA: Keep service worker registration URL versioned with `updateViaCache: 'none'`. +- Cache regression gate: Frontend/PWA cache changes must pass assertions in `tests/test_views.py` and `tests/e2e/test_pwa_versioning.py`. + +## End-to-End Browser Tests (Playwright) +- Tests live in `tests/e2e/`, marked with `e2e`, run against Django's transactional `live_server`. +- Use Chromium and BrowserContext from `tests/e2e/conftest.py`. Function-scoped to avoid deadlocks. +- Page Objects: Encapsulate in `tests/e2e/pages/`. Assert user-visible outcomes only. +- Synchronization: Never use hardcoded sleeps; use Playwright locators and assertions. \ No newline at end of file diff --git a/PROJECT_TREE.txt b/PROJECT_TREE.txt index 9fc0650..7e11c74 100644 --- a/PROJECT_TREE.txt +++ b/PROJECT_TREE.txt @@ -1,11 +1,11 @@ Project Root: khatibi -Generated At: 2026-10-10T06:32:47+00:00 -Python Files: 153 +Generated At: 2026-10-11T12:20:45+00:00 +Python Files: 171 Django Files: 12 JavaScript/TypeScript Files: 14 -HTML Files: 54 -Total Files: 523 -Total Directories: 74 +HTML Files: 58 +Total Files: 682 +Total Directories: 76 khatibi ├── .cursor @@ -20,6 +20,10 @@ khatibi │ │ └── graphify.js │ └── opencode.json ├── .run +│ ├── delivery.pid +│ ├── scanner.pid +│ ├── web.pid +│ └── worker.pid ├── admin │ ├── migrations │ │ └── __init__.py @@ -34,8 +38,11 @@ khatibi │ ├── management │ │ ├── commands │ │ │ ├── __init__.py +│ │ │ ├── fix_customer_phones.py │ │ │ ├── gt_esh.py +│ │ │ ├── recalc_last_bazdid.py │ │ │ ├── send_daily_report.py +│ │ │ ├── sms_delivery.py │ │ │ ├── sms_scan.py │ │ │ ├── sms_worker.py │ │ │ └── sqlite_db_backup.py @@ -103,6 +110,15 @@ khatibi │ │ ├── 0060_sms_permission_label.py │ │ ├── 0061_sms_timing_and_primary.py │ │ ├── 0062_sms_link_provider.py +│ │ ├── 0063_alter_smsqueue_status.py +│ │ ├── 0064_sms_webservice_delivery.py +│ │ ├── 0065_smsproviderconfig_test_message.py +│ │ ├── 0066_smsdedup.py +│ │ ├── 0067_customer_notes.py +│ │ ├── 0068_auto_20261010_1305.py +│ │ ├── 0069_smstemplate_auto_send.py +│ │ ├── 0070_sms_types_seven.py +│ │ ├── 0071_sms_settings_permission.py │ │ └── __init__.py │ ├── models │ │ ├── __init__.py @@ -118,12 +134,16 @@ khatibi │ ├── sms │ │ ├── __init__.py │ │ ├── enqueue.py +│ │ ├── notices.py +│ │ ├── parts.py +│ │ ├── phone_cleanup.py │ │ ├── phones.py │ │ ├── recipients.py │ │ ├── render.py │ │ ├── rules.py │ │ ├── scanner.py -│ │ └── timing.py +│ │ ├── timing.py +│ │ └── triggers.py │ ├── static │ │ └── admin_panel │ │ ├── DataTables @@ -208,6 +228,8 @@ khatibi │ │ ├── customer │ │ │ ├── customerNazarInc.html │ │ │ └── nazar.html +│ │ ├── _customer_info.html +│ │ ├── _customer_phones.html │ │ ├── _customer_sms.html │ │ ├── ActionList.html │ │ ├── base.html @@ -234,6 +256,7 @@ khatibi │ │ ├── customers.html │ │ ├── customerSarresid.html │ │ ├── customersBK.html +│ │ ├── customerSmsHistory.html │ │ ├── entezar.html │ │ ├── entezarBK.html │ │ ├── financial_dashboard.html @@ -253,6 +276,7 @@ khatibi │ │ ├── reportCheckNesiye.html │ │ ├── rezayat.html │ │ ├── sms_queue.html +│ │ ├── sms_report.html │ │ ├── sms_settings.html │ │ ├── sms_templates.html │ │ ├── store.html @@ -299,19 +323,45 @@ khatibi │ │ ├── graph.json │ │ ├── GRAPH_REPORT.md │ │ └── manifest.json +│ ├── 2026-10-10 +│ │ ├── .graphify_analysis.json +│ │ ├── .graphify_labels.json +│ │ ├── .graphify_semantic_marker +│ │ ├── graph.json +│ │ ├── GRAPH_REPORT.md +│ │ └── manifest.json +│ ├── 2026-10-11 +│ │ ├── .graphify_analysis.json +│ │ ├── .graphify_labels.json +│ │ ├── .graphify_semantic_marker +│ │ ├── graph.json +│ │ ├── GRAPH_REPORT.md +│ │ └── manifest.json │ ├── cache │ │ ├── ast │ │ │ └── v0.9.82-s7 │ │ │ ├── 0075bdcd95747f7c750eba8757614aab71d4d8c77bd6764e4c5a98fe18dc8997.json +│ │ │ ├── 0175e34128b5ac5119a5da4cc53d889dce334d8f20357631d54627c53cae9b8e.json +│ │ │ ├── 01850b7fad89dcb9e521fceed2c41e64e9909a0f4c4a54d818d346ccf63bbc5d.json +│ │ │ ├── 0348dd4662ebd96572421864ce77a266334a327f8bcbdc237d54c313d116bafb.json │ │ │ ├── 03c6fb41770a9293658a5db5af36f7e4afe64ae04b3ce8cc6c5874320706fd22.json +│ │ │ ├── 0400cc0eadd706ff70ef627ffe39f401fc72a591dcbcd5463fca07fc271f080d.json │ │ │ ├── 0442dd7e8445ba796e02aaefde16760c8ed37f63359d4319498be512f60266bd.json │ │ │ ├── 056f0cb55bfd5a1ddf744c7c824c342048c1db47f3bb350030a1533ab56bf8d7.json +│ │ │ ├── 05b753e15288fbd02b721b00cfd03c3a523e9e234420330b980a462a1de2dad6.json +│ │ │ ├── 07eb6ef411f8e9b78d19cfd5f262bfc9fc482701f0834c8936cbb5ba2b14efab.json +│ │ │ ├── 0b48631fc75356bef276f694d7db5b457bd2315410fd4538680612fe10b95eb2.json │ │ │ ├── 0b8f8e3b6e4d84256e784dabb96f5646fa6e7295adbd20648c235e7821b4ca1d.json │ │ │ ├── 0e6bfa570caca1f79f549f5cdc37f8bdf9c9b43a023ca28a8fcb83351389b399.json │ │ │ ├── 100f37182d082767395fcd09f1ec937fd74e57ed039885843bf81d349c3fc1f5.json +│ │ │ ├── 10615f5a999e04257afc03cdc52e1b9e4e11ae7818d7c64ae13b976e3974feb4.json +│ │ │ ├── 11042a6b38137d4e725c18d536cde9c98652782cb58390aa6216dacad2d18cf5.json +│ │ │ ├── 1285cdf9b89689e3ac99cea19470a3c14fdbf3a9d1bb2cd8da2cfdbd42ca940f.json │ │ │ ├── 128b2b616c097fbfbcbc3fddafc224df0919ba261bb5ded7bcafa8b445fe118b.json │ │ │ ├── 12d922593c75865b8068057f9244ad61a5ee573b7f3f38a16c31fa83abb9d668.json +│ │ │ ├── 13a39530fbb076496d510363b40bf16093d8d117751e7240bcc09a447bdace35.json │ │ │ ├── 13ca1f37c3eea3c07228d2d98d47f7db6a9c1d8468b259651f3fea5419651569.json +│ │ │ ├── 14f64f9b156b6b520e7af189b303a2844728f3ab8bbe1e65fed49160b1431cb2.json │ │ │ ├── 15be5091a5bfbf8f48c3f3ae344a980be5452eab00f054c001958aae96557514.json │ │ │ ├── 174e406c2649f2384921581cbf912945a95d54aab73707b9aa45752aaa7c54d7.json │ │ │ ├── 183467d5c1ee7daac78dfb428bd335c17e17272bd4510358f38fc5924140c816.json @@ -319,47 +369,88 @@ khatibi │ │ │ ├── 1d5a29a9dabb365c079039c788988636b66c3a4bd2dbe66b249df001c2b5396f.json │ │ │ ├── 1d9f15f04104af07c60349f21bcd9f6f2c0e65c915e3e9c69040a10197b0fa6f.json │ │ │ ├── 1dd0c6958a307a1e4e59c24f20225292277585dd8652b35537ede94fc04807ee.json +│ │ │ ├── 20652a8d348bf514ac266f30a6400f923674dae819d18046d008066943377804.json │ │ │ ├── 21af53aa7322868a25b47458e59f339a8f6e58b7914fbe8e3389ab86e0b0b643.json │ │ │ ├── 22310d4d49f2beee611c83915933ea06e80ec562fd92c170345c556cd3772561.json │ │ │ ├── 2356cb9f067d43739312459bad2cb6d9ecbacac703e7e40a282d5b89d64332b3.json +│ │ │ ├── 23da13b308596ede7678e92e57041bcecdd8e9baff04b759ddf9a69803dd790a.json │ │ │ ├── 23f4e3a62e03a38743659d94dd1350d17775da8be9175846e4ff3f38fa87c515.json +│ │ │ ├── 24508e7a9b6fa93cdd28b9436bdb55fa24ad8f324baa95adb3afd37e301af9ac.json │ │ │ ├── 2501a9885d6fa5292276ee8cc7db63a9a3c08605f38d199d425d33f650dac35e.json │ │ │ ├── 25dbf169bbd8ae50f7cc568b4dbb7d259eb993ccddc9dbb20db96018ed093f5a.json │ │ │ ├── 28df8bbd275806fd77c086629211231bd1329fe8156f521b19b5c5979f0d3b51.json │ │ │ ├── 294e2d390f56acc0445564d428d90c641d76496cbc457bcfb09876382e9d87ab.json +│ │ │ ├── 2976c530804df2075019fedaf2b908eeeff37b2703310c7e031b653d1fec71e5.json +│ │ │ ├── 2c587e304ae13b0717d72f5099508220c82234a8a293219c2ed2c97df9152404.json │ │ │ ├── 2f36d1565911fdcb414da716162085b0fdc4b92d9d2d3fbb9fecc403b1916766.json +│ │ │ ├── 3089c169c5eb18e61b13a19751166813d18c7bbe35b938b2b149406ff349feda.json +│ │ │ ├── 3258762ed4d4077750967504aecb42df00c35a69571f5d4839d4cfebd9b1c838.json +│ │ │ ├── 3330ae8555dab8f7ff4e014183f014681feb3f40a0d8969f30a4b2e9658e27f8.json │ │ │ ├── 338e7d5d2e7d3a7260ab74d9b7b19bbab6b9ce84e7036623060190d9e0cc4bcd.json +│ │ │ ├── 33b32e27be802f61375110f7f4ac9419fd4eb633523e01e31774b874448849c1.json │ │ │ ├── 3614ae46baf3e1b6efa17011c37407a5d2931aba1886d8f8bdf8e4e154ab6c3f.json +│ │ │ ├── 370c10e9d6f01aa67799b1f2191d5749a69ce4541aa55cbf4a28d45732bd641b.json +│ │ │ ├── 38980449465279974120694e1c667f229049f75d656fe518196d9cadc2429155.json +│ │ │ ├── 3ba0d4c75ab8bfa5bc2a60dccb74197cb94bd161e526e54770a9b0a2aa757806.json +│ │ │ ├── 3c9f8987dc7426b442fe2a90c7de5d909fc85988e172e7722c11f24a2c331481.json │ │ │ ├── 3ff644065147b53af40ea703202c0fd832114936fe67e6c9a30d15e3da915455.json │ │ │ ├── 40e7fbe5e765171201ab8016acce76a28304e1339d9aeb5a9cc18da87c5604d2.json +│ │ │ ├── 4162977f2c2251e7a8b331d7229bd43dff56df31356191b19886b5bf5d56377d.json │ │ │ ├── 41952e7c846da15fe658603ff001dafdb0e12d3372a7f0306adfa3a71de8bee7.json +│ │ │ ├── 4204828f3cbb3465c469e28345e0fc4d7ff9a84e30e3dd9f00ab4dbe27014e0d.json │ │ │ ├── 458c1b9a3ce257e789f75305939ab2e79029c557657102eebf2082e50a47a0e5.json │ │ │ ├── 45f7e59ef3f291171180b6763e8da408d8b7579bb7cb96dd4ca859e48aa5ce14.json +│ │ │ ├── 46616763804b2b938d73c75f7651158e94803e0b86b3a759ecdd59375759eea3.json +│ │ │ ├── 46bde576f6f1e69457e4ad33238fdbd602d2a7f027644a84cb81068215fbd5e1.json +│ │ │ ├── 4da7df08a0ef803551d404438fe24d6c8cc11d9a2c3cc8d85e37d5228ba3a166.json +│ │ │ ├── 4e24d52f79dfa76aa2406de05583fa49ce966a28fb653fa284fb8e8204797385.json +│ │ │ ├── 4e604dd03a70463ae25f6615ea3ce4fe8e98fab15d6b11ddafd51d601062e84d.json +│ │ │ ├── 506e815cbeaaf3aa6e448dcbf70c10b4d118dff41e05a292fb8ec6d76dd391c4.json +│ │ │ ├── 507e42a0bbe7a582ce1fcb77651e1cfde643907ca0429fedfe11afe7ae3b11c4.json │ │ │ ├── 52608b276c6f0a2f190186e5bac578715ad888ff5ba3c10ff30879c3baf9d9ce.json +│ │ │ ├── 527de91bd8bbc5a7ab79951457a9f192909faa9bbf02aaec72bb3d26e2298b57.json │ │ │ ├── 5316515ee7fc3e1038e676b361d9028edc735993c3d34791a77a14503deb064d.json +│ │ │ ├── 53bcc38a002a291b8a370a1c24dde42e704cbda5d409d77b8405599e45844cae.json │ │ │ ├── 5730d72cc177474584197921cce656696e3177780028c4629950f5514b530c6d.json │ │ │ ├── 57a18b5350b4a567305ca01b2951efee6eeb3b289b5ffbbf390c3962ff29d61c.json │ │ │ ├── 58ad02e0930221b4f44603dbcf01d3b5d07d9da2772edafe48a94a9894ea148a.json │ │ │ ├── 58bfae9cf8467d2bb9363884d517e98cae38a934f4c474e9e02aed520fdaab95.json │ │ │ ├── 58f2e3419fb2de4a3bdc4a9c0bf48e50f2cc225506096109dbf2b9bfe4778bb5.json │ │ │ ├── 5901400fc36893a9ad85acd97eb73b691cb4b8d1440047c5f85c1534a2c7e492.json +│ │ │ ├── 59dd2e5089bebfb0397e9c492c890c619fcaa4d73b9d035e533320f276e1ebf9.json +│ │ │ ├── 5a15a341e370fb512c0b3f5ae1c30ec0d8a6729a690f286369015d107210124e.json +│ │ │ ├── 5b59421d9713dc21f9f77603d9708edd8c3eb8ba198636f80f1cedcc79ffcaca.json │ │ │ ├── 5b63b5d7fc7a09b2dbf5e2f7db6fe00ccab773762b3291bd666c8e29d32100d3.json +│ │ │ ├── 5c8593cf08ce32f0c569aacfaf83b8baeb352934f6f8749aa4f652d5ce2acff1.json +│ │ │ ├── 5c922734de6f6345b5230507bee5c43dc284c308901e84846666b939493e6258.json │ │ │ ├── 5ce9ee869c507986bf00ae6929f3aae3ab7aa58b5c2bded4965374fcf7f5654b.json │ │ │ ├── 5d5b6249e3cfa0aeee94ba2053fc73b4b7b5b8fa1d21df09d40bda7c90ef273d.json +│ │ │ ├── 5d994876cad71133f4bf804932fb2604ffdc8bb8cc434ca1434667ef789144e7.json +│ │ │ ├── 5e19aa3aba5509cd48a9a93b39db071cfc2c2728ccae46b34caa0e214cc6be01.json +│ │ │ ├── 5f050da3769e151e52138ee1ea5dcbcb6160cb57952c75665e607628d0e08d67.json │ │ │ ├── 60002a01d6fc22147e2994d1a1a447d5e577c02aed2443789af20571ec2d2a19.json │ │ │ ├── 60e00560038df059b1696e54dcf055cd658677d6e1201570a8500531addc0ac6.json +│ │ │ ├── 62d88411a61ee9d3b60dda6c4febf33539b8b9e8226b5d2b9205f8e2b0c4478a.json │ │ │ ├── 63c38f2a8abe98c0d1e9cb5b7d9fd0451e1e46c2bcbf1d2c3b241b263ac0a9cd.json │ │ │ ├── 6448d65fdcfe330b5d4278c8d1540e7b075238672b8101f9d2e1c4052f7c9c49.json │ │ │ ├── 64fc04d8c553b2121a71955d48257160a40f6dd34258e3287945873f02d897dc.json │ │ │ ├── 65d5139792e0ee840c5b1710d7659ecfb5dbdcadbd02d205f7517a6a3a374211.json +│ │ │ ├── 6603c5eb7403f7426c429341d43018fdffcb3430f22972c426ac43956d229b33.json +│ │ │ ├── 6648bc2b25d0bd25846d48f406d3e9a6f89677542600e9f3f57533747ed9b08c.json +│ │ │ ├── 6691dea01045e5561ac1c72f902f96ba84564c221498829443f793fee8784021.json +│ │ │ ├── 6805914035b171d32a1b80948179da15f78c5741e5a80b216d51a6586185c0c3.json │ │ │ ├── 6876f75a9cb4a5f5857c47e3b0d6b38e51941aff76bc6d92cb1b4eef1c51071e.json │ │ │ ├── 6a88f82e5b9f9da12b50f8531eac30c10e1c2a0cf39601d275fd90ee3e28da55.json │ │ │ ├── 6aca679f90689aa4c275389c8b4e2462a9456775e8b124071070af2c988ca0fc.json +│ │ │ ├── 6afa6136d9df8317bdd8d49f4109412776deb21ba123fdd2a97331b400a0230d.json +│ │ │ ├── 6bfbb9eed6842bf331dec78724d7e740919c76345b54be844964f44640482a87.json │ │ │ ├── 6d4d7d7f829f13e46e4898aceb760d66d2255dc838eab0933f9a92d720f420a7.json │ │ │ ├── 6df9c1e88224e95c55dee4238fa91358360231ee47362294e884bc6e24d73612.json │ │ │ ├── 6e262456570c174e207055c7c964fe85431addb0f59298b1e4d912500dfb23a3.json │ │ │ ├── 6e2ed39911a83c75e432ffd0dab22fd095ce8c5bb186d6ab22d594375f3b32bc.json +│ │ │ ├── 6ed362dbf43e46eeb440246d52fdc4e0783c895114249ea33f3de7ddf6898a3a.json │ │ │ ├── 71a07328d56403d55714d76cefc05db3e68c6498719e629e4e76889a1940efc2.json +│ │ │ ├── 72577fde1eae8cf079aff518cecce910ac310cefcc038fbbcb0a5d11f20fa20d.json │ │ │ ├── 73a129efd08cc38274f3c56503c234eef17f58faf74dadbdbbe475efcbfc2777.json │ │ │ ├── 7483cdf00a2d7de8809278d9dcdf2f2330354b02117952da4b9452d4bbd6b81e.json │ │ │ ├── 74efb913c62b374d818cfc5af6d7ee9bb46966eb2bce0e26e0bba461683b98a7.json @@ -369,107 +460,176 @@ khatibi │ │ │ ├── 767dd58146a5d4a713611561a9e5c1b706e5f509a5621891c9b2e195f3b8ee0a.json │ │ │ ├── 77a1520156879de714ee872c29b1387967bcc5ce470b47d37087ea2a110f3875.json │ │ │ ├── 780977bca042ffdde1cca2811a3a46c36bd3fcff2723c94ac527428113131c64.json +│ │ │ ├── 784dd791c21e8837a3e925331cf234b2138ded3d2012968739604d5535a18e09.json │ │ │ ├── 7be7f428e6acfe4c99370707cc7de9253f801feff1564088f2584d5dc8c42a89.json +│ │ │ ├── 7bf404104bb32fc04568c2d287891fa69955ac3f46718c99dcca64a4bfff6fe3.json │ │ │ ├── 7c4fc90402a7836b364425c33cc2ffc98e7634391ebe78143df03c88945d8988.json +│ │ │ ├── 7d106c54b917d9e26357fa71aa871e94cb99c940b6c48808e93cd57415da91e3.json +│ │ │ ├── 7d59da18a8b644e48a164eb8a764efc9f4c3538b99d41c8e93ce38cc5556ba05.json │ │ │ ├── 7e9bad82d0eb642b739dcf11e86c2fcf2005dfb12c94478af143deb9a8129bfa.json │ │ │ ├── 8055bf9fe36a9fa183d0c5dc152684e65410d58c5634e544e8f38f6c9216f6ad.json +│ │ │ ├── 81d6d00c4c975b3289ac212d355856c3d6e60bccde0780fe2e3fc8cac31f3c19.json │ │ │ ├── 82833f6235cb6559bad38e93372d42284eb4e9097e90519337266113da2495bf.json │ │ │ ├── 828dacee98d6f71fcf53c8a051b3bf1bfc9d94c272a79ed8539801e3fb90b3df.json +│ │ │ ├── 837801d7d0e218e49de562a7e03711e75941d42ce5cc2f77552bc6293a45a4e8.json │ │ │ ├── 84281ba94e70a71d52b5179cf2e933f8412da3a9eaf5e7177b815f8fadd0afc5.json │ │ │ ├── 846ba1087a1ee6b5cda2ebdf22d0bc61ad9175b21aba29783429277288dbfaaf.json +│ │ │ ├── 84cef5656079ececae35930e1b26620186b50f25fd977c569537daad2d59db86.json │ │ │ ├── 86e57806cd08cf765ce1f56f0638dd321e93c653bb6613aa35d7ad375808433c.json +│ │ │ ├── 881adf7185e68d203906537117de576a11fba814941cf9eeedd0dfac2d65e536.json │ │ │ ├── 88e349e879d601e1f5c75f3b019ffb4958e90639317ba893c54f82b0cc9066c2.json +│ │ │ ├── 8a70e1324d7f834d2c41a7c3b4cbb3b8ccad92d413970316bc95e13f5132ff76.json +│ │ │ ├── 8d658a3a8b1ae03cbc881e3c47cf2cfe111f9be2b564fb3739cf3d9367845a8b.json │ │ │ ├── 8ffb83ce6f13d2f97d0197c7381748e9b66956c13f0158bc519350b7dd1b9ad0.json │ │ │ ├── 908e0129cefe129d0b562243460fe20793c84876244cef2e99ca1becf8cf6a1f.json +│ │ │ ├── 909129924b48dede801eb946cf98bb93003ab8116be2b6acbd2f7fe8dc166358.json +│ │ │ ├── 92ecdeb228d91f4adb6c1e9bfd1b3f0eea65faec8dbcb27fca3211d1d264cb46.json │ │ │ ├── 940d0349821b0fb7f2c3effcb81fd13bb1c046daa6f6c5f2cd1b157058c55877.json │ │ │ ├── 957f37be1d5d6a5207f90ad07704f8423c915c5bcdfcd130acc85a388fc89d19.json +│ │ │ ├── 95e88ece49092778e7709f6b87db4c05dd055b838cfdf20d7d4705b6373739ba.json │ │ │ ├── 96c3ba545db191ae5811d5dfdde74e7c6396a932b759513c94160cb7b7c81fc8.json +│ │ │ ├── 96f5469edd566b92d9d38423a27779142fbe2b77cfc9d85005d43ffc99ce3de1.json │ │ │ ├── 9766508e5a013c3d3cbb2602719391199e90af5720c9782a77ca71565aa346b1.json │ │ │ ├── 9880af8b991c19abe5ed019ad7b7291b8b281ed9dcb894a00c3fd8259a59b4a4.json │ │ │ ├── 996411678d23c3cf8a0e6c1c5076701e8bf5f3dad21ca907ec4116d15366a668.json +│ │ │ ├── 9c2c2f6f8d4d4c3a9a60da3ed38a26dc789e8cfaff5d46d140d2f208e5fef3c3.json │ │ │ ├── 9cfb6e4f7b08a863675af032e7823c0523170f5201e686c5ef72b39c04b07a12.json │ │ │ ├── 9eabdb0cacba38c8fb6b3e777932e06bd82172cdc0bcccf2fc0bfc23b5bd4974.json +│ │ │ ├── a186e74ebd87e23212ffe7fd72c40e9383c0624a3efdb2fe1e3271d9ea96d9b9.json │ │ │ ├── a31ab0e36b30049a602cc060d77c0715187f7c3838db91b8c20bfaa802f49899.json +│ │ │ ├── a43d68ff03b49e9153a73777c0bde346a6b1969f640d67eae500d60d410a02c0.json +│ │ │ ├── a7c605f57dd37c8725a92f08d4fe18701628701fa986cbb98d3a4aeb3a74abbe.json │ │ │ ├── a823c9c28027c8d525714e6571bc3f3ca38f3bd0420aaf4f4921067cf0672475.json │ │ │ ├── a8311f6e51027ea4606e35bf14d50edc1795e71b2d63cddeae8c7bb7ece215b8.json +│ │ │ ├── aabf6dbb2202699bb1b06fd582c88465b9cf2b8cc060bda2e2bc0c67fc493bed.json │ │ │ ├── add004e78a62164d7565c102eb825debea20811f7fe76afa0567a1b6b289f264.json │ │ │ ├── aeaae7b60339b1283cab517b4ab42806c64933c272d007a68431988ba8e0edb5.json +│ │ │ ├── af128ca7fc8d02ae40fff36925a5b96034862ce0d581e22a8a0c88f41040e526.json │ │ │ ├── b26c22033963df1727407c1fa6c79d8241bfc16056e7a8d3c4383fb2d050d3cd.json │ │ │ ├── b26e251bde84a040dbb2d1a4f700ebcd761f51a124a98a104704e82537e320eb.json │ │ │ ├── b34cb0a1a28e753a900b3d23f01d3810f9c06635b0650d34be4ec9d98daa8fbb.json │ │ │ ├── b543952627caaf0a5c52bec09b33fae93443fca7234365680bbe75acc260dd1c.json │ │ │ ├── b59999c2fd322c241c7bd37e6ed0a95c9124cbb79f8424f1fa7cb2e92fe63817.json │ │ │ ├── b82d5f6840b4930eb62fdb1c7b3a58124fff805e529ede7684516a65322fcae0.json +│ │ │ ├── b9f8ea31056fe0c0fc40a84640c58f29895312c65bf694567487204d8f1170e9.json │ │ │ ├── bb140cfe12cfb3e32dc9815393035fc33c7649c2faeeccb82cdee56a930ea635.json +│ │ │ ├── bc24ddb05a8aa685edc1376cbd110423adf05bcc488fb97a30fe286e7e74c2da.json +│ │ │ ├── bc6f93c7938b3565d50cdfc4abe4fd62f0739fd6299f35d7c50075cd860cab58.json │ │ │ ├── bcea7c584e5031a9c5734144f8169d016ef1ed316e6f4827609509ba2a79d3eb.json +│ │ │ ├── bd00f0b0db2eb10f7e889df5a0f08994e8c1eaa0eb69be8398e45e0e634724fd.json │ │ │ ├── bd6e156d8e427b14676b4707710f2b340026e679cb7bd29b22b247a4b637e7bd.json │ │ │ ├── be1200d7c9b2881ba54c7fa95daf5f3e2ce993619b974c7760a9d44a416e00c0.json +│ │ │ ├── be72e639c12e860a2120c3ecf96b94c86e5c69b2f1f0715cd2804d0475409cd6.json +│ │ │ ├── c12baf851ec40542280cb62d2f878d5e7383357365281938cfb55a64c08a9a10.json +│ │ │ ├── c36b696b57c1deb51e47a057b32d1dd75e3f1560e83931aab1f8ba86abcc1543.json │ │ │ ├── c46ad3fc8fd3db1315ab2e40add760b44e8f98c24c79e334c342c6a411fc671c.json │ │ │ ├── c52300dce4cf464158f8db56df5030359a501a81bbe4a5f36e50c8c38a5ac48c.json +│ │ │ ├── c535c8a7523b768d28903b84f4cfbce7d62181a2a05f3db08ec3a7c8cbf86ce8.json +│ │ │ ├── c56beb43cf063fd1cc92950ca14695b543b3971bf7e7dbd4a676242057258533.json │ │ │ ├── c5c93ccc7715c31fc7538bcd9327c8fb7c1e495bd17d6444b384ec426b7a231f.json │ │ │ ├── c72226544540cc75217ba02f1d09d989eb810d879409ac086c76245f27220cfa.json │ │ │ ├── c795f43bd1f10cf666ff987d18af538b2164c1683629ac689f0a54cb875118be.json │ │ │ ├── c7eb97fa16701c6d9ee531fa032479a7c771292f3e9d8921d3ea12a4897fd43a.json │ │ │ ├── c846b5de286163b5eed16ff6d6e00f2038f30bf7edd2ab3f8d831369e190ae18.json +│ │ │ ├── c850d65440fa6ea38db7300c89c5052b3db430ea2c7d20d35cf38c6b41807d0d.json +│ │ │ ├── c8719ca6bbbd7a4815ce5f982745c94d54d2f753312c08b96964ab7ead661291.json +│ │ │ ├── ca0c19553fc1b40bdec0973b3e0c5363d6bbbf69d9961567483ba1c1cb9023eb.json +│ │ │ ├── cb3b2a1c1afd8d0837cc29b5040ec984dc0c0070af2135d0b0e5cb5ff726f2a7.json │ │ │ ├── cdc5059cf09416d24d03fe57a48af5219727866c84b8109d986456fbaa789dc4.json +│ │ │ ├── cf17cbc72af5f2b56eff6cc03157817d7552c6c5e4565e97278e02ae69a362f3.json │ │ │ ├── cf6668c54cd4f65d268a17df44f100e1cb77f91eae90dd2dc052564731b2f195.json │ │ │ ├── d1289d5406d360e3ff617e213db4e262cd43a32711a61aba97bdcd0a906cbcb5.json +│ │ │ ├── d12cd2b31145213be1b4d89e7be64f3aecc122841cdeac7660eceae3dd871f01.json +│ │ │ ├── d12dccf07d58d7c0a2b5ea5b6aa821aa419fdc8b34a48939ebf9c565bc439551.json +│ │ │ ├── d1f2090208e1aab3b0e377278b02e9fc1007e5e1724c9000fb69d050365c8660.json │ │ │ ├── d3456cd690135f9630e6b0bb79fef5d205804c7acabffc61cbd5ac4032b17bc7.json +│ │ │ ├── d51b2eb414ee4ea432987cd45d6b623013e994a6080c89cde57b69004b576e9d.json +│ │ │ ├── d5676c133bc21ec531e7c17c5e9416781e3459852a4e6e1fac9c77ecdb5c05a2.json +│ │ │ ├── d606b856d99eb59fe3e5378e597bbeff679c812e9f1b173ecced4639ede8ad11.json │ │ │ ├── d88e1e365cafd5d26ce47c2a0fe1f067dc3bfe5ceed1f75a21393fae950be939.json +│ │ │ ├── d899e8cc575f125b908d78f0c8d34408a4af1972ee0544d0517f8d1933239214.json │ │ │ ├── d94bebfe941bb0acc30b633423eaee11690652709e5f8f28d157473d31a07ee3.json │ │ │ ├── db68b8db5217e2295eb1195760d7352960627da044a4c4f8f6b89c67ad2f8f57.json +│ │ │ ├── db794bbcafe219af962673ef26efb1ee1eacf3923e13113352f3eedfd3192d23.json │ │ │ ├── dbbdad9d992922aff62ad3262a531716cfc32e2f15f600773b9a0cf10545058d.json │ │ │ ├── dc0344515d1b3ffe8643560516b88418be0b306fba56101b8a630ed73d48db46.json +│ │ │ ├── dcad55d51bb4494a642af555d5bfe78c5b8ee3d11de1b3d8f4286d608980854b.json +│ │ │ ├── e08c4784f0f82403ff24a1676e3d75e11a208aadf8fe94a723461c9658afd1e0.json │ │ │ ├── e16f78807ff202fa2c81867b321acfacede2d24cc785856eef0633d8d352796b.json │ │ │ ├── e1a7be59904a3dc503c5b12bddc7604c6783b02462c8ec76834c65c5610c3bda.json +│ │ │ ├── e2967fb2aed9420b2cdf823d95f971b7038323c97a6ed139ce63b7a1912381d1.json +│ │ │ ├── e2c62f6012dcae176d19340f6db8db3e94547ebbc4a21577b63caf35cc72ecfb.json │ │ │ ├── e3ee8d9942c74a74be83ca00d5438e4a1e1cb5b23f55d95d308a42954e4757f0.json +│ │ │ ├── e86dc6bb7703d4329623bd33ccbc5e5e6c6daebf8d36d84fd415d6f868c779b0.json +│ │ │ ├── e8e380264b41f789dddd25b7af5d0e3c517b957a14fe8ba2ce7bb8a529e005cf.json │ │ │ ├── e9589ded446a5befb285a0a2e93a182ca9431c76661e68c9210ed252580dd7f0.json │ │ │ ├── e9e14d581ed59799b2d83c67402e8555b5b59e7d49425950ec490422bd0307d9.json +│ │ │ ├── eaceb29a907e3e67bed5255cc1f8b8b3eab9d9946f4d4df2cc99070151285029.json │ │ │ ├── ec07e022005d3513838eee7e6618ff060e25d914cab7745aa6172f22d4089497.json +│ │ │ ├── eccad1f007b52643ce8d81576efb7d1969ecbff2e9db288989fd9a9c21fd7288.json │ │ │ ├── ecd9c1dbfa0a72e253adb680ae7088eff24c26360e53a65862b274ea1281ceae.json │ │ │ ├── ee450e6251e62d8972efb53bc35145c4a65050a7b6db2954b84bda42fca7b704.json │ │ │ ├── ee557c6ed214858651cd3ebceb5e0fd00999354a4c54bb379f26c64b48b509ab.json │ │ │ ├── efd113025c3fb3c0becaecd8090e8494b13d92ff3c49220d54665b3a8af586af.json +│ │ │ ├── f081ba494afddbf02c2599aedee9fc9d38ccdb159ade7f07ce3bdad3323a001e.json │ │ │ ├── f1af209a02f2ec5c5a344e3fa7dd8ad7d443e0479e55f5465449c0a2eed314a1.json │ │ │ ├── f3a6285dcf9eb39df500bc0b76b03b5941103e8b90c06ffaeab1b3129f18fedc.json +│ │ │ ├── f3fe81220df98d0410b1505fa995df65427035dd552c85f1d80e35bb75a9aba1.json +│ │ │ ├── f48decaf1220666fbda1a5328b963dbd999caab9ebbb1d84459d4c556a6e6ba7.json │ │ │ ├── f537b36e2ee33d1303c10e950a6910cc7fd954c7fce3220be2c95d75d70dfd40.json +│ │ │ ├── f72f6f3fe9d226e4717e158dedd318fff0a1d52ece54f965311c2b4f862dcfee.json │ │ │ ├── f76726d2898f2a3ba7168d3fb77c21087432eb9c200631b664ecb3b1837aaf17.json +│ │ │ ├── f92b94479afedf47039d8821a77facb55d9a862338e85876b80cac3b41c844f6.json +│ │ │ ├── fb6825c4e9ffba13d17a2da18a1e07aef792089a7cd24b31e7f4f32ee39287e8.json +│ │ │ ├── fbfc351c35c5272de5612b4740e380b06a24c37f8074b977e35f62ad85a6dcc0.json │ │ │ ├── fc6ae63ae2639372c4ae4fbe9718feca814261dda099ba58bb780d14c69086cd.json │ │ │ ├── fd714e03c631694d30457ac529d05d8d131c183665e086246da607f752dea5eb.json +│ │ │ ├── fe7f3f3c4b234398f7c0fb728456ac41c92c6e939634e147d2fdda12067df842.json +│ │ │ ├── fe8b7b639a9f98fd1b883b7dccf8490ee905a2a21df5671ca1838a173fa0f921.json │ │ │ └── ff862e0ffad7bba911716072ae82f268240d82105ffefe78c7714a160c4c31ba.json │ │ ├── semantic │ │ │ └── p5e80268fecd6 +│ │ │ ├── 0be9cb2f924f20c8cd09e0361a90c8f0b4c8859120101bbe21eee930294ebdfd.json │ │ │ ├── 0c4f87b180cd5b0576b197ff5cb0e9cc5dfd8d82d97d618036df00c7955fdbac.json +│ │ │ ├── 14328edb08c286b631c2ef0ead8ac33b522157be84855de346e8c0688e30c965.json │ │ │ ├── 14e03f8f9b4ebd7cf018188e2351ca859aa4e27d02d5dbfc7b1f8b932d282d16.json +│ │ │ ├── 212a2dc6dcc30c1b45f6cf5bfa9bf16386fa138bf382bbde438f8d6883769b29.json │ │ │ ├── 2679b1fb1647af24f007334e6560733c957ca3cf3831eb02a9c8fb7a7ae44d62.json │ │ │ ├── 296d532aced4658e74a296977efe4fd8a2ec3cf02a997ce3837f59ce36e61343.json │ │ │ ├── 2a13d2bb6f18a81d9511481df0edcdb1a8779e1ec03e919991693cae7e84c8e0.json +│ │ │ ├── 2ac09852870126bb1792a5e9242b34d3f55365bfa6e3b09aa432461a6c72045e.json │ │ │ ├── 2d3b8c0bb7eea6a50a24093abfc3457c0938079b0771b2568498060f6f3a4a74.json -│ │ │ ├── 40373bd2bcd76ed8ed27031299e541005d4c8c0c7df312a9312f390b173d7248.json │ │ │ ├── 42a95a4a8f651dbebe297ce92beca376cb19eab99d6ef6b5d10a16d79d1842e4.json -│ │ │ ├── 4f53e6b5fc48381f8afe6052371989c4df69347c84c2ec65a46b73cd1c464b5f.json │ │ │ ├── 538cdc2fb71798c581f20d37d52a1484c2ef21691d2e01ee79edcf253ba06394.json │ │ │ ├── 569db52f8b9229dc3cd5cbde156a4ac012ab711d32bdeb4175b60ba863ac8cd0.json │ │ │ ├── 57dba5c2a37beb12ccc107807b7544f687d6d8ba6394c8977c3c59b433d2dab4.json │ │ │ ├── 5ad5f87fc2eb132405e3aab9ef8a84e41f76c958fbcb188873c86abd4fbc12fd.json +│ │ │ ├── 5c0f2ea11f5bdcc5272d6344c9015c20bf79f315d74a910d56bf7a2922c92315.json +│ │ │ ├── 5dbe87f1a5fa172581ed5abebee35b5d08ff488e798a334992a2756bff277cdc.json │ │ │ ├── 6aaa93a769a41eb1d37a91608cd6f98b9252c2a5598bd859a130986429d1d53f.json -│ │ │ ├── 7b504b4173cb8d683a7021270372c5c65f078ac3e3e1351601bedfec9c585632.json │ │ │ ├── 7edbffbd4455d071df034927a69faaf76f8e8bbf6495921ce2b70eea1abe8e4f.json +│ │ │ ├── 82db37687728a8df60ccf231c06838b4a0074d22ad93e89d3f5e93d4de48132f.json │ │ │ ├── 8a79c80fca0ed04957e7b4df749ab80b5d0f4ce3d0ae032a47d51d4a2fbfcd82.json -│ │ │ ├── 8f60bda631cbe7457886d1d574b2e3d2850034f469592a3e8c333f0623a2d3c6.json +│ │ │ ├── 8b6c538ab1a901fb71decc7f7f511c8c67a76d72f05f02b1bddc10f5026f9e4e.json +│ │ │ ├── 96f5469edd566b92d9d38423a27779142fbe2b77cfc9d85005d43ffc99ce3de1.json │ │ │ ├── 9897fa1997a41e155ed8de922722238e1f684f5c77a7f56405c8ac7cd1aa376f.json -│ │ │ ├── 9b674bec71dfe6e133af9fb4e35a421fa56cecd230544008ed83a44e6eaae3bf.json +│ │ │ ├── a3f3d3cf99dd02b4d8c41297d4634e120953f1650257c0c6a1888481aa4c5745.json │ │ │ ├── aa41b77ce8563f4b4bee015be5ffa598f1c98d4f2854ec9a465f271e8c3f974b.json │ │ │ ├── b5f6ef338a857f51d581abe7c50f57f41757aa6900bf94f6ed8fc2ad442f6019.json │ │ │ ├── be1200d7c9b2881ba54c7fa95daf5f3e2ce993619b974c7760a9d44a416e00c0.json +│ │ │ ├── c109dbf29a8233cec6f0e4e3c1256a94ecee99887756af2060e620aaa5979e4b.json │ │ │ ├── c1d9e0aa5d7450242681eebd003d4f502744a3624112b10ab091011a50820ce3.json +│ │ │ ├── c89f13bada10536a104ce57788562563a61c2682430e4273ca3195890d45c68c.json │ │ │ ├── c97d49fed39b9aca8dea35a49c3e031a641420e4452e61d1ea2a912e899f6dd9.json │ │ │ ├── cacf6773249daafca21d21004106209d86aa215140de071c64363aff4d9e5431.json │ │ │ ├── cfb57017bc764ee7e9011b52f1e44ad2f41b2670778af73232ecfc309b406a0d.json │ │ │ ├── dc89fcddf8c6f20bc9f52ae15e5cc8b226320d2776dc8f27be16894e53165013.json │ │ │ ├── dd78862d0f7862653c702e86f7acfabfe33ec5d85d40ba764138c0d1784f7b06.json │ │ │ ├── de67f63e3f49d4ee32811dd67676da33a1e584f671d396fca130f3681d54bd76.json -│ │ │ ├── e0a52ba8204140f195e8d30847dda00d3de5439cf903c576b77fac1ec852b5fb.json +│ │ │ ├── e86dc6bb7703d4329623bd33ccbc5e5e6c6daebf8d36d84fd415d6f868c779b0.json +│ │ │ ├── e9ca16b55daa5d6bdf6b0257a4e08c35caeae32eaaeb5c0be11532b77ab658ea.json +│ │ │ ├── ea11661eb53bce4c7a43acb2d636de01d960acdad246c1f1372df351e25bac25.json +│ │ │ ├── eaceb29a907e3e67bed5255cc1f8b8b3eab9d9946f4d4df2cc99070151285029.json │ │ │ ├── f71a50c524a31da468cf7c0c08d929d4d31bf17e77473db59b5830f4c583ce25.json │ │ │ ├── f7fdf480e35ed096b57529bfcafd8695aff9a98851050be1f00e4a736ad79719.json │ │ │ └── fb1977d392a5009580c7a608fd403ac2ae96c72f6f2835ba6e49cf662dd0bf6d.json @@ -484,12 +644,10 @@ khatibi │ │ │ ├── 2a13d2bb6f18a81d9511481df0edcdb1a8779e1ec03e919991693cae7e84c8e0.json │ │ │ ├── 2d3b8c0bb7eea6a50a24093abfc3457c0938079b0771b2568498060f6f3a4a74.json │ │ │ ├── 32267f72ce9ef01cd2e804d43c6828549af8eb6f868c76e7d1619502a953f14d.json -│ │ │ ├── 40373bd2bcd76ed8ed27031299e541005d4c8c0c7df312a9312f390b173d7248.json │ │ │ ├── 40a84c5465882009ab69d2874adbb9aaed373cd706d70c92e9e1f4c35640472e.json │ │ │ ├── 4280dd1094314da773fe73ba1ffb91f58f22eea26281bd5cb51f5e9a201c95f6.json │ │ │ ├── 42a95a4a8f651dbebe297ce92beca376cb19eab99d6ef6b5d10a16d79d1842e4.json │ │ │ ├── 4b8f3b6c0ccb3b5494fb2af8c0f9892b6b198a8d503f53498bd1fe2548ef112a.json -│ │ │ ├── 4f53e6b5fc48381f8afe6052371989c4df69347c84c2ec65a46b73cd1c464b5f.json │ │ │ ├── 538cdc2fb71798c581f20d37d52a1484c2ef21691d2e01ee79edcf253ba06394.json │ │ │ ├── 569db52f8b9229dc3cd5cbde156a4ac012ab711d32bdeb4175b60ba863ac8cd0.json │ │ │ ├── 57dba5c2a37beb12ccc107807b7544f687d6d8ba6394c8977c3c59b433d2dab4.json @@ -497,15 +655,12 @@ khatibi │ │ │ ├── 60002a01d6fc22147e2994d1a1a447d5e577c02aed2443789af20571ec2d2a19.json │ │ │ ├── 6aaa93a769a41eb1d37a91608cd6f98b9252c2a5598bd859a130986429d1d53f.json │ │ │ ├── 7101777f271bbc6bce9a2a4fd3bcf487bb62a846a2436bc8c70008bfb23ce33b.json -│ │ │ ├── 7b504b4173cb8d683a7021270372c5c65f078ac3e3e1351601bedfec9c585632.json │ │ │ ├── 7db83b9e384d80a1584b152f9fbc781e0ae5a7524dac0a57ce98bb4d3432bfae.json │ │ │ ├── 7edbffbd4455d071df034927a69faaf76f8e8bbf6495921ce2b70eea1abe8e4f.json │ │ │ ├── 8a79c80fca0ed04957e7b4df749ab80b5d0f4ce3d0ae032a47d51d4a2fbfcd82.json -│ │ │ ├── 8f60bda631cbe7457886d1d574b2e3d2850034f469592a3e8c333f0623a2d3c6.json │ │ │ ├── 908e0129cefe129d0b562243460fe20793c84876244cef2e99ca1becf8cf6a1f.json │ │ │ ├── 92e1d82ed4571249c09163413ceae360dd4d7d03c162c554f3b5a47e94474cf1.json │ │ │ ├── 9897fa1997a41e155ed8de922722238e1f684f5c77a7f56405c8ac7cd1aa376f.json -│ │ │ ├── 9b674bec71dfe6e133af9fb4e35a421fa56cecd230544008ed83a44e6eaae3bf.json │ │ │ ├── a3f1dac3e797de04ab83d263a12eda947904d8e5015b77363db2d29aba8b4928.json │ │ │ ├── aa41b77ce8563f4b4bee015be5ffa598f1c98d4f2854ec9a465f271e8c3f974b.json │ │ │ ├── b205a6dda565ed51a38a6b75c509f41589425f0c2006aad838978f5dc6810b91.json @@ -522,7 +677,6 @@ khatibi │ │ │ ├── dc89fcddf8c6f20bc9f52ae15e5cc8b226320d2776dc8f27be16894e53165013.json │ │ │ ├── dd78862d0f7862653c702e86f7acfabfe33ec5d85d40ba764138c0d1784f7b06.json │ │ │ ├── de67f63e3f49d4ee32811dd67676da33a1e584f671d396fca130f3681d54bd76.json -│ │ │ ├── e0a52ba8204140f195e8d30847dda00d3de5439cf903c576b77fac1ec852b5fb.json │ │ │ ├── e13320e9716b0be66b7cc677a64b44cf3127f7317f35cfa3b849dfbaa9c1f367.json │ │ │ ├── e1c07127e4cef79fe0bfc63a0ab904e5d1b0d9c4be5278b553b6e48d39753b4d.json │ │ │ ├── f0478454ef37ecdc5128db97c18d0ffd92005c1d1db586b6750d985e30a25d76.json @@ -558,7 +712,9 @@ khatibi │ │ ├── http.py │ │ ├── normalize.py │ │ ├── providers.py -│ │ └── sms0098.py +│ │ ├── sms0098.py +│ │ ├── sms0098_codes.py +│ │ └── sms0098ws.py │ ├── __init__.py │ ├── announcements.py │ └── voip.py @@ -581,16 +737,20 @@ khatibi │ ├── test_sms.py │ ├── test_urls_contract.py │ └── test_view_smoke.py +├── .clinerules ├── .dockerignore ├── .gitignore ├── .graphifyignore +├── 0098sms.md ├── AGENTS.md ├── check_finance.py +├── CLAUDE.md ├── db.json ├── docker-compose.yml ├── Dockerfile ├── entrypoint.sh ├── financial_audit_20260816_151225.csv +├── fix_customer_phones.md ├── GEMINI.md ├── ghost_hunter.py ├── khatibi.zip @@ -598,6 +758,7 @@ khatibi ├── manage.py ├── production.env ├── project_tree.py +├── recalc_last_bazdid.md ├── reconciliation_tool.py ├── requirements.txt ├── requirements2.txt diff --git a/admin_panel/management/commands/recalc_last_bazdid.py b/admin_panel/management/commands/recalc_last_bazdid.py new file mode 100644 index 0000000..bcbb719 --- /dev/null +++ b/admin_panel/management/commands/recalc_last_bazdid.py @@ -0,0 +1,156 @@ +"""بازمحاسبهٔ ``Customer.last_bazdid_dt`` از روی اقدام‌های «بازدیدِ تمام‌شده». + +فیلد ``last_bazdid_dt`` «آخرین بازدید» مشتری است و صفحهٔ ``/panel/entezar/`` بر پایهٔ +آن مشتریانِ معوق (بیش از N ماه بازدید نشده) را لیست می‌کند. این مقدار باید برابر +«بزرگ‌ترین ``finishDateTime`` میان اقدام‌های ``actionType='bazdid'`` و ``status='finish'`` +هر مشتری» باشد — همان چیزی که ویوهای ``customerNewAction``/``customerEditAction`` +می‌نویسند. + +اگر رکوردی از راهی غیر از این ویوها وارد شود (اسکریپت، ایمپورت، ویرایش دستی دیتابیس)، +این فیلد عقب می‌ماند و مشتریِ واقعاً بازدیدشده اشتباهاً «معوق» شمرده می‌شود. این +دستور آن واگرایی (drift) را از منبع حقیقت (جدول Action) ترمیم می‌کند. + + python manage.py recalc_last_bazdid --dry-run # فقط گزارش، بدون هیچ تغییری + python manage.py recalc_last_bazdid # اعمال اصلاحات + +مشخصات: +* idempotent — اجرای دوباره پس از یک‌بار اجرا هیچ تغییری ایجاد نمی‌کند. +* پیش‌فرض محافظه‌کارانه است: فقط مقدار را **به‌روز** می‌کند و هیچ‌وقت روی مقادیری که + اقدامِ پشتیبان ندارند دست نمی‌گذارد (مگر با ``--clear-orphans``). +* روی دیتابیس production هم قابل اجرا است. +""" +from django.core.management.base import BaseCommand +from django.db import transaction +from django.db.models import Max + +from ...models import Action, Customer + + +class Command(BaseCommand): + help = ( + "بازمحاسبهٔ Customer.last_bazdid_dt از MAX(finishDateTime) اقدام‌های " + "بازدیدِ تمام‌شده (ترمیم واگرایی داده؛ idempotent)." + ) + + def add_arguments(self, parser): + parser.add_argument( + "--dry-run", action="store_true", + help="فقط گزارش کن که چه چیزی تغییر می‌کند؛ هیچ رکوردی ذخیره نمی‌شود.", + ) + parser.add_argument( + "--clear-orphans", action="store_true", + help="مقدارهایی که هیچ اقدامِ بازدیدِ تمام‌شده‌ای پشتشان نیست را هم NULL کن " + "(پیش‌فرض: دست‌نخورده بمانند). فقط اگر مطمئنی این ستون آلوده است.", + ) + parser.add_argument( + "--batch-size", type=int, default=500, + help="اندازهٔ دستهٔ خواندن مشتریان در هر گام (پیش‌فرض ۵۰۰).", + ) + + def handle(self, *args, **options): + dry_run = options["dry_run"] + clear_orphans = options["clear_orphans"] + batch_size = max(1, options["batch_size"]) + + prefix = "[شبیه‌سازی] " if dry_run else "" + self.stdout.write(self.style.MIGRATE_HEADING( + f"{prefix}بازمحاسبهٔ آخرین بازدید مشتریان آغاز شد." + )) + + # منبع حقیقت: بزرگ‌ترین زمانِ پایانِ بازدید در اقدام‌های تمام‌شدهٔ هر مشتری. + # (Max مقادیر NULL را نادیده می‌گیرد؛ مشتریِ بدون بازدید به‌کل در نقشه نمی‌آید.) + latest_rows = ( + Action.objects + .filter(actionType="bazdid", status="finish") + .exclude(customer_id__isnull=True) + .values("customer_id") + .annotate(mx=Max("finishDateTime")) + ) + latest = { + row["customer_id"]: row["mx"] + for row in latest_rows.iterator(chunk_size=batch_size) + if row["mx"] is not None + } + + stats = { + "scanned": 0, # کل مشتریان بررسی‌شده + "with_visit": 0, # مشتریان دارای بازدیدِ تمام‌شده + "updated": 0, # مقدار اصلاح/به‌روز شد + "moved_forward": 0, # جدیدتر شد ⇒ از لیست معوق‌ها خارج می‌شود + "moved_backward": 0, # عقب‌تر شد + "set_null": 0, # با --clear-orphans خالی شد + "already_ok": 0, # از قبل درست بود + "kept_orphan": 0, # مقدار داشت ولی اقدامِ پشتیبان نداشت (دست‌نخورده ماند) + "null_and_no_action": 0, # NULL و بدون اقدام (طبیعی؛ کاری لازم نیست) + } + + pending = [] # (customer_id, target_value) برای اعمال گروهی + + qs = Customer.objects.only("id", "last_bazdid_dt").order_by("id") + for customer in qs.iterator(chunk_size=batch_size): + stats["scanned"] += 1 + mx = latest.get(customer.id) + current = customer.last_bazdid_dt + + if mx is not None: + stats["with_visit"] += 1 + if current == mx: + stats["already_ok"] += 1 + continue + if current is None or current < mx: + stats["moved_forward"] += 1 + else: + stats["moved_backward"] += 1 + stats["updated"] += 1 + pending.append((customer.id, mx)) + else: + # هیچ بازدیدِ تمام‌شده‌ای پشتوانهٔ این مقدار نیست. + if current is None: + stats["null_and_no_action"] += 1 + elif clear_orphans: + stats["set_null"] += 1 + pending.append((customer.id, None)) + else: + stats["kept_orphan"] += 1 + + if not dry_run and pending: + with transaction.atomic(): + for customer_id, target in pending: + Customer.objects.filter(pk=customer_id).update( + last_bazdid_dt=target + ) + + self._report(stats, dry_run, clear_orphans) + + def _report(self, stats, dry_run, clear_orphans): + style = self.style + label = "[شبیه‌سازی] " if dry_run else "" + self.stdout.write(style.MIGRATE_LABEL(f"\n{label}گزارش بازمحاسبهٔ آخرین بازدید:")) + self.stdout.write(f" بررسی‌شده (کل مشتریان): {stats['scanned']:,}") + self.stdout.write(f" دارای بازدیدِ تمام‌شده: {stats['with_visit']:,}") + self.stdout.write(f" از قبل درست: {stats['already_ok']:,}") + self.stdout.write(f" نیازمند اصلاح: {stats['updated']:,}") + self.stdout.write(f" → جدیدتر شد (خروج از معوق): {stats['moved_forward']:,}") + self.stdout.write(f" → عقب‌تر شد: {stats['moved_backward']:,}") + if clear_orphans: + self.stdout.write(f" → خالی شد (بدون اقدام): {stats['set_null']:,}") + else: + self.stdout.write( + f" بدون اقدام ولی دارای مقدار (دست‌نخورده): {stats['kept_orphan']:,}" + ) + self.stdout.write( + f" NULL و بدون اقدام (طبیعی): {stats['null_and_no_action']:,}" + ) + + if dry_run: + self.stdout.write(style.WARNING( + "\n[حالت شبیه‌سازی] هیچ رکوردی ذخیره نشد. برای اعمال، بدون --dry-run اجرا کن." + )) + elif stats["updated"]: + self.stdout.write(style.SUCCESS( + f"\nانجام شد: {stats['updated']:,} رکورد به‌روزرسانی شد." + )) + else: + self.stdout.write(style.SUCCESS( + "\nانجام شد: هیچ اصلاحی لازم نبود (داده از قبل درست است)." + )) diff --git a/admin_panel/migrations/0070_sms_types_seven.py b/admin_panel/migrations/0070_sms_types_seven.py new file mode 100644 index 0000000..c3974cd --- /dev/null +++ b/admin_panel/migrations/0070_sms_types_seven.py @@ -0,0 +1,174 @@ +"""کاهش انواع پیامک به همان ۷ نوع مصوب + هم‌راستاسازی قالب‌ها. + +* سه نوع قدیمی (``cheque_due``، ``debt_due``، ``custom``) حذف می‌شوند: + قالب‌ها و تنظیمات opt-out مربوط به آن‌ها پاک می‌شود. +* دو نوع جدید اضافه می‌شود: ``visit_created`` (ثبت اقدام بازدید) و + ``payment_thanks`` (تشکر از پرداخت). +* متن/زمان‌بندی نوع‌های باقی‌مانده به معنای جدید هم‌راستا می‌شود + (مثلاً «تأمین موجودی چک» = چند روز پس از سررسید، و نظرسنجی = بی‌درنگ پس از فاکتور). +""" +from django.db import migrations, models + + +SMS_TYPE_CHOICES = [ + ("welcome", "خوش‌آمدگویی (ایجاد مشتری)"), + ("survey", "نظرسنجی پس از ثبت فاکتور"), + ("cheque_registration", "یادآوری تأمین موجودی چک"), + ("visit", "یادآوری بازدید خدمات دستگاه"), + ("visit_created", "ثبت بازدید (اقدام از نوع بازدید)"), + ("payment_thanks", "تشکر از پرداخت"), + ("nesiye_due", "یادآوری سررسید نسیه"), +] + +REMOVED_TYPES = ["cheque_due", "debt_due", "custom"] + +# نوع → مقادیر پیش‌فرض (خالی‌نماندن قالب‌های جدید و هم‌راستایی نوع‌های تغییریافته). +DEFAULT_TEMPLATES = { + "welcome": { + "title": "خوش‌آمدگویی", + "text": "{name} عزیز، از خرید شما سپاسگزاریم. به‌امید دیدار دوباره.", + "offset_days": 0, + "repeat_interval_days": None, + "repeat_interval_months": None, + }, + "survey": { + "title": "نظرسنجی پس از فاکتور", + "text": "{name} عزیز، از خرید شما سپاسگزاریم. لطفاً با ثبت نظر خود در " + "نظرسنجی ما را همراهی کنید.", + "offset_days": 0, + "repeat_interval_days": None, + "repeat_interval_months": None, + }, + "cheque_registration": { + "title": "یادآوری تأمین موجودی چک", + "text": "{name} عزیز، چک شما به مبلغ {amount_sep} ریال سررسید شده است. " + "لطفاً نسبت به تأمین موجودی حساب اقدام فرمایید.", + "offset_days": 1, + "repeat_interval_days": None, + "repeat_interval_months": None, + }, + "visit": { + "title": "یادآوری بازدید خدمات دستگاه", + "text": "{name} عزیز، بیش از ۹ ماه از تعویض فیلتر دستگاه شما گذشته است. " + "لطفاً جهت هماهنگی برای ثبت بازدید رایگان با تلفن 09139741111 " + "تماس بگیرید.", + "offset_days": 0, + "repeat_interval_days": None, + "repeat_interval_months": 9, + }, + "visit_created": { + "title": "ثبت بازدید", + "text": "{name} عزیز، بازدید شما برای تاریخ {date} ثبت شد. " + "از همکاری شما سپاسگزاریم.", + "offset_days": 0, + "repeat_interval_days": None, + "repeat_interval_months": None, + }, + "payment_thanks": { + "title": "تشکر از پرداخت", + "text": "{name} عزیز، از پرداخت شما به مبلغ {amount_sep} ریال سپاسگزاریم.", + "offset_days": 0, + "repeat_interval_days": None, + "repeat_interval_months": None, + }, + "nesiye_due": { + "title": "یادآوری سررسید نسیه", + "text": "{name} عزیز، سررسید بدهی شما به مبلغ {amount_sep} ریال نزدیک است " + "({date}). لطفاً نسبت به تسویه اقدام فرمایید.", + "offset_days": 0, + "repeat_interval_days": None, + "repeat_interval_months": None, + }, +} + + +def align_templates(apps, schema_editor): + SMSTemplate = apps.get_model("admin_panel", "SMSTemplate") + CustomerSMSSetting = apps.get_model("admin_panel", "CustomerSMSSetting") + + # ۱. پاک‌سازی انواع حذف‌شده. + SMSTemplate.objects.filter(sms_type__in=REMOVED_TYPES).delete() + CustomerSMSSetting.objects.filter(sms_type__in=REMOVED_TYPES).delete() + + # ۲. ایجاد/به‌روزرسانی قالب‌های ۷ نوع مجاز (متن پیش‌فرض جدید + زمان‌بندی). + for sms_type, data in DEFAULT_TEMPLATES.items(): + _template, created = SMSTemplate.objects.get_or_create( + sms_type=sms_type, + defaults={ + "title": data["title"], + "text": data["text"], + "is_enabled": True, + "offset_days": data["offset_days"], + "repeat_interval_days": data["repeat_interval_days"], + "repeat_interval_months": data["repeat_interval_months"], + }, + ) + if not created: + SMSTemplate.objects.filter(sms_type=sms_type).update( + offset_days=data["offset_days"], + repeat_interval_days=data["repeat_interval_days"], + repeat_interval_months=data["repeat_interval_months"], + ) + + # ۳. متن/عنوانِ نوع‌های تغییریافته به معنای جدید هم‌راستا می‌شود. + SMSTemplate.objects.filter(sms_type="visit").update( + title=DEFAULT_TEMPLATES["visit"]["title"], + text=DEFAULT_TEMPLATES["visit"]["text"], + ) + SMSTemplate.objects.filter(sms_type="cheque_registration").update( + title=DEFAULT_TEMPLATES["cheque_registration"]["title"], + text=DEFAULT_TEMPLATES["cheque_registration"]["text"], + ) + SMSTemplate.objects.filter(sms_type="survey").update( + title=DEFAULT_TEMPLATES["survey"]["title"], + text=DEFAULT_TEMPLATES["survey"]["text"], + ) + + +def clear_removed_queue_types(apps, schema_editor): + """نوعِ ردیف‌های صفِ باقی‌مانده از انواع حذف‌شده را خالی می‌کند. + + ردیف صف حذف نمی‌شود (متن و تاریخچه‌اش می‌ماند) چون ممکن است هنوز ارسال‌نشده + باشد؛ فقط ``sms_type`` آن خالی می‌شود تا کدِ نوعِ نامعتبر در دیتابیس نماند. + """ + SMSQueue = apps.get_model("admin_panel", "SMSQueue") + SMSQueue.objects.filter(sms_type__in=REMOVED_TYPES).update(sms_type="") + + +def noop(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ("admin_panel", "0069_smstemplate_auto_send"), + ] + + operations = [ + migrations.AlterField( + model_name="smstemplate", + name="sms_type", + field=models.CharField( + choices=SMS_TYPE_CHOICES, max_length=40, unique=True, + verbose_name="نوع پیامک", + ), + ), + migrations.AlterField( + model_name="customersmssetting", + name="sms_type", + field=models.CharField( + choices=SMS_TYPE_CHOICES, max_length=40, verbose_name="نوع پیامک", + ), + ), + migrations.AlterField( + model_name="smsqueue", + name="sms_type", + field=models.CharField( + blank=True, choices=SMS_TYPE_CHOICES, default="", max_length=40, + verbose_name="نوع", + ), + ), + migrations.RunPython(align_templates, noop), + migrations.RunPython(clear_removed_queue_types, noop), + ] diff --git a/admin_panel/migrations/0071_sms_settings_permission.py b/admin_panel/migrations/0071_sms_settings_permission.py new file mode 100644 index 0000000..7cac1eb --- /dev/null +++ b/admin_panel/migrations/0071_sms_settings_permission.py @@ -0,0 +1,47 @@ +"""افزودن پرمیشن جداگانهٔ ``ManageSMSSettings`` برای صفحات تنظیمات/قالب‌های پیامک. + +پیش از این، همهٔ مسیرهای پیامک با ``ManageSMS`` کنترل می‌شد. از این پس صفحات +«تنظیمات سامانه» و «قالب‌ها» با ``ManageSMSSettings`` جدا می‌شوند تا مدیر بتواند +دسترسی به آن‌ها را فقط به کاربران مشخصی بدهد، بدون دادن کل اختیارات پیامک. +""" +from django.db import migrations +from django.db.models import F + + +def ensure_permission(apps, schema_editor): + """رکورد Permission جدید را (اگر نبود) می‌سازد و نام نمایشی‌اش را اصلاح می‌کند.""" + Permission = apps.get_model('auth', 'Permission') + ContentType = apps.get_model('contenttypes', 'ContentType') + + content_type, _created = ContentType.objects.get_or_create( + app_label='admin_panel', model='smsproviderconfig', + ) + Permission.objects.get_or_create( + content_type=content_type, + codename='ManageSMSSettings', + defaults={'name': 'ManageSMSSettings'}, + ) + # نام نمایشی هر دو پرمیشن پیامک را با قرارداد پروژه هم‌راستا می‌کند. + Permission.objects.filter( + content_type=content_type, codename__in=['ManageSMS', 'ManageSMSSettings'], + ).update(name=F('codename')) + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin_panel', '0070_sms_types_seven'), + ] + + operations = [ + migrations.AlterModelOptions( + name='smsproviderconfig', + options={ + 'permissions': [('ManageSMS', 'ManageSMS'), + ('ManageSMSSettings', 'ManageSMSSettings')], + 'verbose_name': 'تنظیمات سامانه پیامک', + 'verbose_name_plural': 'تنظیمات سامانه پیامک', + }, + ), + migrations.RunPython(ensure_permission, migrations.RunPython.noop), + ] diff --git a/admin_panel/models/sms.py b/admin_panel/models/sms.py index 857ce1f..5322919 100644 --- a/admin_panel/models/sms.py +++ b/admin_panel/models/sms.py @@ -11,16 +11,20 @@ from django.db import models class SMSType(models.TextChoices): - """انواع پیامک‌های سیستم.""" + """انواع پیامک‌های سیستم (دقیقاً ۷ نوع، به ترتیب چرخهٔ کاری). + + ترتیب تعریف = ترتیب نمایش در پنل. هر نوع یا از یک رویداد (view) یا از یک + اسکنر دوره‌ای در صف قرار می‌گیرد؛ نگاشت آن در ``admin_panel/sms/triggers.py`` + و ``admin_panel/sms/scanner.py`` است. + """ WELCOME = "welcome", "خوش‌آمدگویی (ایجاد مشتری)" - CHEQUE_DUE = "cheque_due", "یادآوری سررسید چک" - NESIYE_DUE = "nesiye_due", "یادآوری سررسید نسیه" + SURVEY = "survey", "نظرسنجی پس از ثبت فاکتور" CHEQUE_REGISTRATION = "cheque_registration", "یادآوری تأمین موجودی چک" - DEBT_DUE = "debt_due", "یادآوری بدهی (سررسید وصول مطالبات)" - VISIT = "visit", "یادآوری بازدید خدمات" - SURVEY = "survey", "نظرسنجی پس از فاکتور" - CUSTOM = "custom", "پیام سفارشی" + VISIT = "visit", "یادآوری بازدید خدمات دستگاه" + VISIT_CREATED = "visit_created", "ثبت بازدید (اقدام از نوع بازدید)" + PAYMENT_THANKS = "payment_thanks", "تشکر از پرداخت" + NESIYE_DUE = "nesiye_due", "یادآوری سررسید نسیه" class SMSProviderConfig(models.Model): @@ -33,6 +37,9 @@ class SMSProviderConfig(models.Model): # نام نمایشی مطابق قرارداد سایر پرمیشن‌های پروژه (ViewStore/AddCustomer/...) # تا در فهرست پرمیشن‌های Django Admin به‌سادگی پیدا شود. ("ManageSMS", "ManageSMS"), + # سطح دسترسی جداگانه برای صفحات «تنظیمات» و «قالب‌ها»: مدیر می‌تواند فقط + # به کاربران مشخصی اجازهٔ دیدن/ویرایش این‌ها را بدهد (نه هر کاربر پیامک). + ("ManageSMSSettings", "ManageSMSSettings"), ] ProviderChoices = [ diff --git a/admin_panel/sms/notices.py b/admin_panel/sms/notices.py index 492bdfd..00d6c1e 100644 --- a/admin_panel/sms/notices.py +++ b/admin_panel/sms/notices.py @@ -30,6 +30,10 @@ NOTICES = { "approved": ("success", "پیامک تأیید شد و برای ارسال در صف قرار گرفت."), "not_pending": ("warning", "این پیامک در وضعیت «در انتظار تأیید» نیست."), "deleted": ("success", "پیامک از صف حذف شد."), + "delete_done_blocked": ( + "warning", + "این پیامک ارسال شده است و از صف حذف نمی‌شود؛ سابقهٔ آن در «گزارش ارسال» می‌ماند.", + ), "bulk_approved_all": ("success", "{count} پیامک تأیید شد و برای ارسال در صف قرار گرفت."), "bulk_approved": ("success", "{count} پیامک تأیید شد."), "bulk_deleted": ("success", "{count} پیامک از صف حذف شد."), diff --git a/admin_panel/sms/parts.py b/admin_panel/sms/parts.py new file mode 100644 index 0000000..6e7c14f --- /dev/null +++ b/admin_panel/sms/parts.py @@ -0,0 +1,90 @@ +"""محاسبهٔ «تعداد پیامکِ محاسبه‌شده» (parts) و کدگذاری متن. + +هر پیامک روی شبکهٔ موبایل بر اساس **کدگذاری** و **طول** به یک یا چند «بخش» (part) +شکسته می‌شود و اپراتور بر همان تعداد بخش هزینه می‌گیرد. این ماژول همان قاعدهٔ +استاندارد (3GPP/GSM 03.38) را پیاده می‌کند تا بتوان هزینهٔ ارسال را تحلیل کرد: + +* **GSM-7** — برای متن‌های انگلیسی/عددی. هر بخش ۱۶۰ کاراکتر؛ در حالت چندبخشی + (multipart) هر بخش ۱۵۳ کاراکتر (۷ کاراکتر برای هدر الحاق). + برخی کاراکترها (مثل `{}[]~\\|^€`) در جدول «الحاقی» هستند و **۲ کاراکتر** حساب + می‌شوند. +* **UCS-2** — اگر متن حتی **یک** کاراکتر خارج از جدول GSM-7 داشته باشد (فارسی، + عربی، ایموجی…)، **کل پیامک** با UCS-2 کدگذاری می‌شود: هر بخش ۷۰ کاراکتر؛ در + حالت چندبخشی هر بخش ۶۷ کاراکتر. ایموجی‌ها (خارج از BMP) دو واحد کدِ UTF-16 + حساب می‌شوند. + +بنابراین یک متن فارسی حتی اگر کوتاه باشد، در «۷۰ کاراکتر» جا می‌شود و ممکن است +چند پیامک حساب شود؛ و یک متن ترکیبی فارسی+انگلیسی همیشه UCS-2 است. +""" +from math import ceil + +ENCODING_GSM7 = "gsm7" +ENCODING_UCS2 = "ucs2" + +# جدول کامل GSM 03.38 (بخش پایه). کاراکتر 0x1B عمداً وارد نشده چون نقش «پیشوند +# الحاق» دارد و کاراکتر مستقل قانونی نیست. +GSM7_BASIC = set( + "@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ" + " !\"#¤%&'()*+,-./0123456789:;<=>?" + "¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ" + "¿abcdefghijklmnopqrstuvwxyzäöñüà" +) + +# کاراکترهای «جدول الحاقی»: در GSM-7 دو واحد حساب می‌شوند. +GSM7_EXTENDED = set("^{}\\[~]|€") + +# (سقف تک‌بخشی، سقف هر بخش چندبخشی) برای هر کدگذاری. +LIMITS = { + ENCODING_GSM7: (160, 153), + ENCODING_UCS2: (70, 67), +} + +ENCODING_LABELS = { + ENCODING_GSM7: "GSM-7 (انگلیسی/عدد)", + ENCODING_UCS2: "UCS-2 (فارسی/یونیکد)", +} + + +def is_gsm7(text): + """آیا کل متن با جدول GSM-7 قابل نمایش است؟ (شرط کافی و لازم برای GSM-7).""" + return all(ch in GSM7_BASIC or ch in GSM7_EXTENDED for ch in text or "") + + +def detect_encoding(text): + """کدگذاری متن: ``gsm7`` یا ``ucs2``. متن خالی = ``gsm7``.""" + return ENCODING_GSM7 if is_gsm7(text) else ENCODING_UCS2 + + +def encoded_length(text): + """طول متن بر حسب «واحدهایی که اپراتور می‌شمارد». + + * GSM-7: کاراکترهای الحاقی ۲ واحد. + * UCS-2: واحدهای کد UTF-16 (ایموجی = ۲). + """ + text = text or "" + if is_gsm7(text): + return sum(2 if ch in GSM7_EXTENDED else 1 for ch in text) + return len(text.encode("utf-16-le")) // 2 + + +def parts_count(text): + """تعداد بخش‌هایی که اپراتور برای این متن حساب می‌کند (۱ برای متن خالی = ۰).""" + text = text or "" + if not text: + return 0 + single, multipart = LIMITS[detect_encoding(text)] + length = encoded_length(text) + if length <= single: + return 1 + return ceil(length / multipart) + + +def describe(text): + """خلاصهٔ محاسبه برای نمایش در گزارش (کدگذاری، طول، تعداد بخش).""" + encoding = detect_encoding(text) + return { + "encoding": encoding, + "encoding_label": ENCODING_LABELS[encoding], + "length": encoded_length(text), + "parts": parts_count(text), + } diff --git a/admin_panel/sms/rules.py b/admin_panel/sms/rules.py index ab94640..f39859c 100644 --- a/admin_panel/sms/rules.py +++ b/admin_panel/sms/rules.py @@ -4,7 +4,7 @@ from ..models import CustomerSMSSetting, SMSType def is_sms_enabled(customer, sms_type): """اگر رکوردی با is_enabled=False وجود داشته باشد غیرفعال است؛ در غیر این‌صورت فعال.""" - if customer is None or sms_type == SMSType.CUSTOM: + if customer is None: return True setting = CustomerSMSSetting.objects.filter(customer=customer, sms_type=sms_type).first() if setting is None: @@ -21,5 +21,4 @@ def customer_toggle_map(customer): return [ {"code": code, "label": label, "enabled": overrides.get(code, True)} for code, label in SMSType.choices - if code != SMSType.CUSTOM ] diff --git a/admin_panel/sms/scanner.py b/admin_panel/sms/scanner.py index 1d00acf..46a0f61 100644 --- a/admin_panel/sms/scanner.py +++ b/admin_panel/sms/scanner.py @@ -9,31 +9,29 @@ در هر اجرا حداکثر ``backlog_max`` مورد قدیمی هم صف می‌شود. * **سقف هر اجرا (``max_per_run``)**: سقف کل صف‌کردن هر اسکنر در یک اجرا. +اسکنرها فقط انواع «دوره‌ای» را پوشش می‌دهند (تأمین موجودی چک، سررسید نسیه، بازدید +دوره‌ای). انواع «رویدادی» (خوش‌آمدگویی، نظرسنجی، ثبت بازدید، تشکر از پرداخت) هنگام +وقوع رخداد توسط ویوها و از طریق ``admin_panel/sms/triggers.py`` صف می‌شوند. + هر اسکنر مستقل و مقاوم به خطاست؛ یک خطا بقیه را متوقف نمی‌کند. ``dedup_key`` از ارسال تکراری جلوگیری می‌کند و تضمین می‌کند اجرای دوبارهٔ اسکنر رکورد جدیدی نسازد. -یادآوری‌های **دوره‌ای** (بازدید و ثبت چک) کلیدشان شامل شمارهٔ دوره است. یعنی اگر -مدیر یک مورد را از صف حذف کند، فقط همان دوره کنسل می‌شود و در دورهٔ بعد دوباره -ساخته می‌شود؛ برای همیشه خاموش نمی‌ماند. +یادآوری‌های **دوره‌ای** (بازدید) کلیدشان شامل شمارهٔ دوره است. یعنی اگر مدیر یک مورد +را از صف حذف کند، فقط همان دوره کنسل می‌شود و در دورهٔ بعد دوباره ساخته می‌شود؛ برای +همیشه خاموش نمی‌ماند. """ import logging import os from datetime import datetime, time, timedelta -from django.db.models import Q, Sum from django.utils import timezone -from ..models import (Article, Customer, Factor, Sarresid, SMSProviderConfig, - SMSTemplate, SMSType) +from ..models import Customer, Sarresid, SMSProviderConfig, SMSTemplate, SMSType from .enqueue import enqueue_sms from .timing import DEFAULT_VISIT_MONTHS logger = logging.getLogger(__name__) -# پیش‌فرض فاصلهٔ تکرار «یادآوری ثبت چک» (روز) وقتی قالبی تنظیم نشده باشد. -CHEQUE_REG_INTERVAL_DEFAULT = 3 - - def _template(sms_type): return SMSTemplate.objects.filter(sms_type=sms_type, is_enabled=True).first() @@ -82,33 +80,39 @@ class _Budget: self.new_count += 1 -def scan_cheque_due(today): - template = _template(SMSType.CHEQUE_DUE) +def scan_cheque_funding(today): + """یادآوری «تأمین موجودی چک» چند روز پس از سررسید چکِ پرداخت‌نشده. + + چند روزِ پس از سررسید از قالب همین نوع (قابل تنظیم از پنل) خوانده می‌شود و مبلغ + چک در متن قرار می‌گیرد. اگر چک پرداخت شود (``payStatus=true``) دیگر یادآوری + نمی‌گیرد؛ ``dedup_key`` نیز مانع ارسال تکراری در اجراهای بعدی می‌شود. اگر اسکنر + چند روز از دست برود، چک‌های «معوق‌تر» هم واجد شرط می‌شوند تا یادآوری از دست نرود. + """ + template = _template(SMSType.CHEQUE_REGISTRATION) if template is None: return 0 budget = _Budget(SMSProviderConfig.load()) offset = template.offset_days or 0 + target = today - timedelta(days=offset) qs = Sarresid.objects.filter( - type="check", payStatus="false", - sarresidDT__date__gte=today, - sarresidDT__date__lte=today + timedelta(days=offset), + type="check", payStatus="false", sarresidDT__date__lte=target, ).select_related("customer").order_by("sarresidDT") count = 0 for s in qs: if not s.customer or not s.sarresidDT: continue - # پنجرهٔ یادآوری از «سررسید منهای offset» باز می‌شود. - trigger = _aware(s.sarresidDT) - timedelta(days=offset) + # لحظهٔ باز شدن پنجرهٔ یادآوری = سررسید + offset روز. + trigger = _aware(s.sarresidDT) + timedelta(days=offset) if not budget.can_send(trigger): continue - days = (s.sarresidDT.date() - today).days + days = (today - s.sarresidDT.date()).days made = enqueue_sms( - customer=s.customer, sms_type=SMSType.CHEQUE_DUE, + customer=s.customer, sms_type=SMSType.CHEQUE_REGISTRATION, context={ "name": s.customer.name_family, "amount": s.price, "date": s.sarresidDT, "days": days, "check_row": s.check_row_number, }, - dedup_key=f"cheque_due:{s.id}", + dedup_key=f"cheque_fund:{s.id}", ) if made: budget.record(trigger) @@ -149,119 +153,6 @@ def scan_nesiye_due(today): return count -def scan_cheque_registration(today): - """یادآوری «ثبت اطلاعات چک» برای چک‌های پرداخت‌نشدهٔ ثبت‌نشده. - - هر ``interval`` روز یک‌بار تکرار می‌شود و به‌محض اینکه شمارهٔ صیادی چک ثبت شود - (یا چک پرداخت شود) متوقف می‌گردد. دورهٔ تکرار از پنل قابل تنظیم است. - """ - template = _template(SMSType.CHEQUE_REGISTRATION) - if template is None: - return 0 - budget = _Budget(SMSProviderConfig.load()) - interval = template.repeat_interval_days or CHEQUE_REG_INTERVAL_DEFAULT - qs = Sarresid.objects.filter( - type="check", payStatus="false", sarresidDT__date__gte=today, - ).filter( - Q(check_sayyad_id="") | Q(check_sayyad_id__isnull=True), - ).select_related("customer") - count = 0 - for s in qs: - if not s.customer: - continue - anchor = s.createdDT or s.sarresidDT - if not anchor: - continue - days = (today - anchor.date()).days - if days <= 0 or days % interval != 0: - continue - cycle = days // interval - # لحظهٔ باز شدن همین دورهٔ تکرار (نه خودِ چک) ملاک خط شروع است. - trigger = _aware(anchor) + timedelta(days=days) - if not budget.can_send(trigger): - continue - made = enqueue_sms( - customer=s.customer, sms_type=SMSType.CHEQUE_REGISTRATION, - context={ - "name": s.customer.name_family, "amount": s.price, - "date": s.sarresidDT, "days": (s.sarresidDT.date() - today).days - if s.sarresidDT else "", - "check_row": s.check_row_number, - }, - dedup_key=f"cheque_reg:{s.id}:{cycle}", - ) - if made: - budget.record(trigger) - count += made - return count - - -def scan_debt_due(today): - """یادآوری بدهی برای مانده‌دارها روی سررسید وصول مطالبات.""" - template = _template(SMSType.DEBT_DUE) - if template is None: - return 0 - budget = _Budget(SMSProviderConfig.load()) - offset = template.offset_days or 0 - target = today - timedelta(days=offset) - qs = Sarresid.objects.filter( - payStatus="false", sarresidDT__date=target, - ).select_related("customer").order_by("sarresidDT") - count = 0 - seen = set() - for s in qs: - if not s.customer or s.customer_id in seen or not s.sarresidDT: - continue - trigger = _aware(s.sarresidDT) - if not budget.can_send(trigger): - continue - totals = Article.objects.filter(customer=s.customer).aggregate( - d=Sum("debit"), c=Sum("credit"), - ) - net = (totals["d"] or 0) - (totals["c"] or 0) - if net <= 0: - continue - made = enqueue_sms( - customer=s.customer, sms_type=SMSType.DEBT_DUE, - context={"name": s.customer.name_family, "amount": net}, - dedup_key=f"debt_due:{s.id}", - ) - if made: - seen.add(s.customer_id) - budget.record(trigger) - count += made - return count - - -def scan_survey(today): - """نظرسنجی چند روز پس از ایجاد فاکتور.""" - template = _template(SMSType.SURVEY) - if template is None: - return 0 - budget = _Budget(SMSProviderConfig.load()) - offset = template.offset_days or 3 - target = today - timedelta(days=offset) - qs = Factor.objects.filter( - is_active=True, create_date__date=target, - ).exclude(pay_status="cancell").select_related("customer").order_by("create_date") - count = 0 - for f in qs: - if not f.customer or not f.create_date: - continue - trigger = _aware(f.create_date) + timedelta(days=offset) - if not budget.can_send(trigger): - continue - made = enqueue_sms( - customer=f.customer, sms_type=SMSType.SURVEY, - context={"name": f.customer.name_family, "amount": f.amount}, - dedup_key=f"survey:{f.id}", - ) - if made: - budget.record(trigger) - count += made - return count - - def scan_visit(today): """یادآوری بازدید برای مشتریان عقب‌افتاده از بازدید (منطق صفحهٔ «انتظار»). @@ -313,12 +204,11 @@ def scan_visit(today): return count +# فقط همان ۷ نوع مجاز؛ انواع رویدادی (خوش‌آمدگویی، نظرسنجی، ثبت بازدید، تشکر از +# پرداخت) در ``admin_panel/sms/triggers.py`` توسط ویوها صف می‌شوند، نه این‌جا. SCANNERS = [ - ("cheque_due", scan_cheque_due), + ("cheque_registration", scan_cheque_funding), ("nesiye_due", scan_nesiye_due), - ("cheque_registration", scan_cheque_registration), - ("debt_due", scan_debt_due), - ("survey", scan_survey), ("visit", scan_visit), ] diff --git a/admin_panel/sms/timing.py b/admin_panel/sms/timing.py index 75ff32e..316d43c 100644 --- a/admin_panel/sms/timing.py +++ b/admin_panel/sms/timing.py @@ -9,8 +9,7 @@ from ..models import SMSType NONE = "none" BEFORE_DUE = "before_due" -AFTER_CREATION = "after_creation" -RECURRING = "recurring" +AFTER_DUE = "after_due" PERIOD_MONTHS = "period_months" # پیش‌فرض دورهٔ بازدید (ماه) وقتی مدیر مقداری در قالب نگذاشته باشد. @@ -22,39 +21,18 @@ TIMING = { "field": None, "note": "به‌محض ثبت مشتری و بدون تأخیر ارسال می‌شود؛ تنظیم زمانی ندارد.", }, - SMSType.CHEQUE_DUE: { - "kind": BEFORE_DUE, - "field": "offset_days", - "label": "چند روز قبل از سررسید چک", - "default": 2, - "note": "پنجرهٔ یادآوری از «سررسید − این تعداد روز» باز می‌شود و تا سررسید ادامه دارد.", - }, - SMSType.NESIYE_DUE: { - "kind": BEFORE_DUE, - "field": "offset_days", - "label": "چند روز قبل از سررسید نسیه", - "default": 2, - "note": "پنجرهٔ یادآوری از «سررسید − این تعداد روز» باز می‌شود و تا سررسید ادامه دارد.", - }, - SMSType.CHEQUE_REGISTRATION: { - "kind": RECURRING, - "field": "repeat_interval_days", - "label": "هر چند روز یک‌بار", - "default": 3, - "note": "تا زمانی که اطلاعات ثبت چک (شمارهٔ صیادی) وارد نشده و چک پرداخت نشده، " - "با این فاصله تکرار می‌شود.", - }, SMSType.SURVEY: { - "kind": AFTER_CREATION, - "field": "offset_days", - "label": "چند روز بعد از صدور فاکتور", - "default": 3, - "note": "این تعداد روز پس از ثبت فاکتور ارسال می‌شود.", - }, - SMSType.DEBT_DUE: { "kind": NONE, "field": None, - "note": "روی سررسیدِ وصول مطالبات و فقط برای مشتریانی که مانده بدهکارند ارسال می‌شود.", + "note": "بی‌درنگ پس از ثبت فاکتور ارسال می‌شود؛ تنظیم زمانی ندارد.", + }, + SMSType.CHEQUE_REGISTRATION: { + "kind": AFTER_DUE, + "field": "offset_days", + "label": "چند روز پس از سررسید چک", + "default": 1, + "note": "این تعداد روز پس از سررسید چکِ پرداخت‌نشده، پیامک یادآوری " + "تأمین موجودی با مبلغ چک ارسال می‌شود.", }, SMSType.VISIT: { "kind": PERIOD_MONTHS, @@ -63,10 +41,22 @@ TIMING = { "default": DEFAULT_VISIT_MONTHS, "note": "برای مشتریانی که از این مدت بازدید نشده‌اند ارسال می‌شود.", }, - SMSType.CUSTOM: { + SMSType.VISIT_CREATED: { "kind": NONE, "field": None, - "note": "فقط به‌صورت دستی از صفحهٔ مشتری ارسال می‌شود؛ تنظیم زمانی ندارد.", + "note": "به‌محض ثبت «اقدام از نوع بازدید» ارسال می‌شود؛ تنظیم زمانی ندارد.", + }, + SMSType.PAYMENT_THANKS: { + "kind": NONE, + "field": None, + "note": "بی‌درنگ پس از ثبت پرداخت (سند وصول/بستانکار) ارسال می‌شود؛ تنظیم زمانی ندارد.", + }, + SMSType.NESIYE_DUE: { + "kind": BEFORE_DUE, + "field": "offset_days", + "label": "چند روز قبل از سررسید نسیه", + "default": 0, + "note": "پنجرهٔ یادآوری از «سررسید − این تعداد روز» باز می‌شود و تا سررسید ادامه دارد.", }, } diff --git a/admin_panel/sms/triggers.py b/admin_panel/sms/triggers.py new file mode 100644 index 0000000..162da80 --- /dev/null +++ b/admin_panel/sms/triggers.py @@ -0,0 +1,82 @@ +"""تریگرهای رویدادی پیامک (لایهٔ سرویس، بدون signal). + +چهار نوع پیامک «رویدادی» هستند و لحظهٔ وقوع‌شان یک اکشن پنل است، نه یک سررسید +تاریخی؛ پس به‌جای اسکنر دوره‌ای، ویوها بعد از انجام کارِ اصلی این توابع را صدا +می‌زنند: + +* :func:`notify_welcome` — ایجاد مشتری. +* :func:`notify_factor_survey` — ثبت فاکتور (نظرسنجی). +* :func:`notify_visit_scheduled` — ثبت «اقدام از نوع بازدید». +* :func:`notify_payment_thanks` — ثبت پرداخت (سند بستانکار / وصول). + +قرارداد پروژه: استفاده از signal ممنوع است؛ همه‌چیز صریح از دلِ ویو صدا زده می‌شود. +این توابع هیچ استثنایی به فراخوان پرتاب نمی‌کنند (``enqueue_sms`` خودش مقاوم است) تا +مسیر اصلی هرگز به‌خاطر پیامک مختل نشود. +""" +import logging + +from ..models import SMSType +from .enqueue import enqueue_sms + +logger = logging.getLogger(__name__) + +# روش‌های «غیرپرداخت»: سند بستانکاری که به‌خاطر تخفیف صادر شده، پرداختِ واقعی نیست +# و نباید پیامک «تشکر از پرداخت» بگیرد. +NON_PAYMENT_METHODS = {"تخفیف"} + + +def notify_welcome(customer, *, created_by=None): + """پیامک خوش‌آمدگویی در لحظهٔ ساخت مشتری.""" + if customer is None or customer.pk is None: + return 0 + return enqueue_sms( + customer=customer, sms_type=SMSType.WELCOME, + context={"name": customer.name_family}, + dedup_key=f"welcome:{customer.pk}", + created_by=created_by, + ) + + +def notify_factor_survey(customer, factor, *, created_by=None): + """پیامک نظرسنجی، بی‌درنگ پس از ثبت فاکتور.""" + if customer is None or factor is None or factor.pk is None: + return 0 + return enqueue_sms( + customer=customer, sms_type=SMSType.SURVEY, + context={"name": customer.name_family, "amount": factor.amount}, + dedup_key=f"survey:{factor.pk}", + created_by=created_by, + ) + + +def notify_visit_scheduled(customer, action, *, created_by=None): + """پیامک «بازدید شما برای تاریخ … ثبت شد» پس از ایجاد اقدام بازدید.""" + if customer is None or action is None or action.pk is None: + return 0 + return enqueue_sms( + customer=customer, sms_type=SMSType.VISIT_CREATED, + context={"name": customer.name_family, "date": action.actionDateTime}, + dedup_key=f"visit_created:{action.pk}", + created_by=created_by, + ) + + +def notify_payment_thanks(customer, amount, *, article_id=None, method=None, + created_by=None): + """پیامک تشکر از پرداخت؛ پس از ثبت سند پرداخت (بستانکار/وصول). + + ``article_id`` برای یکتاسازی استفاده می‌شود تا یک پرداخت هرگز دو پیامک نگیرد. + ``method`` نحوهٔ پرداخت است؛ اگر «تخفیف» باشد (که پرداختِ واقعی نیست) هیچ پیامکی + ساخته نمی‌شود. + """ + if customer is None: + return 0 + if method and str(method).strip() in NON_PAYMENT_METHODS: + return 0 + dedup_key = f"payment_thanks:{article_id}" if article_id is not None else None + return enqueue_sms( + customer=customer, sms_type=SMSType.PAYMENT_THANKS, + context={"name": customer.name_family, "amount": amount}, + dedup_key=dedup_key, + created_by=created_by, + ) diff --git a/admin_panel/static/admin_panel/style.css b/admin_panel/static/admin_panel/style.css index 4d83f8c..3373a39 100644 --- a/admin_panel/static/admin_panel/style.css +++ b/admin_panel/static/admin_panel/style.css @@ -129,4 +129,5 @@ input[disabled]{ border-right: 1px solid #b7b7b7; text-align: center; border-left: 1px solid #b7b7b7; -} \ No newline at end of file +} + diff --git a/admin_panel/templates/admin_panel/_customer_info.html b/admin_panel/templates/admin_panel/_customer_info.html new file mode 100644 index 0000000..1b62369 --- /dev/null +++ b/admin_panel/templates/admin_panel/_customer_info.html @@ -0,0 +1,144 @@ +{% load custom_tags %} +{% comment %} + کارت پروفایل مشتری — ستون سمت راست صفحهٔ مشتری. + تک‌منبع (DRY): هم customerBase.html و هم customerBaseNew.html این را include می‌کنند + تا ساختار، استایل و اسکریپت ویرایش/شماره‌ها در همهٔ صفحات مشتری یکسان بماند. + شناسه‌های موردنیاز جاوااسکریپت: + #formcsEdit, #enEdit, #editCs, #cancelEdit, #del_customer, #showUserDT, #userDT +{% endcomment %} +
+ {% csrf_token %} + +
+

{{ customer.name_family }}

+ مشاهده مشخصات +
+ +
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ + {% include "./_customer_phones.html" with phones_editable=1 %} + +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ {% if perms.admin_panel.DelCustomer %} + حذف مشتری + {% endif %} + ویرایش + + +
+ +
+
+ + diff --git a/admin_panel/templates/admin_panel/_customer_phones.html b/admin_panel/templates/admin_panel/_customer_phones.html index f941e88..de79bc2 100644 --- a/admin_panel/templates/admin_panel/_customer_phones.html +++ b/admin_panel/templates/admin_panel/_customer_phones.html @@ -19,6 +19,16 @@ 📞 + {% if perms.admin_panel.ManageSMS and entry.is_mobile %} +
+ +
+ {% endif %} {% if phones_editable and perms.admin_panel.ViewStore %}
@@ -35,3 +45,50 @@ {% endif %}
+ +{% if perms.admin_panel.ManageSMS %} + +{% endif %} diff --git a/admin_panel/templates/admin_panel/_customer_sms.html b/admin_panel/templates/admin_panel/_customer_sms.html index 1934bc7..93b4a99 100644 --- a/admin_panel/templates/admin_panel/_customer_sms.html +++ b/admin_panel/templates/admin_panel/_customer_sms.html @@ -3,37 +3,7 @@

پیامک

-
-

شماره‌های اصلی (گیرنده پیامک)

- {# همهٔ شماره‌ها از هر منبعی که ذخیره شده‌اند نمایش داده می‌شوند (جدول شماره‌ها یا فیلدهای قدیمی) #} - {% for entry in sms_phone_entries %} -
- {% csrf_token %} - - -
- -
-
- {% if entry.is_mobile %} - - {% else %} - - {% endif %} -
-
- {% empty %} -

شماره‌ای برای این مشتری ثبت نشده است.

- {% endfor %} -

اگر شمارهٔ اصلی تعیین نشده باشد، شمارهٔ اولِ معتبر به‌صورت خودکار استفاده می‌شود.

-

گیرندگان فعلی: {% for r in sms_recipients %}{{ r }}{% if not forloop.last %}، {% endif %}{% empty %}—{% endfor %}

-
- -
+

فعال/غیرفعال پیامک‌ها برای این مشتری

{% for item in sms_toggles %}
@@ -58,7 +28,7 @@ {% csrf_token %}
- +

می‌توانید از متغیرهایی مثل {name} (نام مشتری) استفاده کنید.

diff --git a/admin_panel/templates/admin_panel/base.html b/admin_panel/templates/admin_panel/base.html index 2c37cab..b86596d 100644 --- a/admin_panel/templates/admin_panel/base.html +++ b/admin_panel/templates/admin_panel/base.html @@ -120,19 +120,26 @@ - {% if perms.admin_panel.ManageSMS %} + {% if perms.admin_panel.ManageSMS or perms.admin_panel.ManageSMSSettings %} {% endif %} diff --git a/admin_panel/templates/admin_panel/customerBase.html b/admin_panel/templates/admin_panel/customerBase.html index 0877996..de51f35 100644 --- a/admin_panel/templates/admin_panel/customerBase.html +++ b/admin_panel/templates/admin_panel/customerBase.html @@ -6,55 +6,7 @@
- - {% csrf_token %} -
-

محمد جواد فصاحت

- مشاهده مشخصات -
-
-
- -
- -
-
- -
- -
- -
-
- - {% include "./_customer_phones.html" %} - - -
- -
- -
-
- -
- -
- -
-
- -
- {% if perms.admin_panel.DelCustomer %} - حذف مشتری - {% endif %} - - ویرایش - - -
-
- + {% include "./_customer_info.html" %}
@@ -229,11 +181,6 @@ }); - $("#del_customer").click(function(){ - if(confirm("با حذف مشتری، تمام سوابق، فاکتورها و اطلاعات از سیستم حذف میشود، مطمئن هستید؟")){ - window.location.href = "/panel/customer/{{ customer.id }}/del"; - } - }); $(".closeNewIteam").click(function(){ $("#addNewIteam").removeClass("is-active"); @@ -318,22 +265,6 @@ $(document).ready(function() { var test = 0 - $("#enEdit").click(function(){ - $("#formcsEdit").find("input").prop('disabled', false); - $("#enEdit").addClass("is-hidden"); - $("#editCs").removeClass("is-hidden"); - }); - - $("#showUserDT").click(function(){ - if( $(this).html()=='مشاهده مشخصات' ){ - $(this).html('مخفی کردن'); - $("#userDT").removeClass("is-hidden-mobile"); - }else{ - $(this).html('مشاهده مشخصات'); - $("#userDT").addClass("is-hidden-mobile"); - } - - }); $(".closeSearch").click(function(){ $(".SearchBox").removeClass("is-active") }); diff --git a/admin_panel/templates/admin_panel/customerBaseNew.html b/admin_panel/templates/admin_panel/customerBaseNew.html index c01ceb4..888a8ab 100644 --- a/admin_panel/templates/admin_panel/customerBaseNew.html +++ b/admin_panel/templates/admin_panel/customerBaseNew.html @@ -18,76 +18,7 @@
-
- {% csrf_token %} -
-

- {{ customer.name_family }} -

- مشاهده مشخصات -
- -
-
- -
- -
-
- -
- -
- -
-
- - {% include "./_customer_phones.html" with phones_editable=1 %} - - -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- {% if perms.admin_panel.DelCustomer %} - حذف مشتری - {% endif %} - - ویرایش - - -
-
- -
+ {% include "./_customer_info.html" %}
@@ -102,6 +33,7 @@
  • ایجاد فاکتور
  • فاکتورها
  • بازاریابی
  • + {% if perms.admin_panel.ManageSMS %}
  • سوابق پیامک
  • {% endif %} {% endwith %} @@ -473,11 +405,6 @@ }); - $("#del_customer").click(function(){ - if(confirm("با حذف مشتری، تمام سوابق، فاکتورها و اطلاعات از سیستم حذف میشود، مطمئن هستید؟")){ - window.location.href = "/panel/customer/{{ customer.id }}/del"; - } - }); $(".closeNewIteam").click(function(){ $("#addNewIteam").removeClass("is-active"); @@ -603,50 +530,6 @@ $(document).ready(function() { var test = 0; - $("#enEdit").click(function(){ - $("#formcsEdit").find("input, textarea").prop('disabled', false); - // فعال کردن دکمه‌های کنترل تلفن - $("#formcsEdit").find("button.remove-phone-btn, button.add-phone-btn").prop('disabled', false); - - $("#enEdit").addClass("is-hidden"); - $("#editCs").removeClass("is-hidden"); - }); - - // توابع داینامیک فرم ویرایش - $(document).on('click', '.add-phone-btn', function() { - var phoneRow = ` -
    -
    - -
    -
    - -
    -
    - -
    -
    `; - $("#dynamic-phones-edit").append(phoneRow); - }); - - $(document).on('click', '.remove-phone-btn', function() { - if ($("#dynamic-phones-edit .has-addons").length > 1) { - $(this).closest('.field.has-addons').remove(); - } else { - alert("حداقل یک شماره تماس باید وجود داشته باشد."); - } - }); - - $("#showUserDT").click(function(){ - if( $(this).html()=='مشاهده مشخصات' ){ - $(this).html('مخفی کردن'); - $("#userDT").removeClass("is-hidden-mobile"); - }else{ - $(this).html('مشاهده مشخصات'); - $("#userDT").addClass("is-hidden-mobile"); - } - - }); $(".closeSearch").click(function(){ $(".SearchBox").removeClass("is-active") }); diff --git a/admin_panel/templates/admin_panel/customerSmsHistory.html b/admin_panel/templates/admin_panel/customerSmsHistory.html new file mode 100644 index 0000000..a7afa4e --- /dev/null +++ b/admin_panel/templates/admin_panel/customerSmsHistory.html @@ -0,0 +1,137 @@ +{% extends "./customerBaseNew.html" %} +{% load custom_tags %} +{% load static %} + +{% block BodyCustomer1 %} + + + +
    +

    سوابق پیامک مشتری

    +

    + همهٔ پیامک‌های ارسال‌شده برای شماره‌های این مشتری — جمعاً + {{ total_count }} پیامک. +

    + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + {% if status or delivery %} + + {% endif %} +
    + + {% if status or delivery %} +

    نتایج فیلترشده: {{ filtered_count }} مورد

    + {% endif %} + +
    + + + + + + + + + + + + + + + {% for item in items %} + + + + + + + + + + + {% empty %} + + {% endfor %} + +
    شناسهنوع پیامکشماره گیرندهمتن پیامکوضعیت ارسالوضعیت تحویلتاریخ ایجادتاریخ ارسال
    {{ item.id }}{{ item.get_sms_type_display|default:'—' }}{{ item.phone_number }}{{ item.message }} + + {{ item.get_status_display }} + + {% if item.last_error %} +
    + خطا: {{ item.last_error|truncatechars:80 }} +
    + {% endif %} +
    + + {{ item.get_delivery_status_display|default:'بدون پیگیری' }} + + {% if item.delivery_checked_at %} +
    استعلام: {{ item.delivery_checked_at|toJalaliDT }}
    + {% endif %} +
    {{ item.created_at|toJalaliDT }}{{ item.sent_at|toJalaliDT|default:'—' }}
    پیامکی برای این مشتری ثبت نشده است.
    +
    + + {% if items.has_other_pages %} + + {% endif %} +
    + +{% endblock %} diff --git a/admin_panel/templates/admin_panel/sms_queue.html b/admin_panel/templates/admin_panel/sms_queue.html index df66286..4358783 100644 --- a/admin_panel/templates/admin_panel/sms_queue.html +++ b/admin_panel/templates/admin_panel/sms_queue.html @@ -12,6 +12,9 @@ حذف یک مورد، فقط همان نوبت را کنسل می‌کند. یادآوری‌های دوره‌ای (بازدید و ثبت اطلاعات چک) در دورهٔ بعدی دوباره ساخته می‌شوند؛ پس با حذف یک مورد، پیامک برای آن مشتری برای همیشه قطع نمی‌شود. +
    + پیامک‌های ارسال‌شده از این صف بیرون می‌روند؛ فهرست و سابقهٔ + کامل‌شان در گزارش ارسال است.
    @@ -21,6 +24,13 @@

    {{ total_count }}

    + {% for code, label, count in status_stats %} diff --git a/admin_panel/templates/admin_panel/sms_report.html b/admin_panel/templates/admin_panel/sms_report.html new file mode 100644 index 0000000..75a1b83 --- /dev/null +++ b/admin_panel/templates/admin_panel/sms_report.html @@ -0,0 +1,189 @@ +{% extends "./base.html" %} +{% load custom_tags %} + +{% block bodyPage %} +
    +

    گزارش ارسال پیامک

    +

    + پیامک‌های تأییدشده و ارسال‌شده (موارد «در انتظار تأیید» و «لغو‌شده» + در این گزارش نمی‌آیند) به‌همراه «تعداد پیامکی که اپراتور محاسبه کرده» + (بر اساس کدگذاری GSM-7 برای انگلیسی/عدد و UCS-2 برای فارسی/یونیکد) — برای تحلیل هزینه. +

    + + {# --- کارت‌های خلاصه (روی کل نتیجهٔ فیلترشده) --- #} +
    +
    +
    +

    تعداد پیامک (رکورد)

    +

    {{ total_records }}

    +
    +
    +
    +
    +

    مجموع پیامک محاسبه‌شده

    +

    {{ total_parts }}

    +
    +
    +
    +
    +

    GSM-7 / UCS-2

    +

    {{ gsm7_parts }} / {{ ucs2_parts }}

    +
    +
    + {% for code, label, count in status_stats %} + + {% endfor %} +
    + + {# --- فرم فیلتر --- #} +
    +
    +
    + + +
    +
    + + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    +
    + + {% if has_filter %} + حذف فیلترها + {% endif %} +
    +
    + + {# --- جدول --- #} +
    + + + + + + + + + + + + + + + + + + {% for row in rows %} + + + + + + + + + + + + + + {% empty %} + + {% endfor %} + +
    شناسهمتن دقیق پیامکشماره گیرندهنوع پیامکمشتریسوابقتعداد پیامکوضعیت ارسالتحویلتاریخ ایجادتاریخ ارسال
    {{ row.item.id }}{{ row.item.message }}{{ row.item.phone_number }}{{ row.item.get_sms_type_display|default:'دستی/سفارشی' }} + {% if row.item.customer_id %} + {{ row.item.customer.name_family }} + {% else %}—{% endif %} + + {% if row.item.customer_id %} + سوابق پیامک + {% else %}—{% endif %} + + {{ row.parts }} + {{ row.item.get_status_display }}{{ row.item.get_delivery_status_display|default:'—' }}{{ row.item.created_at|toJalaliDT }}{{ row.item.sent_at|toJalaliDT|default:'—' }}
    پیامکی با این شرایط یافت نشد.
    +
    + + {% if page.has_other_pages %} + + {% endif %} +
    +{% endblock %} + +{% block cssEnd %} + +{% endblock %} + +{% block jsEnd %} + +{% endblock %} diff --git a/admin_panel/templates/admin_panel/sms_settings.html b/admin_panel/templates/admin_panel/sms_settings.html index 611b6d0..ea7eaa1 100644 --- a/admin_panel/templates/admin_panel/sms_settings.html +++ b/admin_panel/templates/admin_panel/sms_settings.html @@ -7,6 +7,13 @@
    {% csrf_token %} +

    اتصال به سامانه پیامک

    +

    + این بخش مشخص می‌کند پیامک‌ها از چه مسیری و با چه حساب کاربری به + اپراتور سپرده می‌شوند. تا وقتی «وضعیت ارسال» روشن نباشد، هیچ‌کدام از سرویس‌های + پس‌زمینه (worker) پیامکی نمی‌فرستند. +

    +
    @@ -19,6 +26,19 @@
    +

    + مسیر فنی ارسال را تعیین می‌کند و روی بقیهٔ فیلدها اثر دارد: +
    • 0098SMS (لینکی HTTP): ساده‌ترین حالت؛ درخواست به + آدرس api_url زده می‌شود. شناسهٔ پیامک برنمی‌گردد، + پس استعلام «تحویل به گوشی» ممکن نیست. +
    • 0098SMS (وب‌سرویس): ارسال SOAP به + webservice_url؛ شناسهٔ پیامک (smsid) برمی‌گردد و + می‌توان وضعیت واقعی تحویل را استعلام کرد. +
    • HTTP عمومی: برای سرویس‌های دلخواه؛ با + api_token احراز هویت می‌شود. +
    • Console: فقط برای تست/دیباگ؛ پیامک واقعی نمی‌رود و + متن در لاگ چاپ می‌شود. +

    @@ -29,6 +49,11 @@ فعال باشد
    +

    + کلید اصلی خاموش/روشن ارسال. اگر تیک نخورد، سرویس worker هیچ + پیامکی نمی‌فرستد (ردیف‌ها در صف می‌مانند). اسکنر و صف‌کردن مستقل از این است، + پس می‌توانید پیامک‌ها را از قبل در صف بچینید و بعد ارسال را روشن کنید. +

    @@ -36,6 +61,13 @@
    +

    + حداکثر زمان انتظار worker برای پاسخ سرویس‌دهنده در هر تلاش. + اگر پاسخ دیرتر بیاید، آن تلاش «ناموفق» ثبت می‌شود و بر اساس سیاست تلاش مجدد + دوباره در نوبت قرار می‌گیرد. مقدار کم (مثل ۵) ممکن است در سرویس کند، ارسال‌های + سالم را هم ناموفق کند؛ مقدار زیاد (مثل ۱۲۰) در قطعی سرویس، worker را بیکار + نگه می‌دارد. پیش‌فرض ۳۰ ثانیه متعادل است. +

    @@ -44,9 +76,14 @@

    - برای 0098SMS همین آدرس لینکی ارسال است + آدرس درخواست ارسال برای providerهای «لینکی HTTP» و «HTTP عمومی». +
    • برای 0098SMS لینکی همین آدرس است (http://www.0098sms.com/sendsmslink.aspx)؛ این سرویس - WSDL/SOAP ندارد و آدرس WSDL باعث خطای «Invalid XML content» می‌شود. + WSDL/SOAP ندارد و اگر آدرس WSDL بگذارید خطای «Invalid XML content» می‌گیرید. +
    • برای provider وب‌سرویس، این فیلد استفاده نمی‌شود (آن یکی + webservice_url را می‌خواند). +
    • اگر آدرس اشتباه یا در دسترس نباشد، پیامک‌ها «ناموفق» می‌شوند و در صف تلاش + مجدد می‌خورند.

    @@ -56,8 +93,9 @@

    - ارسال با شناسهٔ پیامک (smsid) و استعلام تحویل به گوشی - از همین آدرس انجام می‌شود. پیش‌فرض: + فقط وقتی provider = «وب‌سرویس» معنا دارد. هم ارسال با شناسهٔ + پیامک (smsid) و هم استعلام تحویل به گوشی از همین آدرس + انجام می‌شود. با providerهای دیگر این فیلد نادیده گرفته می‌شود. پیش‌فرض: https://webservice.0098sms.com/service.asmx

    @@ -67,7 +105,11 @@
    -

    اگر خالی بماند از «شماره خط فرستنده» استفاده می‌شود.

    +

    + شمارهٔ اختصاصی حساب شما در وب‌سرویس (فقط provider وب‌سرویس). + اگر خالی بماند، سیستم از «شماره خط فرستنده» استفاده می‌کند. مقدار اشتباه باعث + رد شدن ارسال با خطای احراز هویت می‌شود. +

    @@ -75,6 +117,10 @@
    +

    + نام کاربری حساب سامانهٔ پیامک (در همهٔ providerها به‌جز Console + لازم است). اگر اشتباه باشد، ارسال‌ها رد می‌شوند و در «خطا» صف ثبت می‌گردد. +

    @@ -82,6 +128,11 @@
    +

    + رمز حساب سامانهٔ پیامک. برای امنیت، مقدار ذخیره‌شده بازخوانی + نمی‌شود؛ این کادر را خالی بگذارید تا رمز قبلی بماند و فقط وقتی + رمز عوض شده، مقدار جدید را وارد کنید. +

    @@ -89,6 +140,12 @@
    +

    + شماره یا عنوان خطی که پیامک با آن به دست مشتری می‌رسد. باید + جزو خطوط مجاز حساب شما باشد؛ در غیر این صورت اپراتور ارسال را رد می‌کند. + همچنین در provider وب‌سرویس، اگر «شمارهٔ پنل» خالی باشد همین مقدار جای آن + استفاده می‌شود. +

    @@ -96,15 +153,23 @@
    +

    + کلید/توکن احراز هویت providerهای «HTTP عمومی». برای 0098SMS + لازم نیست. مثل رمز، خالی گذاشتن آن یعنی «بدون تغییر». +


    کنترل جریان ارسال

    - برای جلوگیری از سیل پیامک: تنها مواردی ارسال می‌شوند که پنجرهٔ یادآوری‌شان از - «خط شروع» به بعد باز شود. موارد قدیمی‌تر تنها با روشن‌کردن «اجازهٔ موارد قدیمی» - و در سقف تعیین‌شده ارسال می‌شوند. بین هر پیامک هم فاصلهٔ زمانی رعایت می‌شود. + این بخش تعیین می‌کند اسکنر دوره‌ای چه چیزی و چقدر در هر اجرا در صف + بگذارد. اسکنر هر چند دقیقه یک‌بار اجرا می‌شود و بر اساس «سررسیدها» تصمیم می‌گیرد + کدام مشتری باید یادآوری بگیرد. منطق بودجه به این شکل است: + سقف هر اجرا ← تعیین می‌کند کلاً چند مورد صف شود؛ + خط شروع ← موارد قدیمی‌تر از آن را کنار می‌گذارد؛ + اجازهٔ موارد قدیمی + سقف آن ← تصمیم می‌گیرد از آن موارد کنارگذاشته‌شده + چند تا (اگر لازم بود) ارسال شود.

    @@ -116,9 +181,15 @@ placeholder="مثلاً 1403/07/18 — خالی = همین حالا" dir="ltr">

    - فقط یادآوری‌هایی صف می‌شوند که «پنجرهٔ» آن‌ها از این تاریخ به بعد باز شده باشد. - مشتریان قدیمی‌تری که پنجره‌شان پیش از این تاریخ باز شده، وارد صف نمی‌شوند. - قالب: YYYY/MM/DD (شمسی) — خالی = همین حالا. + خط جداکنندهٔ «موارد جدید» از «موارد قدیمی». + هر مورد (چک، نسیه، بازدید) یک «لحظهٔ باز شدن پنجرهٔ یادآوری» دارد. اگر این + لحظه از این تاریخ به بعد باشد، مورد «جدید» است و آزادانه صف + می‌شود؛ اگر قبل از آن باشد، «قدیمی» (backlog) حساب می‌شود. +
    • هدف اصلی: در نخستین راه‌اندازی، هزاران مشتری قدیمیِ واجد شرط یک‌باره پیامک + نگیرند. +
    • قالب: YYYY/MM/DD شمسی — خالی = همین حالا. +
    • عوض‌کردن آن به گذشته، مشتریان قدیمی‌تر را «جدید» می‌کند و ممکن است سیل پیامک + بسازد؛ با احتیاط تغییر دهید.

    @@ -127,6 +198,19 @@
    +

    + سقف کل صف‌کردن در یک اجرا — به‌ازای هر اسکنر جداگانه. +
    • «هر اسکنر» یعنی این عدد برای هر نوع یادآوری مستقل اعمال می‌شود + (تأمین موجودی چک، سررسید نسیه، بازدید دورهای)؛ پس اگر ۱۰۰ باشد و هر سه اسکنر + واجد شرط باشند، در یک اجرا حداکثر ۱۰۰ + ۱۰۰ + ۱۰۰ = ۳۰۰ ردیف صف می‌شود. +
    • شمارش از مجموع «جدیدها + موارد قدیمی» است، نه فقط جدیدها. +
    • ۰ = بی‌نهایت (هیچ سقفی اعمال نمی‌شود). +
    • چون اسکنر دوره‌ای اجرا می‌شود، سقف باعث «دسته‌دسته» شدن ارسال‌های انبوه + می‌شود (بقیه در اجراهای بعدی می‌آیند) و از ریختن یک‌بارهٔ صدها پیامک روی ارسال + جلوگیری می‌کند. +
    • نکته: این سقف‌ها روی «صف‌کردن» اثر دارند، نه سرعت ارسال واقعی؛ سرعت ارسال + به «فاصلهٔ بین هر پیامک» بستگی دارد. +

    @@ -134,6 +218,11 @@
    +

    + مکث سرویس worker پس از هر ارسال تا اپراتور زیر بار زیاد نرود. + مثال: با مقدار ۳، ارسال ۱۰۰ پیامک حدود ۵ دقیقه طول می‌کشد. مقدار ۰ یعنی بدون + مکث (سریع ولی پرخطر — ممکن است سرویس‌دهنده محدودتان کند). +

    @@ -144,6 +233,12 @@ فعال باشد
    +

    + دربِ موارد «قدیمی‌تر از تاریخ شروع دوره». به‌صورت پیش‌فرض بسته + است. تا وقتی بسته باشد، موارد قدیمی هرگز صف نمی‌شوند (حتی اگر + سقف باقی باشد). اگر لازم بود این موارد هم پردازش شوند، آن را روشن کنید و مقدار + «حداکثر موارد قدیمی در هر اجرا» را تعیین کنید. +

    @@ -151,7 +246,15 @@
    -

    صفر = هیچ مورد قدیمی ارسال نمی‌شود.

    +

    + سقف ارسال موارد قدیمی در هر اجرا، به‌ازای هر اسکنر. +
    • فقط وقتی «اجازهٔ ارسال موارد قدیمی» روشن باشد اثر دارد. +
    • صفر = هیچ مورد قدیمی ارسال نمی‌شود (حتی با روشن‌بودن کلید). +
    • این سقف در کنار «حداکثر پیامک هر اسکنر» اعمال می‌شود؛ پس + ممکن است ابتدا سقف کل (max_per_run) پر شود. +
    • برای پاک‌کردن تدریجی انبار قدیمی، عدد کوچکی مثل ۵۰ بگذارید تا در هر اجرا + دسته‌ای ارسال شود. +

    diff --git a/admin_panel/urls.py b/admin_panel/urls.py index e56b5aa..9a971e4 100644 --- a/admin_panel/urls.py +++ b/admin_panel/urls.py @@ -81,6 +81,7 @@ urlpatterns = [ path('pardakhtNesiye/', views.pardakhtNesiye, name='pardakhtNesiye'), # === مدیریت پیامک === + path('customer//sms/', views.customer_sms_history, name='customerSmsHistory'), path('sms/settings', views.sms_settings, name='sms_settings'), path('sms/templates', views.sms_templates, name='sms_templates'), path('sms/template//edit', views.sms_template_edit, name='sms_template_edit'), @@ -88,6 +89,7 @@ urlpatterns = [ path('sms/customer//primary', views.sms_primary_toggle, name='sms_primary_toggle'), path('sms/customer//send', views.sms_send_custom, name='sms_send_custom'), path('sms/queue', views.sms_queue, name='sms_queue'), + path('sms/report', views.sms_report, name='sms_report'), path('sms/queue/bulk', views.sms_queue_bulk, name='sms_queue_bulk'), path('sms/queue/empty', views.sms_queue_empty, name='sms_queue_empty'), path('sms/queue//approve', views.sms_queue_approve, name='sms_queue_approve'), diff --git a/admin_panel/views/actions.py b/admin_panel/views/actions.py index 072abc5..9a74408 100644 --- a/admin_panel/views/actions.py +++ b/admin_panel/views/actions.py @@ -7,6 +7,7 @@ from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required from ..models import Action, Customer +from ..sms.triggers import notify_visit_scheduled @login_required(login_url='/panel/p_login/') @@ -91,6 +92,10 @@ def customerNewAction(request ,c_id): customer_obj.last_bazdid_dt = newAction.finishDateTime customer_obj.save() + # پیامک «بازدید شما برای تاریخ … ثبت شد» فقط برای اقدام از نوع بازدید. + if newAction.actionType == 'bazdid': + notify_visit_scheduled(customer_obj, newAction, created_by=request.user) + return redirect('/panel/customer/'+str(c_id)+'/action') diff --git a/admin_panel/views/customers.py b/admin_panel/views/customers.py index dedcbca..6b1fd80 100644 --- a/admin_panel/views/customers.py +++ b/admin_panel/views/customers.py @@ -11,11 +11,10 @@ from django.contrib.auth.decorators import login_required, permission_required from persiantools.jdatetime import JalaliDate from ..models import (Customer, PhoneNumber, ProductCategory, Product, Factor, - FactorIteam, Article, Sarresid, Action, CacheTable, SMSType) -from ..sms.enqueue import enqueue_sms -from ..sms.phones import (customer_phone_entries, normalize_primary_values, + FactorIteam, Article, Sarresid, Action, CacheTable) +from ..sms.phones import (normalize_primary_values, save_primary_normalized) -from ..sms.recipients import resolve_recipients +from ..sms.triggers import notify_payment_thanks, notify_welcome from ..sms.rules import customer_toggle_map from ._helpers import logger, _parse_jalali_date, _parse_phone_rows @@ -122,15 +121,10 @@ def customerNew2(request ,c_id): #return JsonResponse(all_prd,safe = False) dateNow = datetime.datetime.now().date sms_toggles = customer_toggle_map(customer) - # همهٔ شماره‌ها از هر منبعی که ذخیره شده‌اند (PhoneNumber و phone1/2/3 قدیمی). - sms_phone_entries = customer_phone_entries(customer) - sms_effective = {n for n in resolve_recipients(customer)} data = { 'customer': customer, 'categorys': categorys, 'dateNow': dateNow, 'factorha': factorha, 'mandehesabCs': mandehesabCs, - 'sms_toggles': sms_toggles, 'sms_phone_entries': sms_phone_entries, - 'sms_effective': sms_effective, - 'sms_recipients': sorted(sms_effective), + 'sms_toggles': sms_toggles, } return render(request, 'admin_panel/customerNew2.html', data) @@ -271,12 +265,7 @@ def customersAdd(request): ) # پیامک خوش‌آمدگویی (در صورت فعال بودن قالب و تنظیم مشتری) - enqueue_sms( - customer=newCs, sms_type=SMSType.WELCOME, - context={'name': newCs.name_family}, - dedup_key=f'welcome:{newCs.id}', - created_by=request.user, - ) + notify_welcome(newCs, created_by=request.user) return redirect('/panel/customers/') @@ -494,4 +483,9 @@ def customerHesabdariAdd(request ,c_id): newArticl.user = user_op newArticl.nahvePardakht = nahvePardakhat newArticl.save() + # سند بستانکار (credit>0 و debit=0) یعنی مشتری پول پرداخت کرده ⇒ پیامک تشکر. + if type == 'credit': + notify_payment_thanks(newArticl.customer, newArticl.credit, + article_id=newArticl.id, method=nahvePardakhat, + created_by=user_op) return redirect('/panel/customer/'+str(c_id)+'/hesabdari') diff --git a/admin_panel/views/factors.py b/admin_panel/views/factors.py index 65793dd..ca51ad6 100644 --- a/admin_panel/views/factors.py +++ b/admin_panel/views/factors.py @@ -12,6 +12,7 @@ from persiantools import digits from ..models import (Customer, StoreRoom, Factor, FactorIteam, Product, ProductInStore, Article, Sarresid) +from ..sms.triggers import notify_factor_survey, notify_payment_thanks from ._helpers import logger, _safe_int, _parse_jalali_date, _parse_timestamp, InventoryException @@ -196,6 +197,8 @@ def newFactor2(request, c_id): if not str(item.get('garanty_Esh', '')).strip() ) + new_factor = None + payment_articles = [] try: with transaction.atomic(): @@ -231,7 +234,7 @@ def newFactor2(request, c_id): if key in payment_map: nahve_pardakht = payment_map[key] - Article.objects.create( + payment_articles.append((Article.objects.create( debit=0, credit=amount, customer=customer_obj, @@ -240,7 +243,7 @@ def newFactor2(request, c_id): nahvePardakht=nahve_pardakht, payStatus="true", user=user_op - ) + ), nahve_pardakht)) elif key in ['l_nesiye', 'l_checkPrice']: is_nesiye = (key == 'l_nesiye') @@ -359,6 +362,16 @@ def newFactor2(request, c_id): logger.error(f"Error creating factor for customer {c_id}: {str(e)}", exc_info=True) return HttpResponse("خطایی غیرمنتظره در سرور رخ داد. اطلاعات ذخیره نشد.", status=500) + # تریگرهای پیامک پس از تأیید تراکنش (خارج از atomic تا در صورت rollback ساخته نشوند). + if new_factor is not None: + # نظرسنجی بی‌واسطه پس از ثبت فاکتور. + notify_factor_survey(customer_obj, new_factor, created_by=user_op) + # تشکر برای هر پرداخت نقدی/کارتیِ ثبت‌شده روی همین فاکتور (تخفیف مستثناست). + for art, method in payment_articles: + notify_payment_thanks(customer_obj, art.credit, + article_id=art.id, method=method, + created_by=user_op) + return redirect(f'/panel/customer/{c_id}/factors/') diff --git a/admin_panel/views/finance.py b/admin_panel/views/finance.py index fef48ad..a19dc71 100644 --- a/admin_panel/views/finance.py +++ b/admin_panel/views/finance.py @@ -16,6 +16,7 @@ from persiantools.jdatetime import JalaliDate from ..models import (Article, Sarresid, Customer, Factor, ProductInStore, StoreRoom, Action) +from ..sms.triggers import notify_payment_thanks from ._helpers import logger, _safe_int @@ -713,6 +714,11 @@ def passCheck(request, articl_id): sarresidINC.desc = desc sarresidINC.save() + # سند وصول (credit>0 و debit=0) یعنی مشتری پول داده ⇒ پیامک تشکر از پرداخت. + notify_payment_thanks(articleHesabDari2.customer, articleHesabDari2.credit, + article_id=articleHesabDari2.id, + method=articleHesabDari2.nahvePardakht, created_by=user_op) + return HttpResponse("ok") #return redirect(request.META.get('HTTP_REFERER')) @@ -916,6 +922,11 @@ def pardakhtNesiye(request, cs_id): articleHesabDariObj.user = user_op articleHesabDariObj.save() + # پرداخت نسیه (بستانکار) ⇒ پیامک تشکر از پرداخت (تخفیف مستثناست). + notify_payment_thanks(customerObj, articleHesabDariObj.credit, + article_id=articleHesabDariObj.id, + method=nahvePardakht, created_by=user_op) + return redirect(request.META.get('HTTP_REFERER')) except: diff --git a/admin_panel/views/sms.py b/admin_panel/views/sms.py index 7047293..e58a192 100644 --- a/admin_panel/views/sms.py +++ b/admin_panel/views/sms.py @@ -1,11 +1,11 @@ """ویوهای مدیریت پیامک در پنل (نیازمند پرمیشن ``admin_panel.ManageSMS``).""" import re -from datetime import datetime, time +from datetime import datetime, time, timedelta from urllib.parse import urlencode from django.contrib.auth.decorators import login_required, permission_required from django.core.paginator import Paginator -from django.db.models import Count +from django.db.models import Count, Q from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils import timezone @@ -13,12 +13,34 @@ from django.utils import timezone from ..models import (Customer, SMSDedup, SMSProviderConfig, SMSQueue, SMSTemplate, SMSType) from ..sms.enqueue import enqueue_sms +from ..sms.parts import ENCODING_LABELS, describe, parts_count from ..sms.phones import (customer_phone_entries, save_primary_normalized, stored_primary_normalized) from ..sms.render import format_jalali, render_template from ..sms.timing import apply_timing, current_timing_value, timing_for _manage_required = permission_required("admin_panel.ManageSMS", raise_exception=True) +# صفحات «تنظیمات سامانه» و «قالب‌ها» یک سطح دسترسی جداگانه دارند تا مدیر بتواند +# فقط به کاربران موردنظرش اجازهٔ دیدن/ویرایش بدهد (نه هر کاربر دارای ManageSMS). +_settings_required = permission_required( + "admin_panel.ManageSMSSettings", raise_exception=True, +) + +# گزارش هزینه فقط روی پیامک‌هایی است که از مرحلهٔ «تأیید مدیر» گذشته و واقعاً به +# سامانه/اپراتور سپرده شده‌اند. موارد «در انتظار تأیید» (هنوز ارسال نشده) و +# «لغو‌شده» (هرگز ارسال نمی‌شود) نباید در تحلیل هزینه بیایند. +SENT_STATUSES = [ + SMSQueue.Status.APPROVED, + SMSQueue.Status.PROCESSING, + SMSQueue.Status.SENT, + SMSQueue.Status.FAILED, +] + +# وضعیت‌های «پایانی» که دیگر کاری روی آن‌ها لازم نیست. صفحهٔ «صف ارسال» فقط مواردِ +# نیازمند اقدام را نشان می‌دهد؛ پیامکِ ارسال‌شده از صف بیرون می‌رود و سابقه‌اش در +# صفحهٔ «گزارش ارسال» (/panel/sms/report) دیده می‌شود. این کار جلوی حذفِ ناخواستهٔ +# تاریخچه از روی صف را می‌گیرد و صف را به فهرستِ کاریِ تمیز تبدیل می‌کند. +DONE_STATUSES = [SMSQueue.Status.SENT] def _notice(url, code, **extra): @@ -47,14 +69,13 @@ TEMPLATE_VARIABLES = [ # فقط برای نمایش: آیکن و توضیح کوتاه «چه زمانی ارسال می‌شود» هر نوع (لایهٔ UI، نه دامنه). SMS_TYPE_UI = { - SMSType.WELCOME: {"icon": "👋", "when": "بیدرنگ پس از ثبت مشتری"}, - SMSType.CHEQUE_DUE: {"icon": "🧾", "when": "پیش از سررسید چک"}, + SMSType.WELCOME: {"icon": "👋", "when": "بی‌درنگ پس از ثبت مشتری"}, + SMSType.SURVEY: {"icon": "⭐", "when": "بی‌درنگ پس از ثبت فاکتور"}, + SMSType.CHEQUE_REGISTRATION: {"icon": "🧾", "when": "چند روز پس از سررسید چک"}, + SMSType.VISIT: {"icon": "🔧", "when": "پس از گذشت دورهٔ بازدید دستگاه"}, + SMSType.VISIT_CREATED: {"icon": "📅", "when": "پس از ثبت اقدام بازدید"}, + SMSType.PAYMENT_THANKS: {"icon": "🙏", "when": "پس از ثبت پرداخت"}, SMSType.NESIYE_DUE: {"icon": "💳", "when": "پیش از سررسید نسیه"}, - SMSType.CHEQUE_REGISTRATION: {"icon": "⏳", "when": "تا ثبت‌شدن اطلاعات چک"}, - SMSType.DEBT_DUE: {"icon": "💰", "when": "روز سررسید وصول مطالبات"}, - SMSType.SURVEY: {"icon": "⭐", "when": "پس از صدور فاکتور"}, - SMSType.VISIT: {"icon": "🔧", "when": "پس از گذشت دورهٔ بازدید"}, - SMSType.CUSTOM: {"icon": "✍️", "when": "ارسال دستی از صفحهٔ مشتری"}, } # نمونهٔ داده برای «پیش‌نمایش زندهٔ» متن در پنل (هیچ پیامکی با این مقادیر ارسال نمی‌شود). @@ -70,7 +91,7 @@ SAMPLE_CONTEXT = { @login_required(login_url='/panel/p_login/') -@_manage_required +@_settings_required def sms_settings(request): config = SMSProviderConfig.load() if request.method == "POST": @@ -82,7 +103,7 @@ def sms_settings(request): config.test_message = test_text config.save(update_fields=["test_message"]) enqueue_sms( - customer=None, sms_type=SMSType.CUSTOM, + customer=None, sms_type="", message=test_text or config.test_message, recipients=resolve_recipients_test(phone), created_by=request.user, approved=True, # ارسال دستیِ مدیر؛ نیاز به بازبینی صف ندارد. @@ -192,7 +213,7 @@ def _sample_context(): @login_required(login_url='/panel/p_login/') -@_manage_required +@_settings_required def sms_templates(request): raw_sample, text_sample = _sample_context() rows = [] @@ -236,7 +257,7 @@ def _templates_redirect(anchor="", notice="", **extra): @login_required(login_url='/panel/p_login/') -@_manage_required +@_settings_required def sms_template_edit(request, t_id): """ویرایش یک قالب. فیلد زمانی بر اساس نوع پیامک انتخاب و اعتبارسنجی می‌شود. @@ -321,22 +342,17 @@ def sms_primary_toggle(request, c_id): def sms_send_custom(request, c_id): """ارسال پیامک با متن سفارشی برای همین مشتری. - اگر کادر متن خالی بماند، متن قالب «پیام سفارشی» استفاده می‌شود. متغیرهایی مثل + متن باید توسط مدیر نوشته شود (قالب پیش‌فرضی وجود ندارد)؛ متغیرهایی مثل ``{name}`` در متن واردشده هم رندر می‌شوند. """ customer = get_object_or_404(Customer, id=c_id) if request.method == "POST": text = (request.POST.get("text", "") or "").strip() - if not text: - template = SMSTemplate.objects.filter( - sms_type=SMSType.CUSTOM, is_enabled=True, - ).first() - text = (template.text if template else "").strip() rendered = render_template(text, {"name": customer.name_family}).strip() if not rendered: return _notice(_customer_url(c_id), "custom_empty") created = enqueue_sms( - customer=customer, sms_type=SMSType.CUSTOM, message=rendered, + customer=customer, sms_type="", message=rendered, created_by=request.user, approved=True, # متن را خودِ مدیر نوشته و ارسال را خواسته است. ) @@ -348,9 +364,19 @@ def sms_send_custom(request, c_id): @login_required(login_url='/panel/p_login/') @_manage_required def sms_queue(request): + """صف ارسال: فقط مواردی که هنوز «کارِ باقی‌مانده» دارند. + + پیامک‌های ارسال‌شده (``sent``) از این فهرست بیرون می‌مانند چون کارشان تمام است و + سابقه‌شان در «گزارش ارسال» دیده می‌شود؛ ماندن‌شان در صف فقط باعث می‌شد دکمهٔ + «حذف» روی تاریخچه ظاهر شود. بقیهٔ وضعیت‌ها (در انتظار تأیید، تأییدشده، در حال + پردازش، ناموفق) در صف می‌مانند چون هنوز نیاز به اقدام دارند. + """ status = request.GET.get("status", "") delivery = request.GET.get("delivery", "") - qs = SMSQueue.objects.all().select_related("customer").order_by("-created_at") + qs = ( + SMSQueue.objects.exclude(status__in=DONE_STATUSES) + .select_related("customer").order_by("-created_at") + ) if status: qs = qs.filter(status=status) if delivery: @@ -364,24 +390,177 @@ def sms_queue(request): counts = dict( SMSQueue.objects.order_by().values_list("status").annotate(n=Count("id")) ) + # «کل صف» = موارد باقی‌مانده (بدون ارسال‌شده‌ها). + queue_total = sum(n for code, n in counts.items() if code not in DONE_STATUSES) data = { "queue": page, "status": status, "delivery": delivery, - "status_choices": SMSQueue.Status.choices, + # گزینه‌های فیلتر وضعیت: فقط وضعیت‌های درون صف (ارسال‌شده حذف می‌شود). + "status_choices": [ + (code, label) for code, label in SMSQueue.Status.choices + if code not in DONE_STATUSES + ], "delivery_choices": SMSQueue.DeliveryStatus.choices, "pending_count": counts.get(SMSQueue.Status.PENDING, 0), - "total_count": sum(counts.values()), + "total_count": queue_total, + # تعداد ارسال‌شده‌ها جداگانه نمایش داده می‌شود (برای رفتن به گزارش). + "sent_count": counts.get(SMSQueue.Status.SENT, 0), "filtered_count": paginator.count, - # (کد وضعیت، برچسب، تعداد) — به ترتیب تعریف Status. + # (کد وضعیت، برچسب، تعداد) — فقط وضعیت‌های درون صف. "status_stats": [ (code, label, counts.get(code, 0)) for code, label in SMSQueue.Status.choices + if code not in DONE_STATUSES ], } return render(request, "admin_panel/sms_queue.html", data) +@login_required(login_url='/panel/p_login/') +@_manage_required +def customer_sms_history(request, c_id): + """سوابق پیامک‌های یک مشتری روی صفحهٔ خود مشتری. + + همهٔ پیامک‌هایی که برای شماره‌های این مشتری در صف قرار گرفته/ارسال شده‌اند + (هر وضعیتی) با متن دقیق، تاریخ شمسی و وضعیت تحویل (دلیوری) نمایش داده می‌شوند و + فهرست صفحه‌بندی می‌شود. با ``?status=`` و ``?delivery=`` می‌توان فیلتر کرد. + """ + customer = get_object_or_404(Customer, id=c_id) + status = request.GET.get("status", "") + delivery = request.GET.get("delivery", "") + qs = ( + SMSQueue.objects.filter(customer=customer).order_by("-created_at") + ) + if status: + qs = qs.filter(status=status) + if delivery: + qs = qs.filter(delivery_status=delivery) + paginator = Paginator(qs, 20) + page = paginator.get_page(request.GET.get("page")) + data = { + "customer": customer, + "items": page, + "status": status, + "delivery": delivery, + "status_choices": SMSQueue.Status.choices, + "delivery_choices": SMSQueue.DeliveryStatus.choices, + "total_count": SMSQueue.objects.filter(customer=customer).count(), + "filtered_count": paginator.count, + } + return render(request, "admin_panel/customerSmsHistory.html", data) + + +@login_required(login_url='/panel/p_login/') +@_manage_required +def sms_report(request): + """گزارش پیامک‌های ارسال‌شده برای تحلیل هزینه. + + امکان فیلتر بر اساس **بازهٔ تاریخ شمسی** (از/تا)، وضعیت ارسال، وضعیت تحویل و + نوع پیامک، به‌همراه جستجوی متنی روی متن پیامک/شماره/نام مشتری. + + برای هر ردیف، «تعداد پیامکی که اپراتور محاسبه کرده» (parts) بر اساس کدگذاری + واقعی متن (GSM-7 برای انگلیسی/عدد، UCS-2 برای فارسی/یونیکد) محاسبه و نمایش + داده می‌شود؛ جمع کل بخش‌ها هم برای برآورد هزینه گزارش می‌شود. + """ + date_from_raw = (request.GET.get("date_from") or "").strip() + date_to_raw = (request.GET.get("date_to") or "").strip() + status = request.GET.get("status", "") + delivery = request.GET.get("delivery", "") + sms_type = request.GET.get("sms_type", "") + query = (request.GET.get("q") or "").strip() + + qs = ( + SMSQueue.objects.select_related("customer") + .filter(status__in=SENT_STATUSES) + .order_by("-created_at") + ) + + # بازهٔ تاریخ شمسی روی «زمان ایجاد» (ثبت در سیستم) اعمال می‌شود. بازهٔ «تا» تا + # پایان همان روز در نظر گرفته می‌شود تا پیامک‌های همان روز هم بیایند. + parsed_from = parse_jalali_date(date_from_raw) if date_from_raw else None + parsed_to = parse_jalali_date(date_to_raw) if date_to_raw else None + if parsed_from is not None: + qs = qs.filter(created_at__gte=parsed_from) + if parsed_to is not None: + qs = qs.filter(created_at__lt=parsed_to + timedelta(days=1)) + + if status: + qs = qs.filter(status=status) + if delivery: + qs = qs.filter(delivery_status=delivery) + if sms_type: + qs = qs.filter(sms_type=sms_type) + if query: + qs = qs.filter( + Q(message__icontains=query) + | Q(phone_number__icontains=query) + | Q(customer__name_family__icontains=query) + ) + + # --- آمار کلی روی کل نتیجهٔ فیلترشده (نه فقط صفحهٔ جاری) --- + status_counts = dict( + qs.order_by().values_list("status").annotate(n=Count("id")) + ) + total_records = sum(status_counts.values()) + # جمع بخش‌ها (parts) باید در پایتون حساب شود چون به کدگذاری هر متن بستگی دارد. + total_parts = 0 + gsm7_parts = ucs2_parts = 0 + for message in qs.values_list("message", flat=True).iterator(chunk_size=500): + info = describe(message) + total_parts += info["parts"] + if info["encoding"] == "gsm7": + gsm7_parts += info["parts"] + else: + ucs2_parts += info["parts"] + + paginator = Paginator(qs, 50) + page = paginator.get_page(request.GET.get("page")) + + # برای هر ردیف صفحه، محاسبهٔ کدگذاری/طول/بخش (نمایشی). + rows = [] + for item in page: + info = describe(item.message) + rows.append({"item": item, "parts": info["parts"], + "encoding": info["encoding_label"], "length": info["length"]}) + + # برای لینک صفحه‌بندی که همهٔ فیلترها را حفظ کند (بدون خودِ page). + params = request.GET.copy() + params.pop("page", None) + base_query = params.urlencode() + + data = { + "rows": rows, + "page": page, + "status": status, + "delivery": delivery, + "sms_type": sms_type, + "query": query, + "date_from": date_from_raw, + "date_to": date_to_raw, + "status_choices": [ + (code, label) for code, label in SMSQueue.Status.choices + if code in SENT_STATUSES + ], + "delivery_choices": SMSQueue.DeliveryStatus.choices, + "type_choices": SMSType.choices, + "total_records": total_records, + "total_parts": total_parts, + "gsm7_parts": gsm7_parts, + "ucs2_parts": ucs2_parts, + "encoding_labels": ENCODING_LABELS, + "status_stats": [ + (code, label, status_counts.get(code, 0)) + for code, label in SMSQueue.Status.choices + if code in SENT_STATUSES + ], + "filtered": paginator.count, + "base_query": base_query, + "has_filter": any([date_from_raw, date_to_raw, status, delivery, sms_type, query]), + } + return render(request, "admin_panel/sms_report.html", data) + + def _approve(queryset): """پیامک‌های «در انتظار تأیید» را تأیید و آمادهٔ ارسال می‌کند.""" return queryset.filter(status=SMSQueue.Status.PENDING).update( @@ -403,9 +582,15 @@ def sms_queue_approve(request, q_id): @login_required(login_url='/panel/p_login/') @_manage_required def sms_queue_delete(request, q_id): - """حذف یک پیامک از صف (مثلاً پس از بازبینی و رد آن).""" + """حذف یک پیامک از صف (مثلاً پس از بازبینی و رد آن). + + پیامکِ ارسال‌شده حذف نمی‌شود؛ سابقهٔ آن در «گزارش ارسال» می‌ماند و حذفش یعنی + پاک‌کردنِ تاریخچه. برای چنین موردی فقط پیام هشدار برمی‌گردد. + """ item = get_object_or_404(SMSQueue, id=q_id) if request.method == "POST": + if item.status in DONE_STATUSES: + return _notice(reverse("admin_panel:sms_queue"), "delete_done_blocked") item.delete() return _notice(reverse("admin_panel:sms_queue"), "deleted") return redirect("admin_panel:sms_queue") @@ -427,6 +612,8 @@ def sms_queue_bulk(request): count = _approve(selected) code, extra = "bulk_approved", {"count": count} elif action == "delete_selected": + # تاریخچهٔ ارسال‌شده‌ها از طریق صف حذف نمی‌شود. + selected = selected.exclude(status__in=DONE_STATUSES) count = selected.count() selected.delete() code, extra = "bulk_deleted", {"count": count} @@ -439,14 +626,14 @@ def sms_queue_bulk(request): @login_required(login_url='/panel/p_login/') @_manage_required def sms_queue_empty(request): - """خالی کردن کل صف — هر وضعیتی، بدون بازگشت. + """خالی کردن صف — فقط موارد باقی‌مانده (بدون بازگشت). - پیش از حذف، کلید یکتاسازیِ هر رکورد در دفتر ``SMSDedup`` تضمین می‌شود تا اسکنر - در اجرای بعدی همان پیامک‌ها را از نو نسازد. رکوردهایی که کلید ندارند (مثلاً - پیامک دستی) کلید مشتق از خودشان را می‌گیرند. + پیامک‌های ارسال‌شده جزو صف نیستند و حذف نمی‌شوند؛ تاریخچه‌شان در «گزارش ارسال» + لازم است. پیش از حذف، کلید یکتاسازیِ هر رکورد در دفتر ``SMSDedup`` تضمین می‌شود + تا اسکنر در اجرای بعدی همان پیامک‌ها را از نو نسازد. """ if request.method == "POST": - rows = SMSQueue.objects.all() + rows = SMSQueue.objects.exclude(status__in=DONE_STATUSES) keys = [k for k in rows.values_list("dedup_key", flat=True) if k] SMSDedup.objects.bulk_create( [SMSDedup(key=k) for k in keys], ignore_conflicts=True, diff --git a/graphify-out/.graphify_analysis.json b/graphify-out/.graphify_analysis.json index efc78d5..0ea7db7 100644 --- a/graphify-out/.graphify_analysis.json +++ b/graphify-out/.graphify_analysis.json @@ -1,146 +1,137 @@ { "communities": { "0": [ - "abstractbaseuser", - "admin_panel_models", - "admin_panel_models_articletype", - "admin_panel_models_cachetable", - "admin_panel_models_cachetable_str", - "admin_panel_models_phonenumber", - "admin_panel_models_phonenumber_str", - "admin_panel_models_product", - "admin_panel_models_product_meta", - "admin_panel_models_product_str", - "admin_panel_models_productstockhistory", - "admin_panel_models_productstockhistory_meta", - "admin_panel_models_productstockhistory_str", - "admin_panel_models_rationale_97", - "admin_panel_models_storeroomowner", - "admin_panel_models_storetransferrequest", - "admin_panel_models_storetransferrequest_clean", - "admin_panel_models_storetransferrequest_meta", - "admin_panel_models_storetransferrequest_status", - "admin_panel_models_storetransferrequest_str", - "admin_panel_models_userprofile", - "admin_panel_models_userprofile_str", - "admin_panel_models_visitqueue", - "admin_panel_services", - "admin_panel_services_approve_transfer_request", - "admin_panel_services_cancel_transfer_request", - "admin_panel_services_check_source_store_permission", - "admin_panel_services_check_target_store_permission", - "admin_panel_services_create_transfer_request", - "admin_panel_services_get_product_current_stock", - "admin_panel_services_rationale_160", - "admin_panel_services_rationale_187", - "admin_panel_services_rationale_20", - "admin_panel_services_rationale_30", - "admin_panel_services_rationale_40", - "admin_panel_services_rationale_64", - "admin_panel_services_rationale_99", - "admin_panel_services_reject_transfer_request", - "admin_panel_views_process_transfer_action", - "admin_panel_views_rationale_1320", - "atomic", - "django_contrib_auth", - "django_contrib_auth_models", - "django_core_exceptions", - "django_db_models", - "django_dispatch", - "django_utils_translation", - "typing" + "admin_panel_sms_phones_rationale_98", + "admin_panel_sms_phones_stored_primary_normalized", + "admin_panel_sms_render", + "admin_panel_sms_render_format_amount", + "admin_panel_sms_render_format_jalali", + "admin_panel_sms_render_rationale_1", + "admin_panel_sms_render_rationale_20", + "admin_panel_sms_render_rationale_6", + "admin_panel_sms_render_render_template", + "admin_panel_sms_render_safedict", + "admin_panel_sms_render_safedict_missing", + "admin_panel_sms_timing", + "admin_panel_sms_timing_current_timing_value", + "admin_panel_sms_timing_rationale_1", + "admin_panel_sms_timing_rationale_100", + "admin_panel_sms_timing_rationale_75", + "admin_panel_sms_timing_timing_for", + "admin_panel_views_sms", + "admin_panel_views_sms_approve", + "admin_panel_views_sms_customer_url", + "admin_panel_views_sms_notice", + "admin_panel_views_sms_parse_jalali_date", + "admin_panel_views_sms_parse_scan_since", + "admin_panel_views_sms_py_login_required", + "admin_panel_views_sms_rationale_1", + "admin_panel_views_sms_rationale_146", + "admin_panel_views_sms_rationale_165", + "admin_panel_views_sms_rationale_184", + "admin_panel_views_sms_rationale_228", + "admin_panel_views_sms_rationale_241", + "admin_panel_views_sms_rationale_25", + "admin_panel_views_sms_rationale_294", + "admin_panel_views_sms_rationale_322", + "admin_panel_views_sms_rationale_386", + "admin_panel_views_sms_rationale_395", + "admin_panel_views_sms_rationale_406", + "admin_panel_views_sms_rationale_417", + "admin_panel_views_sms_rationale_442", + "admin_panel_views_sms_resolve_recipients_test", + "admin_panel_views_sms_sample_context", + "admin_panel_views_sms_sms_customer_toggle", + "admin_panel_views_sms_sms_primary_toggle", + "admin_panel_views_sms_sms_queue", + "admin_panel_views_sms_sms_queue_approve", + "admin_panel_views_sms_sms_queue_bulk", + "admin_panel_views_sms_sms_queue_delete", + "admin_panel_views_sms_sms_queue_empty", + "admin_panel_views_sms_sms_queue_retry", + "admin_panel_views_sms_sms_send_custom", + "admin_panel_views_sms_sms_settings", + "admin_panel_views_sms_sms_template_edit", + "admin_panel_views_sms_sms_templates", + "admin_panel_views_sms_templates_redirect", + "dict", + "manage_required", + "tests_test_sms_rationale_907", + "tests_test_sms_timingstests", + "tests_test_sms_timingstests_test_apply_timing_pins_only_the_relevant_field", + "tests_test_sms_timingstests_test_edit_view_rejects_non_numeric_timing", + "tests_test_sms_timingstests_test_edit_view_saves_the_relevant_timing_field", + "tests_test_sms_timingstests_test_every_sms_type_has_timing_metadata", + "tests_test_sms_timingstests_test_types_without_timing_have_no_fields", + "tests_test_sms_timingstests_test_visit_default_is_nine_months" ], "1": [ - "admin_panel_context_processor", - "admin_panel_context_processor_dashboxdata", - "admin_panel_management_commands_send_daily_report", - "admin_panel_management_commands_send_daily_report_command", - "admin_panel_management_commands_send_daily_report_command_handle", - "admin_panel_management_commands_send_daily_report_py_basecommand", - "admin_panel_models_article", - "admin_panel_models_article_meta", - "admin_panel_models_customer", - "admin_panel_models_customer_delete", - "admin_panel_models_customer_meta", - "admin_panel_models_customer_str", - "admin_panel_models_factor", - "admin_panel_models_factor_delete", - "admin_panel_models_factor_meta", - "admin_panel_models_factoriteam", - "admin_panel_models_sarresid", - "admin_panel_templatetags_custom_tags", - "admin_panel_templatetags_custom_tags_has_group", - "admin_panel_templatetags_custom_tags_tojalali", - "admin_panel_templatetags_remove_q_p", - "admin_panel_templatetags_remove_q_p_rationale_7", - "admin_panel_templatetags_remove_q_p_remove_query_param", - "admin_panel_views_customerhesabdariadd", - "admin_panel_views_customersadd", - "admin_panel_views_newfactor", - "check_finance", - "check_finance_check_financial_discrepancy", - "csv", - "datetime", - "django", - "django_core_asgi", - "django_core_wsgi", - "django_db_models_functions", - "django_utils", - "filter", - "ghost_hunter", - "ghost_hunter_run_360_audit", - "khatibi_asgi", - "khatibi_asgi_rationale_1", - "khatibi_wsgi", - "khatibi_wsgi_rationale_1", - "os", - "persiantools_jdatetime", - "reconciliation_tool", - "reconciliation_tool_run_audit", - "requests" + "admin_panel_management_commands_sms_scan_command", + "admin_panel_management_commands_sms_scan_command_add_arguments", + "admin_panel_management_commands_sms_scan_command_emit", + "admin_panel_management_commands_sms_scan_command_handle", + "admin_panel_management_commands_sms_scan_command_handle_signal", + "admin_panel_management_commands_sms_scan_command_init", + "admin_panel_management_commands_sms_scan_py_basecommand", + "admin_panel_management_commands_sms_scan_rationale_46", + "admin_panel_sms_scanner_rationale_327", + "admin_panel_sms_scanner_run_all", + "admin_panel_sms_timing_apply_timing", + "admin_panel_sms_timing_rationale_80", + "tests_test_sms_activate_sms", + "tests_test_sms_autosendtests", + "tests_test_sms_autosendtests_overdue_visit_customer", + "tests_test_sms_autosendtests_test_auto_send_does_not_bypass_opt_out_or_disabled", + "tests_test_sms_autosendtests_test_auto_send_enqueues_approved_without_manager", + "tests_test_sms_autosendtests_test_default_is_pending_so_manager_must_approve", + "tests_test_sms_autosendtests_test_template_edit_persists_auto_send", + "tests_test_sms_autosendtests_test_templates_page_shows_auto_send_switch", + "tests_test_sms_autosendtests_test_welcome_auto_send_skips_approval_on_customer_creation", + "tests_test_sms_coldstarttests", + "tests_test_sms_coldstarttests_make_due_cheque", + "tests_test_sms_coldstarttests_setup", + "tests_test_sms_coldstarttests_test_backlog_blocked_until_admin_allows_it", + "tests_test_sms_coldstarttests_test_backlog_respects_admin_cap", + "tests_test_sms_coldstarttests_test_first_run_sends_nothing_for_existing_records", + "tests_test_sms_coldstarttests_test_max_per_run_caps_each_scanner", + "tests_test_sms_coldstarttests_test_new_record_after_scan_since_is_sent", + "tests_test_sms_coldstarttests_test_scan_since_defaults_to_now_on_first_load", + "tests_test_sms_consoleencodingtests_test_worker_survives_a_failing_drain_batch", + "tests_test_sms_deletedstaysdeletedtests_test_delete_view_then_rescan_creates_nothing", + "tests_test_sms_rationale_1051", + "tests_test_sms_rationale_245", + "tests_test_sms_rationale_263", + "tests_test_sms_rationale_285", + "tests_test_sms_rationale_306", + "tests_test_sms_rationale_352", + "tests_test_sms_rationale_36", + "tests_test_sms_rationale_464", + "tests_test_sms_rationale_543", + "tests_test_sms_scannertests", + "tests_test_sms_scannertests_setup", + "tests_test_sms_scannertests_test_cheque_due_scan_is_idempotent", + "tests_test_sms_scannertests_test_cheque_registration_rearms_per_cycle", + "tests_test_sms_scannertests_test_cheque_registration_stops_once_registered", + "tests_test_sms_scannertests_test_debt_due_requires_positive_balance", + "tests_test_sms_scannertests_test_deleted_visit_reminder_rearms_next_period_only", + "tests_test_sms_scannertests_test_deleted_visit_reminder_rearms_next_period_only_keys", + "tests_test_sms_scannertests_test_disabled_template_is_skipped", + "tests_test_sms_scannertests_test_period_start_filters_out_very_old_customers", + "tests_test_sms_scannertests_test_visit_period_comes_from_template", + "tests_test_sms_workertests", + "tests_test_sms_workertests_config", + "tests_test_sms_workertests_setup", + "tests_test_sms_workertests_test_console_provider_marks_sent", + "tests_test_sms_workertests_test_disabled_config_leaves_queue_untouched", + "tests_test_sms_workertests_test_failing_provider_backs_off_then_fails", + "tests_test_sms_workertests_test_pending_item_is_not_sent_until_approved", + "tests_test_sms_workertests_test_send_interval_is_respected_between_messages" ], "2": [ - "admin_panel_management_commands_gt_esh", - "admin_panel_management_commands_gt_esh_command", - "admin_panel_management_commands_gt_esh_command_add_arguments", - "admin_panel_management_commands_gt_esh_command_execute_by_immediate_chunks", - "admin_panel_management_commands_gt_esh_command_execute_hybrid_migration", - "admin_panel_management_commands_gt_esh_command_handle", - "admin_panel_management_commands_gt_esh_command_process_single_chunk", - "admin_panel_management_commands_gt_esh_py_basecommand", - "admin_panel_management_commands_gt_esh_rationale_115", - "admin_panel_management_commands_gt_esh_rationale_123", - "admin_panel_management_commands_gt_esh_rationale_89", - "admin_panel_management_commands_sqlite_db_backup", - "admin_panel_management_commands_sqlite_db_backup_command", - "admin_panel_management_commands_sqlite_db_backup_command_cleanup_resources", - "admin_panel_management_commands_sqlite_db_backup_command_handle", - "admin_panel_management_commands_sqlite_db_backup_command_handle_critical_failure", - "admin_panel_management_commands_sqlite_db_backup_command_handle_progress_callback", - "admin_panel_management_commands_sqlite_db_backup_command_init", - "admin_panel_management_commands_sqlite_db_backup_command_signal_handler", - "admin_panel_management_commands_sqlite_db_backup_py_basecommand", - "admin_panel_management_commands_sqlite_db_backup_rationale_158", - "admin_panel_management_commands_sqlite_db_backup_rationale_30", - "boto3", - "botocore_config", - "botocore_exceptions", - "contextlib", - "django_core_management_base", - "fcntl", - "khatibi_production_settings", - "khatibi_settings", - "khatibi_settings_rationale_1", - "logging", - "pathlib", - "shutil", - "signal", - "sqlite3", - "time" - ], - "3": [ "admin_panel_migrations_0004_factor_paytype", "admin_panel_migrations_0004_factor_paytype_migration", + "admin_panel_migrations_0010_auto_20220122_1546", + "admin_panel_migrations_0010_auto_20220122_1546_migration", "admin_panel_migrations_0013_alter_factor_options", "admin_panel_migrations_0013_alter_factor_options_migration", "admin_panel_migrations_0014_auto_20220123_1348", @@ -149,199 +140,594 @@ "admin_panel_migrations_0017_factoriteam_garantyesh_migration", "admin_panel_migrations_0020_auto_20220126_1532", "admin_panel_migrations_0020_auto_20220126_1532_migration", - "admin_panel_migrations_0021_alter_article_type", - "admin_panel_migrations_0021_alter_article_type_migration", "admin_panel_migrations_0022_article_typepy", "admin_panel_migrations_0022_article_typepy_migration", "admin_panel_migrations_0023_alter_article_typepy", "admin_panel_migrations_0023_alter_article_typepy_migration", + "admin_panel_migrations_0025_action_actiontype", + "admin_panel_migrations_0025_action_actiontype_migration", "admin_panel_migrations_0027_alter_action_nazar", "admin_panel_migrations_0027_alter_action_nazar_migration", + "admin_panel_migrations_0028_action_grade", + "admin_panel_migrations_0028_action_grade_migration", "admin_panel_migrations_0029_alter_article_options", "admin_panel_migrations_0029_alter_article_options_migration", + "admin_panel_migrations_0030_auto_20220414_1047", + "admin_panel_migrations_0030_auto_20220414_1047_migration", + "admin_panel_migrations_0033_article_nahvepardakht", + "admin_panel_migrations_0033_article_nahvepardakht_migration", "admin_panel_migrations_0034_alter_article_sarresid", "admin_panel_migrations_0034_alter_article_sarresid_migration", - "admin_panel_migrations_0035_alter_article_sarresid", - "admin_panel_migrations_0035_alter_article_sarresid_migration", - "admin_panel_migrations_0037_sarresid_sarresiddt", - "admin_panel_migrations_0037_sarresid_sarresiddt_migration", - "admin_panel_migrations_0040_customer_laghv_bazdid", - "admin_panel_migrations_0040_customer_laghv_bazdid_migration", - "admin_panel_migrations_0047_alter_productinstore_movement_type", - "admin_panel_migrations_0047_alter_productinstore_movement_type_migration", + "admin_panel_migrations_0039_customer_last_bazdid_dt", + "admin_panel_migrations_0039_customer_last_bazdid_dt_migration", + "admin_panel_migrations_0041_auto_20240216_1607", + "admin_panel_migrations_0041_auto_20240216_1607_migration", + "admin_panel_migrations_0044_sarresid_desc", + "admin_panel_migrations_0044_sarresid_desc_migration", + "admin_panel_migrations_0048_alter_productinstore_options", + "admin_panel_migrations_0048_alter_productinstore_options_migration", "admin_panel_migrations_0055_action_nextnazardt", "admin_panel_migrations_0055_action_nextnazardt_migration", + "admin_panel_migrations_0063_alter_smsqueue_status", + "admin_panel_migrations_0063_alter_smsqueue_status_migration", + "admin_panel_migrations_0069_smstemplate_auto_send", + "admin_panel_migrations_0069_smstemplate_auto_send_migration", "django_db" ], + "3": [ + "abstractbaseuser", + "admin_panel_models_init", + "admin_panel_models_init_rationale_1", + "admin_panel_models_store_storetransferrequest", + "admin_panel_models_store_storetransferrequest_clean", + "admin_panel_models_store_storetransferrequest_meta", + "admin_panel_models_store_storetransferrequest_status", + "admin_panel_models_store_storetransferrequest_str", + "admin_panel_services", + "admin_panel_services_approve_transfer_request", + "admin_panel_services_cancel_transfer_request", + "admin_panel_services_check_source_store_permission", + "admin_panel_services_check_target_store_permission", + "admin_panel_services_create_transfer_request", + "admin_panel_services_rationale_160", + "admin_panel_services_rationale_187", + "admin_panel_services_rationale_20", + "admin_panel_services_rationale_30", + "admin_panel_services_rationale_64", + "admin_panel_services_rationale_99", + "admin_panel_services_reject_transfer_request", + "atomic", + "django_core_exceptions", + "django_utils_translation", + "storeroom", + "tests_test_inventory", + "tests_test_inventory_rationale_1", + "tests_test_inventory_transferworkflowtests", + "tests_test_inventory_transferworkflowtests_test_approval_permission_denied_for_non_target_member", + "tests_test_inventory_transferworkflowtests_test_approve_moves_stock_and_links_transactions", + "tests_test_inventory_transferworkflowtests_test_cancel_by_requester", + "tests_test_inventory_transferworkflowtests_test_create_rejects_insufficient_stock", + "tests_test_inventory_transferworkflowtests_test_create_rejects_non_positive_quantity", + "tests_test_inventory_transferworkflowtests_test_create_rejects_same_store", + "tests_test_inventory_transferworkflowtests_test_create_transfer_request_pending", + "tests_test_inventory_transferworkflowtests_test_double_approval_is_rejected", + "tests_test_inventory_transferworkflowtests_test_permission_denied_for_outsider", + "tests_test_inventory_transferworkflowtests_test_reject_keeps_stock_untouched" + ], "4": [ - "admin_panel_templates_admin_panel_customeridbackup_html", - "admin_panel_templates_admin_panel_customeridbackup_html_all_products", - "admin_panel_templates_admin_panel_customeridbackup_html_buildorderlist", - "admin_panel_templates_admin_panel_customeridbackup_html_orderlist", - "admin_panel_templates_admin_panel_customeridfactorbackup_html", - "admin_panel_templates_admin_panel_customeridfactorbackup_html_js", - "admin_panel_templates_admin_panel_customernewbackup_html", - "admin_panel_urls_customer", - "admin_panel_urls_customeredit", - "admin_panel_urls_newfactor", - "api_endpoint_panel_customer_del", - "api_endpoint_panel_productmojodi", - "concept_category", - "concept_client_side_form_logic", - "concept_customer", - "concept_factor", - "concept_permission", - "concept_product", - "concept_productcategory", - "concept_user", - "django_concepts_csrf_token", - "django_contrib_staticfiles_templatetags_static", - "django_template_filter_tojalali", - "django_template_tag_custom_tags", - "django_template_tags_custom_tags", - "django_template_tags_static", - "django_url_admin_panel_customeredit", - "django_url_admin_panel_customerfactors", - "django_url_admin_panel_newfactor", - "django_url_admin_panel_productsjson", - "js_library_pdatepicker", - "panel_urls_productmojodi" + "admin_apps", + "admin_apps_adminconfig", + "admin_apps_py_appconfig", + "admin_panel_apps", + "admin_panel_apps_adminpanelconfig", + "admin_panel_apps_adminpanelconfig_ready", + "admin_panel_apps_make_std_streams_unicode_safe", + "admin_panel_apps_rationale_7", + "admin_panel_signals", + "admin_panel_signals_configure_sqlite_connection", + "admin_panel_signals_rationale_1", + "admin_panel_signals_rationale_12", + "appconfig", + "django_apps", + "django_db_backends_signals", + "django_dispatch", + "entrypoint", + "entrypoint_sh__entry", + "manage", + "manage_main", + "manage_rationale_2", + "manage_rationale_8", + "receiver", + "run", + "run_sh__entry", + "run_start_service", + "run_usage", + "stop", + "stop_is_our_process", + "stop_say", + "stop_sh__entry", + "stop_stop_pid", + "stop_windows_cleanup", + "sys", + "tests_test_sms_consoleencodingtests_test_std_streams_helper_is_safe_on_non_reconfigurable_streams" ], "5": [ - "admin_panel_views", - "admin_panel_views_actionjson", - "admin_panel_views_api_check_status_inquiry", - "admin_panel_views_customerjson", - "admin_panel_views_customers", - "admin_panel_views_p_login", - "admin_panel_views_p_logout", - "admin_panel_views_parse_cheque_result_html", - "admin_panel_views_rationale_2852", - "admin_panel_views_run_cbi_sayad_inquiry_logic", - "admin_panel_views_solve_base64_captcha", - "asgiref_sync", - "asyncio", - "base64", - "csrf_exempt", - "cv2", - "ddddocr", - "django_contrib_auth_decorators", - "django_core_paginator", - "django_urls_base", - "django_views_decorators_csrf", - "django_views_decorators_http", - "json", - "numpy", - "persiantools", - "playwright_async_api", - "re" + "admin_panel_management_commands_fix_customer_phones", + "admin_panel_management_commands_fix_customer_phones_command", + "admin_panel_management_commands_fix_customer_phones_command_add_arguments", + "admin_panel_management_commands_fix_customer_phones_command_handle", + "admin_panel_management_commands_fix_customer_phones_py_basecommand", + "admin_panel_management_commands_fix_customer_phones_rationale_1", + "admin_panel_migrations_0067_customer_notes", + "admin_panel_migrations_0067_customer_notes_migration", + "admin_panel_migrations_0067_customer_notes_rationale_7", + "admin_panel_migrations_0067_customer_notes_scrub_legacy_phones", + "admin_panel_sms_phone_cleanup", + "admin_panel_sms_phone_cleanup_append_note", + "admin_panel_sms_phone_cleanup_clean_all", + "admin_panel_sms_phone_cleanup_clean_customer", + "admin_panel_sms_phone_cleanup_clean_legacy_cell", + "admin_panel_sms_phone_cleanup_numeric_tokens", + "admin_panel_sms_phone_cleanup_rationale_1", + "admin_panel_sms_phone_cleanup_rationale_117", + "admin_panel_sms_phone_cleanup_rationale_30", + "admin_panel_sms_phone_cleanup_rationale_36", + "admin_panel_sms_phone_cleanup_rationale_75", + "admin_panel_sms_phone_cleanup_rationale_86", + "tests_test_sms_phonecleanuptests", + "tests_test_sms_phonecleanuptests_test_canonical_cell_is_noop", + "tests_test_sms_phonecleanuptests_test_clean_all_reports_stats", + "tests_test_sms_phonecleanuptests_test_clean_customer_is_idempotent", + "tests_test_sms_phonecleanuptests_test_command_applies_and_reruns_clean", + "tests_test_sms_phonecleanuptests_test_command_dry_run_is_noop", + "tests_test_sms_phonecleanuptests_test_eight_digit_landline_untouched", + "tests_test_sms_phonecleanuptests_test_embedded_mobile_is_extracted_and_text_noted", + "tests_test_sms_phonecleanuptests_test_embedded_mobile_keeps_dropped_landline_in_note", + "tests_test_sms_phonecleanuptests_test_mobile_without_leading_zero_becomes_canonical", + "tests_test_sms_phonecleanuptests_test_name_only_cell_cleared_and_noted", + "tests_test_sms_phonecleanuptests_test_pure_junk_fragment_cleared_but_preserved_in_note", + "tests_test_sms_rationale_1717" ], "6": [ - "admin_panel_views_addsarresid", - "admin_panel_views_bedehi", - "admin_panel_views_bedehikhadamat", - "admin_panel_views_bedehinasb", - "admin_panel_views_customerdel", - "admin_panel_views_customerssearch", - "admin_panel_views_entezar", - "admin_panel_views_factordel", - "admin_panel_views_financial_dashboard", - "admin_panel_views_hesabdari", - "admin_panel_views_hesabdari_checkpass", - "admin_panel_views_hesabdari_checkpassfail", - "admin_panel_views_hesabdari_residnashone", - "admin_panel_views_pardakhtnesiye", - "admin_panel_views_passcheck", - "admin_panel_views_productdel", - "admin_panel_views_rationale_395", - "admin_panel_views_report", - "admin_panel_views_report_debtors_no_vosol", - "admin_panel_views_rezayat", - "admin_panel_views_rezayatedit", - "admin_panel_views_sarresidh", - "admin_panel_views_update_check_info", - "login_required", - "permission_required", - "require_post" + "admin_panel_models_sms_smsproviderconfig_load", + "admin_panel_sms_enqueue", + "admin_panel_sms_enqueue_enqueue_sms", + "admin_panel_sms_enqueue_rationale_1", + "admin_panel_sms_enqueue_rationale_17", + "admin_panel_sms_scanner", + "admin_panel_sms_scanner_aware", + "admin_panel_sms_scanner_budget", + "admin_panel_sms_scanner_budget_can_send", + "admin_panel_sms_scanner_budget_init", + "admin_panel_sms_scanner_budget_is_backlog", + "admin_panel_sms_scanner_budget_record", + "admin_panel_sms_scanner_rationale_1", + "admin_panel_sms_scanner_rationale_153", + "admin_panel_sms_scanner_rationale_200", + "admin_panel_sms_scanner_rationale_237", + "admin_panel_sms_scanner_rationale_266", + "admin_panel_sms_scanner_rationale_43", + "admin_panel_sms_scanner_rationale_57", + "admin_panel_sms_scanner_scan_cheque_due", + "admin_panel_sms_scanner_scan_cheque_registration", + "admin_panel_sms_scanner_scan_debt_due", + "admin_panel_sms_scanner_scan_nesiye_due", + "admin_panel_sms_scanner_scan_survey", + "admin_panel_sms_scanner_scan_visit", + "admin_panel_sms_scanner_template", + "tests_test_sms_rationale_1335", + "tests_test_sms_rationale_1530", + "tests_test_sms_settingspaneltests", + "tests_test_sms_settingspaneltests_test_flow_controls_are_saved_from_panel", + "tests_test_sms_settingspaneltests_test_invalid_scan_since_keeps_previous_value", + "tests_test_sms_settingspaneltests_test_jalali_period_start_saved_as_start_of_day", + "tests_test_sms_testmessagesettingstests", + "tests_test_sms_testmessagesettingstests_test_default_test_message_has_no_brand_name", + "tests_test_sms_testmessagesettingstests_test_editing_test_message_persists_and_is_sent" ], "7": [ - "admin_panel_templates_admin_panel_base", - "admin_panel_templates_admin_panel_basehesabdari", - "admin_panel_templates_admin_panel_basestore", - "admin_panel_templates_admin_panel_customerfactorbackup", - "concept_django_custom_tags", - "concept_url_admin_panel_actionlistusertoday", - "concept_url_admin_panel_bedehi", - "concept_url_admin_panel_customers", - "concept_url_admin_panel_entezar", - "concept_url_admin_panel_hesabdari", - "concept_url_admin_panel_hesabdari_residnashone", - "concept_url_admin_panel_index", - "concept_url_admin_panel_operations", - "concept_url_admin_panel_p_logout", - "concept_url_admin_panel_productadd", - "concept_url_admin_panel_rezayat", - "concept_url_admin_panel_store", - "concept_url_admin_panel_store_transfers", - "concept_url_admin_panel_stores", - "concept_url_panel_customer_factor_del", - "static_admin_panel_bulma_min_css", - "static_admin_panel_datatables_datatables_min_js", - "static_admin_panel_kamadatepicker_min_js", - "static_admin_panel_persian-datepicker_min_js", - "static_admin_panel_style_css" + "admin_panel_models_sms", + "admin_panel_models_sms_customersmssetting", + "admin_panel_models_sms_customersmssetting_meta", + "admin_panel_models_sms_customersmssetting_str", + "admin_panel_models_sms_rationale_1", + "admin_panel_models_sms_rationale_136", + "admin_panel_models_sms_rationale_14", + "admin_panel_models_sms_rationale_183", + "admin_panel_models_sms_rationale_205", + "admin_panel_models_sms_rationale_216", + "admin_panel_models_sms_rationale_27", + "admin_panel_models_sms_rationale_275", + "admin_panel_models_sms_rationale_295", + "admin_panel_models_sms_smsdedup", + "admin_panel_models_sms_smsdedup_meta", + "admin_panel_models_sms_smsdedup_str", + "admin_panel_models_sms_smslog", + "admin_panel_models_sms_smslog_meta", + "admin_panel_models_sms_smslog_str", + "admin_panel_models_sms_smsproviderconfig", + "admin_panel_models_sms_smsproviderconfig_meta", + "admin_panel_models_sms_smsproviderconfig_save", + "admin_panel_models_sms_smsproviderconfig_str", + "admin_panel_models_sms_smsqueue", + "admin_panel_models_sms_smsqueue_deliverystatus", + "admin_panel_models_sms_smsqueue_meta", + "admin_panel_models_sms_smsqueue_status", + "admin_panel_models_sms_smsqueue_str", + "admin_panel_models_sms_smstemplate", + "admin_panel_models_sms_smstemplate_meta", + "admin_panel_models_sms_smstemplate_str", + "admin_panel_models_sms_smstype" ], "8": [ + "admin_panel_management_commands_gt_esh", + "admin_panel_management_commands_send_daily_report", + "admin_panel_management_commands_sms_delivery", + "admin_panel_management_commands_sms_delivery_rationale_1", + "admin_panel_management_commands_sms_scan", + "admin_panel_management_commands_sms_scan_rationale_1", + "admin_panel_management_commands_sms_worker", + "admin_panel_management_commands_sms_worker_rationale_1", + "admin_panel_management_commands_sqlite_db_backup", + "admin_panel_models_products", + "admin_panel_models_products_productcategory", + "admin_panel_models_products_productcategory_str", + "admin_panel_templatetags_custom_tags", + "admin_panel_templatetags_custom_tags_has_group", + "admin_panel_templatetags_custom_tags_tojalali", + "boto3", + "botocore_config", + "botocore_exceptions", + "contextlib", + "datetime", + "django_core_management_base", + "django_db_models", + "django_utils", + "fcntl", + "logging", + "persiantools_jdatetime", + "shutil", + "signal", + "sqlite3", + "time" + ], + "9": [ + "admin_panel_sms_phones_normalize_primary_values", + "admin_panel_sms_phones_rationale_106", + "admin_panel_sms_phones_rationale_120", + "admin_panel_sms_phones_save_primary_normalized", + "admin_panel_sms_rules_customer_toggle_map", + "admin_panel_sms_rules_rationale_16", + "admin_panel_views_customers", + "admin_panel_views_customers_actionjson", + "admin_panel_views_customers_customer", + "admin_panel_views_customers_customerdel", + "admin_panel_views_customers_customeredit", + "admin_panel_views_customers_customerfactor", + "admin_panel_views_customers_customerfactors", + "admin_panel_views_customers_customerhesabdari", + "admin_panel_views_customers_customerhesabdariadd", + "admin_panel_views_customers_customerjson", + "admin_panel_views_customers_customernew", + "admin_panel_views_customers_customernew2", + "admin_panel_views_customers_customers", + "admin_panel_views_customers_customersadd", + "admin_panel_views_customers_customerssearch", + "admin_panel_views_customers_entezar", + "admin_panel_views_customers_productsjson", + "admin_panel_views_customers_py_login_required", + "admin_panel_views_customers_sarresidh", + "admin_panel_views_helpers_parse_phone_rows", + "admin_panel_views_helpers_rationale_42", + "csrf_exempt", + "django_views_decorators_csrf", + "permission_required" + ], + "10": [ "admin_panel_migrations_0001_initial", "admin_panel_migrations_0001_initial_migration", "admin_panel_migrations_0007_productinstore_factor", "admin_panel_migrations_0007_productinstore_factor_migration", "admin_panel_migrations_0008_storeroom_user", "admin_panel_migrations_0008_storeroom_user_migration", + "admin_panel_migrations_0012_visitqueue_user", + "admin_panel_migrations_0012_visitqueue_user_migration", + "admin_panel_migrations_0016_factor_user", + "admin_panel_migrations_0016_factor_user_migration", "admin_panel_migrations_0018_alter_factoriteam_product", "admin_panel_migrations_0018_alter_factoriteam_product_migration", "admin_panel_migrations_0019_article_articletype", "admin_panel_migrations_0019_article_articletype_migration", - "admin_panel_migrations_0024_action", - "admin_panel_migrations_0024_action_migration", "admin_panel_migrations_0036_sarresid", "admin_panel_migrations_0036_sarresid_migration", "admin_panel_migrations_0038_auto_20240213_1735", "admin_panel_migrations_0038_auto_20240213_1735_migration", - "admin_panel_migrations_0042_article_user", - "admin_panel_migrations_0042_article_user_migration", + "admin_panel_migrations_0045_auto_20260610_1421", + "admin_panel_migrations_0045_auto_20260610_1421_migration", "admin_panel_migrations_0046_auto_20260624_1818", "admin_panel_migrations_0046_auto_20260624_1818_migration", + "admin_panel_migrations_0050_storetransferrequest", + "admin_panel_migrations_0050_storetransferrequest_migration", "admin_panel_migrations_0054_auto_20260715_2250", "admin_panel_migrations_0054_auto_20260715_2250_migration", "django_db_models_deletion" ], - "9": [ + "11": [ + "admin_panel_init", "admin_panel_migrations_0011_storeroom_user", "admin_panel_migrations_0011_storeroom_user_migration", - "admin_panel_migrations_0012_visitqueue_user", - "admin_panel_migrations_0012_visitqueue_user_migration", "admin_panel_migrations_0015_auto_20220124_0858", "admin_panel_migrations_0015_auto_20220124_0858_migration", - "admin_panel_migrations_0016_factor_user", - "admin_panel_migrations_0016_factor_user_migration", + "admin_panel_migrations_0024_action", + "admin_panel_migrations_0024_action_migration", "admin_panel_migrations_0026_auto_20220209_1924", "admin_panel_migrations_0026_auto_20220209_1924_migration", + "admin_panel_migrations_0042_article_user", + "admin_panel_migrations_0042_article_user_migration", "admin_panel_migrations_0043_sarresid_user", "admin_panel_migrations_0043_sarresid_user_migration", - "admin_panel_migrations_0045_auto_20260610_1421", - "admin_panel_migrations_0045_auto_20260610_1421_migration", - "admin_panel_migrations_0050_storetransferrequest", - "admin_panel_migrations_0050_storetransferrequest_migration", "admin_panel_migrations_0051_auto_20260705_1356", "admin_panel_migrations_0051_auto_20260705_1356_migration", "admin_panel_migrations_0052_storeroomowner", "admin_panel_migrations_0052_storeroomowner_migration", "admin_panel_migrations_0053_storeroom_owners", "admin_panel_migrations_0053_storeroom_owners_migration", - "django_conf" + "admin_panel_migrations_0057_sms", + "admin_panel_migrations_0057_sms_migration", + "admin_panel_urls", + "django_conf", + "django_conf_urls_static", + "khatibi_urls", + "khatibi_urls_rationale_1" ], - "10": [ + "12": [ + "admin_panel_sms_phones_customer_phone_entries", + "admin_panel_sms_phones_rationale_127", + "admin_panel_sms_recipients_rationale_21", + "admin_panel_sms_recipients_resolve_recipients", + "admin_panel_templatetags_custom_tags_customer_phones", + "admin_panel_templatetags_custom_tags_rationale_25", + "simple_tag", + "tests_test_sms_mark_primary", + "tests_test_sms_queuereviewtests_test_custom_manual_send_is_auto_approved", + "tests_test_sms_rationale_111", + "tests_test_sms_rationale_127", + "tests_test_sms_rationale_27", + "tests_test_sms_rationale_63", + "tests_test_sms_rationale_89", + "tests_test_sms_recipienttests", + "tests_test_sms_recipienttests_test_display_adds_leading_zero", + "tests_test_sms_recipienttests_test_fallback_to_first_number_when_no_primary", + "tests_test_sms_recipienttests_test_legacy_phone_fields_are_discovered", + "tests_test_sms_recipienttests_test_meaningless_values_are_discarded", + "tests_test_sms_recipienttests_test_multiple_primary_numbers", + "tests_test_sms_recipienttests_test_phone_number_rows_and_legacy_fields_are_merged_without_duplicates", + "tests_test_sms_recipienttests_test_primary_phones_are_used", + "tests_test_sms_recipienttests_test_primary_selection_self_heals_when_number_changes", + "tests_test_sms_welcometriggertests", + "tests_test_sms_welcometriggertests_test_customer_creation_enqueues_welcome", + "tests_test_sms_welcometriggertests_test_customer_edit_preserves_primary_selection" + ], + "13": [ + "admin_admin", + "admin_panel_models_products_product", + "admin_panel_models_products_product_meta", + "admin_panel_models_products_product_str", + "admin_panel_views_store", + "admin_panel_views_store_newoperation", + "admin_panel_views_store_newstore", + "admin_panel_views_store_operation", + "admin_panel_views_store_operationproduct", + "admin_panel_views_store_operations", + "admin_panel_views_store_process_transfer_action", + "admin_panel_views_store_productadd", + "admin_panel_views_store_productdel", + "admin_panel_views_store_productedit", + "admin_panel_views_store_productmojodi", + "admin_panel_views_store_py_login_required", + "admin_panel_views_store_py_permission_required", + "admin_panel_views_store_py_require_post", + "admin_panel_views_store_rationale_165", + "admin_panel_views_store_rationale_204", + "admin_panel_views_store_store", + "admin_panel_views_store_store_transfers", + "admin_panel_views_store_stores", + "django_contrib", + "django_core_paginator" + ], + "14": [ + "admin_panel_models_finance_article", + "admin_panel_models_finance_article_meta", + "admin_panel_views_finance", + "admin_panel_views_finance_addsarresid", + "admin_panel_views_finance_bedehi", + "admin_panel_views_finance_bedehikhadamat", + "admin_panel_views_finance_bedehinasb", + "admin_panel_views_finance_financial_dashboard", + "admin_panel_views_finance_hesabdari", + "admin_panel_views_finance_hesabdari_checkpass", + "admin_panel_views_finance_hesabdari_checkpassfail", + "admin_panel_views_finance_hesabdari_residnashone", + "admin_panel_views_finance_inventory_movements_report", + "admin_panel_views_finance_pardakhtnesiye", + "admin_panel_views_finance_passcheck", + "admin_panel_views_finance_py_login_required", + "admin_panel_views_finance_py_permission_required", + "admin_panel_views_finance_py_require_post", + "admin_panel_views_finance_rationale_236", + "admin_panel_views_finance_rationale_319", + "admin_panel_views_finance_report", + "admin_panel_views_finance_report_debtors_no_vosol", + "admin_panel_views_finance_rezayat_dashboard", + "admin_panel_views_finance_update_check_info", + "json" + ], + "15": [ + "abc", + "django_utils_module_loading", + "integrations_sms_base", + "integrations_sms_base_rationale_23", + "integrations_sms_base_rationale_29", + "integrations_sms_base_smsprovider", + "integrations_sms_base_smsprovider_get_delivery_status", + "integrations_sms_base_smsprovider_send_message", + "integrations_sms_base_smsprovider_send_otp", + "integrations_sms_base_webotp_message", + "integrations_sms_console", + "integrations_sms_console_rationale_1", + "integrations_sms_factory", + "integrations_sms_factory_get_provider_class", + "integrations_sms_factory_get_sms_provider", + "integrations_sms_factory_rationale_1", + "integrations_sms_factory_rationale_26", + "integrations_sms_factory_rationale_37", + "integrations_sms_factory_reset_sms_provider", + "integrations_sms_http", + "integrations_sms_http_rationale_1", + "integrations_sms_providers", + "integrations_sms_providers_rationale_1", + "urllib_parse" + ], + "16": [ + "dataclasses", + "integrations_sms_sms0098_codes", + "integrations_sms_sms0098_codes_classify_delivery", + "integrations_sms_sms0098_codes_deliverycode", + "integrations_sms_sms0098_codes_describe_send_code", + "integrations_sms_sms0098_codes_rationale_1", + "integrations_sms_sms0098_codes_rationale_62", + "integrations_sms_sms0098_codes_rationale_85", + "integrations_sms_sms0098ws", + "integrations_sms_sms0098ws_as_text", + "integrations_sms_sms0098ws_rationale_1", + "integrations_sms_sms0098ws_rationale_132", + "integrations_sms_sms0098ws_rationale_32", + "integrations_sms_sms0098ws_rationale_43", + "integrations_sms_sms0098ws_rationale_69", + "integrations_sms_sms0098ws_zerozeroninetyeightwebserviceprovider", + "integrations_sms_sms0098ws_zerozeroninetyeightwebserviceprovider_client", + "integrations_sms_sms0098ws_zerozeroninetyeightwebserviceprovider_configuration_error", + "integrations_sms_sms0098ws_zerozeroninetyeightwebserviceprovider_get_delivery_status", + "integrations_sms_sms0098ws_zerozeroninetyeightwebserviceprovider_init", + "integrations_sms_sms0098ws_zerozeroninetyeightwebserviceprovider_safe_exception", + "integrations_sms_sms0098ws_zerozeroninetyeightwebserviceprovider_send_message", + "zeep_transports" + ], + "17": [ + "action", + "admin_panel_admin", + "admin_panel_admin_customersmssettingadmin", + "admin_panel_admin_rationale_54", + "admin_panel_admin_rationale_75", + "admin_panel_admin_smslogadmin", + "admin_panel_admin_smslogadmin_has_add_permission", + "admin_panel_admin_smslogadmin_has_change_permission", + "admin_panel_admin_smsproviderconfigadmin", + "admin_panel_admin_smsproviderconfigadmin_has_add_permission", + "admin_panel_admin_smsproviderconfigadmin_has_delete_permission", + "admin_panel_admin_smsproviderconfigform", + "admin_panel_admin_smsproviderconfigform_meta", + "admin_panel_admin_smsqueueadmin", + "admin_panel_admin_smsqueueadmin_approve_selected", + "admin_panel_admin_smsqueueadmin_retry_selected", + "admin_panel_admin_smstemplateadmin", + "admin_panel_admin_storeroomadmin", + "admin_panel_admin_storeroomadmin_get_owners_count", + "admin_panel_admin_storeroomownerinline", + "admin_panel_admin_storetransferrequestadmin", + "register" + ], + "18": [ + "admin_models_user", + "admin_panel_models_actions", + "admin_panel_models_actions_action", + "admin_panel_models_actions_visitqueue", + "admin_panel_models_users", + "admin_panel_models_users_userprofile", + "admin_panel_models_users_userprofile_str", + "admin_panel_views_actions", + "admin_panel_views_actions_actionlist", + "admin_panel_views_actions_actionlistall", + "admin_panel_views_actions_actionlisttoday", + "admin_panel_views_actions_actionlistuser", + "admin_panel_views_actions_actionlistusertoday", + "admin_panel_views_actions_customeraction", + "admin_panel_views_actions_customereditaction", + "admin_panel_views_actions_customernazar", + "admin_panel_views_actions_customernewaction", + "admin_panel_views_actions_py_login_required", + "admin_panel_views_actions_rezayat", + "admin_panel_views_actions_rezayatedit", + "django_contrib_auth_models" + ], + "19": [ + "apps_core_models", + "integrations_announcements", + "integrations_announcements_announcementrejected", + "integrations_announcements_place_announcement_call", + "integrations_announcements_rationale_1", + "integrations_announcements_rationale_7", + "integrations_bale_providers", + "integrations_bale_providers_balebotprovider", + "integrations_bale_providers_balebotprovider_send_message", + "integrations_bale_providers_balebotprovider_send_photo", + "integrations_bale_providers_consolebaleprovider", + "integrations_bale_providers_consolebaleprovider_send_message", + "integrations_bale_providers_get_bale_provider", + "integrations_bale_providers_notificationresult", + "integrations_bale_providers_rationale_19", + "integrations_voip", + "integrations_voip_place_voip_call", + "integrations_voip_voipcallresult", + "requests", + "uuid", + "valueerror" + ], + "20": [ + "tests_factories_login_superuser", + "tests_factories_rationale_47", + "tests_test_customers", + "tests_test_customers_customercrudtests", + "tests_test_customers_customercrudtests_setup", + "tests_test_customers_customercrudtests_test_add_customer_creates_phones", + "tests_test_customers_customercrudtests_test_edit_customer_replaces_phones", + "tests_test_customers_customerdeleteguardtests", + "tests_test_customers_customerdeleteguardtests_setup", + "tests_test_customers_customerdeleteguardtests_test_customer_with_financial_history_is_never_deleted", + "tests_test_customers_customerdeleteguardtests_test_customer_without_history_is_deactivated", + "tests_test_customers_inactivecustomerhiddentests", + "tests_test_customers_inactivecustomerhiddentests_setup", + "tests_test_customers_inactivecustomerhiddentests_test_inactive_absent_from_default_listing", + "tests_test_customers_inactivecustomerhiddentests_test_inactive_absent_from_path_search_view", + "tests_test_customers_inactivecustomerhiddentests_test_inactive_absent_from_search_by_name", + "tests_test_customers_inactivecustomerhiddentests_test_inactive_absent_from_search_by_phone", + "tests_test_customers_py_testcase", + "tests_test_customers_rationale_1", + "tests_test_customers_rationale_59", + "tests_test_sms_paneltestcase_setup" + ], + "21": [ + "admin_panel_sms_phones", + "admin_panel_sms_phones_iter_sources", + "admin_panel_sms_phones_phoneentry", + "admin_panel_sms_phones_phoneentry_as_dict", + "admin_panel_sms_phones_phoneentry_is_mobile", + "admin_panel_sms_phones_phoneentry_repr", + "admin_panel_sms_phones_primary_phones", + "admin_panel_sms_phones_rationale_1", + "admin_panel_sms_phones_rationale_139", + "admin_panel_sms_phones_rationale_144", + "admin_panel_sms_phones_rationale_155", + "admin_panel_sms_phones_rationale_23", + "admin_panel_sms_phones_rationale_66", + "admin_panel_sms_phones_resolved_recipients", + "admin_panel_sms_phones_usable_entries", + "admin_panel_sms_recipients", + "admin_panel_sms_recipients_primary_phones", + "admin_panel_sms_recipients_rationale_1", + "admin_panel_sms_recipients_rationale_16" + ], + "22": [ "admin_panel_templates_admin_panel_login", "admin_panel_templates_admin_panel_storebk1", "django_csrf_token", @@ -362,32 +748,417 @@ "template_variable_productsum", "template_variable_s_id" ], - "11": [ - "admin_panel_models_productcategory", - "admin_panel_models_productcategory_str", - "admin_panel_models_productinstore", - "admin_panel_models_productinstore_delete", - "admin_panel_models_productinstore_meta", - "admin_panel_models_productinstore_movementtype", - "admin_panel_models_productinstore_save", - "admin_panel_models_productinstore_str", - "admin_panel_views_customer", - "admin_panel_views_customerfactor", - "admin_panel_views_customernew", - "admin_panel_views_customernew2", - "admin_panel_views_operationproduct", - "admin_panel_views_operations", - "admin_panel_views_productadd", - "admin_panel_views_productedit", - "admin_panel_views_store" + "23": [ + "admin_panel_views_cheque_inquiry", + "admin_panel_views_cheque_inquiry_api_check_sabt_inquiry", + "admin_panel_views_cheque_inquiry_api_check_status_inquiry", + "admin_panel_views_cheque_inquiry_parse_cheque_result_html", + "admin_panel_views_cheque_inquiry_py_login_required", + "admin_panel_views_cheque_inquiry_py_permission_required", + "admin_panel_views_cheque_inquiry_rationale_229", + "admin_panel_views_cheque_inquiry_rationale_252", + "admin_panel_views_cheque_inquiry_run_bpi_cheque_inquiry_logic", + "admin_panel_views_cheque_inquiry_run_cbi_sayad_inquiry_logic", + "admin_panel_views_cheque_inquiry_solve_base64_captcha", + "asgiref_sync", + "asyncio", + "base64", + "cv2", + "ddddocr", + "numpy", + "playwright_async_api", + "re" ], - "12": [ + "24": [ + "itertools", + "tests_factories", + "tests_factories_make_category", + "tests_factories_make_product", + "tests_factories_make_store", + "tests_factories_next", + "tests_factories_rationale_1", + "tests_factories_rationale_54", + "tests_factories_rationale_86", + "tests_factories_stock_in", + "tests_test_factors", + "tests_test_factors_factorsoftdeletetests_test_instore_transaction_protects_its_factor", + "tests_test_factors_newfactor2viewtests_setup", + "tests_test_factors_rationale_1", + "tests_test_inventory_productinstoremathtests_setup", + "tests_test_inventory_transferworkflowtests_setup", + "tests_test_view_smoke", + "tests_test_view_smoke_rationale_1", + "tests_test_view_smoke_viewsmoketests_setuptestdata" + ], + "25": [ + "ripwire_readme_against_the_leading_graph_database_code_context_mcp_server", + "ripwire_readme_better_code_it_automates_the_review_judgments_nobody_has_time_to_make_every_lens_from_published_research", + "ripwire_readme_documentation", + "ripwire_readme_graph_ranked_retrieval_it_finds_the_right_files_more_often_than_the_alternatives", + "ripwire_readme_if_it_works_on_your_codebase_tell_us_what_it_got_wrong", + "ripwire_readme_improve_it_with_your_agent", + "ripwire_readme_languages", + "ripwire_readme_licence", + "ripwire_readme_quickstart", + "ripwire_readme_real_runs", + "ripwire_readme_release_notes", + "ripwire_readme_rip_n_fast_fewer_tokens_better_code", + "ripwire_readme_same_answer_a_fraction_of_the_tokens_read_this_table_first_if_your_agent_is_on_a_budget", + "ripwire_readme_saves_tokens_it_answers_for_a_fraction_of_the_context", + "ripwire_readme_see_the_map_not_just_the_numbers", + "ripwire_readme_standing_on_the_whole_field", + "ripwire_readme_the_goal_one_question_one_complete_answer", + "ripwire_readme_what_it_answers", + "ripwire_readme_where_it_pays_most_and_where_it_does_not" + ], + "26": [ + "admin_panel_views_factors", + "admin_panel_views_factors_newfactor2", + "admin_panel_views_helpers", + "admin_panel_views_helpers_inventoryexception", + "admin_panel_views_helpers_parse_jalali_date", + "admin_panel_views_helpers_parse_timestamp", + "admin_panel_views_helpers_rationale_1", + "admin_panel_views_helpers_rationale_13", + "admin_panel_views_helpers_rationale_18", + "admin_panel_views_helpers_rationale_29", + "admin_panel_views_helpers_rationale_60", + "admin_panel_views_helpers_safe_int", + "admin_panel_views_init", + "admin_panel_views_init_rationale_1", + "django_contrib_auth_decorators", + "django_views_decorators_http", + "exception", + "persiantools" + ], + "27": [ + "integrations_sms_base_smsresult", + "integrations_sms_sms0098", + "integrations_sms_sms0098_configured_codes", + "integrations_sms_sms0098_rationale_1", + "integrations_sms_sms0098_rationale_45", + "integrations_sms_sms0098_rationale_50", + "integrations_sms_sms0098_rationale_71", + "integrations_sms_sms0098_rationale_79", + "integrations_sms_sms0098_response_code", + "integrations_sms_sms0098_zerozeroninetyeightsmsprovider", + "integrations_sms_sms0098_zerozeroninetyeightsmsprovider_configuration_error", + "integrations_sms_sms0098_zerozeroninetyeightsmsprovider_init", + "integrations_sms_sms0098_zerozeroninetyeightsmsprovider_safe_exception", + "integrations_sms_sms0098_zerozeroninetyeightsmsprovider_send_message", + "integrations_sms_sms0098_zerozeroninetyeightsmsprovider_unconfirmed", + "tests_test_sms_failingprovider", + "tests_test_sms_failingprovider_send_message", + "tests_test_sms_failingprovider_send_otp" + ], + "28": [ + "tests_factories_make_user", + "tests_factories_rationale_23", + "tests_test_sms_rationale_1192", + "tests_test_sms_rationale_1234", + "tests_test_sms_rationale_1290", + "tests_test_sms_smspermissiontests", + "tests_test_sms_smspermissiontests_call", + "tests_test_sms_smspermissiontests_setup", + "tests_test_sms_smspermissiontests_test_anonymous_user_is_redirected_to_login", + "tests_test_sms_smspermissiontests_test_customer_page_hides_sms_box_without_permission", + "tests_test_sms_smspermissiontests_test_every_sms_url_is_covered_by_this_test", + "tests_test_sms_smspermissiontests_test_navbar_link_visible_only_with_permission", + "tests_test_sms_smspermissiontests_test_panel_user_without_permission_gets_403", + "tests_test_sms_smspermissiontests_test_permission_granted_in_django_admin_grants_access", + "tests_test_sms_smspermissiontests_test_permission_granted_via_group_grants_access", + "tests_test_sms_smspermissiontests_test_permission_is_assignable_from_django_admin_user_page", + "tests_test_sms_smspermissiontests_test_superuser_has_access", + "tests_test_sms_smspermissiontests_urls" + ], + "29": [ + "tests_factories_make_customer", + "tests_test_sms_deletedstaysdeletedtests", + "tests_test_sms_deletedstaysdeletedtests_test_deleted_queue_item_is_not_re_enqueued", + "tests_test_sms_emptyqueuetests", + "tests_test_sms_emptyqueuetests_test_emptied_keys_are_ledgered_so_nothing_returns", + "tests_test_sms_emptyqueuetests_test_empty_removes_every_row_regardless_of_status", + "tests_test_sms_emptyqueuetests_test_get_does_not_empty", + "tests_test_sms_paneltestcase", + "tests_test_sms_primarynumberpaneltests", + "tests_test_sms_primarynumberpaneltests_test_customer_page_lists_legacy_numbers_and_toggles", + "tests_test_sms_primarynumberpaneltests_test_toggle_accepts_number_without_leading_zero", + "tests_test_sms_primarynumberpaneltests_test_toggle_marks_legacy_number_as_primary", + "tests_test_sms_primarynumberpaneltests_test_toggle_rejects_unknown_number", + "tests_test_sms_rationale_1137", + "tests_test_sms_rationale_1551", + "tests_test_sms_rationale_1676", + "tests_test_sms_rationale_898" + ], + "30": [ + "admin_panel_context_processor", + "admin_panel_context_processor_dashboxdata", + "admin_panel_sms_notices", + "admin_panel_sms_notices_rationale_1", + "admin_panel_sms_notices_rationale_50", + "admin_panel_sms_notices_resolve", + "collections", + "tests_test_sms_noticemechanismtests", + "tests_test_sms_noticemechanismtests_item", + "tests_test_sms_noticemechanismtests_test_approve_redirects_with_notice_code_not_session_message", + "tests_test_sms_noticemechanismtests_test_bulk_counts_are_passed_through_querystring", + "tests_test_sms_noticemechanismtests_test_rendered_page_shows_the_notice", + "tests_test_sms_noticemechanismtests_test_resolve_fills_placeholders_from_querystring", + "tests_test_sms_noticemechanismtests_test_resolve_ignores_unknown_or_missing_code", + "tests_test_sms_noticemechanismtests_test_resolve_returns_text_and_kind_for_known_code", + "tests_test_sms_rationale_1468" + ], + "31": [ + "admin_panel_models_factors", + "admin_panel_models_factors_factor", + "admin_panel_models_factors_factor_delete", + "admin_panel_models_factors_factor_meta", + "admin_panel_models_factors_factoriteam", + "admin_panel_models_products_productinstore", + "admin_panel_models_products_productinstore_delete", + "admin_panel_models_products_productinstore_meta", + "admin_panel_models_products_productinstore_movementtype", + "admin_panel_models_products_productinstore_save", + "admin_panel_models_products_productinstore_str", + "admin_panel_views_factors_factordel", + "admin_panel_views_factors_newfactor", + "admin_panel_views_factors_py_login_required", + "admin_panel_views_factors_py_permission_required", + "admin_panel_views_factors_py_require_post" + ], + "32": [ + "admin_panel_sms_phones_phoneentry_init", + "admin_panel_sms_phones_rationale_55", + "admin_panel_sms_phones_to_display", + "integrations_sms_http_httpsmsprovider_send_message", + "integrations_sms_normalize", + "integrations_sms_normalize_clean", + "integrations_sms_normalize_normalize_mobile", + "integrations_sms_normalize_rationale_1", + "integrations_sms_normalize_rationale_17", + "integrations_sms_normalize_rationale_36", + "integrations_sms_normalize_to_local", + "tests_test_sms_normalizetests", + "tests_test_sms_normalizetests_test_invalid_returns_empty", + "tests_test_sms_normalizetests_test_to_local", + "tests_test_sms_normalizetests_test_variants_normalize_to_canonical", + "tests_test_sms_py_testcase" + ], + "33": [ + "admin_panel_sms_rules", + "admin_panel_sms_rules_is_sms_enabled", + "admin_panel_sms_rules_rationale_1", + "admin_panel_sms_rules_rationale_6", + "django_core_management", + "tests_test_sms", + "tests_test_sms_enqueueidempotencytests", + "tests_test_sms_enqueueidempotencytests_setup", + "tests_test_sms_enqueueidempotencytests_test_multi_recipient_dedup_per_number", + "tests_test_sms_enqueueidempotencytests_test_same_dedup_key_creates_one_row", + "tests_test_sms_rationale_1", + "tests_test_sms_ruletests", + "tests_test_sms_ruletests_test_custom_type_always_enabled", + "tests_test_sms_ruletests_test_enqueue_respects_opt_out", + "tests_test_sms_ruletests_test_opt_out_model", + "unittest" + ], + "34": [ + "tests_test_sms_rationale_694", + "tests_test_sms_rationale_710", + "tests_test_sms_rationale_734", + "tests_test_sms_zerozeroninetyeightwebservicetests", + "tests_test_sms_zerozeroninetyeightwebservicetests_patched_client", + "tests_test_sms_zerozeroninetyeightwebservicetests_provider", + "tests_test_sms_zerozeroninetyeightwebservicetests_test_delivery_query_error_returns_none", + "tests_test_sms_zerozeroninetyeightwebservicetests_test_delivery_state_is_classified", + "tests_test_sms_zerozeroninetyeightwebservicetests_test_error_code_is_translated_to_persian", + "tests_test_sms_zerozeroninetyeightwebservicetests_test_invalid_number_never_reaches_the_service", + "tests_test_sms_zerozeroninetyeightwebservicetests_test_missing_credentials_is_a_config_error", + "tests_test_sms_zerozeroninetyeightwebservicetests_test_missing_panel_uses_sender_as_fallback", + "tests_test_sms_zerozeroninetyeightwebservicetests_test_provider_is_registered_in_factory", + "tests_test_sms_zerozeroninetyeightwebservicetests_test_send_returns_smsid_and_uses_vendor_params", + "tests_test_sms_zerozeroninetyeightwebservicetests_test_send_uses_exact_wsdl_kwarg_names", + "tests_test_sms_zerozeroninetyeightwebservicetests_test_send_zero_is_accepted_without_id" + ], + "35": [ + "admin_panel_templates_admin_panel_basehesabdari", + "concept_url_admin_panel_actionlistusertoday", + "concept_url_admin_panel_bedehi", + "concept_url_admin_panel_entezar", + "concept_url_admin_panel_hesabdari", + "concept_url_admin_panel_hesabdari_residnashone", + "concept_url_admin_panel_index", + "concept_url_admin_panel_p_logout", + "concept_url_admin_panel_rezayat", + "concept_url_admin_panel_stores", + "static_admin_panel_bulma_min_css", + "static_admin_panel_datatables_datatables_min_js", + "static_admin_panel_kamadatepicker_min_js", + "static_admin_panel_persian-datepicker_min_js", + "static_admin_panel_style_css" + ], + "36": [ + "ripwire_readme_10_quality_evaluation", + "ripwire_readme_11_agent_integration", + "ripwire_readme_12_supported_languages_and_formats", + "ripwire_readme_13_performance", + "ripwire_readme_14_known_limits", + "ripwire_readme_15_documentation", + "ripwire_readme_16_improvement", + "ripwire_readme_17_license", + "ripwire_readme_1_purpose_and_function", + "ripwire_readme_2_system_requirements", + "ripwire_readme_5_command_families", + "ripwire_readme_7_accuracy_and_disclosure_rules", + "ripwire_readme_8_determinism", + "ripwire_readme_9_verification_and_quality_control", + "ripwire_readme_reference_guide" + ], + "37": [ + "admin_panel_templates_admin_panel_customeridbackup_html", + "admin_panel_templates_admin_panel_customeridbackup_html_all_products", + "admin_panel_templates_admin_panel_customeridbackup_html_buildorderlist", + "admin_panel_templates_admin_panel_customeridbackup_html_orderlist", + "api_endpoint_panel_customer_del", + "api_endpoint_panel_productmojodi", + "concept_permission", + "django_template_filter_tojalali", + "django_template_tag_custom_tags", + "django_url_admin_panel_customeredit", + "django_url_admin_panel_customerfactors", + "django_url_admin_panel_newfactor", + "django_url_admin_panel_productsjson", + "js_library_pdatepicker" + ], + "38": [ + "admin_panel_templates_admin_panel_hesabdaricheckpass_html", + "admin_panel_templates_admin_panel_hesabdariresidnashode", + "admin_panel_urls_customernew", + "admin_panel_urls_pardakhtnesiye", + "admin_panel_urls_passcheck", + "concept_all_sarresid", + "django_context_variable_mandehesabkp", + "django_permissions_admin_panel_viewreportmali", + "javascript_pdatepicker", + "lib_jquery", + "lib_pdatepicker", + "templatetags_custom_tags", + "templatetags_custom_tags_tocurrency", + "templatetags_custom_tags_tojalali" + ], + "39": [ + "argparse", + "direntry", + "namespace", + "project_tree", + "project_tree_is_excluded_file", + "project_tree_main", + "project_tree_non_negative_integer", + "project_tree_parse_args", + "project_tree_rationale_1", + "project_tree_rationale_110", + "project_tree_rationale_91", + "project_tree_sorted_entries", + "project_tree_warn", + "typing" + ], + "40": [ + "path", + "project_tree_counts", + "project_tree_counts_add_file", + "project_tree_format_header", + "project_tree_generate_snapshot", + "project_tree_normalized_path", + "project_tree_rationale_145", + "project_tree_rationale_176", + "project_tree_rationale_192", + "project_tree_rationale_67", + "project_tree_rationale_97", + "project_tree_write_tree", + "project_tree_write_tree_visit", + "textio" + ], + "41": [ + "tests_test_sms_rationale_1007", + "tests_test_sms_rationale_1018", + "tests_test_sms_rationale_965", + "tests_test_sms_templatespageuitests", + "tests_test_sms_templatespageuitests_get", + "tests_test_sms_templatespageuitests_test_editor_config_is_valid_json", + "tests_test_sms_templatespageuitests_test_editor_uses_a_dedicated_text_class_per_type", + "tests_test_sms_templatespageuitests_test_every_field_cell_starts_with_its_own_label", + "tests_test_sms_templatespageuitests_test_invalid_timing_also_returns_to_the_same_card", + "tests_test_sms_templatespageuitests_test_layout_css_is_actually_rendered_in_the_page", + "tests_test_sms_templatespageuitests_test_only_one_card_is_open_initially", + "tests_test_sms_templatespageuitests_test_page_renders_a_card_per_sms_type", + "tests_test_sms_templatespageuitests_test_preview_substitutes_sample_values", + "tests_test_sms_templatespageuitests_test_save_redirects_back_to_the_same_card" + ], + "42": [ + "0098sms", + "0098sms_جدول_کدهای_خطای_لینک_ارتباطی", + "0098sms_جدول_کدهای_خطای_وب_سرویس_و_وضعیت_های_دلیوری", + "0098sms_راهنمای_استفاده_از_web_service", + "0098sms_راهنمای_استفاده_از_لینک_ارتباطی_url_get", + "0098sms_راهنمای_استفاده_از_لینک_ارتباطی_و_وب_سرویس_سامانه_پیامک_0098vip_com_و_0098sms_com", + "0098sms_فهرست_مطالب", + "0098sms_مقدمات_اتصال", + "0098sms_نمونه_کدهای_پیاده_سازی", + "0098sms_پارامترهای_ورودی_توابع_وب_سرویس", + "0098sms_۱_نمونه_کدهای_c_asp_net", + "0098sms_۲_نمونه_کدهای_php", + "0098sms_۳_نمونه_کد_python" + ], + "43": [ + "admin_models", + "admin_models_customer", + "admin_models_factor", + "admin_models_factoriteam", + "admin_models_phonenumber", + "admin_models_product", + "admin_models_productcategory", + "admin_models_productinstore", + "admin_models_storeroom", + "admin_models_visitqueue", + "admin_panel_management_commands_send_daily_report_command", + "admin_panel_management_commands_send_daily_report_command_handle", + "admin_panel_management_commands_send_daily_report_py_basecommand" + ], + "44": [ + "admin_panel_migrations_0056_auto_20260731_1824", + "admin_panel_migrations_0056_auto_20260731_1824_migration", + "admin_panel_models_finance", + "admin_panel_models_finance_articletype", + "admin_panel_models_finance_sarresid", + "admin_panel_views_auth", + "admin_panel_views_auth_p_login", + "admin_panel_views_auth_p_logout", + "admin_views", + "django_contrib_auth", + "django_core_validators", + "django_http", + "django_shortcuts" + ], + "45": [ + "admin_panel_templates_admin_panel_base", + "admin_panel_templates_admin_panel_basestore", + "admin_panel_templates_admin_panel_customerbase", + "admin_panel_templates_admin_panel_customerbasenew", + "admin_panel_templates_admin_panel_customerid", + "admin_panel_templates_admin_panel_customernew2", + "admin_panel_templates_admin_panel_customers", + "concept_django_custom_tags", + "concept_url_admin_panel_newfactor", + "concept_url_admin_panel_operations", + "concept_url_admin_panel_productadd", + "concept_url_admin_panel_store", + "concept_url_admin_panel_store_transfers" + ], + "46": [ "admin_panel_templates_admin_panel_bedehbk", "admin_panel_templates_admin_panel_customeraction", "admin_panel_templates_admin_panel_customeractioninc_html", "admin_panel_templates_admin_panel_customersbk_html", - "admin_panel_url_customernew", - "admin_panel_urls_customernew", "admin_panel_urls_customernewaction", "concept_action_model", "concept_datatables", @@ -398,26 +1169,220 @@ "library_datatables", "library_pdatepicker" ], - "13": [ - "admin_models_user", - "admin_panel_models_action", - "admin_panel_views_actionlist", - "admin_panel_views_actionlistall", - "admin_panel_views_actionlisttoday", - "admin_panel_views_actionlistuser", - "admin_panel_views_actionlistusertoday", - "admin_panel_views_customeraction", - "admin_panel_views_customereditaction", - "admin_panel_views_customernazar", - "admin_panel_views_customernewaction", - "admin_panel_views_rezayat_dashboard" + "47": [ + "integrations_sms_console_console_write", + "integrations_sms_console_consolesmsprovider", + "integrations_sms_console_consolesmsprovider_init", + "integrations_sms_console_consolesmsprovider_send_message", + "integrations_sms_console_consolesmsprovider_send_otp", + "integrations_sms_console_rationale_13", + "tests_test_sms_consoleencodingtests", + "tests_test_sms_consoleencodingtests_cp1252_stdout", + "tests_test_sms_consoleencodingtests_setup", + "tests_test_sms_consoleencodingtests_test_console_provider_never_raises_on_unencodable_text", + "tests_test_sms_consoleencodingtests_test_worker_marks_sent_when_console_cannot_encode", + "tests_test_sms_rationale_493", + "tests_test_sms_rationale_505" ], - "14": [ + "48": [ + "tests_test_sms_rationale_610", + "tests_test_sms_rationale_633", + "tests_test_sms_rationale_651", + "tests_test_sms_rationale_667", + "tests_test_sms_zerozeroninetyeightlinktests", + "tests_test_sms_zerozeroninetyeightlinktests_mock_get", + "tests_test_sms_zerozeroninetyeightlinktests_provider", + "tests_test_sms_zerozeroninetyeightlinktests_test_credentials_come_from_the_database_config", + "tests_test_sms_zerozeroninetyeightlinktests_test_explicit_error_body_is_a_clean_failure", + "tests_test_sms_zerozeroninetyeightlinktests_test_invalid_number_never_reaches_the_provider", + "tests_test_sms_zerozeroninetyeightlinktests_test_missing_credentials_is_a_config_error", + "tests_test_sms_zerozeroninetyeightlinktests_test_request_matches_vendor_contract_and_uses_local_number", + "tests_test_sms_zerozeroninetyeightlinktests_test_timeout_is_unconfirmed_not_silent_success" + ], + "49": [ + "admin_panel_management_commands_sms_worker_command", + "admin_panel_management_commands_sms_worker_command_add_arguments", + "admin_panel_management_commands_sms_worker_command_drain_batch", + "admin_panel_management_commands_sms_worker_command_emit", + "admin_panel_management_commands_sms_worker_command_handle", + "admin_panel_management_commands_sms_worker_command_handle_signal", + "admin_panel_management_commands_sms_worker_command_init", + "admin_panel_management_commands_sms_worker_command_send", + "admin_panel_management_commands_sms_worker_command_sleep_interruptible", + "admin_panel_management_commands_sms_worker_py_basecommand", + "admin_panel_management_commands_sms_worker_rationale_129", + "admin_panel_management_commands_sms_worker_rationale_61" + ], + "50": [ + "admin_panel_services_get_product_current_stock", + "admin_panel_services_rationale_40", + "product", + "tests_factories_current_stock", + "tests_factories_rationale_99", + "tests_test_inventory_productinstoremathtests", + "tests_test_inventory_productinstoremathtests_test_deleting_a_row_creates_history_and_reduces_stock", + "tests_test_inventory_productinstoremathtests_test_editing_a_row_updates_stock_and_history", + "tests_test_inventory_productinstoremathtests_test_garbage_numeric_values_are_coerced_to_zero", + "tests_test_inventory_productinstoremathtests_test_initial_in_records_history_and_stock", + "tests_test_inventory_productinstoremathtests_test_multiple_movements_accumulate_correctly", + "tests_test_inventory_py_testcase" + ], + "51": [ + "fix_customer_phones", + "fix_customer_phones_اجرا_روی_سرور_اصلی_production", + "fix_customer_phones_اجرای_دستور", + "fix_customer_phones_اصلاح_دستی_شماره_های_قدیمی_مشتریان", + "fix_customer_phones_راه_جایگزین_مهاجرت_migration", + "fix_customer_phones_قاعده_پاک_سازی", + "fix_customer_phones_مشاهده_نتیجه", + "fix_customer_phones_نکته_ها", + "fix_customer_phones_چرا", + "fix_customer_phones_۱_اول_شبیه_سازی_بدون_هیچ_تغییری", + "fix_customer_phones_۲_اجرای_واقعی", + "fix_customer_phones_۳_اجرای_دوباره_برای_اطمینان_اختیاری" + ], + "52": [ + "admin_panel_templatetags_custom_tags_rationale_13", + "admin_panel_templatetags_custom_tags_tojalalidt", + "tests_test_sms_queuestatstests", + "tests_test_sms_queuestatstests_item", + "tests_test_sms_queuestatstests_test_customer_name_links_to_customer_page", + "tests_test_sms_queuestatstests_test_dates_render_as_jalali_datetime", + "tests_test_sms_queuestatstests_test_filtered_count_reflects_active_filter", + "tests_test_sms_queuestatstests_test_jalali_datetime_filter_handles_none_and_localizes", + "tests_test_sms_queuestatstests_test_list_is_paginated", + "tests_test_sms_queuestatstests_test_page_shows_total_and_per_status_counts", + "tests_test_sms_rationale_1604" + ], + "53": [ + "admin_panel_templatetags_remove_q_p", + "admin_panel_templatetags_remove_q_p_rationale_7", + "admin_panel_templatetags_remove_q_p_remove_query_param", + "csv", + "django", + "django_db_models_functions", + "filter", + "ghost_hunter", + "ghost_hunter_run_360_audit", + "reconciliation_tool", + "reconciliation_tool_run_audit" + ], + "54": [ + "agents", + "agents_1_codebase_navigation_analysis_ripwire_vs_graphify", + "agents_2_dynamic_help_self_correction_staleness", + "agents_3_git_operations_safety", + "agents_4_development_server_lifecycle", + "agents_5_environment_frontend_pwa_safety", + "agents_6_end_to_end_browser_tests_playwright", + "agents_project_operating_agent_guidelines", + "agents_routing_gate_pipeline", + "claude", + "ripwire_readme" + ], + "55": [ + "integrations_sms_base_smsdeliveryresult", + "tests_test_sms_rationale_828", + "tests_test_sms_smsdeliverycommandtests", + "tests_test_sms_smsdeliverycommandtests_row", + "tests_test_sms_smsdeliverycommandtests_run", + "tests_test_sms_smsdeliverycommandtests_setup", + "tests_test_sms_smsdeliverycommandtests_test_delivered_row_is_updated_and_becomes_final", + "tests_test_sms_smsdeliverycommandtests_test_final_row_is_not_queried_again", + "tests_test_sms_smsdeliverycommandtests_test_non_webservice_provider_is_a_noop", + "tests_test_sms_smsdeliverycommandtests_test_none_result_leaves_row_untouched_for_next_round", + "tests_test_sms_smsdeliverycommandtests_test_row_without_provider_id_is_skipped" + ], + "56": [ + "integrations_sms_behin", + "integrations_sms_behin_behinsmsprovider", + "integrations_sms_behin_behinsmsprovider_client", + "integrations_sms_behin_behinsmsprovider_configuration_error", + "integrations_sms_behin_behinsmsprovider_get_credit", + "integrations_sms_behin_behinsmsprovider_init", + "integrations_sms_behin_behinsmsprovider_send_many", + "integrations_sms_behin_behinsmsprovider_send_message", + "integrations_sms_behin_rationale_10", + "zeep", + "zeep_helpers" + ], + "57": [ + "admin_init", + "admin_panel_tests", + "admin_tests", + "admin_urls", + "django_test", + "django_urls", + "tests_test_models_contract", + "tests_test_models_contract_rationale_1", + "tests_test_urls_contract", + "tests_test_urls_contract_rationale_1" + ], + "58": [ + "admin_panel_data_customer", + "admin_panel_data_factor", + "admin_panel_data_sarresid", + "admin_panel_templates_admin_panel_hesabdaricheckpassfail_html", + "admin_panel_url_customernew", + "admin_panel_url_pardakhtnesiye", + "admin_panel_url_passcheck", + "django_permission_viewreportmali", + "javascript_library_jquery", + "javascript_library_pdatepicker" + ], + "59": [ + "admin_panel_management_commands_gt_esh_command", + "admin_panel_management_commands_gt_esh_command_add_arguments", + "admin_panel_management_commands_gt_esh_command_execute_by_immediate_chunks", + "admin_panel_management_commands_gt_esh_command_execute_hybrid_migration", + "admin_panel_management_commands_gt_esh_command_handle", + "admin_panel_management_commands_gt_esh_command_process_single_chunk", + "admin_panel_management_commands_gt_esh_py_basecommand", + "admin_panel_management_commands_gt_esh_rationale_115", + "admin_panel_management_commands_gt_esh_rationale_123", + "admin_panel_management_commands_gt_esh_rationale_89" + ], + "60": [ + "admin_panel_management_commands_sms_delivery_command", + "admin_panel_management_commands_sms_delivery_command_add_arguments", + "admin_panel_management_commands_sms_delivery_command_drain_batch", + "admin_panel_management_commands_sms_delivery_command_emit", + "admin_panel_management_commands_sms_delivery_command_handle", + "admin_panel_management_commands_sms_delivery_command_handle_signal", + "admin_panel_management_commands_sms_delivery_command_init", + "admin_panel_management_commands_sms_delivery_command_sleep_interruptible", + "admin_panel_management_commands_sms_delivery_py_basecommand", + "admin_panel_management_commands_sms_delivery_rationale_64" + ], + "61": [ + "admin_panel_management_commands_sqlite_db_backup_command", + "admin_panel_management_commands_sqlite_db_backup_command_cleanup_resources", + "admin_panel_management_commands_sqlite_db_backup_command_handle", + "admin_panel_management_commands_sqlite_db_backup_command_handle_critical_failure", + "admin_panel_management_commands_sqlite_db_backup_command_handle_progress_callback", + "admin_panel_management_commands_sqlite_db_backup_command_init", + "admin_panel_management_commands_sqlite_db_backup_command_signal_handler", + "admin_panel_management_commands_sqlite_db_backup_py_basecommand", + "admin_panel_management_commands_sqlite_db_backup_rationale_158", + "admin_panel_management_commands_sqlite_db_backup_rationale_30" + ], + "62": [ + "admin_panel_migrations_0061_sms_timing_and_primary", + "admin_panel_migrations_0061_sms_timing_and_primary_backfill_primary_phones", + "admin_panel_migrations_0061_sms_timing_and_primary_migration", + "admin_panel_migrations_0061_sms_timing_and_primary_noop", + "admin_panel_migrations_0061_sms_timing_and_primary_normalize", + "admin_panel_migrations_0061_sms_timing_and_primary_normalize_template_timing", + "admin_panel_migrations_0061_sms_timing_and_primary_rationale_1", + "admin_panel_migrations_0061_sms_timing_and_primary_rationale_12", + "admin_panel_migrations_0061_sms_timing_and_primary_rationale_19", + "admin_panel_migrations_0061_sms_timing_and_primary_rationale_40" + ], + "63": [ "admin_panel_templates_admin_panel_bedehi_html", - "admin_panel_templates_admin_panel_hesabdaricheckpass_html", - "admin_panel_urls_passcheck", - "concept_all_sarresid", - "django_context_variable_mandehesabkp", + "concept_articl_model", + "concept_customer_model", + "django_context_variable_articls", "django_templatetags_tojalali", "django_urls_admin_panel_bedehi", "django_urls_admin_panel_bedehikhadamat", @@ -425,76 +1390,41 @@ "django_urls_admin_panel_customer", "javascript_jquery" ], - "15": [ - "admin_admin", - "admin_panel_admin", - "admin_panel_admin_rationale_54", - "admin_panel_admin_rationale_75", - "admin_panel_admin_storeroomadmin", - "admin_panel_admin_storeroomadmin_get_owners_count", - "admin_panel_admin_storeroomownerinline", - "admin_panel_admin_storetransferrequestadmin", - "django_contrib", - "register" + "64": [ + "admin_panel_templates_admin_panel_customeridfactorbackup_html", + "admin_panel_templates_admin_panel_customeridfactorbackup_html_js", + "admin_panel_urls_customer", + "admin_panel_urls_customeredit", + "concept_factor", + "concept_product", + "concept_productcategory", + "concept_user", + "django_contrib_staticfiles_templatetags_static", + "panel_urls_productmojodi" ], - "16": [ - "admin_models", - "admin_models_customer", - "admin_models_factor", - "admin_models_factoriteam", - "admin_models_phonenumber", - "admin_models_product", - "admin_models_productcategory", - "admin_models_productinstore", - "admin_models_storeroom", - "admin_models_visitqueue" + "65": [ + "admin_panel_models_store", + "admin_panel_models_store_productstockhistory", + "admin_panel_models_store_productstockhistory_meta", + "admin_panel_models_store_productstockhistory_str", + "admin_panel_models_store_rationale_34", + "admin_panel_models_store_storeroom", + "admin_panel_models_store_storeroom_meta", + "admin_panel_models_store_storeroom_str", + "admin_panel_models_store_storeroomowner" ], - "17": [ - "admin_panel_templates_admin_panel_customerbase", - "admin_panel_templates_admin_panel_customerid", - "admin_panel_views_customer_delete", - "admin_panel_views_customeredit", - "admin_panel_views_customerhesabdari", - "admin_panel_views_productsjson", - "concept_url_admin_panel_newfactor", - "django_contrib_auth_models_permission", - "django_templatetags_static", - "javascript_pdatepicker" + "66": [ + "check_finance", + "check_finance_check_financial_discrepancy", + "django_core_asgi", + "django_core_wsgi", + "khatibi_asgi", + "khatibi_asgi_rationale_1", + "khatibi_wsgi", + "khatibi_wsgi_rationale_1", + "os" ], - "18": [ - "admin_panel_data_customer", - "admin_panel_data_factor", - "admin_panel_data_sarresid", - "admin_panel_templates_admin_panel_hesabdaricheckpassfail_html", - "admin_panel_url_pardakhtnesiye", - "admin_panel_url_passcheck", - "django_permission_viewreportmali", - "javascript_library_jquery", - "javascript_library_pdatepicker" - ], - "19": [ - "admin_panel_models_storeroom", - "admin_panel_models_storeroom_meta", - "admin_panel_models_storeroom_str", - "admin_panel_views_newoperation", - "admin_panel_views_newstore", - "admin_panel_views_productmojodi", - "admin_panel_views_rationale_1281", - "admin_panel_views_store_transfers", - "admin_panel_views_stores" - ], - "20": [ - "admin_panel_views_customerfactors", - "admin_panel_views_inventoryexception", - "admin_panel_views_newfactor2", - "admin_panel_views_parse_jalali_date", - "admin_panel_views_parse_timestamp", - "admin_panel_views_rationale_39", - "admin_panel_views_rationale_53", - "admin_panel_views_rationale_65", - "exception" - ], - "21": [ + "67": [ "copilot_chat", "github_copilot_instructions", "github_copilot_instructions_graphify", @@ -505,36 +1435,89 @@ "graphify_path", "graphify_query" ], - "22": [ - "admin_panel_templates_admin_panel_hesabdariresidnashode", - "admin_panel_urls_pardakhtnesiye", - "django_permissions_admin_panel_viewreportmali", - "lib_jquery", - "lib_pdatepicker", - "templatetags_custom_tags", - "templatetags_custom_tags_tocurrency", - "templatetags_custom_tags_tojalali" + "68": [ + "tests_test_factors_factorsoftdeletetests", + "tests_test_factors_factorsoftdeletetests_setup", + "tests_test_factors_factorsoftdeletetests_test_delete_is_soft", + "tests_test_factors_newfactor2viewtests", + "tests_test_factors_newfactor2viewtests_test_new_factor2_creates_factor_article_and_stock_out", + "tests_test_factors_newfactor2viewtests_test_new_factor2_rejects_get", + "tests_test_factors_newfactor2viewtests_test_new_factor2_rejects_oversell_without_creating_factor", + "tests_test_factors_py_testcase", + "tests_test_factors_rationale_47" ], - "23": [ - "admin_panel_views_api_check_sabt_inquiry", - "admin_panel_views_inventory_movements_report", - "admin_panel_views_operation", - "admin_panel_views_rationale_2875", - "admin_panel_views_rationale_313", - "admin_panel_views_rationale_43", - "admin_panel_views_run_bpi_cheque_inquiry_logic", - "admin_panel_views_safe_int" + "69": [ + "tests_test_sms_queuereviewtests", + "tests_test_sms_queuereviewtests_item", + "tests_test_sms_queuereviewtests_test_approve_view_ignores_non_pending_items", + "tests_test_sms_queuereviewtests_test_approve_view_moves_item_to_approved", + "tests_test_sms_queuereviewtests_test_bulk_approve_all_approves_only_pending", + "tests_test_sms_queuereviewtests_test_bulk_delete_selected_removes_only_chosen", + "tests_test_sms_queuereviewtests_test_delete_view_removes_item_from_queue", + "tests_test_sms_queuereviewtests_test_scan_enqueue_lands_in_pending_review", + "tests_test_sms_rationale_1403" ], - "24": [ - "admin_apps", - "admin_apps_adminconfig", - "admin_apps_py_appconfig", - "admin_panel_apps", - "admin_panel_apps_adminpanelconfig", - "admin_panel_apps_py_appconfig", - "django_apps" + "70": [ + "admin_panel_migrations_0058_seed_sms_templates", + "admin_panel_migrations_0058_seed_sms_templates_backfill_primary_phones", + "admin_panel_migrations_0058_seed_sms_templates_migration", + "admin_panel_migrations_0058_seed_sms_templates_noop", + "admin_panel_migrations_0058_seed_sms_templates_rationale_1", + "admin_panel_migrations_0058_seed_sms_templates_rationale_90", + "admin_panel_migrations_0058_seed_sms_templates_seed_templates", + "admin_panel_migrations_0058_seed_sms_templates_unseed_templates" ], - "25": [ + "71": [ + "admin_panel_templates_admin_panel_customerfactorbackup", + "admin_panel_templates_admin_panel_entezar_html", + "concept_customer_data", + "concept_kamadatepicker", + "concept_url_admin_panel_customers", + "concept_url_panel_customer_factor_del", + "django_templatetags_custom_tags", + "django_templatetags_custom_tags_tojalali" + ], + "72": [ + "admin_panel_templates_admin_panel_customernewbackup_html", + "admin_panel_urls_newfactor", + "concept_category", + "concept_client_side_form_logic", + "concept_customer", + "django_concepts_csrf_token", + "django_template_tags_custom_tags", + "django_template_tags_static" + ], + "73": [ + "integrations_sms_http_httpsmsprovider", + "integrations_sms_http_httpsmsprovider_init", + "tests_test_sms_httpproviderrobustnesstests", + "tests_test_sms_httpproviderrobustnesstests_provider", + "tests_test_sms_httpproviderrobustnesstests_test_invalid_recipient_is_rejected_without_any_request", + "tests_test_sms_httpproviderrobustnesstests_test_non_json_response_becomes_a_clean_error", + "tests_test_sms_rationale_573", + "tests_test_sms_rationale_600" + ], + "74": [ + "tests_test_models_contract_modelspackagecontracttests", + "tests_test_models_contract_modelspackagecontracttests_test_all_models_importable_from_package", + "tests_test_models_contract_modelspackagecontracttests_test_db_table_names_are_unchanged_defaults", + "tests_test_models_contract_modelspackagecontracttests_test_models_are_registered_under_admin_panel_app", + "tests_test_models_contract_modelspackagecontracttests_test_models_package_has_no_duplicate_registration", + "tests_test_models_contract_modelspackagecontracttests_test_star_import_leak_names_still_present", + "tests_test_models_contract_py_simpletestcase", + "tests_test_models_contract_rationale_67" + ], + "75": [ + "tests_test_view_smoke_py_testcase", + "tests_test_view_smoke_rationale_96", + "tests_test_view_smoke_viewsmoketests", + "tests_test_view_smoke_viewsmoketests_endpoints", + "tests_test_view_smoke_viewsmoketests_now_ms", + "tests_test_view_smoke_viewsmoketests_setup", + "tests_test_view_smoke_viewsmoketests_test_every_endpoint_responds_without_server_error", + "tests_test_view_smoke_viewsmoketests_url" + ], + "76": [ "admin_panel_migrations_0002_auto_20220114_1616", "admin_panel_migrations_0002_auto_20220114_1616_migration", "admin_panel_migrations_0003_auto_20220115_1401", @@ -543,457 +1526,653 @@ "admin_panel_migrations_0031_article_sarresid_migration", "django_utils_timezone" ], - "26": [ - "entrypoint", - "entrypoint_sh__entry", - "manage", - "manage_main", - "manage_rationale_2", - "manage_rationale_8", - "sys" + "77": [ + "admin_panel_models_customers", + "admin_panel_models_customers_customer", + "admin_panel_models_customers_customer_delete", + "admin_panel_models_customers_customer_meta", + "admin_panel_models_customers_customer_str", + "admin_panel_models_customers_phonenumber", + "admin_panel_models_customers_phonenumber_str" ], - "27": [ - "admin_init", - "admin_urls", - "admin_views", - "admin_views_index", - "django_http", - "django_shortcuts" + "78": [ + "simpletestcase", + "tests_test_urls_contract_rationale_165", + "tests_test_urls_contract_urlcontracttests", + "tests_test_urls_contract_urlcontracttests_all_names", + "tests_test_urls_contract_urlcontracttests_test_all_view_callables_are_importable", + "tests_test_urls_contract_urlcontracttests_test_every_url_reverses", + "tests_test_urls_contract_urlcontracttests_test_url_name_snapshot_unchanged" ], - "28": [ - "admin_panel_init", - "admin_panel_urls", - "django_conf_urls_static", - "django_urls", - "khatibi_urls", - "khatibi_urls_rationale_1" + "79": [ + "admin_panel_migrations_0065_smsproviderconfig_test_message", + "admin_panel_migrations_0065_smsproviderconfig_test_message_migration", + "admin_panel_migrations_0065_smsproviderconfig_test_message_noop", + "admin_panel_migrations_0065_smsproviderconfig_test_message_rationale_7", + "admin_panel_migrations_0065_smsproviderconfig_test_message_scrub_brand" ], - "29": [ - "admin_panel_templates_admin_panel_entezar_html", - "concept_customer_data", - "concept_kamadatepicker", - "django_templatetags_custom_tags", - "django_templatetags_custom_tags_tojalali" + "80": [ + "admin_panel_migrations_0066_smsdedup", + "admin_panel_migrations_0066_smsdedup_backfill_keys", + "admin_panel_migrations_0066_smsdedup_migration", + "admin_panel_migrations_0066_smsdedup_noop", + "admin_panel_migrations_0066_smsdedup_rationale_7" ], - "30": [ - "admin_panel_migrations_0056_auto_20260731_1824", - "admin_panel_migrations_0056_auto_20260731_1824_migration", - "django_core_validators" + "81": [ + "ripwire_readme_3_1_install_a_prebuilt_binary", + "ripwire_readme_3_2_build_from_source", + "ripwire_readme_3_3_install_the_agent_skills", + "ripwire_readme_3_4_register_the_mcp_server_optional", + "ripwire_readme_3_installation" ], - "31": [ + "82": [ + "ripwire_readme_in_the_numbers", + "ripwire_readme_in_the_output", + "ripwire_readme_in_the_tests", + "ripwire_readme_the_honesty_contract", + "ripwire_readme_what_it_misses_and_what_to_run_next" + ], + "83": [ + "admin_panel_migrations_0059_sms_flow_control", + "admin_panel_migrations_0059_sms_flow_control_migration", + "admin_panel_migrations_0059_sms_flow_control_rationale_7", + "admin_panel_migrations_0059_sms_flow_control_stamp_scan_since" + ], + "84": [ + "admin_panel_migrations_0060_sms_permission_label", + "admin_panel_migrations_0060_sms_permission_label_migration", + "admin_panel_migrations_0060_sms_permission_label_rationale_7", + "admin_panel_migrations_0060_sms_permission_label_rename_manage_sms_permission" + ], + "85": [ + "khatibi_production_settings", + "khatibi_settings", + "khatibi_settings_rationale_1", + "pathlib" + ], + "86": [ + "ripwire_readme_6_1_output_example", + "ripwire_readme_6_2_exit_codes", + "ripwire_readme_6_3_json_output", + "ripwire_readme_6_output_format" + ], + "87": [ + "ripwire_readme_against_other_tools", + "ripwire_readme_measured", + "ripwire_readme_what_it_saves_you_in_tokens", + "ripwire_readme_where_its_own_cycles_go_hardware_counters_per_scope" + ], + "88": [ + "0098sms_md", + "admin_panel_templates_admin_panel_sms_settings_html", + "requirements_txt" + ], + "89": [ + "admin_panel_models_system", + "admin_panel_models_system_cachetable", + "admin_panel_models_system_cachetable_str" + ], + "90": [ "admin_panel_templates_admin_panel_customersbk_html_createcs", "admin_panel_templates_admin_panel_customersbk_html_openrow", "concept_customer_modal_ui" ], - "32": [ - "admin_panel_tests", - "admin_tests", - "django_test" + "91": [ + "admin_views_index", + "ripwire_readme_nest_is_a_max_so_it_cannot_tell_a_long_function_from_a_tangled_one", + "ripwire_readme_the_quality_panel" ], - "33": [ - "concept_articl_model", - "concept_customer_model", - "django_context_variable_articls" + "92": [ + "docker_compose_yml", + "sms_delivery", + "sms_worker" ], - "34": [ + "93": [ + "ripwire_readme_1_optional_register_the_mcp_server", + "ripwire_readme_2_install_the_skills", + "ripwire_readme_set_it_up_in_your_coding_agent" + ], + "94": [ + "ripwire_readme_4_1_make_the_first_map", + "ripwire_readme_4_2_use_the_common_commands", + "ripwire_readme_4_first_use" + ], + "95": [ "admin_panel_migrations_0005_auto_20220121_2054", "admin_panel_migrations_0005_auto_20220121_2054_migration" ], - "35": [ + "96": [ "admin_panel_migrations_0006_auto_20220121_2100", "admin_panel_migrations_0006_auto_20220121_2100_migration" ], - "36": [ + "97": [ "admin_panel_migrations_0009_remove_storeroom_user", "admin_panel_migrations_0009_remove_storeroom_user_migration" ], - "37": [ - "admin_panel_migrations_0010_auto_20220122_1546", - "admin_panel_migrations_0010_auto_20220122_1546_migration" + "98": [ + "admin_panel_migrations_0021_alter_article_type", + "admin_panel_migrations_0021_alter_article_type_migration" ], - "38": [ - "admin_panel_migrations_0025_action_actiontype", - "admin_panel_migrations_0025_action_actiontype_migration" - ], - "39": [ - "admin_panel_migrations_0028_action_grade", - "admin_panel_migrations_0028_action_grade_migration" - ], - "40": [ - "admin_panel_migrations_0030_auto_20220414_1047", - "admin_panel_migrations_0030_auto_20220414_1047_migration" - ], - "41": [ + "99": [ "admin_panel_migrations_0032_article_paystatus", "admin_panel_migrations_0032_article_paystatus_migration" ], - "42": [ - "admin_panel_migrations_0033_article_nahvepardakht", - "admin_panel_migrations_0033_article_nahvepardakht_migration" + "100": [ + "admin_panel_migrations_0035_alter_article_sarresid", + "admin_panel_migrations_0035_alter_article_sarresid_migration" ], - "43": [ - "admin_panel_migrations_0039_customer_last_bazdid_dt", - "admin_panel_migrations_0039_customer_last_bazdid_dt_migration" + "101": [ + "admin_panel_migrations_0037_sarresid_sarresiddt", + "admin_panel_migrations_0037_sarresid_sarresiddt_migration" ], - "44": [ - "admin_panel_migrations_0041_auto_20240216_1607", - "admin_panel_migrations_0041_auto_20240216_1607_migration" + "102": [ + "admin_panel_migrations_0040_customer_laghv_bazdid", + "admin_panel_migrations_0040_customer_laghv_bazdid_migration" ], - "45": [ - "admin_panel_migrations_0044_sarresid_desc", - "admin_panel_migrations_0044_sarresid_desc_migration" + "103": [ + "admin_panel_migrations_0047_alter_productinstore_movement_type", + "admin_panel_migrations_0047_alter_productinstore_movement_type_migration" ], - "46": [ - "admin_panel_migrations_0048_alter_productinstore_options", - "admin_panel_migrations_0048_alter_productinstore_options_migration" - ], - "47": [ + "104": [ "admin_panel_migrations_0049_alter_productinstore_options", "admin_panel_migrations_0049_alter_productinstore_options_migration" ], - "48": [ + "105": [ + "admin_panel_migrations_0062_sms_link_provider", + "admin_panel_migrations_0062_sms_link_provider_migration" + ], + "106": [ + "admin_panel_migrations_0064_sms_webservice_delivery", + "admin_panel_migrations_0064_sms_webservice_delivery_migration" + ], + "107": [ + "admin_panel_migrations_0068_auto_20261010_1305", + "admin_panel_migrations_0068_auto_20261010_1305_migration" + ], + "108": [ + "admin_panel_sms_init", + "admin_panel_sms_init_rationale_1" + ], + "109": [ + "admin_panel_templates_admin_panel_customer_phones_html", + "admin_panel_templates_admin_panel_customer_sms_html" + ], + "110": [ + "admin_panel_templates_admin_panel_sms_templates_html", + "screencapture_sms_templates" + ], + "111": [ "gemini", "gemini_graphify" ], - "49": [ + "112": [ "admin_migrations_init" ], - "50": [ + "113": [ + "admin_panel_apps_py_appconfig" + ], + "114": [ "admin_panel_customer_action_inc" ], - "51": [ - "admin_panel_customer_base_new" - ], - "52": [ + "115": [ "admin_panel_customer_customernazarinc" ], - "53": [ + "116": [ "admin_panel_customer_factor" ], - "54": [ + "117": [ "admin_panel_customer_hesabdari" ], - "55": [ + "118": [ "admin_panel_customer_id_factor" ], - "56": [ + "119": [ "admin_panel_customer_nazar" ], - "57": [ + "120": [ "admin_panel_management_commands_init" ], - "58": [ + "121": [ "admin_panel_management_init" ], - "59": [ + "122": [ "admin_panel_migrations_init" ], - "60": [ + "123": [ "admin_panel_templates_admin_panel_actionlist" ], - "61": [ + "124": [ "admin_panel_templates_admin_panel_base_html" ], - "62": [ + "125": [ "admin_panel_templates_admin_panel_customerNew" ], - "63": [ - "admin_panel_templates_admin_panel_customerNew2" - ], - "64": [ + "126": [ "admin_panel_templates_admin_panel_customerRezayat" ], - "65": [ + "127": [ "admin_panel_templates_admin_panel_customerRezayatinc" ], - "66": [ + "128": [ "admin_panel_templates_admin_panel_customerSarresid" ], - "67": [ + "129": [ "admin_panel_templates_admin_panel_customerbasenew_html" ], - "68": [ - "admin_panel_templates_admin_panel_customers" - ], - "69": [ + "130": [ "admin_panel_templates_admin_panel_entezarbk" ], - "70": [ + "131": [ "admin_panel_templates_admin_panel_financial_dashboard" ], - "71": [ + "132": [ "admin_panel_templates_admin_panel_hesabdari" ], - "72": [ + "133": [ "admin_panel_templates_admin_panel_index" ], - "73": [ + "134": [ "admin_panel_templates_admin_panel_newOperation" ], - "74": [ + "135": [ "admin_panel_templates_admin_panel_operations" ], - "75": [ + "136": [ "admin_panel_templates_admin_panel_operationsbk" ], - "76": [ + "137": [ "admin_panel_templates_admin_panel_report" ], - "77": [ + "138": [ "admin_panel_templates_admin_panel_report_debtors_no_vosol" ], - "78": [ + "139": [ "admin_panel_templates_admin_panel_report_inventory_movements" ], - "79": [ + "140": [ "admin_panel_templates_admin_panel_report_rezayat" ], - "80": [ + "141": [ "admin_panel_templates_admin_panel_reportchecknesiye" ], - "81": [ + "142": [ "admin_panel_templates_admin_panel_rezayat" ], - "82": [ + "143": [ + "admin_panel_templates_admin_panel_sms_queue_html" + ], + "144": [ "admin_panel_templates_admin_panel_store" ], - "83": [ + "145": [ "admin_panel_templates_admin_panel_store_transfers" ], - "84": [ + "146": [ "admin_panel_templates_admin_panel_stores" ], - "85": [ + "147": [ "admin_panel_templatetags_custom_tags_tocurrency" ], - "86": [ + "148": [ "admin_panel_templatetags_init" ], - "87": [ + "149": [ "admin_panel_urls_newstore" ], - "88": [ + "150": [ "admin_panel_urls_processtransferaction" ], - "89": [ + "151": [ "admin_panel_urls_rezayatedit" ], - "90": [ + "152": [ + "admin_panel_views_customer_delete" + ], + "153": [ + "admin_panel_views_customers_py_permission_required" + ], + "154": [ + "django_contrib_auth_models_permission" + ], + "155": [ + "django_templatetags_static" + ], + "156": [ + "fix_customer_phones_md" + ], + "157": [ + "integrations_bale_init" + ], + "158": [ + "integrations_init" + ], + "159": [ + "integrations_sms_init" + ], + "160": [ "khatibi_init" ], - "91": [ + "161": [ "requirements2_txt" ], - "92": [ - "requirements_txt" + "162": [ + "tests_init" + ], + "163": [ + "tests_test_urls_contract_py_simpletestcase" ] }, "cohesion": { - "0": 0.0673758865248227, - "1": 0.07123034227567067, - "2": 0.06606606606606606, - "3": 0.06060606060606061, - "4": 0.06653225806451613, - "5": 0.08262108262108261, - "6": 0.12923076923076923, - "7": 0.08333333333333333, - "8": 0.08695652173913043, - "9": 0.08695652173913043, - "10": 0.1111111111111111, - "11": 0.14705882352941177, - "12": 0.1523809523809524, - "13": 0.3181818181818182, - "14": 0.2, - "15": 0.2222222222222222, - "16": 0.2, - "17": 0.2, - "18": 0.2222222222222222, - "19": 0.2222222222222222, - "20": 0.2222222222222222, - "21": 0.2222222222222222, - "22": 0.32142857142857145, - "23": 0.25, - "24": 0.2857142857142857, - "25": 0.2857142857142857, - "26": 0.2857142857142857, - "27": 0.3333333333333333, - "28": 0.4666666666666667, - "29": 0.4, - "30": 0.6666666666666666, - "31": 0.6666666666666666, - "32": 0.6666666666666666, - "33": 0.6666666666666666, - "34": 1.0, - "35": 1.0, - "36": 1.0, - "37": 1.0, - "38": 1.0, - "39": 1.0, - "40": 1.0, - "41": 1.0, - "42": 1.0, - "43": 1.0, - "44": 1.0, - "45": 1.0, - "46": 1.0, - "47": 1.0, - "48": 1.0, - "49": 1.0, - "50": 1.0, - "51": 1.0, - "52": 1.0, - "53": 1.0, - "54": 1.0, - "55": 1.0, - "56": 1.0, - "57": 1.0, - "58": 1.0, - "59": 1.0, - "60": 1.0, - "61": 1.0, - "62": 1.0, - "63": 1.0, - "64": 1.0, - "65": 1.0, - "66": 1.0, - "67": 1.0, - "68": 1.0, - "69": 1.0, - "70": 1.0, - "71": 1.0, - "72": 1.0, - "73": 1.0, - "74": 1.0, - "75": 1.0, - "76": 1.0, - "77": 1.0, - "78": 1.0, - "79": 1.0, - "80": 1.0, - "81": 1.0, - "82": 1.0, - "83": 1.0, - "84": 1.0, - "85": 1.0, - "86": 1.0, - "87": 1.0, - "88": 1.0, - "89": 1.0, - "90": 1.0, - "91": 1.0, - "92": 1.0 + "0": 0.06144393241167435, + "1": 0.0576271186440678, + "2": 0.044444444444444446, + "3": 0.10526315789473684, + "4": 0.07226890756302522, + "5": 0.08235294117647059, + "6": 0.11764705882352941, + "7": 0.0625, + "8": 0.11264367816091954, + "9": 0.1103448275862069, + "10": 0.07407407407407407, + "11": 0.08307692307692308, + "12": 0.11076923076923077, + "13": 0.15666666666666668, + "14": 0.19, + "15": 0.14130434782608695, + "16": 0.12648221343873517, + "17": 0.12121212121212122, + "18": 0.2, + "19": 0.1380952380952381, + "20": 0.11428571428571428, + "21": 0.13450292397660818, + "22": 0.1111111111111111, + "23": 0.1871345029239766, + "24": 0.2222222222222222, + "25": 0.10526315789473684, + "26": 0.20261437908496732, + "27": 0.17647058823529413, + "28": 0.21568627450980393, + "29": 0.16911764705882354, + "30": 0.175, + "31": 0.14166666666666666, + "32": 0.16666666666666666, + "33": 0.15, + "34": 0.275, + "35": 0.13333333333333333, + "36": 0.13333333333333333, + "37": 0.15384615384615385, + "38": 0.18681318681318682, + "39": 0.2087912087912088, + "40": 0.2087912087912088, + "41": 0.21978021978021978, + "42": 0.15384615384615385, + "43": 0.16666666666666666, + "44": 0.15384615384615385, + "45": 0.15384615384615385, + "46": 0.1794871794871795, + "47": 0.20512820512820512, + "48": 0.2948717948717949, + "49": 0.25757575757575757, + "50": 0.21212121212121213, + "51": 0.16666666666666666, + "52": 0.2545454545454545, + "53": 0.23636363636363636, + "54": 0.18181818181818182, + "55": 0.43636363636363634, + "56": 0.23636363636363636, + "57": 0.2, + "58": 0.2, + "59": 0.28888888888888886, + "60": 0.28888888888888886, + "61": 0.24444444444444444, + "62": 0.2222222222222222, + "63": 0.2, + "64": 0.2, + "65": 0.2222222222222222, + "66": 0.2222222222222222, + "67": 0.2222222222222222, + "68": 0.2222222222222222, + "69": 0.3888888888888889, + "70": 0.25, + "71": 0.25, + "72": 0.25, + "73": 0.35714285714285715, + "74": 0.25, + "75": 0.35714285714285715, + "76": 0.2857142857142857, + "77": 0.2857142857142857, + "78": 0.3333333333333333, + "79": 0.4, + "80": 0.4, + "81": 0.4, + "82": 0.4, + "83": 0.5, + "84": 0.5, + "85": 0.6666666666666666, + "86": 0.5, + "87": 0.5, + "88": 0.6666666666666666, + "89": 0.6666666666666666, + "90": 0.6666666666666666, + "91": 0.6666666666666666, + "92": 0.6666666666666666, + "93": 0.6666666666666666, + "94": 0.6666666666666666, + "95": 1.0, + "96": 1.0, + "97": 1.0, + "98": 1.0, + "99": 1.0, + "100": 1.0, + "101": 1.0, + "102": 1.0, + "103": 1.0, + "104": 1.0, + "105": 1.0, + "106": 1.0, + "107": 1.0, + "108": 1.0, + "109": 1.0, + "110": 1.0, + "111": 1.0, + "112": 1.0, + "113": 1.0, + "114": 1.0, + "115": 1.0, + "116": 1.0, + "117": 1.0, + "118": 1.0, + "119": 1.0, + "120": 1.0, + "121": 1.0, + "122": 1.0, + "123": 1.0, + "124": 1.0, + "125": 1.0, + "126": 1.0, + "127": 1.0, + "128": 1.0, + "129": 1.0, + "130": 1.0, + "131": 1.0, + "132": 1.0, + "133": 1.0, + "134": 1.0, + "135": 1.0, + "136": 1.0, + "137": 1.0, + "138": 1.0, + "139": 1.0, + "140": 1.0, + "141": 1.0, + "142": 1.0, + "143": 1.0, + "144": 1.0, + "145": 1.0, + "146": 1.0, + "147": 1.0, + "148": 1.0, + "149": 1.0, + "150": 1.0, + "151": 1.0, + "152": 1.0, + "153": 1.0, + "154": 1.0, + "155": 1.0, + "156": 1.0, + "157": 1.0, + "158": 1.0, + "159": 1.0, + "160": 1.0, + "161": 1.0, + "162": 1.0, + "163": 1.0 }, "gods": [ { - "id": "admin_panel_models_storeroom", - "label": "StoreRoom", - "degree": 25 + "id": "tests_factories_make_customer", + "label": "make_customer()", + "degree": 54 }, { - "id": "admin_panel_models_productinstore", - "label": "ProductInStore", + "id": "integrations_sms_base_smsresult", + "label": "SMSResult", "degree": 23 }, + { + "id": "admin_panel_sms_enqueue_enqueue_sms", + "label": "enqueue_sms()", + "degree": 23 + }, + { + "id": "ripwire_readme_rip_n_fast_fewer_tokens_better_code", + "label": "Rip'n Fast. Fewer Tokens. Better Code.", + "degree": 23 + }, + { + "id": "admin_panel_sms_scanner_run_all", + "label": "run_all()", + "degree": 22 + }, + { + "id": "tests_factories_make_user", + "label": "make_user()", + "degree": 22 + }, + { + "id": "admin_panel_services_create_transfer_request", + "label": "create_transfer_request()", + "degree": 22 + }, + { + "id": "integrations_sms_normalize_normalize_mobile", + "label": "normalize_mobile()", + "degree": 22 + }, + { + "id": "integrations_sms_base_smsprovider", + "label": "SMSProvider", + "degree": 21 + }, { "id": "admin_models_user", "label": "User", "degree": 21 - }, - { - "id": "admin_panel_models_action", - "label": "Action", - "degree": 20 - }, - { - "id": "admin_panel_models_product", - "label": "Product", - "degree": 18 - }, - { - "id": "admin_panel_models_storetransferrequest", - "label": "StoreTransferRequest", - "degree": 15 - }, - { - "id": "admin_panel_models_article", - "label": "Article", - "degree": 15 - }, - { - "id": "admin_panel_templates_admin_panel_customeridbackup_html", - "label": "Customer ID Backup/New Factor Template", - "degree": 15 - }, - { - "id": "admin_panel_models_productcategory", - "label": "ProductCategory", - "degree": 14 - }, - { - "id": "admin_panel_models_customer", - "label": "Customer", - "degree": 14 } ], "surprises": [ { - "source": "VisitQueue", - "target": "User", + "source": "SMS Templates UI", + "target": "SMS Templates UI Screenshot", "source_files": [ - "admin_panel/models.py", - "admin/models.py" + "admin_panel/templates/admin_panel/sms_templates.html", + "screencapture-localhost-8000-panel-sms-templates-2026-10-09-23_01_18.png" ], - "confidence": "INFERRED", - "relation": "uses", - "why": "inferred connection - not explicitly stated in source; connects across different repos/directories; bridges separate communities; peripheral node `VisitQueue` unexpectedly reaches hub `User`" + "confidence": "EXTRACTED", + "relation": "semantically_similar_to", + "why": "crosses file types (doc ↔ image); connects across different repos/directories; semantically similar concepts with no structural link" }, { - "source": "Factor", - "target": "User", + "source": "check_financial_discrepancy()", + "target": "Customer", "source_files": [ - "admin_panel/models.py", + "check_finance.py", "admin/models.py" ], "confidence": "INFERRED", "relation": "uses", - "why": "inferred connection - not explicitly stated in source; connects across different repos/directories; bridges separate communities" + "why": "inferred connection - not explicitly stated in source; connects across different repos/directories; bridges separate communities; peripheral node `check_financial_discrepancy()` unexpectedly reaches hub `Customer`" }, { - "source": "StoreRoom", - "target": "User", + "source": "run_360_audit()", + "target": "Customer", "source_files": [ - "admin_panel/models.py", + "ghost_hunter.py", "admin/models.py" ], "confidence": "INFERRED", "relation": "uses", - "why": "inferred connection - not explicitly stated in source; connects across different repos/directories; bridges separate communities" + "why": "inferred connection - not explicitly stated in source; connects across different repos/directories; bridges separate communities; peripheral node `run_360_audit()` unexpectedly reaches hub `Customer`" }, { - "source": "StoreRoomOwner", - "target": "User", + "source": "NewFactor2ViewTests", + "target": "FactorIteam", "source_files": [ - "admin_panel/models.py", + "tests/test_factors.py", "admin/models.py" ], "confidence": "INFERRED", "relation": "uses", - "why": "inferred connection - not explicitly stated in source; connects across different repos/directories; bridges separate communities" + "why": "inferred connection - not explicitly stated in source; connects across different repos/directories; bridges separate communities; peripheral node `FactorIteam` unexpectedly reaches hub `NewFactor2ViewTests`" }, { - "source": "StoreTransferRequest", - "target": "User", + "source": "`nest=` is a max, so it cannot tell a long function from a tangled one", + "target": "index()", "source_files": [ - "admin_panel/models.py", - "admin/models.py" + "ripwire - README.md", + "admin/views.py" ], "confidence": "INFERRED", - "relation": "uses", - "why": "inferred connection - not explicitly stated in source; connects across different repos/directories; bridges separate communities" + "relation": "references", + "why": "inferred connection - not explicitly stated in source; crosses file types (code ↔ doc); connects across different repos/directories" } ], - "tokens": { - "input": 76900, - "output": 126792 - } + "questions": [ + { + "type": "ambiguous_edge", + "question": "What is the exact relationship between `admin_panel/templates/admin_panel/baseHesabdari.html` and `Base Template`?", + "why": "Edge tagged AMBIGUOUS (relation: conceptually_related_to) - confidence is low.", + "low_confidence": true + }, + { + "type": "bridge_node", + "question": "Why does `Rip'n Fast. Fewer Tokens. Better Code.` connect `Graph Retrieval Documentation` to `System Documentation Guide`, `Project Honesty Contract`, `Agent Operating Guidelines`, `Performance Benchmarks`, `Quality Panel Docs`, `Agent Setup Guide`?", + "why": "High betweenness centrality (0.055) - this node is a cross-community bridge." + }, + { + "type": "verify_inferred", + "question": "Are the 6 inferred relationships involving `SMSResult` (e.g. with `BehinSMSProvider` and `ConsoleSMSProvider`) actually correct?", + "why": "`SMSResult` has 6 INFERRED edges - model-reasoned connections that need verification." + }, + { + "type": "isolated_nodes", + "question": "What connects `base.html`, `toCurrency`, `DataTables Library` to the rest of the system?", + "why": "118 weakly-connected nodes found - possible documentation gaps or missing edges." + }, + { + "type": "low_cohesion", + "question": "Should `Text Rendering Utilities` be split into smaller, more focused modules?", + "why": "Cohesion score 0.06144393241167435 - nodes in this community are weakly interconnected." + }, + { + "type": "bridge_node", + "question": "Why does `Languages` connect `Graph Retrieval Documentation` to `SMS Queue Management`?", + "why": "High betweenness centrality (0.053) - this node is a cross-community bridge." + }, + { + "type": "low_cohesion", + "question": "Should `Command Execution Handler` be split into smaller, more focused modules?", + "why": "Cohesion score 0.0576271186440678 - nodes in this community are weakly interconnected." + } + ] } \ No newline at end of file diff --git a/graphify-out/.graphify_labels.json b/graphify-out/.graphify_labels.json index 83b4ee6..2197bc3 100644 --- a/graphify-out/.graphify_labels.json +++ b/graphify-out/.graphify_labels.json @@ -1,142 +1,173 @@ { - "0": "views/store.py", - "1": "project_tree.py", - "2": "sqlite_db_backup.py", - "3": "django_db", - "4": "Customer ID Backup/New Factor Template", - "5": "views/customers.py", - "6": "cheque_inquiry.py", - "7": "admin_panel/templates/admin_panel/baseHesabdari.html", - "8": "django_db_models_deletion", - "9": "0051_auto_20260705_1356.py", - "10": "admin_panel/storeBK1.html", - "11": "ViewSmokeTests", - "12": "admin_panel/templates/admin_panel/customerAction.html", - "13": "User", - "14": "run_all", - "15": "admin_panel/admin.py", - "16": "stock_in", - "17": "admin_panel/templates/admin_panel/bedehi.html", - "18": "hesabdariCheckPassFail.html", - "19": "Reference guide", - "20": "normalize_mobile", - "21": "graphify", - "22": "hesabdariResidNashode.html", - "23": "0017_factoriteam_garantyesh.py", - "24": "scanner.py", - "25": "0002_auto_20220114_1616.py", - "26": "SMSResult", - "27": "Rip'n Fast. Fewer Tokens. Better Code.", - "28": "models/sms.py", - "29": "admin_panel/templates/admin_panel/baseStore.html", - "30": "views/finance.py", - "31": "Customer Modals UI", - "32": "views/factors.py", - "33": "0034_alter_article_sarresid.py", - "34": "views/sms.py", - "35": "0035_alter_article_sarresid.py", - "36": "django_db_models", - "37": "0010_auto_20220122_1546.py", - "38": "0040_customer_laghv_bazdid.py", - "39": "test_sms.py", - "40": "0048_alter_productinstore_options.py", - "41": "0032_article_paystatus.py", - "42": "0033_article_nahvepardakht.py", - "43": "make_customer", - "44": "sms0098.py", - "45": "0044_sarresid_desc.py", - "46": "models/__init__.py", - "47": "make_user", - "48": "GEMINI.md", - "49": "admin/migrations/__init__.py", - "50": "Customer Action Include", - "51": "New Customer Base Template", - "52": "Customer Feedback Partial", - "53": "Customer Factor Detail", - "54": "Customer Accounting Template", - "55": "Customer Factor List", - "56": "Customer Feedback Page", - "57": "commands/__init__.py", - "58": "management/__init__.py", - "59": "admin_panel/migrations/__init__.py", - "60": "Action List Template", - "61": "base.html", - "62": "Customer New Factor", - "63": "Customer New Factor V2", - "64": "Customer Satisfaction List", - "65": "Customer Satisfaction Partial", - "66": "Customer Check/Debt Management", - "67": "admin_panel/templates/admin_panel/customerBaseNew.html", - "68": "Customer List", - "69": "admin_panel/templates/admin_panel/entezarBK.html", - "70": "Financial Dashboard", - "71": "Accounting Main View", - "72": "admin_panel/templates/admin_panel/index.html", - "73": "New Inventory Operation", - "74": "Inventory Operations List", - "75": "Operations Backup Template", - "76": "Financial Report Template", - "77": "Debt Collection Dashboard", - "78": "Inventory Movement Report", - "79": "Customer Satisfaction Dashboard", - "80": "Check and Credit Report Template", - "81": "Satisfaction List Template", - "82": "Store Inventory Template", - "83": "Store Transfers Template", - "84": "Stores List Template", - "85": "toCurrency", - "86": "templatetags/__init__.py", - "87": "URL: newStore", - "88": "URL: process_transfer_action", - "89": "URL: rezayatEdit", - "90": "khatibi/__init__.py", - "91": "Extended Requirements File", - "92": "Requirements File", - "93": "django_shortcuts", - "94": "resolve_recipients", - "95": "factories.py", - "96": "write_tree", - "97": "TemplatesPageUITests", + "0": "views/sms.py", + "1": "run_all", + "2": "django_db", + "3": "views/store.py", + "4": "ModelsPackageContractTests", + "5": "PhoneCleanupTests", + "6": ".load", + "7": "models/sms.py", + "8": "logging", + "9": "views/customers.py", + "10": "django_db_models_deletion", + "11": "make_customer", + "12": "customer_phone_entries", + "13": "EventTriggerTests", + "14": "views/finance.py", + "15": "SMSResult", + "16": "sms0098ws.py", + "17": "Admin Panel Customization", + "18": "User", + "19": "django_conf", + "20": "test_customers.py", + "21": "phones.py", + "22": "Login Template Forms", + "23": "cheque_inquiry.py", + "24": "factories.py", + "25": "Rip'n Fast. Fewer Tokens. Better Code.", + "26": "views/factors.py", + "27": "to_local", + "28": "make_user", + "29": "QueueReviewTests", + "30": "NoticeMechanismTests", + "31": "enqueue.py", + "32": "enqueue_sms", + "33": "PanelTestCase", + "34": "ProductInStore", + "35": "admin_panel/templates/admin_panel/baseHesabdari.html", + "36": "Reference guide", + "37": "Factor Creation UI", + "38": "admin_panel/templates/admin_panel/bedehi.html", + "39": "project_tree.py", + "40": "CustomerSmsHistoryTests", + "41": "TemplatesPageUITests", + "42": "SMS Integration Docs", + "43": "timing_for", + "44": "django_shortcuts", + "45": "stock_in", + "46": "admin_panel/templates/admin_panel/customerAction.html", + "47": "parts_count", + "48": "write_tree", + "49": "CustomerInfoCardUITests", + "50": "Customer", + "51": "Phone Cleanup Guide", + "52": "SmsReportTests", + "53": "manage.py", + "54": "Agent Operating Guidelines", + "55": "SMSDeliveryCommandTests", + "56": "BehinSMSProvider", + "57": "test_sms.py", + "58": "Financial Data Objects", + "59": "Hybrid Migration Command", + "60": "بازمحاسبهٔ «آخرین بازدید» مشتریان", + "61": "Resource Cleanup Handler", + "62": "SMS Timing Migration", + "63": "Articl Model", + "64": "customerIDFactorBackup.html", + "65": "test_factors.py", + "66": "os", + "67": "Graphify Tooling Docs", + "68": "django_urls", + "69": "0063_alter_smsqueue_status.py", + "70": "SMS Template Seeding", + "71": "admin_panel/entezar.html", + "72": "customerNewBackup.html", + "73": "hesabdariResidNashode.html", + "74": "0070_sms_types_seven.py", + "75": "Endpoint Smoke Tests", + "76": "Timezone Migration History", + "77": "models/__init__.py", + "78": "UrlContractTests", + "79": "Brand Scrubbing Migration", + "80": "SMS Dedup Migration", + "81": "Installation Guide", + "82": "Project Honesty Contract", + "83": "Flow Control Migration", + "84": "Permission Label Migration", + "85": "stop.sh", + "86": "Output Format Specs", + "87": "Performance Benchmarks", + "88": "SMS Integration Overview", + "89": "System Cache Table", + "90": "Customer Modal UI", + "91": "SentQueueProtectionTests", + "92": "Infrastructure Services", + "93": "Agent Setup Guide", + "94": "First Use Guide", + "95": "0056_auto_20260731_1824.py", + "96": "0009_remove_storeroom_user.py", + "97": "PhoneEntry", "98": "reconciliation_tool.py", - "99": "ModelsPackageContractTests", - "100": "django_conf", + "99": "announcements.py", + "100": "pathlib", "101": "Command", - "102": "UrlContractTests", - "103": "customerIDFactorBackup.html", - "104": "system.py", - "105": "BehinSMSProvider", - "106": "test_customers.py", - "107": "tests/__init__.py", - "108": "Command", - "109": "Command", - "110": "0061_sms_timing_and_primary.py", - "111": "bale/providers.py", - "112": "os", - "113": "Command", - "114": "0058_seed_sms_templates.py", - "115": "test_factors.py", - "116": "Customer", - "117": "customerNewBackup.html", - "118": "0059_sms_flow_control.py", - "119": "0060_sms_permission_label.py", - "120": "datetime", - "121": "0008_storeroom_user.py", - "122": "0013_alter_factor_options.py", - "123": "0018_alter_factoriteam_product.py", - "124": "0020_auto_20220126_1532.py", - "125": "0022_article_typepy.py", - "126": "0025_action_actiontype.py", - "127": "0028_action_grade.py", - "128": "0030_auto_20220414_1047.py", - "129": "0036_sarresid.py", - "130": "0037_sarresid_sarresiddt.py", - "131": "0039_customer_last_bazdid_dt.py", - "132": "0041_auto_20240216_1607.py", - "133": "0047_alter_productinstore_movement_type.py", - "134": "0049_alter_productinstore_options.py", - "135": "0057_sms.py", - "136": "admin_panel/sms/__init__.py", - "137": "bale/__init__.py", - "138": "integrations/__init__.py", - "139": "integrations/sms/__init__.py" + "102": "0064_sms_webservice_delivery.py", + "103": "0040_customer_laghv_bazdid.py", + "104": "0068_auto_20261010_1305.py", + "105": "0069_smstemplate_auto_send.py", + "106": "0047_alter_productinstore_movement_type.py", + "107": "Command", + "108": "SMS Domain Logic", + "109": "Customer Communication Partials", + "110": "SMS Template Management", + "111": "Documentation and Graphing", + "112": "Admin Migration Package", + "113": "bale/providers.py", + "114": "Customer Action Templates", + "115": "Customer Feedback Components", + "116": "Customer Invoice Details", + "117": "Accounting Document Templates", + "118": "Customer Invoice List", + "119": "Customer Feedback Interface", + "120": "Custom Management Commands", + "121": "Django Management Package", + "122": "Admin Panel Migrations", + "123": "Action List Interface", + "124": "Base HTML Layout", + "125": "New Invoice Creation", + "126": "Customer Satisfaction Records", + "127": "Satisfaction UI Components", + "128": "Debt and Credit Management", + "129": "Customer Base Layout", + "130": "Waiting List Template", + "131": "Financial Dashboard", + "132": "Accounting Main View", + "133": "Admin Dashboard Index", + "134": "Inventory Entry Form", + "135": "Inventory Operations History", + "136": "Operations Backup View", + "137": "Financial Reporting", + "138": "Debt Collection Interface", + "139": "Inventory Movement Analytics", + "140": "Satisfaction Analytics Dashboard", + "141": "Credit Status Reports", + "142": "Satisfaction List View", + "143": "SMS Queue Management", + "144": "Store Inventory View", + "145": "Store Transfer Interface", + "146": "Store Directory Template", + "147": "Currency Formatting Utility", + "148": "Django Template Tags", + "149": "Store Creation Endpoint", + "150": "Transfer Processing Endpoint", + "151": "Satisfaction Edit Endpoint", + "152": "Customer Deletion Endpoint", + "154": "Django Permissions System", + "155": "Static Asset Management", + "156": "Data Cleanup Documentation", + "157": "Bale Integration Package", + "158": "External Integrations", + "159": "SMS Integration Module", + "160": "Khatibi Custom Module", + "161": "Project Dependencies", + "162": "Test Suite Package", + "164": "QueueStatsTests", + "166": "sms_settings", + "167": "Command", + "168": "0071_sms_settings_permission.py", + "169": "index", + "172": "0017_factoriteam_garantyesh.py", + "173": "0028_action_grade.py", + "174": "0034_alter_article_sarresid.py", + "175": "0039_customer_last_bazdid_dt.py" } diff --git a/graphify-out/.graphify_labels.json.sig b/graphify-out/.graphify_labels.json.sig index f63deaa..f0cf306 100644 --- a/graphify-out/.graphify_labels.json.sig +++ b/graphify-out/.graphify_labels.json.sig @@ -1 +1 @@ -{"0": "5ff8a72726a083db", "1": "219b01f43ec5f76d", "2": "edab4be6f8bf260c", "3": "70018840272ab80e", "4": "d36df3c85181f806", "5": "9eb1daeea9dda6c2", "6": "076144c85b2925b4", "7": "0928f3aa4e597117", "8": "0e080dc901d1f670", "9": "a00d0684d05c01a4", "10": "9701f1591d8fda6e", "11": "5290f7a7558a9c3e", "12": "e6a8282720eded0c", "13": "05119fe8608a0cbd", "14": "903ab35120e04b54", "15": "91b75fb079f9c02c", "16": "c12f70f2136b1a97", "17": "06959e26b0aedfb2", "18": "90146abe85d9419f", "19": "21f888d34b160f31", "20": "bc287e5355564fe6", "21": "0f632fc57e9feeb0", "22": "af89b22ae0662d6d", "23": "a800ecffc01fc9d5", "24": "955593b6e631a7a7", "25": "a19c211702401a5b", "26": "e72a0ec031902754", "27": "c9e7bf6ac46f263c", "28": "1a67e40fab1eeadd", "29": "3f3a4eb6e6b2f4d6", "30": "389c17cd386d28f2", "31": "03f8896e9b60ca96", "32": "40771cc7fa5ee930", "33": "c02bc9595eaedd36", "34": "d76e40111efda56a", "35": "7714d721fe893ef5", "36": "466b12915e08a4f0", "37": "2f1621907db6f86f", "38": "ef8221c5fdeee7f8", "39": "a890d0d5da72d758", "40": "bf60a8731033e467", "41": "d5c0f5f6714c9991", "42": "9948fb8b548535ed", "43": "61882165e32527ce", "44": "f1310e6117296d59", "45": "90e57f26f669372d", "46": "95f91e2eaf52002c", "47": "32e693b104a691c2", "48": "66ed76491a412a72", "49": "e67668b62ee6c62c", "50": "68aee39cd24db354", "51": "09b7297ea62919f2", "52": "054401d27ffaa4b2", "53": "dace81c1995fb321", "54": "19e301aeace95bb2", "55": "d6fd0cec4634a0b0", "56": "0230d355a6dabb54", "57": "5cebbb5528321795", "58": "536fe28d24c361f5", "59": "b08bdc2712cb0f12", "60": "6ce8444f504022cc", "61": "8021f52559633383", "62": "d8e34a3b3eb43ee0", "63": "056b8d6b5072289b", "64": "171c34c74a95481b", "65": "73e9eca6f2ca1c9b", "66": "dcc2230ae751c29a", "67": "2a617b932f5d92b2", "68": "2f5417a69eb1d15e", "69": "d55cb638532fdfeb", "70": "06cae4389208269f", "71": "e9c0a7be255e9b26", "72": "455b4ca39e55ecb0", "73": "8105c55538c34081", "74": "74304670b54c018f", "75": "a27f876eabb044b2", "76": "73f74dd7a40776d6", "77": "29d433d164b99f89", "78": "3f44aa48aa05d597", "79": "ca8a7efba0a3e579", "80": "5cc770af9d184d77", "81": "2d31fa723128b164", "82": "9d814c2520329846", "83": "f97d1a5c3a066598", "84": "14f84a6e4cd311d6", "85": "3a592d27b1f4ccc9", "86": "0bf6f9d0aeab4fd8", "87": "8b491bb3ff0649dd", "88": "638812cee15d1b9b", "89": "3dd16d68928a479c", "90": "72dad91d66d471ce", "91": "353a9d09eaae47ea", "92": "2d29260874184e4a", "93": "00d4d9fc79267971", "94": "8fedb8424b4f7d54", "95": "c7ce06d5e4e87224", "96": "9d46bbe4641a8543", "97": "68b25e60cbe5368c", "98": "8f5a6aa62c618b5f", "99": "33819123dbb4d454", "100": "e8d32595704b722a", "101": "e4fcae6cb365fba9", "102": "32ed40052fe2c3f8", "103": "8ff365aee1c1a767", "104": "c5743041b2101132", "105": "891ec2c014046f05", "106": "0c4707ef57c917a2", "107": "43bbf35b96423d46", "108": "88f880da5a61f9dc", "109": "dbfbf8557361f5ea", "110": "6e90b41fbd82c265", "111": "118bde95524551e0", "112": "b85898490162caed", "113": "d55977ec0e3ae213", "114": "df7ea8ce3fa52be3", "115": "460dadaff123e841", "116": "4faac8c35fa8b703", "117": "aea8fc3ff351253f", "118": "bda9be183e0f933e", "119": "63526d01888c4f22", "120": "329e844b7c9edf2d", "121": "37d2f4398ee065c5", "122": "49438bf9f08f11ff", "123": "b9687fc2d4953aed", "124": "6288b9f165b037f4", "125": "f97753a176d20dae", "126": "9cc7a507cfecd8fb", "127": "edb619bf5ac0fc49", "128": "8cb6b3db3aadb973", "129": "c9638aef2c5c82d8", "130": "c6b3f6e0a15a6a0b", "131": "52502a4c1ef75ce6", "132": "8d20e5c1bce8916d", "133": "b5ce3fce9192f8b8", "134": "6cef5bc37e710e62", "135": "0098e532f53735f2", "136": "b738c5aefe939cff", "137": "a9bf69b3d550abef", "138": "b039e1c69c8fb7b9", "139": "60029bf0948fc02a"} \ No newline at end of file +{"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"} \ No newline at end of file diff --git a/graphify-out/.graphify_semantic_marker b/graphify-out/.graphify_semantic_marker index cd973a0..535b43e 100644 --- a/graphify-out/.graphify_semantic_marker +++ b/graphify-out/.graphify_semantic_marker @@ -1 +1 @@ -{"output_tokens": 126792} \ No newline at end of file +{"output_tokens": 201} \ No newline at end of file diff --git a/graphify-out/GRAPH_REPORT.md b/graphify-out/GRAPH_REPORT.md index 0113548..ada8975 100644 --- a/graphify-out/GRAPH_REPORT.md +++ b/graphify-out/GRAPH_REPORT.md @@ -1,183 +1,213 @@ -# Graph Report - khatibi (2026-10-10) +# Graph Report - khatibi (2026-10-11) ## Corpus Check -- 160 files · ~127,728 words +- 182 files · ~145,325 words - Verdict: corpus is large enough that graph structure adds value. -- Unclassified: 7 file(s) not represented in the graph (top: (none) 4, .env 2, .csv 1) +- Unclassified: 8 file(s) not represented in the graph (top: (none) 5, .env 2, .csv 1) ## Summary -- 1280 nodes · 2237 edges · 140 communities (47 shown, 93 thin omitted) -- Extraction: 94% EXTRACTED · 6% INFERRED · 0% AMBIGUOUS · INFERRED: 131 edges (avg confidence: 0.87) +- 1684 nodes · 3008 edges · 171 communities (74 shown, 97 thin omitted) +- Extraction: 95% EXTRACTED · 5% INFERRED · 0% AMBIGUOUS · INFERRED: 151 edges (avg confidence: 0.89) - Token cost: 0 input · 0 output ## Graph Freshness -- Built from commit: `250cdbab` +- Built from commit: `1dea3509` - Run `git rev-parse HEAD` and compare to check if the graph is stale. - Run `graphify update .` after code changes (no API cost). ## Community Hubs (Navigation) -- views/store.py -- project_tree.py -- django_db -- Customer ID Backup/New Factor Template -- views/customers.py -- cheque_inquiry.py -- admin_panel/templates/admin_panel/baseHesabdari.html -- django_db_models_deletion -- 0051_auto_20260705_1356.py -- admin_panel/storeBK1.html -- ViewSmokeTests -- admin_panel/templates/admin_panel/customerAction.html -- User -- run_all -- admin_panel/admin.py -- stock_in -- admin_panel/templates/admin_panel/bedehi.html -- hesabdariCheckPassFail.html -- Reference guide -- normalize_mobile -- graphify -- hesabdariResidNashode.html -- 0017_factoriteam_garantyesh.py -- scanner.py -- 0002_auto_20220114_1616.py -- SMSResult -- Rip'n Fast. Fewer Tokens. Better Code. -- models/sms.py -- admin_panel/templates/admin_panel/baseStore.html -- views/finance.py -- Customer Modals UI -- views/factors.py -- 0034_alter_article_sarresid.py - views/sms.py -- 0035_alter_article_sarresid.py -- django_db_models -- 0010_auto_20220122_1546.py -- 0040_customer_laghv_bazdid.py -- test_sms.py -- 0048_alter_productinstore_options.py -- 0032_article_paystatus.py -- 0033_article_nahvepardakht.py +- run_all +- django_db +- views/store.py +- ModelsPackageContractTests +- PhoneCleanupTests +- .load +- models/sms.py +- logging +- views/customers.py +- django_db_models_deletion - make_customer -- sms0098.py -- 0044_sarresid_desc.py -- models/__init__.py +- customer_phone_entries +- EventTriggerTests +- views/finance.py +- SMSResult +- sms0098ws.py +- Admin Panel Customization +- User +- django_conf +- test_customers.py +- phones.py +- Login Template Forms +- cheque_inquiry.py +- factories.py +- Rip'n Fast. Fewer Tokens. Better Code. +- views/factors.py +- to_local - make_user -- GEMINI.md -- Customer Action Include -- New Customer Base Template -- Customer Feedback Partial -- Customer Factor Detail -- Customer Accounting Template -- Customer Factor List -- Customer Feedback Page -- Action List Template -- base.html -- Customer New Factor -- Customer New Factor V2 -- Customer Satisfaction List -- Customer Satisfaction Partial -- Customer Check/Debt Management -- admin_panel/templates/admin_panel/customerBaseNew.html -- Customer List +- QueueReviewTests +- NoticeMechanismTests +- enqueue.py +- enqueue_sms +- PanelTestCase +- ProductInStore +- admin_panel/templates/admin_panel/baseHesabdari.html +- Reference guide +- Factor Creation UI +- admin_panel/templates/admin_panel/bedehi.html +- project_tree.py +- CustomerSmsHistoryTests +- TemplatesPageUITests +- SMS Integration Docs +- timing_for +- django_shortcuts +- stock_in +- admin_panel/templates/admin_panel/customerAction.html +- parts_count +- write_tree +- CustomerInfoCardUITests +- Customer +- Phone Cleanup Guide +- SmsReportTests +- manage.py +- Agent Operating Guidelines +- SMSDeliveryCommandTests +- BehinSMSProvider +- test_sms.py +- Financial Data Objects +- Hybrid Migration Command +- بازمحاسبهٔ «آخرین بازدید» مشتریان +- Resource Cleanup Handler +- SMS Timing Migration +- Articl Model +- customerIDFactorBackup.html +- test_factors.py +- os +- Graphify Tooling Docs +- 0063_alter_smsqueue_status.py +- SMS Template Seeding +- admin_panel/entezar.html +- customerNewBackup.html +- hesabdariResidNashode.html +- 0070_sms_types_seven.py +- Endpoint Smoke Tests +- Timezone Migration History +- models/__init__.py +- UrlContractTests +- Brand Scrubbing Migration +- SMS Dedup Migration +- Installation Guide +- Project Honesty Contract +- Flow Control Migration +- Permission Label Migration +- stop.sh +- Output Format Specs +- Performance Benchmarks +- SMS Integration Overview +- System Cache Table +- Customer Modal UI +- SentQueueProtectionTests +- Infrastructure Services +- Agent Setup Guide +- First Use Guide +- 0056_auto_20260731_1824.py +- 0009_remove_storeroom_user.py +- PhoneEntry +- reconciliation_tool.py +- announcements.py +- Command +- 0064_sms_webservice_delivery.py +- 0040_customer_laghv_bazdid.py +- 0068_auto_20261010_1305.py +- 0069_smstemplate_auto_send.py +- 0047_alter_productinstore_movement_type.py +- Command +- Customer Communication Partials +- SMS Template Management +- Documentation and Graphing +- bale/providers.py +- Customer Action Templates +- Customer Feedback Components +- Customer Invoice Details +- Accounting Document Templates +- Customer Invoice List +- Customer Feedback Interface +- Action List Interface +- Base HTML Layout +- New Invoice Creation +- Customer Satisfaction Records +- Satisfaction UI Components +- Debt and Credit Management +- Customer Base Layout - Financial Dashboard - Accounting Main View -- New Inventory Operation -- Inventory Operations List -- Operations Backup Template -- Financial Report Template -- Debt Collection Dashboard -- Inventory Movement Report -- Customer Satisfaction Dashboard -- Check and Credit Report Template -- Satisfaction List Template -- Store Inventory Template -- Store Transfers Template -- Stores List Template -- toCurrency -- Extended Requirements File -- Requirements File -- django_shortcuts -- resolve_recipients -- factories.py -- write_tree -- TemplatesPageUITests -- reconciliation_tool.py -- ModelsPackageContractTests -- django_conf +- Inventory Entry Form +- Inventory Operations History +- Operations Backup View +- Financial Reporting +- Debt Collection Interface +- Inventory Movement Analytics +- Satisfaction Analytics Dashboard +- Credit Status Reports +- Satisfaction List View +- SMS Queue Management +- Store Inventory View +- Store Transfer Interface +- Store Directory Template +- Currency Formatting Utility +- Data Cleanup Documentation +- Project Dependencies +- QueueStatsTests +- sms_settings - Command -- UrlContractTests -- customerIDFactorBackup.html -- system.py -- BehinSMSProvider -- test_customers.py -- Command -- Command -- 0061_sms_timing_and_primary.py -- bale/providers.py -- Command -- 0058_seed_sms_templates.py -- Customer -- customerNewBackup.html -- 0059_sms_flow_control.py -- 0060_sms_permission_label.py -- 0008_storeroom_user.py -- 0013_alter_factor_options.py -- 0018_alter_factoriteam_product.py -- 0020_auto_20220126_1532.py -- 0022_article_typepy.py -- 0025_action_actiontype.py +- 0071_sms_settings_permission.py +- index +- 0017_factoriteam_garantyesh.py - 0028_action_grade.py -- 0030_auto_20220414_1047.py -- 0036_sarresid.py -- 0037_sarresid_sarresiddt.py +- 0034_alter_article_sarresid.py - 0039_customer_last_bazdid_dt.py -- 0041_auto_20240216_1607.py -- 0047_alter_productinstore_movement_type.py -- 0049_alter_productinstore_options.py -- 0057_sms.py ## God Nodes (most connected - your core abstractions) -1. `make_customer()` - 37 edges -2. `make_user()` - 24 edges -3. `Rip'n Fast. Fewer Tokens. Better Code.` - 23 edges -4. `User` - 22 edges -5. `create_transfer_request()` - 22 edges -6. `enqueue_sms()` - 21 edges -7. `SMSResult` - 20 edges -8. `normalize_mobile()` - 20 edges -9. `SMSProvider` - 19 edges -10. `Reference guide` - 18 edges +1. `make_customer()` - 70 edges +2. `make_user()` - 28 edges +3. `run_all()` - 24 edges +4. `enqueue_sms()` - 23 edges +5. `SMSResult` - 23 edges +6. `Rip'n Fast. Fewer Tokens. Better Code.` - 23 edges +7. `User` - 22 edges +8. `create_transfer_request()` - 22 edges +9. `normalize_mobile()` - 22 edges +10. `SMSProvider` - 21 edges ## Surprising Connections (you probably didn't know these) +- `نکته‌های مهم` --references--> `Action` [INFERRED] + recalc_last_bazdid.md → admin_panel/models/actions.py +- `SMS Templates UI` --semantically_similar_to--> `SMS Templates UI Screenshot` [EXTRACTED] [semantically similar] + admin_panel/templates/admin_panel/sms_templates.html → screencapture-localhost-8000-panel-sms-templates-2026-10-09-23_01_18.png - `check_financial_discrepancy()` --uses--> `Customer` [INFERRED] check_finance.py → admin/models.py - `run_360_audit()` --uses--> `Customer` [INFERRED] ghost_hunter.py → admin/models.py - ``nest=` is a max, so it cannot tell a long function from a tangled one` --references--> `index()` [INFERRED] ripwire - README.md → admin/views.py -- `VisitQueue` --uses--> `User` [INFERRED] - admin_panel/models/actions.py → admin/models.py -- `StoreTransferRequest` --uses--> `User` [INFERRED] - admin_panel/models/store.py → admin/models.py ## Import Cycles - None detected. ## Hyperedges (group relationships) - **Customer Satisfaction & Collection** — admin_panel_templates_admin_panel_report_rezayat, admin_panel_templates_admin_panel_report_debtors_no_vosol, admin_panel_customer_customernazarinc [EXTRACTED 0.90] -- **Customer Management Flow** — admin_panel_customer_base_new, admin_panel_customer_action_inc, admin_panel_customer_id_factor, admin_panel_customer_hesabdari [EXTRACTED 1.00] +- **Customer Management Flow** — admin_panel_customer_action_inc, admin_panel_customer_id_factor, admin_panel_customer_hesabdari [EXTRACTED 1.00] - **Financial & Debt Management** — admin_panel_templates_admin_panel_financial_dashboard, admin_panel_templates_admin_panel_hesabdari [EXTRACTED 1.00] - **Graphify Output Artifacts** — graphify-out_graph_json, graphify-out_wiki_index_md, graphify-out_graph_report_md [EXTRACTED 1.00] - **Graphify Subcommands** — graphify_query, graphify_path, graphify_explain [EXTRACTED 1.00] - **Inventory Management System** — admin_panel_templates_admin_panel_store, admin_panel_templates_admin_panel_store_transfers, admin_panel_templates_admin_panel_report_inventory_movements, admin_panel_templates_admin_panel_operationsbk [EXTRACTED 1.00] +- **SMS Management System** — admin_panel_templates_admin_panel_sms_settings_html, admin_panel_templates_admin_panel_sms_templates_html, admin_panel_templates_admin_panel_sms_queue_html, sms_worker, sms_delivery [EXTRACTED 1.00] - **Bedehi Report Navigation** — django_urls_admin_panel_bedehi, django_urls_admin_panel_bedehikhadamat, django_urls_admin_panel_bedehinasb [EXTRACTED] - **Login Form** — admin_panel_templates_admin_panel_login, django_url_admin_panel_p_login, django_csrf_token, form_field_username, form_field_password [EXTRACTED] - **New Store Form** — admin_panel_templates_admin_panel_storebk1, django_url_admin_panel_newstore, django_csrf_token, form_field_storename [EXTRACTED] - **Store Inventory Display** — admin_panel_templates_admin_panel_storebk1, template_variable_productsum, product_property_product_name, product_property_mojodi [EXTRACTED] - **Customer List Page** — admin_panel_templates_admin_panel_entezar_html, concept_customer_data, concept_url_admin_panel_customers [INFERRED 0.80] - **Customer Lifecycle Management** — admin_panel_templates_admin_panel_customers [INFERRED 0.85] -- **Customer Interaction Lifecycle** — admin_panel_templates_admin_panel_customers, admin_panel_templates_admin_panel_customerNew, admin_panel_templates_admin_panel_customerRezayat [INFERRED 0.85] - **New Factor Creation Flow** — admin_panel_templates_admin_panel_customernewbackup_html, admin_panel_urls_newfactor, concept_client_side_form_logic, concept_customer, concept_category [INFERRED 0.85] - **Customer Management UI Flow** — admin_panel_templates_admin_panel_customersbk_html, admin_panel_templates_admin_panel_customersbk_html_createcs, admin_panel_templates_admin_panel_customersbk_html_openrow, concept_customer_modal_ui [INFERRED 0.90] - **Financial & Debt Management Flow** — admin_panel_templates_admin_panel_customerSarresid, admin_panel_templates_admin_panel_hesabdari, admin_panel_templates_admin_panel_financial_dashboard [INFERRED 0.90] @@ -192,223 +222,331 @@ - **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] -## Communities (140 total, 93 thin omitted) +## Communities (171 total, 97 thin omitted) -### Community 0 - "views/store.py" -Cohesion: 0.07 -Nodes (26): StoreTransferRequest, Meta, Status, approve_transfer_request(), cancel_transfer_request(), _check_source_store_permission(), _check_target_store_permission(), create_transfer_request() (+18 more) +### Community 0 - "views/sms.py" +Cohesion: 0.14 +Nodes (14): _approve(), customer_sms_history(), _customer_url(), _notice(), sms_customer_toggle(), sms_primary_toggle(), sms_queue(), sms_queue_approve() (+6 more) -### Community 1 - "project_tree.py" -Cohesion: 0.21 -Nodes (6): is_excluded_file(), main(), non_negative_integer(), parse_args(), sorted_entries(), warn() - -### Community 3 - "django_db" -Cohesion: 0.07 -Nodes (14): Migration, Migration, Migration, Migration, Migration, Migration, Migration, Migration (+6 more) - -### Community 4 - "Customer ID Backup/New Factor Template" -Cohesion: 0.15 -Nodes (4): Customer ID Backup/New Factor Template, all_products (JS variable), buildOrderList (JS function), orderList (JS variable) - -### Community 5 - "views/customers.py" -Cohesion: 0.13 -Nodes (16): customer_toggle_map(), customer(), customerDel(), customerEdit(), customerFactor(), customerFactors(), customerHesabdari(), customerHesabdariAdd() (+8 more) - -### Community 6 - "cheque_inquiry.py" -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() - -### Community 7 - "admin_panel/templates/admin_panel/baseHesabdari.html" -Cohesion: 0.13 -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 8 - "django_db_models_deletion" +### Community 1 - "run_all" Cohesion: 0.06 -Nodes (15): Migration, Migration, Migration, Migration, Migration, Migration, Migration, Migration (+7 more) +Nodes (9): Command, run_all(), apply_timing(), activate_sms(), AutoSendTests, ColdStartTests, ConsoleEncodingTests, ScannerTests (+1 more) -### Community 10 - "admin_panel/storeBK1.html" +### Community 2 - "django_db" +Cohesion: 0.04 +Nodes (24): Migration, Migration, Migration, Migration, Migration, Migration, Migration, Migration (+16 more) + +### Community 3 - "views/store.py" +Cohesion: 0.07 +Nodes (26): Product, Meta, StoreTransferRequest, Meta, Status, approve_transfer_request(), cancel_transfer_request(), _check_source_store_permission() (+18 more) + +### Community 4 - "ModelsPackageContractTests" +Cohesion: 0.07 +Nodes (5): AdminConfig, AdminPanelConfig, make_std_streams_unicode_safe(), configure_sqlite_connection(), ModelsPackageContractTests + +### Community 5 - "PhoneCleanupTests" +Cohesion: 0.09 +Nodes (9): Command, Migration, scrub_legacy_phones(), _append_note(), clean_all(), clean_customer(), clean_legacy_cell(), _numeric_tokens() (+1 more) + +### Community 6 - ".load" +Cohesion: 0.06 +Nodes (9): 12. Supported languages and formats, Languages, HttpProviderRobustnessTests, RuleTests, SettingsPanelTests, TestMessageSettingsTests, WorkerTests, ZeroZeroNinetyEightLinkTests (+1 more) + +### Community 7 - "models/sms.py" +Cohesion: 0.06 +Nodes (15): CustomerSMSSetting, Meta, SMSDedup, Meta, SMSLog, Meta, SMSProviderConfig, Meta (+7 more) + +### Community 8 - "logging" +Cohesion: 0.09 +Nodes (3): get_provider_class(), get_sms_provider(), reset_sms_provider() + +### Community 9 - "views/customers.py" +Cohesion: 0.09 +Nodes (18): normalize_primary_values(), customer_toggle_map(), notify_welcome(), customer(), customerDel(), customerEdit(), customerFactor(), customerFactors() (+10 more) + +### Community 10 - "django_db_models_deletion" +Cohesion: 0.08 +Nodes (12): Migration, Migration, Migration, Migration, Migration, Migration, Migration, Migration (+4 more) + +### Community 11 - "make_customer" +Cohesion: 0.14 +Nodes (3): make_customer(), EmptyQueueTests, PrimaryNumberPanelTests + +### Community 12 - "customer_phone_entries" +Cohesion: 0.13 +Nodes (5): customer_phone_entries(), _iter_sources(), resolve_recipients(), mark_primary(), RecipientTests + +### Community 14 - "views/finance.py" +Cohesion: 0.18 +Nodes (19): Article, Meta, notify_payment_thanks(), addSarresid(), bedehi(), bedehiKhadamat(), bedehiNasb(), financial_dashboard() (+11 more) + +### Community 15 - "SMSResult" +Cohesion: 0.13 +Nodes (7): SMSProvider, SMSResult, webotp_message(), console_write(), ConsoleSMSProvider, HTTPSMSProvider, _FailingProvider + +### Community 16 - "sms0098ws.py" +Cohesion: 0.10 +Nodes (7): classify_delivery(), DeliveryCode, describe_send_code(), _as_text(), ZeroZeroNinetyEightWebServiceProvider, place_voip_call(), VoipCallResult + +### Community 17 - "Admin Panel Customization" +Cohesion: 0.12 +Nodes (10): CustomerSMSSettingAdmin, SMSLogAdmin, SMSProviderConfigAdmin, SMSProviderConfigForm, Meta, SMSQueueAdmin, SMSTemplateAdmin, StoreRoomAdmin (+2 more) + +### Community 18 - "User" +Cohesion: 0.16 +Nodes (17): User, Action, VisitQueue, UserProfile, notify_visit_scheduled(), ActionList(), ActionListAll(), ActionListToday() (+9 more) + +### Community 19 - "django_conf" +Cohesion: 0.09 +Nodes (11): Migration, Migration, Migration, Migration, Migration, Migration, Migration, Migration (+3 more) + +### Community 20 - "test_customers.py" +Cohesion: 0.11 +Nodes (4): login_superuser(), CustomerCrudTests, CustomerDeleteGuardTests, InactiveCustomerHiddenTests + +### Community 21 - "phones.py" +Cohesion: 0.18 +Nodes (6): primary_phones(), resolved_recipients(), save_primary_normalized(), stored_primary_normalized(), usable_entries(), primary_phones() + +### Community 22 - "Login Template Forms" 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) -### Community 12 - "admin_panel/templates/admin_panel/customerAction.html" -Cohesion: 0.13 -Nodes (13): admin_panel/templates/admin_panel/customerActionInc.html, admin_panel:customerNew, admin_panel:customerNewAction URL, Action Data Model, Articl Model, Customer Model, DataTables Library, jQuery (+5 more) +### Community 23 - "cheque_inquiry.py" +Cohesion: 0.19 +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 13 - "User" -Cohesion: 0.07 -Nodes (30): User, Action, VisitQueue, Factor, Meta, FactorIteam, Product, Meta (+22 more) - -### Community 14 - "run_all" -Cohesion: 0.05 -Nodes (9): run_all(), activate_sms(), ColdStartTests, ConsoleEncodingTests, HttpProviderRobustnessTests, NormalizeTests, ScannerTests, WorkerTests (+1 more) - -### Community 15 - "admin_panel/admin.py" -Cohesion: 0.09 -Nodes (10): CustomerSMSSettingAdmin, SMSLogAdmin, SMSProviderConfigAdmin, SMSProviderConfigForm, Meta, SMSQueueAdmin, SMSTemplateAdmin, StoreRoomAdmin (+2 more) - -### Community 16 - "stock_in" -Cohesion: 0.11 -Nodes (11): Customer, Factor, FactorIteam, Product, ProductInStore, StoreRoom, VisitQueue, Command (+3 more) - -### Community 17 - "admin_panel/templates/admin_panel/bedehi.html" -Cohesion: 0.12 -Nodes (11): admin_panel:pardakhtNesiye, /panel/passCheck/, all_sarresid, URL: admin_panel:newFactor, mandehesabKP, admin_panel.ViewReportMali, toJalali, admin_panel:bedehi (+3 more) - -### Community 18 - "hesabdariCheckPassFail.html" -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) - -### Community 19 - "Reference guide" -Cohesion: 0.07 -Nodes (27): 10. Quality evaluation, 11. Agent integration, 12. Supported languages and formats, 13. Performance, 14. Known limits, 15. Documentation, 16. Improvement, 17. License (+19 more) - -### Community 20 - "normalize_mobile" -Cohesion: 0.09 -Nodes (12): customer_phone_entries(), _iter_sources(), normalize_primary_values(), PhoneEntry, primary_phones(), resolved_recipients(), save_primary_normalized(), stored_primary_normalized() (+4 more) - -### Community 21 - "graphify" -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 - -### Community 22 - "hesabdariResidNashode.html" -Cohesion: 0.47 -Nodes (5): jQuery, pDatepicker JS Library, custom_tags, toCurrency, toJalali - -### Community 24 - "scanner.py" -Cohesion: 0.13 -Nodes (12): enqueue_sms(), _aware(), _Budget, scan_cheque_due(), scan_cheque_registration(), scan_debt_due(), scan_nesiye_due(), scan_survey() (+4 more) - -### Community 25 - "0002_auto_20220114_1616.py" -Cohesion: 0.29 -Nodes (3): Migration, Migration, Migration - -### Community 26 - "SMSResult" -Cohesion: 0.13 -Nodes (8): SMSDeliveryResult, SMSProvider, SMSResult, webotp_message(), console_write(), ConsoleSMSProvider, HTTPSMSProvider, _FailingProvider - -### Community 27 - "Rip'n Fast. Fewer Tokens. Better Code." -Cohesion: 0.05 -Nodes (42): index(), 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 (+34 more) - -### Community 28 - "models/sms.py" -Cohesion: 0.08 -Nodes (12): CustomerSMSSetting, Meta, SMSLog, Meta, SMSProviderConfig, Meta, SMSQueue, Meta (+4 more) - -### Community 29 - "admin_panel/templates/admin_panel/baseStore.html" -Cohesion: 0.13 -Nodes (12): admin_panel/base.html, Customer Data Structure, Django Custom Tags: custom_tags, kamaDatepicker Library, URL: admin_panel:customers, URL: admin_panel:operations, URL: admin_panel:productAdd, URL: admin_panel:store (+4 more) - -### Community 30 - "views/finance.py" -Cohesion: 0.20 -Nodes (18): Article, Meta, addSarresid(), bedehi(), bedehiKhadamat(), bedehiNasb(), financial_dashboard(), hesabdari() (+10 more) - -### Community 31 - "Customer Modals UI" -Cohesion: 0.67 -Nodes (3): createCS click handler, openRow click handler, Customer Modals UI - -### Community 32 - "views/factors.py" -Cohesion: 0.16 -Nodes (6): FactorDel(), newFactor2(), InventoryException, _parse_jalali_date(), _parse_timestamp(), _safe_int() - -### Community 34 - "views/sms.py" -Cohesion: 0.17 -Nodes (12): parse_scan_since(), resolve_recipients_test(), _sample_context(), sms_customer_toggle(), sms_primary_toggle(), sms_queue(), sms_queue_retry(), sms_send_custom() (+4 more) - -### Community 36 - "django_db_models" -Cohesion: 0.15 -Nodes (3): get_provider_class(), get_sms_provider(), reset_sms_provider() - -### Community 39 - "test_sms.py" -Cohesion: 0.14 -Nodes (4): apply_timing(), current_timing_value(), timing_for(), TimingsTests - -### Community 43 - "make_customer" -Cohesion: 0.15 -Nodes (6): PhoneNumber, make_customer(), PanelTestCase, PrimaryNumberPanelTests, RuleTests, WelcomeTriggerTests - -### Community 44 - "sms0098.py" -Cohesion: 0.15 -Nodes (5): _clean(), to_local(), _configured_codes(), _response_code(), ZeroZeroNinetyEightSMSProvider - -### Community 46 - "models/__init__.py" -Cohesion: 0.15 -Nodes (5): format_amount(), format_jalali(), render_template(), _SafeDict, is_sms_enabled() - -### Community 93 - "django_shortcuts" -Cohesion: 0.13 -Nodes (3): Migration, ArticleType, Sarresid - -### Community 94 - "resolve_recipients" -Cohesion: 0.20 -Nodes (3): resolve_recipients(), mark_primary(), RecipientTests - -### Community 95 - "factories.py" +### Community 24 - "factories.py" Cohesion: 0.27 Nodes (5): ProductCategory, make_category(), make_product(), make_store(), _next() -### Community 96 - "write_tree" +### Community 25 - "Rip'n Fast. Fewer Tokens. Better Code." +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) + +### Community 26 - "views/factors.py" +Cohesion: 0.19 +Nodes (6): notify_factor_survey(), newFactor2(), InventoryException, _parse_jalali_date(), _parse_timestamp(), _safe_int() + +### Community 27 - "to_local" +Cohesion: 0.17 +Nodes (4): to_local(), _configured_codes(), _response_code(), ZeroZeroNinetyEightSMSProvider + +### Community 30 - "NoticeMechanismTests" +Cohesion: 0.17 +Nodes (3): dashBoxData(), resolve(), NoticeMechanismTests + +### Community 31 - "enqueue.py" +Cohesion: 0.15 +Nodes (6): format_amount(), format_jalali(), render_template(), _SafeDict, is_sms_enabled(), _sample_context() + +### Community 32 - "enqueue_sms" +Cohesion: 0.21 +Nodes (7): enqueue_sms(), _aware(), _Budget, scan_cheque_funding(), scan_nesiye_due(), scan_visit(), _template() + +### Community 33 - "PanelTestCase" +Cohesion: 0.17 +Nodes (5): PhoneNumber, DeletedStaysDeletedTests, EnqueueIdempotencyTests, PanelTestCase, WelcomeTriggerTests + +### Community 34 - "ProductInStore" +Cohesion: 0.14 +Nodes (8): Factor, Meta, FactorIteam, ProductInStore, Meta, MovementType, FactorDel(), newFactor() + +### Community 35 - "admin_panel/templates/admin_panel/baseHesabdari.html" +Cohesion: 0.07 +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) + +### Community 36 - "Reference guide" +Cohesion: 0.14 +Nodes (14): 10. Quality evaluation, 11. Agent integration, 13. Performance, 14. Known limits, 15. Documentation, 16. Improvement, 17. License, 1. Purpose and function (+6 more) + +### Community 37 - "Factor Creation UI" +Cohesion: 0.15 +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" +Cohesion: 0.16 +Nodes (10): admin_panel:pardakhtNesiye, /panel/passCheck/, all_sarresid, mandehesabKP, admin_panel.ViewReportMali, toJalali, admin_panel:bedehi, admin_panel:bedehiKhadamat (+2 more) + +### Community 39 - "project_tree.py" +Cohesion: 0.21 +Nodes (6): is_excluded_file(), main(), non_negative_integer(), parse_args(), sorted_entries(), warn() + +### Community 40 - "CustomerSmsHistoryTests" +Cohesion: 0.15 +Nodes (3): customer_phones(), toJalaliDT(), CustomerSmsHistoryTests + +### Community 42 - "SMS Integration Docs" +Cohesion: 0.15 +Nodes (12): **جدول کدهای خطای لینک ارتباطی**, **جدول کدهای خطای وب‌سرویس و وضعیت‌های دلیوری**, **راهنمای استفاده از Web Service**, **راهنمای استفاده از لینک ارتباطی (URL GET)**, **راهنمای استفاده از لینک ارتباطی و وب‌سرویس سامانه پیامک (0098vip.com و 0098sms.com)**, **فهرست مطالب**, **مقدمات اتصال**, **نمونه کدهای پیاده‌سازی** (+4 more) + +### Community 43 - "timing_for" +Cohesion: 0.12 +Nodes (6): current_timing_value(), timing_for(), sms_template_edit(), sms_templates(), _templates_redirect(), TimingsTests + +### Community 45 - "stock_in" +Cohesion: 0.25 +Nodes (6): Product, ProductInStore, StoreRoom, current_stock(), stock_in(), ProductInStoreMathTests + +### Community 46 - "admin_panel/templates/admin_panel/customerAction.html" +Cohesion: 0.16 +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) + +### Community 47 - "parts_count" +Cohesion: 0.16 +Nodes (6): describe(), detect_encoding(), encoded_length(), is_gsm7(), parts_count(), SmsPartsTests + +### Community 48 - "write_tree" Cohesion: 0.21 Nodes (6): Counts, format_header(), generate_snapshot(), normalized_path(), write_tree(), visit() -### Community 98 - "reconciliation_tool.py" -Cohesion: 0.19 -Nodes (4): remove_query_param(), check_financial_discrepancy(), run_360_audit(), run_audit() +### Community 50 - "Customer" +Cohesion: 0.16 +Nodes (6): Customer, Factor, FactorIteam, VisitQueue, Command, NewFactor2ViewTests -### Community 99 - "ModelsPackageContractTests" -Cohesion: 0.05 -Nodes (15): AdminConfig, AdminPanelConfig, make_std_streams_unicode_safe(), configure_sqlite_connection(), entrypoint.sh script, main(), run.sh script, start_service() (+7 more) +### Community 51 - "Phone Cleanup Guide" +Cohesion: 0.17 +Nodes (11): اجرا روی سرور اصلی (production), اجرای دستور, اصلاح دستی شماره‌های قدیمی مشتریان, راه جایگزین: مهاجرت (migration), قاعدهٔ پاک‌سازی, مشاهدهٔ نتیجه, نکته‌ها, چرا؟ (+3 more) -### Community 100 - "django_conf" +### Community 53 - "manage.py" Cohesion: 0.22 -Nodes (4): AnnouncementRejected, place_announcement_call(), place_voip_call(), VoipCallResult +Nodes (5): entrypoint.sh script, main(), run.sh script, start_service(), usage() -### Community 103 - "customerIDFactorBackup.html" +### Community 54 - "Agent Operating Guidelines" Cohesion: 0.18 -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 (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 110 - "0061_sms_timing_and_primary.py" +### Community 57 - "test_sms.py" +Cohesion: 0.17 +Nodes (4): to_display(), _clean(), normalize_mobile(), NormalizeTests + +### Community 58 - "Financial Data Objects" +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) + +### Community 60 - "بازمحاسبهٔ «آخرین بازدید» مشتریان" +Cohesion: 0.18 +Nodes (10): اجرا روی production, بازمحاسبهٔ «آخرین بازدید» مشتریان, بررسی صحت داده (SQL، پیش از اجرا), دستور, دقیقاً چه می‌کند؟, نمونهٔ خروجی, نکته‌های مهم, گزینه‌ها (+2 more) + +### Community 62 - "SMS Timing Migration" Cohesion: 0.22 Nodes (4): backfill_primary_phones(), Migration, _normalize(), normalize_template_timing() -### Community 111 - "bale/providers.py" -Cohesion: 0.31 -Nodes (4): BaleBotProvider, ConsoleBaleProvider, get_bale_provider(), NotificationResult +### Community 63 - "Articl Model" +Cohesion: 0.67 +Nodes (3): Articl Model, Customer Model, articls -### Community 116 - "Customer" -Cohesion: 0.29 -Nodes (3): Customer, Meta, PhoneNumber +### Community 64 - "customerIDFactorBackup.html" +Cohesion: 0.18 +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/ -### Community 117 - "customerNewBackup.html" +### Community 67 - "Graphify Tooling Docs" +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 + +### Community 71 - "admin_panel/entezar.html" +Cohesion: 0.40 +Nodes (4): Customer Data Structure, kamaDatepicker Library, custom_tags, toJalali filter + +### Community 72 - "customerNewBackup.html" Cohesion: 0.29 Nodes (6): admin_panel:newFactor URL, Category Data, Client-side Form Logic, CSRF Token, custom_tags, static +### Community 73 - "hesabdariResidNashode.html" +Cohesion: 0.47 +Nodes (5): jQuery, pDatepicker JS Library, custom_tags, toCurrency, toJalali + +### Community 76 - "Timezone Migration History" +Cohesion: 0.29 +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" +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 + +### Community 82 - "Project Honesty Contract" +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 + +### 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" +Cohesion: 0.50 +Nodes (4): 6.1 Output example, 6.2 Exit codes, 6.3 JSON output, 6. Output format + +### Community 87 - "Performance Benchmarks" +Cohesion: 0.50 +Nodes (4): Against other tools, Measured, What it saves you, in tokens, Where its own cycles go — hardware counters, per scope + +### Community 88 - "SMS Integration Overview" +Cohesion: 0.67 +Nodes (3): SMS Gateway Integration Guide, SMS Settings UI, Python Dependencies + +### Community 90 - "Customer Modal UI" +Cohesion: 0.67 +Nodes (3): createCS click handler, openRow click handler, Customer Modals UI + +### Community 92 - "Infrastructure Services" +Cohesion: 0.67 +Nodes (3): Docker Infrastructure, SMS Delivery Tracker, SMS Worker Service + +### Community 93 - "Agent Setup Guide" +Cohesion: 0.67 +Nodes (3): 1. (Optional) Register the MCP server, 2. Install the skills, Set it up in your coding agent + +### Community 94 - "First Use Guide" +Cohesion: 0.67 +Nodes (3): 4.1 Make the first map, 4.2 Use the common commands, 4. First use + +### Community 98 - "reconciliation_tool.py" +Cohesion: 0.24 +Nodes (3): remove_query_param(), run_360_audit(), run_audit() + +### Community 113 - "bale/providers.py" +Cohesion: 0.36 +Nodes (4): BaleBotProvider, ConsoleBaleProvider, get_bale_provider(), NotificationResult + +### Community 166 - "sms_settings" +Cohesion: 0.33 +Nodes (4): parse_jalali_date(), parse_scan_since(), resolve_recipients_test(), sms_settings() + +### Community 169 - "index" +Cohesion: 0.67 +Nodes (3): index(), `nest=` is a max, so it cannot tell a long function from a tangled one, The quality panel + ## Ambiguous Edges - Review These -- `admin_panel/base.html` → `admin_panel/templates/admin_panel/baseHesabdari.html` [AMBIGUOUS] +- `admin_panel/templates/admin_panel/baseHesabdari.html` → `Base Template` [AMBIGUOUS] admin_panel/templates/admin_panel/baseHesabdari.html · relation: conceptually_related_to ## Knowledge Gaps -- **110 isolated node(s):** `DataTables Library`, `Customer Data Object`, `Factor Data Object`, `admin_panel:pardakhtNesiye URL`, `/panel/passCheck/ URL` (+105 more) - These have ≤1 connection - possible missing edges or undocumented components. (Counts symbols only; 575 node(s) total have ≤1 connection when file, concept and rationale nodes are included.) -- **93 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes. +- **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.) +- **97 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes. ## Suggested Questions _Questions this graph is uniquely positioned to answer:_ -- **Why does `SMSProviderConfig` connect `models/sms.py` to `scanner.py`?** - _High betweenness centrality (0.031) - this node is a cross-community bridge._ +- **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`?** + _High betweenness centrality (0.046) - this node is a cross-community bridge._ - **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._ -- **What connects `DataTables Library`, `Customer Data Object`, `Factor Data Object` to the rest of the system?** - _110 weakly-connected nodes found - possible documentation gaps or missing edges._ -- **Should `views/store.py` be split into smaller, more focused modules?** - _Cohesion score 0.06540825285338016 - nodes in this community are weakly interconnected._ -- **Why does `Languages` connect `Rip'n Fast. Fewer Tokens. Better Code.` to `scanner.py`?** - _High betweenness centrality (0.026) - this node is a cross-community bridge._ -- **Should `django_db` be split into smaller, more focused modules?** - _Cohesion score 0.06896551724137931 - nodes in this community are weakly interconnected._ +- **What connects `base.html`, `toCurrency`, `DataTables Library` to the rest of the system?** + _118 weakly-connected nodes found - possible documentation gaps or missing edges._ +- **Should `views/sms.py` be split into smaller, more focused modules?** + _Cohesion score 0.1425287356321839 - nodes in this community are weakly interconnected._ +- **Why does `Languages` connect `.load` to `Rip'n Fast. Fewer Tokens. Better Code.`?** + _High betweenness centrality (0.044) - this node is a cross-community bridge._ +- **Should `run_all` be split into smaller, more focused modules?** + _Cohesion score 0.05552617662612375 - nodes in this community are weakly interconnected._ ### Low-confidence Hints _AMBIGUOUS edges — the extractor was unsure. Verify before acting on these._ -- **What is the exact relationship between `admin_panel/base.html` and `admin_panel/templates/admin_panel/baseHesabdari.html`?** +- **What is the exact relationship between `admin_panel/templates/admin_panel/baseHesabdari.html` and `Base Template`?** _Edge tagged AMBIGUOUS (relation: conceptually_related_to) - confidence is low._ \ No newline at end of file diff --git a/graphify-out/graph.html b/graphify-out/graph.html index ee05928..0eaba09 100644 --- a/graphify-out/graph.html +++ b/graphify-out/graph.html @@ -63,12 +63,12 @@
    -
    1280 nodes · 2237 edges · 140 communities
    +
    1684 nodes · 3008 edges · 171 communities