diff --git a/TAX_ARTICLES_NORMALIZATION.md b/TAX_ARTICLES_NORMALIZATION.md deleted file mode 100644 index e449909..0000000 --- a/TAX_ARTICLES_NORMALIZATION.md +++ /dev/null @@ -1,159 +0,0 @@ -# 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.master_data.sync.sync_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 788fd4c..103fd9d 100644 --- a/taxes_az/fixtures/tax_article.json +++ b/taxes_az/fixtures/tax_article.json @@ -1661,13 +1661,13 @@ "parent_tax_article": "Gəlirlər üzrə azadolmalar barədə məlumat" }, { - "article_name": "Vergi Məcəlləsinin 106.1.24-cü maddəsinə əsasən \"Məşğulluq haqqında” Azərbaycan Respublikasının Qanununa uyğun olaraq Azərbaycan Respublikasının Prezidentinin müəyyən etdiyi orqanın (qurumun) haqqı ödənilən ictimai işlərin təşkili ilə bağlı fəaliyyətdən əldə etdiyi gəlir", + "article_name": "Vergi Məcəlləsinin 106.1.24-cü maddəsinə əsasən \"Məşğulluq haqqında\" Azərbaycan Respublikasının Qanununa uyğun olaraq Azərbaycan Respublikasının Prezidentinin müəyyən etdiyi orqanın (qurumun) haqqı ödənilən ictimai işlərin təşkili ilə bağlı fəaliyyətdən əldə etdiyi gəlir", "declaration": "Income tax return", "docstatus": 0, "doctype": "Tax Article", "is_group": 0, "modified": "2025-09-19 16:35:19.703000", - "name": "Vergi Məcəlləsinin 106.1.24-cü maddəsinə əsasən \"Məşğulluq haqqında” Azərbaycan Respublikasının Qanununa uyğun olaraq Azərbaycan Respubli...", + "name": "Vergi Məcəlləsinin 106.1.24-cü maddəsinə əsasən \"Məşğulluq haqqında\" Azərbaycan Respublikasının Qanununa uyğun olaraq Azərbaycan Respubli...", "old_parent": null, "parent_tax_article": "Gəlirlər üzrə azadolmalar barədə məlumat" }, @@ -1848,7 +1848,7 @@ "parent_tax_article": "Gəlirlər üzrə azadolmalar barədə məlumat" }, { - "article_name": "Vergi Məcəlləsinin 106.1.23-cü maddəsinə əsasən mikro və ya kiçik sahibkarlıq subyekti olan və hüquqi şəxs kimi fəaliyyət göstərən startapların “Startap” şəhadətnaməsi aldığı tarixdən innovasiya fəaliyyətindən əldə etdikləri mənfəəti", + "article_name": "Vergi Məcəlləsinin 106.1.23-cü maddəsinə əsasən mikro və ya kiçik sahibkarlıq subyekti olan və hüquqi şəxs kimi fəaliyyət göstərən startapların \"Startap\" şəhadətnaməsi aldığı tarixdən innovasiya fəaliyyətindən əldə etdikləri mənfəəti", "declaration": "Income tax return", "docstatus": 0, "doctype": "Tax Article", @@ -1903,13 +1903,13 @@ "parent_tax_article": "Verginin hesablanması" }, { - "article_name": "Vergi ödəyicisi kimi vergi orqanında uçotda olmayan fiziki şəxslərin “Nağdsız hesablaşmalar haqqında” Azərbaycan Respublikasının Qanununun 3.5-ci maddəsində göstərilən malların təqdim edilməsindən əldə edilən gəlirləri (vergidən azad olunan gəlirlər istisna olmaqla) xərclər çıxılmadan", + "article_name": "Vergi ödəyicisi kimi vergi orqanında uçotda olmayan fiziki şəxslərin \"Nağdsız hesablaşmalar haqqında\" Azərbaycan Respublikasının Qanununun 3.5-ci maddəsində göstərilən malların təqdim edilməsindən əldə edilən gəlirləri (vergidən azad olunan gəlirlər istisna olmaqla) xərclər çıxılmadan", "declaration": "Tax return withheld at source of payment", "docstatus": 0, "doctype": "Tax Article", "is_group": 0, "modified": "2025-09-19 16:35:19.707000", - "name": "Vergi ödəyicisi kimi vergi orqanında uçotda olmayan fiziki şəxslərin “Nağdsız hesablaşmalar haqqında” Azərbaycan Respublikasının Qanununu...", + "name": "Vergi ödəyicisi kimi vergi orqanında uçotda olmayan fiziki şəxslərin \"Nağdsız hesablaşmalar haqqında\" Azərbaycan Respublikasının Qanununu...", "old_parent": null, "parent_tax_article": "Verginin hesablanması" }, @@ -2156,13 +2156,13 @@ "parent_tax_article": "Verginin hesablanması" }, { - "article_name": "Vergi Məcəlləsinin 207.7-ci maddəsinə əsasən \"Aqrarkredit” Qapalı Səhmdar Cəmiyyəti Bank Olmayan Kredit Təşkilatı və “Azərbaycan Sənaye Korporasiyası” Açıq Səhmdar Cəmiyyəti ödəmə qabiliyyətini itirmiş bankların rezolyusiyası və sağlamlaşdırma tədbirləri çərçivəsində Azərbaycan Respublikasının Prezidentinin qərarına əsasən aldığı problemli aktivlərin (borcların) əvəzində əldə etdiyi, onun mülkiyyətində (balansında) olan və siyahısını Azərbaycan Respublikasının İqtisadiyyat Nazirliyi ilə razılaşdırdığı torpağa görə", + "article_name": "Vergi Məcəlləsinin 207.7-ci maddəsinə əsasən \"Aqrarkredit\" Qapalı Səhmdar Cəmiyyəti Bank Olmayan Kredit Təşkilatı və \"Azərbaycan Sənaye Korporasiyası\" Açıq Səhmdar Cəmiyyəti ödəmə qabiliyyətini itirmiş bankların rezolyusiyası və sağlamlaşdırma tədbirləri çərçivəsində Azərbaycan Respublikasının Prezidentinin qərarına əsasən aldığı problemli aktivlərin (borcların) əvəzində əldə etdiyi, onun mülkiyyətində (balansında) olan və siyahısını Azərbaycan Respublikasının İqtisadiyyat Nazirliyi ilə razılaşdırdığı torpağa görə", "declaration": "Land tax declaration", "docstatus": 0, "doctype": "Tax Article", "is_group": 0, "modified": "2025-09-19 16:35:19.714000", - "name": "Vergi Məcəlləsinin 207.7-ci maddəsinə əsasən \"Aqrarkredit” Qapalı Səhmdar Cəmiyyəti Bank Olmayan Kredit Təşkilatı və “Azərbaycan Sənaye K...", + "name": "Vergi Məcəlləsinin 207.7-ci maddəsinə əsasən \"Aqrarkredit\" Qapalı Səhmdar Cəmiyyəti Bank Olmayan Kredit Təşkilatı və \"Azərbaycan Sənaye K...", "old_parent": null, "parent_tax_article": "Verginin hesablanması" }, diff --git a/taxes_az/hooks.py b/taxes_az/hooks.py index eb2eccc..ed3657c 100644 --- a/taxes_az/hooks.py +++ b/taxes_az/hooks.py @@ -21,7 +21,6 @@ fixtures = [ after_migrate = [ "taxes_az.master_data.sync.sync_main_type_of_activity", "taxes_az.master_data.sync.sync_item_groups", - "taxes_az.normalize_tax_articles.normalize_on_migrate", "taxes_az.setup_accounts.check_and_create_accounts" ] diff --git a/taxes_az/normalize_tax_articles.py b/taxes_az/normalize_tax_articles.py deleted file mode 100644 index 77505fc..0000000 --- a/taxes_az/normalize_tax_articles.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Normalize Tax Articles - fix Unicode normalization, curly quotes, and NBSP - -This script: -1. Replaces non-breaking spaces (NBSP U+00A0) with regular spaces -2. Replaces typographic (curly) quotes with straight quotes -3. Normalizes Unicode to NFC form (composed characters) -4. Fixes both `name` and `article_name` fields - -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 name and article_name values: - - Replace non-breaking spaces (U+00A0) with regular spaces - - 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 non-breaking spaces (NBSP) with regular spaces - # C2A0 = UTF-8 for U+00A0 (NBSP) - # 20 = regular space - - frappe.db.sql(""" - UPDATE `tabTax Article` - SET article_name = REPLACE(article_name, UNHEX('C2A0'), ' ') - WHERE HEX(article_name) LIKE '%C2A0%' - """) - nbsp_article_fixed = frappe.db.sql("SELECT ROW_COUNT()")[0][0] - - frappe.db.sql(""" - UPDATE `tabTax Article` - SET name = REPLACE(name, UNHEX('C2A0'), ' ') - WHERE HEX(name) LIKE '%C2A0%' - """) - nbsp_name_fixed = frappe.db.sql("SELECT ROW_COUNT()")[0][0] - - # Also fix NBSP in Sales Invoice Item tax_article references - if frappe.db.table_exists("Sales Invoice Item"): - frappe.db.sql(""" - UPDATE `tabSales Invoice Item` - SET tax_article = REPLACE(tax_article, UNHEX('C2A0'), ' ') - WHERE HEX(tax_article) LIKE '%C2A0%' - """) - nbsp_ref_fixed = frappe.db.sql("SELECT ROW_COUNT()")[0][0] - else: - nbsp_ref_fixed = 0 - - # Step 2: Replace curly quotes with straight quotes using HEX - # E2809C = left curly quote \u201c - # E2809D = right curly quote \u201d - # 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 3: 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 - encoding_errors = 0 - - for article in articles: - try: - original = article['article_name'] - - # Handle potential encoding issues - if isinstance(original, bytes): - original = original.decode('utf-8', errors='replace') - - 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 - - except (UnicodeDecodeError, UnicodeEncodeError, TypeError) as e: - encoding_errors += 1 - frappe.log_error( - f"Encoding error in Tax Article {article['name']}: {str(e)}", - "Tax Article Encoding Error" - ) - - frappe.db.commit() - - total_fixed = nbsp_article_fixed + nbsp_name_fixed + nbsp_ref_fixed + quotes_fixed + nfc_fixed - if total_fixed > 0: - parts = [] - if nbsp_article_fixed > 0 or nbsp_name_fixed > 0: - parts.append(f"{nbsp_article_fixed + nbsp_name_fixed} NBSP fixed") - if nbsp_ref_fixed > 0: - parts.append(f"{nbsp_ref_fixed} NBSP in references fixed") - if quotes_fixed > 0: - parts.append(f"{quotes_fixed} curly quotes fixed") - if nfc_fixed > 0: - parts.append(f"{nfc_fixed} NFC normalized") - if encoding_errors > 0: - parts.append(f"{encoding_errors} encoding errors") - message = f"Tax Articles normalized: {', '.join(parts)}" - 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)}")