From 7478d4d66c6ae1e387585ee1ccb8669c5b209e60 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Tue, 2 Jun 2026 12:25:24 +0000 Subject: [PATCH] feat(reports): tax articles via SI child-table join; drop Tax Article Items Report Revenue and VAT-allocation reports now resolve tax articles by JOINing the Sales Invoice Tax Article child table (each article carries the full line amount) instead of the single sii.tax_article; add 302.x/303.x mappings. Remove the obsolete Tax Article Items Report doctype. Also stop tracking .etaxes_notes/cabinet_internals.md (contains live secrets) and broaden .gitignore to the whole .etaxes_notes/ folder. Co-Authored-By: Claude Opus 4.8 --- .etaxes_notes/cabinet_internals.md | 533 ------------------ .gitignore | 4 +- .../tax_article_items_report/__init__.py | 0 .../tax_article_items_report.js | 113 ---- .../tax_article_items_report.json | 129 ----- .../tax_article_items_report.py | 115 ---- .../test_tax_article_items_report.py | 30 - .../doctype/vat_allocation/vat_allocation.py | 10 +- .../tax_articles_revenue_report.py | 44 +- .../vat_allocation_tax_articles_report.py | 34 +- 10 files changed, 78 insertions(+), 934 deletions(-) delete mode 100644 .etaxes_notes/cabinet_internals.md delete mode 100644 taxes_az/taxes_az/doctype/tax_article_items_report/__init__.py delete mode 100644 taxes_az/taxes_az/doctype/tax_article_items_report/tax_article_items_report.js delete mode 100644 taxes_az/taxes_az/doctype/tax_article_items_report/tax_article_items_report.json delete mode 100644 taxes_az/taxes_az/doctype/tax_article_items_report/tax_article_items_report.py delete mode 100644 taxes_az/taxes_az/doctype/tax_article_items_report/test_tax_article_items_report.py diff --git a/.etaxes_notes/cabinet_internals.md b/.etaxes_notes/cabinet_internals.md deleted file mode 100644 index 5c3079c..0000000 --- a/.etaxes_notes/cabinet_internals.md +++ /dev/null @@ -1,533 +0,0 @@ -# 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 `