From 7f772fcb34944f5a00731de1bc39c354e5da0cd1 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Mon, 1 Jun 2026 11:36:43 +0000 Subject: [PATCH] =?UTF-8?q?feat(property-tax-return-ai-fill):=20autofill?= =?UTF-8?q?=20elav=C9=99=201/2=20+=20507=20from=20Asset=20accounting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two parallel DocType families that don't touch the existing `Property tax return` family: - Property Tax Return AI (parent + 10 children): cabinet-verified port of the property-tax formulas (501/503/504 mirrors, 506=1, 509.1/509.2, 511.x) with parity 3/3 vs. the original. - Property Tax Return AI Fill (parent + 10 children): same formulas plus auto-fill from ERPNext Asset records. On form open the seeded grid is populated from the single Company's Assets, routed by `taxable_asset_type` (→ elavə 2 rows 1.1–1.6, 2–5) and `tax_exempt_tax_article` (→ elavə 1 rows by article-code regex). Residuals at year boundaries come from Depreciation Schedule; Company.date_of_establishment drives 507 ayla when il_erzinde=1. Coverage: 16/16 smoke (seeding + formulas + 11 autofill scenarios) and 3/3 parity vs. original Property tax return pass. Also adds .etaxes_notes/cabinet_internals.md (sanitised cabinet probe notes) and gitignores .etaxes_notes/scratch/ which holds live JWTs and HAR-like dumps. Co-Authored-By: Claude Opus 4.7 --- .etaxes_notes/cabinet_internals.md | 533 +++++++++++++ .gitignore | 6 +- .../property_tax_return_ai/__init__.py | 0 .../property_tax_return_ai/parity_check.py | 555 ++++++++++++++ .../property_tax_return_ai.js | 270 +++++++ .../property_tax_return_ai.json | 385 ++++++++++ .../property_tax_return_ai.py | 363 +++++++++ .../property_tax_return_ai/test_smoke.py | 235 ++++++ .../__init__.py | 0 .../property_tax_return_ai_elave_1.json | 76 ++ .../property_tax_return_ai_elave_1.py | 6 + .../__init__.py | 0 .../property_tax_return_ai_elave_2.json | 74 ++ .../property_tax_return_ai_elave_2.py | 6 + .../__init__.py | 0 ...ai_emlak_vergi_hesablamasi_1ci_cedvel.json | 41 + ...n_ai_emlak_vergi_hesablamasi_1ci_cedvel.py | 6 + .../__init__.py | 0 ..._emlak_vergi_hesablamasi_1ci_cedvel_1.json | 41 + ...ai_emlak_vergi_hesablamasi_1ci_cedvel_1.py | 6 + .../__init__.py | 0 ...ai_emlak_vergi_hesablamasi_2ci_cedvel.json | 41 + ...n_ai_emlak_vergi_hesablamasi_2ci_cedvel.py | 6 + .../__init__.py | 0 ...ai_emlak_vergi_hesablamasi_3cu_cedvel.json | 41 + ...n_ai_emlak_vergi_hesablamasi_3cu_cedvel.py | 6 + .../__init__.py | 0 ...ai_emlak_vergi_hesablamasi_4cu_cedvel.json | 42 + ...n_ai_emlak_vergi_hesablamasi_4cu_cedvel.py | 6 + .../__init__.py | 0 ..._emlak_vergi_hesablamasi_4cu_cedvel_1.json | 41 + ...ai_emlak_vergi_hesablamasi_4cu_cedvel_1.py | 6 + .../__init__.py | 0 ...ai_emlak_vergi_hesablamasi_5ci_cedvel.json | 40 + ...n_ai_emlak_vergi_hesablamasi_5ci_cedvel.py | 6 + .../__init__.py | 0 ...ai_emlak_vergi_hesablamasi_6ci_cedvel.json | 43 ++ ...n_ai_emlak_vergi_hesablamasi_6ci_cedvel.py | 6 + .../property_tax_return_ai_fill/__init__.py | 0 .../parity_check.py | 555 ++++++++++++++ .../property_tax_return_ai_fill.js | 368 +++++++++ .../property_tax_return_ai_fill.json | 385 ++++++++++ .../property_tax_return_ai_fill.py | 718 ++++++++++++++++++ .../property_tax_return_ai_fill/test_smoke.py | 518 +++++++++++++ .../__init__.py | 0 .../property_tax_return_ai_fill_elave_1.json | 76 ++ .../property_tax_return_ai_fill_elave_1.py | 6 + .../__init__.py | 0 .../property_tax_return_ai_fill_elave_2.json | 74 ++ .../property_tax_return_ai_fill_elave_2.py | 6 + .../__init__.py | 0 ..._ai_fill_vergi_hesablamasi_1ci_cedvel.json | 41 + ...rn_ai_fill_vergi_hesablamasi_1ci_cedvel.py | 6 + .../__init__.py | 0 ...i_fill_vergi_hesablamasi_1ci_cedvel_1.json | 41 + ..._ai_fill_vergi_hesablamasi_1ci_cedvel_1.py | 6 + .../__init__.py | 0 ..._ai_fill_vergi_hesablamasi_2ci_cedvel.json | 41 + ...rn_ai_fill_vergi_hesablamasi_2ci_cedvel.py | 6 + .../__init__.py | 0 ..._ai_fill_vergi_hesablamasi_3cu_cedvel.json | 41 + ...rn_ai_fill_vergi_hesablamasi_3cu_cedvel.py | 6 + .../__init__.py | 0 ..._ai_fill_vergi_hesablamasi_4cu_cedvel.json | 42 + ...rn_ai_fill_vergi_hesablamasi_4cu_cedvel.py | 6 + .../__init__.py | 0 ...i_fill_vergi_hesablamasi_4cu_cedvel_1.json | 41 + ..._ai_fill_vergi_hesablamasi_4cu_cedvel_1.py | 6 + .../__init__.py | 0 ..._ai_fill_vergi_hesablamasi_5ci_cedvel.json | 40 + ...rn_ai_fill_vergi_hesablamasi_5ci_cedvel.py | 6 + .../__init__.py | 0 ..._ai_fill_vergi_hesablamasi_6ci_cedvel.json | 43 ++ ...rn_ai_fill_vergi_hesablamasi_6ci_cedvel.py | 6 + 74 files changed, 5970 insertions(+), 1 deletion(-) create mode 100644 .etaxes_notes/cabinet_internals.md create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai/parity_check.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai/property_tax_return_ai.js create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai/property_tax_return_ai.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai/property_tax_return_ai.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai/test_smoke.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_elave_1/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_elave_1/property_tax_return_ai_elave_1.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_elave_1/property_tax_return_ai_elave_1.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_elave_2/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_elave_2/property_tax_return_ai_elave_2.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_elave_2/property_tax_return_ai_elave_2.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_1ci_cedvel/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_1ci_cedvel/property_tax_return_ai_emlak_vergi_hesablamasi_1ci_cedvel.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_1ci_cedvel/property_tax_return_ai_emlak_vergi_hesablamasi_1ci_cedvel.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_1ci_cedvel_1/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_1ci_cedvel_1/property_tax_return_ai_emlak_vergi_hesablamasi_1ci_cedvel_1.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_1ci_cedvel_1/property_tax_return_ai_emlak_vergi_hesablamasi_1ci_cedvel_1.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_2ci_cedvel/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_2ci_cedvel/property_tax_return_ai_emlak_vergi_hesablamasi_2ci_cedvel.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_2ci_cedvel/property_tax_return_ai_emlak_vergi_hesablamasi_2ci_cedvel.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_3cu_cedvel/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_3cu_cedvel/property_tax_return_ai_emlak_vergi_hesablamasi_3cu_cedvel.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_3cu_cedvel/property_tax_return_ai_emlak_vergi_hesablamasi_3cu_cedvel.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_4cu_cedvel/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_4cu_cedvel/property_tax_return_ai_emlak_vergi_hesablamasi_4cu_cedvel.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_4cu_cedvel/property_tax_return_ai_emlak_vergi_hesablamasi_4cu_cedvel.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_4cu_cedvel_1/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_4cu_cedvel_1/property_tax_return_ai_emlak_vergi_hesablamasi_4cu_cedvel_1.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_4cu_cedvel_1/property_tax_return_ai_emlak_vergi_hesablamasi_4cu_cedvel_1.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_5ci_cedvel/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_5ci_cedvel/property_tax_return_ai_emlak_vergi_hesablamasi_5ci_cedvel.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_5ci_cedvel/property_tax_return_ai_emlak_vergi_hesablamasi_5ci_cedvel.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_6ci_cedvel/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_6ci_cedvel/property_tax_return_ai_emlak_vergi_hesablamasi_6ci_cedvel.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_emlak_vergi_hesablamasi_6ci_cedvel/property_tax_return_ai_emlak_vergi_hesablamasi_6ci_cedvel.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill/parity_check.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill/property_tax_return_ai_fill.js create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill/property_tax_return_ai_fill.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill/property_tax_return_ai_fill.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill/test_smoke.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_elave_1/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_elave_1/property_tax_return_ai_fill_elave_1.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_elave_1/property_tax_return_ai_fill_elave_1.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_elave_2/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_elave_2/property_tax_return_ai_fill_elave_2.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_elave_2/property_tax_return_ai_fill_elave_2.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_1ci_cedvel/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_1ci_cedvel/property_tax_return_ai_fill_vergi_hesablamasi_1ci_cedvel.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_1ci_cedvel/property_tax_return_ai_fill_vergi_hesablamasi_1ci_cedvel.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_1ci_cedvel_1/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_1ci_cedvel_1/property_tax_return_ai_fill_vergi_hesablamasi_1ci_cedvel_1.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_1ci_cedvel_1/property_tax_return_ai_fill_vergi_hesablamasi_1ci_cedvel_1.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_2ci_cedvel/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_2ci_cedvel/property_tax_return_ai_fill_vergi_hesablamasi_2ci_cedvel.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_2ci_cedvel/property_tax_return_ai_fill_vergi_hesablamasi_2ci_cedvel.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_3cu_cedvel/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_3cu_cedvel/property_tax_return_ai_fill_vergi_hesablamasi_3cu_cedvel.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_3cu_cedvel/property_tax_return_ai_fill_vergi_hesablamasi_3cu_cedvel.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_4cu_cedvel/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_4cu_cedvel/property_tax_return_ai_fill_vergi_hesablamasi_4cu_cedvel.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_4cu_cedvel/property_tax_return_ai_fill_vergi_hesablamasi_4cu_cedvel.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_4cu_cedvel_1/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_4cu_cedvel_1/property_tax_return_ai_fill_vergi_hesablamasi_4cu_cedvel_1.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_4cu_cedvel_1/property_tax_return_ai_fill_vergi_hesablamasi_4cu_cedvel_1.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_5ci_cedvel/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_5ci_cedvel/property_tax_return_ai_fill_vergi_hesablamasi_5ci_cedvel.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_5ci_cedvel/property_tax_return_ai_fill_vergi_hesablamasi_5ci_cedvel.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_6ci_cedvel/__init__.py create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_6ci_cedvel/property_tax_return_ai_fill_vergi_hesablamasi_6ci_cedvel.json create mode 100644 taxes_az/taxes_az/doctype/property_tax_return_ai_fill_vergi_hesablamasi_6ci_cedvel/property_tax_return_ai_fill_vergi_hesablamasi_6ci_cedvel.py diff --git a/.etaxes_notes/cabinet_internals.md b/.etaxes_notes/cabinet_internals.md new file mode 100644 index 0000000..5c3079c --- /dev/null +++ b/.etaxes_notes/cabinet_internals.md @@ -0,0 +1,533 @@ +# e-taxes.gov.az Cabinet Internals — Operational Notes + +Persistent notes for future Claude sessions about the ГНС cabinet (new.e-taxes.gov.az). +Add to this file when you learn something non-obvious. Keep entries dated. + +--- + +## High-level architecture (verified 2026-05-26) + +Two separate SPAs under one domain: + +| Path prefix | Mount point | Purpose | webpackJsonp name | +|---|---|---|---| +| `/etaxes/` | `
` | Public services (lookup taxpayer, debt query, etc.) | `webpackJsonpetaxes` | +| `/eportal/` | `
` | **Cabinet for authenticated users — declarations live here** | (varies) | + +Both: React + Redux Toolkit + RTK Query, CRA-ish bundle layout (`static/js/N.HASH.chunk.js`). +Source maps NOT exposed. + +**Pitfall:** the cabinet's mount point is `#app`, NOT `#root`. A snippet that walks fibers from `#root` will return `hasRoot: false`. + +--- + +## Auth — how the cabinet checks login + +REST (Python `requests`) — **simple**: send `x-authorization: Bearer ` header. Works for every documented endpoint. No cookies needed, no session. + +Browser SPA — **NOT** simple. Just injecting `x-authorization` into outgoing requests via Playwright's `route` is **NOT enough**. The SPA has a route guard that checks **Redux state** for the auth token. If Redux is empty → redirect to `/eportal/login`, regardless of what's in HTTP headers. + +Verified storage usage (grepped eportal bundle): +- `localStorage` — only `surveyModal` (popup tracking) and `aztax-lang`. **No auth token**. +- `sessionStorage` — empty after fresh page load. +- Cookies — **none set** by any cabinet endpoint (verified `Set-Cookie` headers: empty on all responses including `/chooseTaxpayer`). + +**Implication:** to drive the cabinet headlessly you must EITHER: +1. Login through the SPA UI (PIN1 push to phone), OR +2. Inject the token into the Redux store via `page.evaluate()` after the SPA mounts. Action type is dynamically constructed in Redux Toolkit — grep `*-api`, `Slice/` patterns to find it. Untested as of 2026-05-26. + +Easier in practice: UI login + Playwright's `storage_state` to persist cookies+localStorage between runs (note: storage_state is mostly empty here, so reuse value is limited — the actual auth lives in in-memory Redux, which Playwright cannot serialize). + +### REST ASAN flow (works without browser) + +``` +POST /api/po/auth/public/v1/asanImza/start body={"phone": "+994...", "userId": "..."} + → returns 200 with bearer_token in x-authorization response header + → response body: {"verificationCode": "1234"} (shown on user's phone screen) + +POLL GET /api/po/auth/public/v1/asanImza/status headers={x-authorization: Bearer } + → returns {"successful": false, "error": null} while waiting + → when user approves PIN1, eventually returns successful: true (or just becomes silently true) + → BETTER: poll /asanImza/certificates instead — when it returns 200 with non-empty list, you're authed + +GET /api/po/auth/public/v1/asanImza/certificates + → returns [{taxpayerType: "individual"|"legal", legalInfo|individualInfo, ...}, ...] + +POST /api/po/auth/public/v1/asanImza/chooseTaxpayer body={"ownerType": "legal", "legalTin": "VOEN"} + → returns 200 with main_token in x-authorization response header + → main_token JWT validity: 10 min (exp = iat + 600s) +``` + +**Pitfall — polling format:** my first explorer.py polled `data.get("status")` but the actual field is `data.get("successful")` (bool). Wrong field name → silent infinite loop. Now using "did certificates endpoint return non-empty data?" as success signal — more robust. + +**Pitfall — ГНС polling latency:** user approves PIN1 quickly, but ГНС server takes several seconds (5-30s observed) to flip `successful: true`. Use timeout >= 90s with 2s interval. + +### Renewing tokens + +`RENEW_URL = "https://new.e-taxes.gov.az/api/po/auth/public/v1/renew"` — used by `taxes_az/auth.py:renew_token`. Returns 401 if token expired without recent activity. There's an `ACTIVITY_TIMEOUT_MINUTES = 10` gate in the project code. + +--- + +## Login form (UI flow, /eportal/az/login) + +Initial page: `/eportal/az/login` — shows 5 method tiles. Click "Asan İmza" link (`` with text "Asan İmza", class `login-item-link`) → SPA navigates to `/eportal/az/login/asan` with form. + +ASAN form selectors (verified 2026-05-26): +| Selector | Field | Notes | +|---|---|---| +| `#phone` | Phone (type=text) | placeholder `00 000 00 00` — enter LOCAL format only (e.g. `558239674`), **without** +994 | +| `#userId` | User ID (type=password) | The Asan İmza user ID, e.g. `171082` | +| `#loginPageSignInButton` | Submit (type=submit) | Text: `Daxil ol` | + +Page also has a language `