From 2795ce869bf565d57bcb6d1afad87d691850c784 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Mon, 2 Feb 2026 19:11:45 +0400 Subject: [PATCH] fixed bug with tax article report --- TAX_ARTICLES_NORMALIZATION.md | 159 +++++++++++++++++++++++++++++ taxes_az/fixtures/tax_article.json | 24 ++--- taxes_az/hooks.py | 5 +- taxes_az/normalize_tax_articles.py | 89 ++++++++++++++++ 4 files changed, 264 insertions(+), 13 deletions(-) create mode 100644 TAX_ARTICLES_NORMALIZATION.md create mode 100644 taxes_az/normalize_tax_articles.py diff --git a/TAX_ARTICLES_NORMALIZATION.md b/TAX_ARTICLES_NORMALIZATION.md new file mode 100644 index 0000000..430e006 --- /dev/null +++ b/TAX_ARTICLES_NORMALIZATION.md @@ -0,0 +1,159 @@ +# Tax Articles Normalization + +## Проблема + +В Tax Articles были обнаружены проблемы с кодировкой символов, которые приводили к тому, что отчет **Tax Articles Revenue Report** показывал 0 вместо реальных сумм для некоторых статей. + +### Две найденные проблемы: + +#### 1. Типографские кавычки (Curly Quotes) + +**Проблема:** В базе данных использовались типографские (curly) кавычки `"` `"` (Unicode U+201C/U+201D), а в коде Python - обычные прямые кавычки `"` (ASCII 0x22). + +**Пример:** +``` +БД: "Məşğulluq haqqında" (типографские) +Код: "Məşğulluq haqqında" (прямые) +``` + +**Затронутые статьи:** +- VM 164.1.42 - "Məşğulluq haqqında" +- VM 164.1.51 - "Tibbi sığorta haqqında" +- Vergi Məcəlləsinin 102.1-ci +- Vergi Məcəlləsinin 106.1.24-cü +- Vergi Məcəlləsinin 207.7-ci + +#### 2. Unicode Нормализация (NFD vs NFC) + +**Проблема:** В базе данных использовалась NFD форма (decomposed - буква + диакритический знак), а в коде - NFC форма (composed - готовая буква). + +**Пример:** +``` +NFD: s + cedilla (2 символа) = ş +NFC: ş (1 символ) +``` + +**Затронутые статьи:** +- VM 164.1.34-1 +- VM 164.1.41-2 +- VM 164.1.47 +- Vergi Məcəlləsinin 106.1.5-ci + +## Решение + +### Автоматическое исправление + +Создан скрипт `taxes_az/normalize_tax_articles.py`, который автоматически: + +1. **Заменяет типографские кавычки на прямые** + - Использует SQL REPLACE с HEX значениями для точной замены байтов + - `E2809C` (U+201C) → `22` (ASCII ") + - `E2809D` (U+201D) → `22` (ASCII ") + +2. **Нормализует Unicode в форму NFC** + - Конвертирует разложенные символы в композитные + - Обеспечивает соответствие с кодом Python + +### Запуск скрипта + +Скрипт запускается автоматически после каждой миграции благодаря хуку в `hooks.py`: + +```python +after_migrate = [ + "taxes_az.create_item_group.create_item_groups", + "taxes_az.normalize_tax_articles.normalize_on_migrate" +] +``` + +### Ручной запуск + +Если нужно запустить вручную: + +```bash +cd /home/frappe/frappe-bench +bench --site [site-name] execute " +from taxes_az.normalize_tax_articles import normalize_tax_articles +normalize_tax_articles() +" +``` + +## Установка на новой машине + +При установке приложения на новой машине: + +1. **Установите приложение:** + ```bash + bench get-app https://github.com/your-repo/taxes_az + bench --site [site-name] install-app taxes_az + ``` + +2. **Скрипт нормализации запустится автоматически** во время установки (через after_migrate hook) + +3. **Проверьте логи:** + ```bash + bench --site [site-name] console + ``` + Вы должны увидеть: + ``` + Tax Articles normalized: X curly quotes fixed, Y NFC normalized + ``` + +## Проверка + +Для проверки что все исправлено правильно: + +```bash +cd /home/frappe/frappe-bench +bench --site [site-name] mariadb --execute " +SELECT COUNT(*) as remaining_problems +FROM \`tabTax Article\` +WHERE HEX(article_name) LIKE '%E2809C%' + OR HEX(article_name) LIKE '%E2809D%'; +" +``` + +Результат должен быть: `remaining_problems: 0` + +## Fixtures + +Файл fixtures (`taxes_az/fixtures/tax_article.json`) также был обновлен: +- Заменены типографские кавычки на прямые +- Применена NFC нормализация + +При экспорте новых fixtures они будут автоматически в правильном формате, так как данные в БД уже нормализованы. + +## Технические детали + +### Файлы + +- **Скрипт нормализации:** `taxes_az/normalize_tax_articles.py` +- **Hooks конфигурация:** `taxes_az/hooks.py` +- **Fixtures (обновлен):** `taxes_az/fixtures/tax_article.json` +- **Отчет использующий маппинг:** `taxes_az/report/tax_articles_revenue_report/` + +### HEX коды + +| Тип кавычки | Символ | Unicode | UTF-8 HEX | +|-------------|--------|---------|-----------| +| Левая типографская | " | U+201C | E2 80 9C | +| Правая типографская | " | U+201D | E2 80 9D | +| Прямая | " | ASCII 0x22 | 22 | + +### Логирование + +Все действия логируются в Frappe Error Log с заголовком "Tax Article Normalization". + +## История изменений + +**2026-02-02:** Первая версия +- Исправлено 5 статей с кавычками +- Исправлено 4 статьи с NFD нормализацией +- Добавлен автоматический скрипт в hooks +- Обновлены fixtures + +## Поддержка + +При проблемах проверьте: +1. Логи миграции: `bench --site [site-name] migrate` +2. Error Log в интерфейсе Frappe +3. Запустите скрипт вручную для диагностики diff --git a/taxes_az/fixtures/tax_article.json b/taxes_az/fixtures/tax_article.json index 57ac53f..656855b 100644 --- a/taxes_az/fixtures/tax_article.json +++ b/taxes_az/fixtures/tax_article.json @@ -11,7 +11,7 @@ "parent_tax_article": "Verginin hesablanması" }, { - "article_name": "VM-nin 164.1.47-ci maddəsinə əsasən bina tikintisi fəaliyyəti ilə məşğul olan şəxslər tərəfindən tikilən binanın yaşayış sahəsinin dövlət...", + "article_name": "VM-nin 164.1.47-ci maddəsinə əsasən bina tikintisi fəaliyyəti ilə məşğul olan şəxslər tərəfindən tikilən binanın yaşayış sahəsinin dövlətə ayrılan hissəsi üzrə dövriyyələr", "declaration": "Declaration of value added tax", "docstatus": 0, "doctype": "Tax Article", @@ -957,13 +957,13 @@ "parent_tax_article": "ƏDV-dən azad olunan əməliyyatlar" }, { - "article_name": "VM-nin 164.1.42-ci maddəsinə əsasən “Məşğulluq haqqında” Azərbaycan Respublikasının Qanununa uyğun olaraq müvafiq icra hakimiyyəti orqanının müəyyən etdiyi orqan (qurum) tərəfindən haqqı ödənilən ictimai işlərin təşkili ilə əlaqədar işçi qüvvəsinin təqdim edilməsi üzrə əməliyyatlar", + "article_name": "VM-nin 164.1.42-ci maddəsinə əsasən \"Məşğulluq haqqında\" Azərbaycan Respublikasının Qanununa uyğun olaraq müvafiq icra hakimiyyəti orqanının müəyyən etdiyi orqan (qurum) tərəfindən haqqı ödənilən ictimai işlərin təşkili ilə əlaqədar işçi qüvvəsinin təqdim edilməsi üzrə əməliyyatlar", "declaration": "Declaration of value added tax", "docstatus": 0, "doctype": "Tax Article", "is_group": 0, "modified": "2025-09-19 16:35:19.690000", - "name": "VM-nin 164.1.42-ci maddəsinə əsasən “Məşğulluq haqqında” Azərbaycan Respublikasının Qanununa uyğun olaraq müvafiq icra hakimiyyəti orqanı...", + "name": "VM-nin 164.1.42-ci maddəsinə əsasən \"Məşğulluq haqqında\" Azərbaycan Respublikasının Qanununa uyğun olaraq müvafiq icra hakimiyyəti orqanı...", "old_parent": null, "parent_tax_article": "ƏDV-dən azad olunan əməliyyatlar" }, @@ -1034,13 +1034,13 @@ "parent_tax_article": "ƏDV-dən azad olunan əməliyyatlar" }, { - "article_name": "VM-nin 164.1.51-ci maddəsinə əsasən “Tibbi sığorta haqqında” Azərbaycan Respublikasının Qanununa uyğun olaraq icbari tibbi sığorta fondunun, habelə siyahısı müvafiq icra hakimiyyəti orqanının müəyyən etdiyi orqan (qurum) tərəfindən təsdiq edilən ictimai və sosial məqsədlər üçün yaradılmış fondların vəsaiti hesabına tibbi xidmətlərin göstərilməsi", + "article_name": "VM-nin 164.1.51-ci maddəsinə əsasən \"Tibbi sığorta haqqında\" Azərbaycan Respublikasının Qanununa uyğun olaraq icbari tibbi sığorta fondunun, habelə siyahısı müvafiq icra hakimiyyəti orqanının müəyyən etdiyi orqan (qurum) tərəfindən təsdiq edilən ictimai və sosial məqsədlər üçün yaradılmış fondların vəsaiti hesabına tibbi xidmətlərin göstərilməsi", "declaration": "Declaration of value added tax", "docstatus": 0, "doctype": "Tax Article", "is_group": 0, "modified": "2025-09-19 16:35:19.690000", - "name": "VM-nin 164.1.51-ci maddəsinə əsasən “Tibbi sığorta haqqında” Azərbaycan Respublikasının Qanununa uyğun olaraq icbari tibbi sığorta fondun...", + "name": "VM-nin 164.1.51-ci maddəsinə əsasən \"Tibbi sığorta haqqında\" Azərbaycan Respublikasının Qanununa uyğun olaraq icbari tibbi sığorta fondun...", "old_parent": null, "parent_tax_article": "ƏDV-dən azad olunan əməliyyatlar" }, @@ -1056,13 +1056,13 @@ "parent_tax_article": "ƏDV-dən azad olunan əməliyyatlar" }, { - "article_name": "VM-nin 164.1.34-1-ci maddəsinə əsasən kənd təsərrüfatı texnikalarının mülkiyyət hüququnun keçməməsi şərtilə kənd təsərrüfatı istehsalçılarına lizinqə (icarəyə) verilməsi", + "article_name": "VM-nin 164.1.34-1-ci maddəsinə əsasən kənd təsərrüfatı texnikalarının mülkiyyət hüququnun keçməməsi şərtilə kənd təsərrüfatı istehsalçılarına lizinqə (icarəyə) verilməsi", "declaration": "Declaration of value added tax", "docstatus": 0, "doctype": "Tax Article", "is_group": 0, "modified": "2025-09-19 16:35:19.690000", - "name": "VM-nin 164.1.34-1-ci maddəsinə əsasən kənd təsərrüfatı texnikalarının mülkiyyət hüququnun keçməməsi şərtilə kənd təsərrüfatı istehsalçıl...", + "name": "VM-nin 164.1.34-1-ci maddəsinə əsasən kənd təsərrüfatı texnikalarının mülkiyyət hüququnun keçməməsi şərtilə kənd təsərrüfatı istehsalçıl...", "old_parent": null, "parent_tax_article": "ƏDV-dən azad olunan əməliyyatlar" }, @@ -1100,13 +1100,13 @@ "parent_tax_article": "ƏDV-dən azad olunan əməliyyatlar" }, { - "article_name": "VM-nin 164.1.41-2-ci maddəsinə əsasən Elektrik mühərriki ilə işləyən avtomobillər üçün ikinci və üçüncü səviyyə elektrik enerji doldurucularının satışı", + "article_name": "VM-nin 164.1.41-2-ci maddəsinə əsasən Elektrik mühərriki ilə işləyən avtomobillər üçün ikinci və üçüncü səviyyə elektrik enerji doldurucularının satışı", "declaration": "Declaration of value added tax", "docstatus": 0, "doctype": "Tax Article", "is_group": 0, "modified": "2025-09-19 16:35:19.690000", - "name": "VM-nin 164.1.41-2-ci maddəsinə əsasən Elektrik mühərriki ilə işləyən avtomobillər üçün ikinci və üçüncü səviyyə elektrik enerji dolduruc...", + "name": "VM-nin 164.1.41-2-ci maddəsinə əsasən Elektrik mühərriki ilə işləyən avtomobillər üçün ikinci və üçüncü səviyyə elektrik enerji dolduruc...", "old_parent": null, "parent_tax_article": "ƏDV-dən azad olunan əməliyyatlar" }, @@ -1243,7 +1243,7 @@ "parent_tax_article": "ƏDV-dən azad olunan əməliyyatlar" }, { - "article_name": "VM-nin 164.1.51-1-ci maddəsinə əsasən tədris bazası kimi fəaliyyət göstərən tibb müəssisələrində tibbi xidmətlərin göstərilməsi", + "article_name": "VM-nin 164.1.51-ci maddəsinə əsasən \"Tibbi sığorta haqqında\" Azərbaycan Respublikasının Qanununa uyğun olaraq icbari tibbi sığorta fondunun, habelə siyahısı müvafiq icra hakimiyyəti orqanının müəyyən etdiyi orqan (qurum) tərəfindən təsdiq edilən ictimai və sosial məqsədlər üçün yaradılmış fondların vəsaiti hesabına tibbi xidmətlərin göstərilməsi", "declaration": "Declaration of value added tax", "docstatus": 0, "doctype": "Tax Article", @@ -1551,13 +1551,13 @@ "parent_tax_article": "Gəlirlər üzrə azadolmalar barədə məlumat" }, { - "article_name": "Vergi Məcəlləsinin 106.1.5-ci maddəsinə əsasən Azərbaycan Respublikası Mərkəzi Bankının və onun qurumlarının, ipoteka kreditləşməsi və sahibkarların aldıqları kreditlərə təminat verilməsi sahəsində fəaliyyət göstərən müvafiq icra hakimiyyəti orqanının müəyyən etdiyi orqanın (qurumun), Azərbaycan Respublikası Dövlət Neft Fondunun, Əmanətlərin Sığortalanması Fondunun, İcbari Sığorta Bürosunun gəlirləri, habelə müvafiq icra hakimiyyəti orqanının müəyyən etdiyi orqanın (qurumun) nizamnaməsinə (əsasnaməsinə) uyğun olaraq ayrılmış güzəştli kreditlər üzrə müvəkkil kredit təşkilatları tərəfindən həmin orqanlara (qurumlara) ödənilən faiz gəlirləri", + "article_name": "Vergi Məcəlləsinin 106.1.5-ci maddəsinə əsasən Azərbaycan Respublikası Mərkəzi Bankının və onun qurumlarının, ipoteka kreditləşməsi və sahibkarların aldıqları kreditlərə təminat verilməsi sahəsində fəaliyyət göstərən müvafiq icra hakimiyyəti orqanının müəyyən etdiyi orqanın (qurumun), Azərbaycan Respublikası Dövlət Neft Fondunun, Əmanətlərin Sığortalanması Fondunun, İcbari Sığorta Bürosunun gəlirləri, habelə müvafiq icra hakimiyyəti orqanının müəyyən etdiyi orqanın (qurumun) nizamnaməsinə (əsasnaməsinə) uyğun olaraq ayrılmış güzəştli kreditlər üzrə müvəkkil kredit təşkilatları tərəfindən həmin orqanlara (qurumlara) ödənilən faiz gəlirləri", "declaration": "Income tax return", "docstatus": 0, "doctype": "Tax Article", "is_group": 0, "modified": "2025-09-19 16:35:19.702000", - "name": "Vergi Məcəlləsinin 106.1.5-ci maddəsinə əsasən Azərbaycan Respublikası Mərkəzi Bankının və onun qurumlarının, ipoteka kreditləşməsi və s...", + "name": "Vergi Məcəlləsinin 106.1.5-ci maddəsinə əsasən Azərbaycan Respublikası Mərkəzi Bankının və onun qurumlarının, ipoteka kreditləşməsi və s...", "old_parent": null, "parent_tax_article": "Gəlirlər üzrə azadolmalar barədə məlumat" }, diff --git a/taxes_az/hooks.py b/taxes_az/hooks.py index 7f1a00d..9f28b79 100644 --- a/taxes_az/hooks.py +++ b/taxes_az/hooks.py @@ -22,7 +22,10 @@ fixtures = [ } ] -after_migrate = "taxes_az.create_item_group.create_item_groups" +after_migrate = [ + "taxes_az.create_item_group.create_item_groups", + "taxes_az.normalize_tax_articles.normalize_on_migrate" +] doctype_js = { "XML Mapping Tool": [ diff --git a/taxes_az/normalize_tax_articles.py b/taxes_az/normalize_tax_articles.py new file mode 100644 index 0000000..110ffe3 --- /dev/null +++ b/taxes_az/normalize_tax_articles.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Normalize Tax Articles - fix Unicode normalization and curly quotes + +This script: +1. Replaces typographic (curly) quotes with straight quotes +2. Normalizes Unicode to NFC form (composed characters) + +Run after migration to ensure all Tax Article names match the code mapping. +""" + +import frappe +import unicodedata + + +def normalize_tax_articles(): + """ + Normalize all Tax Article article_name values: + - Replace typographic quotes (U+201C, U+201D) with straight quotes (") + - Normalize Unicode to NFC form + """ + + if not frappe.db.table_exists("Tax Article"): + frappe.log_error("Tax Article table does not exist, skipping normalization") + return + + try: + # Step 1: Replace curly quotes with straight quotes using HEX + # E2809C = left curly quote " + # E2809D = right curly quote " + # 22 = straight quote " + + frappe.db.sql(""" + UPDATE `tabTax Article` + SET article_name = REPLACE( + REPLACE(article_name, UNHEX('E2809C'), '"'), + UNHEX('E2809D'), '"' + ) + WHERE HEX(article_name) LIKE '%E2809C%' + OR HEX(article_name) LIKE '%E2809D%' + """) + + quotes_fixed = frappe.db.sql("SELECT ROW_COUNT()")[0][0] + + # Step 2: Normalize Unicode to NFC form + articles = frappe.db.sql(""" + SELECT name, article_name + FROM `tabTax Article` + WHERE article_name IS NOT NULL AND article_name != '' + """, as_dict=True) + + nfc_fixed = 0 + for article in articles: + original = article['article_name'] + normalized = unicodedata.normalize('NFC', original) + + if original != normalized: + frappe.db.sql(""" + UPDATE `tabTax Article` + SET article_name = %s + WHERE name = %s + """, (normalized, article['name'])) + nfc_fixed += 1 + + frappe.db.commit() + + if quotes_fixed > 0 or nfc_fixed > 0: + message = f"Tax Articles normalized: {quotes_fixed} curly quotes fixed, {nfc_fixed} NFC normalized" + print(message) + frappe.log_error(message, "Tax Article Normalization") + else: + print("Tax Articles: Already normalized, no changes needed") + + except Exception as e: + frappe.log_error(f"Error normalizing Tax Articles: {str(e)}", "Tax Article Normalization Error") + raise + + +def normalize_on_migrate(): + """ + Called by hooks.py after_migrate + """ + try: + normalize_tax_articles() + except Exception as e: + # Don't fail migration if normalization fails + frappe.log_error(f"Tax Article normalization failed: {str(e)}", "Migration Warning") + print(f"Warning: Tax Article normalization failed: {str(e)}")