diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fb8bdca --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,256 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**taxes_az** is a Frappe/ERPNext application for managing Azerbaijan tax declarations and compliance. It integrates with Azerbaijan's E-Taxes system (new.e-taxes.gov.az) to automate tax filing, company profile management, and tax calculations. + +## Development Environment + +This is a Frappe app that must be developed within a Frappe bench environment. + +### Common Commands + +```bash +# Development server (run from bench directory) +cd /home/frappe/frappe-bench +bench start + +# Database migrations +bench --site [site-name] migrate + +# Install/reinstall app +bench --site [site-name] install-app taxes_az +bench --site [site-name] reinstall-app taxes_az + +# Console access +bench --site [site-name] console + +# Clear cache +bench --site [site-name] clear-cache + +# Build assets (after JS/CSS changes) +bench build --app taxes_az + +# Run tests +bench --site [site-name] run-tests --app taxes_az +``` + +### Post-migration Hook + +The app automatically creates Item Groups after migration via: +```python +after_migrate = "taxes_az.create_item_group.create_item_groups" +``` + +## Architecture + +### Core Integration Modules + +#### 1. E-Taxes Authentication (`taxes_az/auth.py`) +- Handles authentication with Azerbaijan E-Taxes API +- Token management and renewal (endpoint: `https://new.e-taxes.gov.az/api/po/auth/public/v1/renew`) +- Activity tracking with 10-minute timeout +- Key functions: + - `record_etaxes_activity()` - Tracks user activity for session management + - Authentication state stored in "Asan Login" doctype + +#### 2. Company E-Taxes Integration (`taxes_az/company_etaxes.py`) +- Fetches company profile data from E-Taxes +- Handles special gambling activity logic (codes: 92000, 9200003, 9200004, 9200001, 9200002, 9200005) +- Key functions: + - `force_update_gambling_fields()` - Special handling for gambling companies +- Client-side counterpart: `taxes_az/client/company.js` + +### DocTypes Structure + +The app contains **199 doctypes** organized into main categories: + +#### Tax Declarations (Main Documents) +- **Declaration of Value Added Tax** - VAT declarations with 10+ child tables for different sections (elave1-10, bolme, hesabat, etc.) +- **Declaration of Simplified Tax** - Simplified tax with calculation child tables +- **Declaration of Mining Tax** - Mining resource tax with wholesale pricing tables +- **Excise Declaration** - Excise tax with extensive child tables for different product categories +- **Income Tax Return** - Corporate income tax with 5+ child tables +- **Property Tax Return** - Property/land tax declarations +- **Export Declaration** - Export-related tax declarations +- **Withholding Tax declarations** - Various withholding tax types + +#### Supporting Doctypes +- **Certificate on Non-Emergence of Tax Liability** - Tax clearance certificates (4 variations: tediyye, tediyye2, tediyye3, tediyye_4) +- **Company metadata** - Additional activity, affiliate organizations, business objects, non-resident services +- **Business Classification** - NACE-style activity codes +- **Main Type of Activity** - Primary business activity classifier +- **Agricultural Purpose Type** - Agricultural land use types +- **EQM Codes** - E-Taxes specific codes +- **Tax Article** - Tax article/clause definitions + +#### Utility Doctypes +- **XML Mapping Tool** - Maps Frappe doctypes to E-Taxes XML formats +- **VAT Allocation** - VAT distribution logic +- **Tax Article Items Report** - Links items to tax articles + +### XML Mapping System + +The app includes a sophisticated XML mapping system (`taxes_az/public/js/xml_mapping/`) to convert Frappe documents to E-Taxes XML format: + +- **xml_parser.js** - Parses E-Taxes XML templates +- **field_generator.js** - Generates mapping field UI +- **doctype_field_selector.js** - Selects Frappe fields for mapping +- **mapping_storage.js** - Stores mapping rules +- **xml_generator.js** - Generates final XML from mapped data + +Usage: Create an XML Mapping Tool document, upload E-Taxes XML template, map fields to Frappe doctype fields, then generate XML for submission. + +### Reports + +Located in `taxes_az/taxes_az/report/`: + +- **land_tax_report** - Land/property tax calculations by tax article +- **customer_outstanding_balance** - Outstanding customer balances +- **fuel_products_purchase_report** - Fuel/oil product purchases +- **mining_resources_stock_report** - Mining resources inventory +- **gaming_revenue_report** - Gaming/gambling revenue (for gambling companies) +- **sport_and_lottery_report** - Sports betting and lottery reporting +- **taxable_assets_report** / **tax_exempt_assets_report** - Asset tax status +- **tax_articles_revenue_report** - Revenue by tax article +- **sales_invoice_info_report** / **payment_entry_info_report** - Transaction reporting + +### Item Groups + +The app manages specialized item groups for Azerbaijan tax purposes: +- **Neft Məhsulları** (Oil Products) - Includes fuel types +- **Faydalı qazıntılar** (Mining Resources) - 20+ types of minerals with units +- **İdman, mərc və lotoreya oyunları** (Gaming) - Sports betting and lottery + +These are created via `create_item_group.py` after migration. + +## Frappe Framework Conventions + +### DocType Structure +Each doctype folder contains: +- `{doctype_name}.json` - Field definitions +- `{doctype_name}.py` - Server-side controller +- `{doctype_name}.js` - Client-side controller +- `test_{doctype_name}.py` - Unit tests (optional) + +### Naming Conventions +- Child table doctypes often have parent name + description (e.g., `declaration_of_value_added_tax_elave_1`) +- Azerbaijani terms used extensively in doctype names + +### Hooks Registration + +Hooks are defined in `taxes_az/hooks.py`: + +```python +# Custom JS for doctypes +doctype_js = { + "XML Mapping Tool": [ + "public/js/xml_mapping/xml_parser.js", + "public/js/xml_mapping/field_generator.js", + "public/js/xml_mapping/doctype_field_selector.js", + "public/js/xml_mapping/mapping_storage.js", + "public/js/xml_mapping/xml_generator.js" + ], + "Company": "client/company.js", + "Item Group": "public/js/item_group.js" +} + +# Document events +doc_events = { + "Item Group": { + "on_trash": "taxes_az.item_group.validate_item_group_deletion" + } +} + +# Fixtures (master data) +fixtures = [ + {"doctype": "Client Script", "filters": [["module", "=", "Taxes Az"]]}, + {"doctype": "Main type of activity"}, + {"doctype": "Business Classification"} +] +``` + +## Working with E-Taxes API + +### Authentication Flow +1. User authenticates via ASAN Login (Azerbaijan's e-government system) +2. Tokens stored in "Asan Login" doctype with `is_default` flag +3. Activity tracked every API call to prevent timeout +4. Automatic token renewal via `RENEW_URL` + +### API Integration Pattern +```python +# Always record activity before E-Taxes API calls +from taxes_az.auth import record_etaxes_activity +record_etaxes_activity() + +# Make API call with proper headers +headers = { + "Accept": "application/json, text/plain, */*", + "Cache-Control": "no-cache", + "Referer": "https://new.e-taxes.gov.az/eportal/az/login/asan" +} +``` + +## Special Business Logic + +### Gambling Companies +Companies with main_activity in gambling codes must have either `seller` or `organizer` checkbox enabled. This is enforced in both Python (`company_etaxes.py`) and JavaScript (`client/company.js`). + +### Tax Declaration Dependencies +Many tax declarations have complex child table structures. When modifying declarations: +1. Check all related child tables (elave, bolme, hisse, vergi_hesab, etc.) +2. Update calculation logic in parent doctype +3. Ensure XML mapping includes all child table fields if used for E-Taxes submission + +## File Structure Reference + +``` +taxes_az/ +├── taxes_az/ # Main module +│ ├── auth.py # E-Taxes authentication +│ ├── company_etaxes.py # Company integration +│ ├── create_item_group.py # Item group creation +│ ├── item_group.py # Item group validation +│ ├── hooks.py # Frappe hooks +│ ├── client/ # Client scripts +│ │ └── company.js +│ ├── public/js/ +│ │ ├── xml_mapping/ # XML mapping system +│ │ └── item_group.js +│ ├── data/ # JSON data files +│ │ ├── industrial_land_data.json +│ │ └── cadastral_points_data.json +│ ├── fixtures/ # Master data +│ └── taxes_az/ # Nested module (doctypes/reports) +│ ├── doctype/ # 199 doctypes +│ ├── report/ # Custom reports +│ └── print_format/ # Print templates +├── convert-int-to-float.js # Utility scripts +├── float-change.js +└── fixtures.json # Fixture configuration +``` + +## Key Dependencies + +- **Frappe Framework** (~=15.0.0) - Managed by bench +- **ERPNext** - Expected to be installed (uses Company, Item Group, Asset doctypes) +- Python 3.10+ + +## Testing + +Run tests for specific doctypes: +```bash +bench --site [site-name] run-tests --doctype "Declaration of Value Added Tax" +``` + +## Language Note + +The codebase uses a mix of: +- **Russian** - Comments and some internal documentation +- **Azerbaijani** - DocType names, field labels, Item Group names +- **English** - Code, function names, technical terms + +When adding new features, follow existing language conventions for each context. diff --git a/taxes_az/taxes_az/report/vat_allocation_report/__init__.py b/taxes_az/taxes_az/report/vat_allocation_report/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/report/vat_allocation_report/vat_allocation_report.js b/taxes_az/taxes_az/report/vat_allocation_report/vat_allocation_report.js new file mode 100644 index 0000000..e1346fa --- /dev/null +++ b/taxes_az/taxes_az/report/vat_allocation_report/vat_allocation_report.js @@ -0,0 +1,30 @@ +// Copyright (c) 2025, Jey Soft and contributors +// For license information, please see license.txt + +frappe.query_reports["VAT Allocation Report"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname": "year", + "label": __("Year"), + "fieldtype": "Int", + "default": new Date().getFullYear(), + "reqd": 1 + }, + { + "fieldname": "month", + "label": __("Month"), + "fieldtype": "Select", + "options": "January\nFebruary\nMarch\nApril\nMay\nJune\nJuly\nAugust\nSeptember\nOctober\nNovember\nDecember", + "default": ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][new Date().getMonth()], + "reqd": 1 + } + ] +}; diff --git a/taxes_az/taxes_az/report/vat_allocation_report/vat_allocation_report.json b/taxes_az/taxes_az/report/vat_allocation_report/vat_allocation_report.json new file mode 100644 index 0000000..58dee0c --- /dev/null +++ b/taxes_az/taxes_az/report/vat_allocation_report/vat_allocation_report.json @@ -0,0 +1,27 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2025-11-26 12:00:00.000000", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "letter_head": "", + "modified": "2025-11-26 12:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "VAT Allocation Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "VAT allocation", + "report_name": "VAT Allocation Report", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + } + ] +} diff --git a/taxes_az/taxes_az/report/vat_allocation_report/vat_allocation_report.py b/taxes_az/taxes_az/report/vat_allocation_report/vat_allocation_report.py new file mode 100644 index 0000000..0ebc9af --- /dev/null +++ b/taxes_az/taxes_az/report/vat_allocation_report/vat_allocation_report.py @@ -0,0 +1,158 @@ +# Copyright (c) 2025, Jey Soft and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.utils import flt + + +def execute(filters=None): + """Main function for report execution""" + columns = get_columns() + data = get_data(filters) + return columns, data + + +def get_columns(): + """Define 13 columns: 1 ID + 12 data columns""" + columns = [ + { + "label": _("VAT Allocation"), + "fieldname": "vat_allocation", + "fieldtype": "Link", + "options": "VAT allocation", + "width": 150 + } + ] + + # Column Group 1: Ayın əvvəlinə (начало месяца) + columns.extend([ + {"label": _("Ayın əvvəlinə 18%"), "fieldname": "prev_month_18", "fieldtype": "Currency", "width": 130}, + {"label": _("Ayın əvvəlinə 0%"), "fieldname": "prev_month_0", "fieldtype": "Currency", "width": 130}, + {"label": _("Ayın əvvəlinə Azad"), "fieldname": "prev_month_azad", "fieldtype": "Currency", "width": 130} + ]) + + # Column Group 2: Yaranan borc (возникший долг) + columns.extend([ + {"label": _("Yaranan borc 18%"), "fieldname": "unallocated_18", "fieldtype": "Currency", "width": 130}, + {"label": _("Yaranan borc 0%"), "fieldname": "unallocated_0", "fieldtype": "Currency", "width": 130}, + {"label": _("Yaranan borc Azad"), "fieldname": "unallocated_azad", "fieldtype": "Currency", "width": 130} + ]) + + # Column Group 3: Silənən borc (погашенный долг) + columns.extend([ + {"label": _("Silənən borc 18%"), "fieldname": "allocated_18", "fieldtype": "Currency", "width": 130}, + {"label": _("Silənən borc 0%"), "fieldname": "allocated_0", "fieldtype": "Currency", "width": 130}, + {"label": _("Silənən borc Azad"), "fieldname": "allocated_azad", "fieldtype": "Currency", "width": 130} + ]) + + # Column Group 4: Ayın sonuna (конец месяца) + columns.extend([ + {"label": _("Ayın sonuna 18%"), "fieldname": "balance_18", "fieldtype": "Currency", "width": 130}, + {"label": _("Ayın sonuna 0%"), "fieldname": "balance_0", "fieldtype": "Currency", "width": 130}, + {"label": _("Ayın sonuna Azad"), "fieldname": "balance_azad", "fieldtype": "Currency", "width": 130} + ]) + + return columns + + +def get_data(filters): + """Get VAT allocation documents and calculate values""" + + # Validate filters + if not filters.get('company'): + frappe.throw(_("Company is required")) + if not filters.get('year'): + frappe.throw(_("Year is required")) + if not filters.get('month'): + frappe.throw(_("Month is required")) + + # Find VAT allocation document (max one result due to autoname format) + vat_name = f"VAT-{filters.get('year')}-{filters.get('month')}" + + if not frappe.db.exists("VAT allocation", vat_name): + return [] + + # Check if submitted + docstatus = frappe.db.get_value("VAT allocation", vat_name, "docstatus") + if docstatus != 1: + return [] + + row = calculate_row_data(vat_name) + return [row] if row else [] + + +def calculate_row_data(vat_allocation_name): + """Calculate all values for one VAT allocation document""" + + # Get data from sales_invoice_previous_table + prev_month_data = frappe.db.sql(""" + SELECT + item_tax_template, + SUM(amount) as total_amount, + SUM(allocated_amount) as total_allocated + FROM `tabVAT allocation sales invoice` + WHERE parent = %(parent)s + AND parentfield = 'sales_invoice_previous_table' + GROUP BY item_tax_template + """, {"parent": vat_allocation_name}, as_dict=1) + + # Get data from sales_invoice_table + current_month_data = frappe.db.sql(""" + SELECT + item_tax_template, + SUM(unallocated_amount) as total_unallocated + FROM `tabVAT allocation sales invoice` + WHERE parent = %(parent)s + AND parentfield = 'sales_invoice_table' + GROUP BY item_tax_template + """, {"parent": vat_allocation_name}, as_dict=1) + + # Initialize row + row = { + 'vat_allocation': vat_allocation_name, + 'prev_month_18': 0.0, + 'prev_month_0': 0.0, + 'prev_month_azad': 0.0, + 'unallocated_18': 0.0, + 'unallocated_0': 0.0, + 'unallocated_azad': 0.0, + 'allocated_18': 0.0, + 'allocated_0': 0.0, + 'allocated_azad': 0.0 + } + + # Process previous month data + for item in prev_month_data: + template = (item.item_tax_template or '').strip() + amount = flt(item.total_amount) + allocated = flt(item.total_allocated) + + if template in ['ƏDV 18%', 'ƏDV daxil 18%']: + row['prev_month_18'] += amount + row['allocated_18'] += allocated + elif template == 'ƏDV 0%': + row['prev_month_0'] += amount + row['allocated_0'] += allocated + elif template == 'ƏDV-dən azadolma': + row['prev_month_azad'] += amount + row['allocated_azad'] += allocated + + # Process current month data + for item in current_month_data: + template = (item.item_tax_template or '').strip() + unallocated = flt(item.total_unallocated) + + if template in ['ƏDV 18%', 'ƏDV daxil 18%']: + row['unallocated_18'] += unallocated + elif template == 'ƏDV 0%': + row['unallocated_0'] += unallocated + elif template == 'ƏDV-dən azadolma': + row['unallocated_azad'] += unallocated + + # Calculate balances + row['balance_18'] = (row['prev_month_18'] + row['unallocated_18']) - row['allocated_18'] + row['balance_0'] = (row['prev_month_0'] + row['unallocated_0']) - row['allocated_0'] + row['balance_azad'] = (row['prev_month_azad'] + row['unallocated_azad']) - row['allocated_azad'] + + return row