From 6afb8ca3d3a986e340725ff2ad5b30c82a59a783 Mon Sep 17 00:00:00 2001 From: Ceyhun Date: Thu, 2 Apr 2026 15:41:26 +0000 Subject: [PATCH] add cash method --- taxes_az/cash_method.py | 281 +++ taxes_az/client/company.js | 114 +- taxes_az/hooks.py | 13 +- taxes_az/setup_accounts.py | 235 +++ .../tax_closing_account_map/__init__.py | 0 .../tax_closing_account_map.json | 75 + .../tax_closing_account_map.py | 5 + .../doctype/tax_closing_benefit/__init__.py | 0 .../tax_closing_benefit.json | 118 ++ .../tax_closing_benefit.py | 5 + .../tax_closing_entry_preview/__init__.py | 0 .../tax_closing_entry_preview.json | 125 ++ .../tax_closing_entry_preview.py | 5 + .../tax_closing_journal_entry/__init__.py | 0 .../tax_closing_journal_entry.json | 74 + .../tax_closing_journal_entry.py | 5 + .../doctype/tax_closing_log_entry/__init__.py | 0 .../tax_closing_log_entry.json | 61 + .../tax_closing_log_entry.py | 5 + .../tax_closing_payroll_summary/__init__.py | 0 .../tax_closing_payroll_summary.json | 65 + .../tax_closing_payroll_summary.py | 5 + .../doctype/tax_closing_tax_item/__init__.py | 0 .../tax_closing_tax_item.json | 119 ++ .../tax_closing_tax_item.py | 5 + .../tax_period_closing_settings/__init__.py | 0 .../tax_period_closing_settings.js | 395 ++++ .../tax_period_closing_settings.json | 686 +++++++ .../tax_period_closing_settings.py | 375 ++++ .../tax_period_closing_wizard/__init__.py | 0 .../tax_period_closing_wizard.js | 806 ++++++++ .../tax_period_closing_wizard.json | 582 ++++++ .../tax_period_closing_wizard.py | 1757 +++++++++++++++++ .../taxes_az/doctype/tax_rate_row/__init__.py | 0 .../doctype/tax_rate_row/tax_rate_row.json | 56 + .../doctype/tax_rate_row/tax_rate_row.py | 5 + .../doctype/tax_year_closing_step/__init__.py | 0 .../tax_year_closing_step.json | 149 ++ .../tax_year_closing_step.py | 5 + .../tax_year_closing_wizard/__init__.py | 0 .../tax_year_closing_wizard.js | 956 +++++++++ .../tax_year_closing_wizard.json | 317 +++ .../tax_year_closing_wizard.py | 1345 +++++++++++++ taxes_az/translations/az.csv | 1101 +++++++++++ taxes_az/translations/ru.csv | 1207 +++++++++++ 45 files changed, 11054 insertions(+), 3 deletions(-) create mode 100644 taxes_az/cash_method.py create mode 100644 taxes_az/setup_accounts.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_account_map/__init__.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_account_map/tax_closing_account_map.json create mode 100644 taxes_az/taxes_az/doctype/tax_closing_account_map/tax_closing_account_map.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_benefit/__init__.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_benefit/tax_closing_benefit.json create mode 100644 taxes_az/taxes_az/doctype/tax_closing_benefit/tax_closing_benefit.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_entry_preview/__init__.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_entry_preview/tax_closing_entry_preview.json create mode 100644 taxes_az/taxes_az/doctype/tax_closing_entry_preview/tax_closing_entry_preview.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_journal_entry/__init__.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_journal_entry/tax_closing_journal_entry.json create mode 100644 taxes_az/taxes_az/doctype/tax_closing_journal_entry/tax_closing_journal_entry.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_log_entry/__init__.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_log_entry/tax_closing_log_entry.json create mode 100644 taxes_az/taxes_az/doctype/tax_closing_log_entry/tax_closing_log_entry.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_payroll_summary/__init__.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_payroll_summary/tax_closing_payroll_summary.json create mode 100644 taxes_az/taxes_az/doctype/tax_closing_payroll_summary/tax_closing_payroll_summary.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_tax_item/__init__.py create mode 100644 taxes_az/taxes_az/doctype/tax_closing_tax_item/tax_closing_tax_item.json create mode 100644 taxes_az/taxes_az/doctype/tax_closing_tax_item/tax_closing_tax_item.py create mode 100644 taxes_az/taxes_az/doctype/tax_period_closing_settings/__init__.py create mode 100644 taxes_az/taxes_az/doctype/tax_period_closing_settings/tax_period_closing_settings.js create mode 100644 taxes_az/taxes_az/doctype/tax_period_closing_settings/tax_period_closing_settings.json create mode 100644 taxes_az/taxes_az/doctype/tax_period_closing_settings/tax_period_closing_settings.py create mode 100644 taxes_az/taxes_az/doctype/tax_period_closing_wizard/__init__.py create mode 100644 taxes_az/taxes_az/doctype/tax_period_closing_wizard/tax_period_closing_wizard.js create mode 100644 taxes_az/taxes_az/doctype/tax_period_closing_wizard/tax_period_closing_wizard.json create mode 100644 taxes_az/taxes_az/doctype/tax_period_closing_wizard/tax_period_closing_wizard.py create mode 100644 taxes_az/taxes_az/doctype/tax_rate_row/__init__.py create mode 100644 taxes_az/taxes_az/doctype/tax_rate_row/tax_rate_row.json create mode 100644 taxes_az/taxes_az/doctype/tax_rate_row/tax_rate_row.py create mode 100644 taxes_az/taxes_az/doctype/tax_year_closing_step/__init__.py create mode 100644 taxes_az/taxes_az/doctype/tax_year_closing_step/tax_year_closing_step.json create mode 100644 taxes_az/taxes_az/doctype/tax_year_closing_step/tax_year_closing_step.py create mode 100644 taxes_az/taxes_az/doctype/tax_year_closing_wizard/__init__.py create mode 100644 taxes_az/taxes_az/doctype/tax_year_closing_wizard/tax_year_closing_wizard.js create mode 100644 taxes_az/taxes_az/doctype/tax_year_closing_wizard/tax_year_closing_wizard.json create mode 100644 taxes_az/taxes_az/doctype/tax_year_closing_wizard/tax_year_closing_wizard.py create mode 100644 taxes_az/translations/az.csv create mode 100644 taxes_az/translations/ru.csv diff --git a/taxes_az/cash_method.py b/taxes_az/cash_method.py new file mode 100644 index 0000000..0e43e7e --- /dev/null +++ b/taxes_az/cash_method.py @@ -0,0 +1,281 @@ +""" +Cash Method (Kassa Metodu) — Payment Entry Hook + +When a company uses Kassa metodu, VAT obligations and revenue are recognized +only when payment is received/made, not at invoice time. + +This hook runs on Payment Entry submission and creates Journal Entries +to transfer deferred amounts: + - 245.1 (ƏDV gözləmədə satış) → 521.3 (ƏDV к уплате) + - 245.2 (ƏDV gözləmədə alış) → 226 (ƏDV к зачёту) + - 542 (Отложенный доход) → 601 (Доход) +""" + +import frappe +from frappe import _ +from frappe.utils import flt + + +def on_payment_entry_submit(doc, method): + """Hook: called when Payment Entry is submitted.""" + company = doc.company + accounting_method = frappe.db.get_value("Company", company, "accounting_method") + + if accounting_method != "Kassa metodu": + return + + settings = frappe.get_single("Tax Period Closing Settings") + deferred_vat_sales = settings.deferred_vat_sales_account + deferred_vat_purchase = settings.deferred_vat_purchase_account + deferred_revenue = settings.deferred_revenue_account + vat_output = settings.vat_output_account + vat_input = settings.vat_input_account + + if not deferred_vat_sales or not vat_output: + return + + for ref in doc.references or []: + if not ref.reference_doctype or not ref.reference_name: + continue + + if ref.reference_doctype == "Sales Invoice": + _process_sales_payment( + doc, ref, deferred_vat_sales, vat_output, + deferred_revenue, settings + ) + elif ref.reference_doctype == "Purchase Invoice": + _process_purchase_payment( + doc, ref, deferred_vat_purchase, vat_input, settings + ) + + +def _process_sales_payment(payment, ref, deferred_vat_acc, vat_output_acc, + deferred_revenue_acc, settings): + """Transfer deferred VAT and revenue for a sales payment.""" + si = frappe.get_doc("Sales Invoice", ref.reference_name) + if not si or si.docstatus != 1: + return + + # Calculate payment ratio + total_invoice = flt(si.grand_total) or 1 + paid_amount = flt(ref.allocated_amount) + ratio = paid_amount / total_invoice + + # VAT portion + total_vat = flt(si.total_taxes_and_charges) + vat_to_transfer = flt(total_vat * ratio, 2) + + # Revenue portion (net total) + total_net = flt(si.net_total) + revenue_to_transfer = flt(total_net * ratio, 2) + + entries = [] + + # Transfer deferred VAT → actual VAT obligation + if vat_to_transfer > 0 and deferred_vat_acc: + entries.append({ + "debit_account": deferred_vat_acc, + "credit_account": vat_output_acc, + "amount": vat_to_transfer, + "remark": (f"Kassa metodu: ƏDV tanınması — {ref.reference_name} " + f"({ratio*100:.0f}% ödənildi). Payment: {payment.name}"), + }) + + # Transfer deferred revenue → actual revenue + if revenue_to_transfer > 0 and deferred_revenue_acc: + # Find the income account from the invoice + income_account = None + for item in si.items: + if item.income_account: + income_account = item.income_account + break + + if income_account: + entries.append({ + "debit_account": deferred_revenue_acc, + "credit_account": income_account, + "amount": revenue_to_transfer, + "remark": (f"Kassa metodu: gəlir tanınması — {ref.reference_name} " + f"({ratio*100:.0f}% ödənildi). Payment: {payment.name}"), + }) + + # Create Journal Entries + for entry in entries: + _create_transfer_je(payment, entry, settings) + + +def _process_purchase_payment(payment, ref, deferred_vat_acc, vat_input_acc, settings): + """Transfer deferred input VAT for a purchase payment.""" + pi = frappe.get_doc("Purchase Invoice", ref.reference_name) + if not pi or pi.docstatus != 1: + return + + total_invoice = flt(pi.grand_total) or 1 + paid_amount = flt(ref.allocated_amount) + ratio = paid_amount / total_invoice + + total_vat = flt(pi.total_taxes_and_charges) + vat_to_transfer = flt(total_vat * ratio, 2) + + if vat_to_transfer > 0 and deferred_vat_acc and vat_input_acc: + _create_transfer_je(payment, { + "debit_account": vat_input_acc, + "credit_account": deferred_vat_acc, + "amount": vat_to_transfer, + "remark": (f"Kassa metodu: ƏDV əvəzləşdirmə — {ref.reference_name} " + f"({ratio*100:.0f}% ödənildi). Payment: {payment.name}"), + }, settings) + + +def _create_transfer_je(payment, entry, settings): + """Create a Journal Entry for cash method transfer.""" + je = frappe.new_doc("Journal Entry") + je.company = payment.company + je.posting_date = payment.posting_date + je.voucher_type = "Journal Entry" + je.user_remark = entry["remark"] + + cost_center = settings.default_cost_center or frappe.db.get_value( + "Company", payment.company, "cost_center") + + je.append("accounts", { + "account": entry["debit_account"], + "debit_in_account_currency": entry["amount"], + "cost_center": cost_center, + }) + je.append("accounts", { + "account": entry["credit_account"], + "credit_in_account_currency": entry["amount"], + "cost_center": cost_center, + }) + + je.insert(ignore_permissions=True) + je.submit() + + +def on_journal_entry_submit(doc, method): + """Hook: when JE with Дт 226 / Кт 211 is submitted, auto-allocate via FIFO.""" + company = doc.company + accounting_method = frappe.db.get_value("Company", company, "accounting_method") + + if accounting_method != "Kassa metodu": + return + + # Skip if this JE is itself a FIFO allocation + if "FIFO" in (doc.user_remark or ""): + return + + # Check if this JE has Дт 226 / Кт 211 (НДС от клиента) + has_226_debit = False + vat_amount = 0 + customer = None + + for row in doc.accounts: + if "226" in (row.account or "") and flt(row.debit_in_account_currency) > 0: + has_226_debit = True + vat_amount += flt(row.debit_in_account_currency) + if "211" in (row.account or "") and flt(row.credit_in_account_currency) > 0: + customer = row.party + + if not has_226_debit or vat_amount <= 0: + return + + # Auto FIFO allocation for this payment + settings = frappe.get_single("Tax Period Closing Settings") + deferred_vat = settings.deferred_vat_sales_account + deferred_rev = settings.deferred_revenue_account + vat_output = settings.vat_output_account + + if not deferred_vat or not deferred_rev or not vat_output: + return + + # Find oldest unrecognized SI for this customer + sis = frappe.db.sql(""" + SELECT si.name, si.grand_total, si.net_total, si.total_taxes_and_charges + FROM `tabSales Invoice` si + WHERE si.company=%s AND si.docstatus=1 AND si.customer=%s + ORDER BY si.posting_date + """, (company, customer), as_dict=True) + + cost_center = settings.default_cost_center or frappe.db.get_value( + "Company", company, "cost_center") + income_account = frappe.db.get_value("Account", { + "company": company, "account_number": "601", "is_group": 0 + }, "name") + + remaining = vat_amount # НДС amount = proxy for payment amount + + for si in sis: + if remaining <= 0.01: + break + + # Check how much already recognized + recognized_vat = abs(flt(frappe.db.sql(""" + SELECT SUM(debit) FROM `tabGL Entry` + WHERE account=%s AND is_cancelled=0 + AND against_voucher_type='Sales Invoice' AND against_voucher=%s + """, (deferred_vat, si.name))[0][0] or 0)) + + si_vat = flt(si.total_taxes_and_charges) + unrecognized_vat = si_vat - recognized_vat + if unrecognized_vat <= 0.01: + continue + + allocate_vat = min(remaining, unrecognized_vat) + ratio = allocate_vat / si_vat if si_vat else 0 + allocate_rev = flt(si.net_total * ratio, 2) + + # Create transfer JE + transfer_je = frappe.new_doc("Journal Entry") + transfer_je.company = company + transfer_je.posting_date = doc.posting_date + transfer_je.voucher_type = "Journal Entry" + transfer_je.user_remark = ( + f"FIFO Kassa auto: {si.name} ({ratio*100:.0f}%). " + f"Source JE: {doc.name}" + ) + + if allocate_vat > 0: + transfer_je.append("accounts", { + "account": deferred_vat, "debit_in_account_currency": allocate_vat, + "cost_center": cost_center, + "against_voucher_type": "Sales Invoice", "against_voucher": si.name, + }) + transfer_je.append("accounts", { + "account": vat_output, "credit_in_account_currency": allocate_vat, + "cost_center": cost_center, + }) + + if allocate_rev > 0 and income_account: + transfer_je.append("accounts", { + "account": deferred_rev, "debit_in_account_currency": allocate_rev, + "cost_center": cost_center, + "against_voucher_type": "Sales Invoice", "against_voucher": si.name, + }) + transfer_je.append("accounts", { + "account": income_account, "credit_in_account_currency": allocate_rev, + "cost_center": cost_center, + }) + + transfer_je.insert(ignore_permissions=True) + transfer_je.submit() + remaining -= allocate_vat + + +def on_payment_entry_cancel(doc, method): + """Hook: cancel related cash method JEs when Payment is cancelled.""" + company = doc.company + accounting_method = frappe.db.get_value("Company", company, "accounting_method") + + if accounting_method != "Kassa metodu": + return + + # Find JEs created by this payment + jes = frappe.get_all("Journal Entry", filters={ + "user_remark": ["like", f"%Payment: {doc.name}%"], + "docstatus": 1, + }, pluck="name") + + for je_name in jes: + je = frappe.get_doc("Journal Entry", je_name) + je.cancel() diff --git a/taxes_az/client/company.js b/taxes_az/client/company.js index 2d0ada5..d430d07 100644 --- a/taxes_az/client/company.js +++ b/taxes_az/client/company.js @@ -20,6 +20,11 @@ frappe.ui.form.on('Company', { // Check gambling activity on refresh ETaxesCompany.checkGamblingActivity(frm); + + // Render tax wizard links + if (frm.fields_dict.tax_wizards_html && frm.doc.name) { + render_tax_wizards(frm); + } }, main_activity: function(frm) { @@ -1741,7 +1746,112 @@ window.ETaxesCompany = { $('#auth_loading_title').text(title); $('#auth_loading_message').text(message); $('#auth_verification_code_container').hide(); - + dialog.set_primary_action(__('Close'), () => dialog.hide()); } -}; \ No newline at end of file +}; + + +function render_tax_wizards(frm) { + const company = frm.doc.name; + + // Get existing wizards + frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: "Tax Year Closing Wizard", + filters: { company: company }, + fields: ["name", "fiscal_year", "year", "overall_status", "overall_progress"], + order_by: "year desc", + limit: 5, + }, + callback(r) { + const year_wizards = r.message || []; + + frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: "Tax Period Closing Wizard", + filters: { company: company }, + fields: ["name", "fiscal_year", "wizard_status", "period_type"], + order_by: "creation desc", + limit: 5, + }, + callback(r2) { + const period_wizards = r2.message || []; + build_wizard_html(frm, year_wizards, period_wizards); + } + }); + } + }); +} + +function build_wizard_html(frm, year_wizards, period_wizards) { + const status_badge = (status) => { + const colors = { + "Not Started": "background:#6c757d;", "In Progress": "background:#ffc107;color:#333;", + "Completed": "background:#28a745;", "Draft": "background:#6c757d;", + "Calculated": "background:#17a2b8;", "Entries Created": "background:#28a745;", + }; + return `${status}`; + }; + + let html = `
`; + + // Year Wizard card + html += `
+
+
📅 İllik bağlanış
+ + Yeni +
`; + + if (year_wizards.length > 0) { + for (const w of year_wizards) { + const pct = Math.round(w.overall_progress || 0); + html += ` +
+ ${w.name} — ${w.fiscal_year} + ${status_badge(w.overall_status)} +
+
+
+
+
`; + } + } else { + html += `
Hələ yaradılmayıb
`; + } + html += `
`; + + // Period Wizard card + html += `
+
+
📋 Dövr bağlanışı
+ + Yeni +
`; + + if (period_wizards.length > 0) { + for (const w of period_wizards) { + html += ` +
+ ${w.name} — ${w.fiscal_year} (${w.period_type}) + ${status_badge(w.wizard_status)} +
+
`; + } + } else { + html += `
Hələ yaradılmayıb
`; + } + html += `
`; + + // Settings link + html += `
+
⚙ Parametrlər
+ Tax Closing Settings +
`; + + html += `
`; + frm.fields_dict.tax_wizards_html.$wrapper.html(html); +} \ No newline at end of file diff --git a/taxes_az/hooks.py b/taxes_az/hooks.py index 9f28b79..df66f6f 100644 --- a/taxes_az/hooks.py +++ b/taxes_az/hooks.py @@ -24,7 +24,8 @@ fixtures = [ after_migrate = [ "taxes_az.create_item_group.create_item_groups", - "taxes_az.normalize_tax_articles.normalize_on_migrate" + "taxes_az.normalize_tax_articles.normalize_on_migrate", + "taxes_az.setup_accounts.check_and_create_accounts" ] doctype_js = { @@ -42,6 +43,16 @@ doctype_js = { doc_events = { "Item Group": { "on_trash": "taxes_az.item_group.validate_item_group_deletion" + }, + "Company": { + "on_update": "taxes_az.setup_accounts.on_company_update" + }, + "Payment Entry": { + "on_submit": "taxes_az.cash_method.on_payment_entry_submit", + "on_cancel": "taxes_az.cash_method.on_payment_entry_cancel" + }, + "Journal Entry": { + "on_submit": "taxes_az.cash_method.on_journal_entry_submit" } } diff --git a/taxes_az/setup_accounts.py b/taxes_az/setup_accounts.py new file mode 100644 index 0000000..6463f1a --- /dev/null +++ b/taxes_az/setup_accounts.py @@ -0,0 +1,235 @@ +""" +Setup required accounts for Tax Year Closing Wizard. +Runs after migrate — checks if accounts exist and creates them if missing. +""" + +import frappe +from frappe import _ + +REQUIRED_ACCOUNTS = [ + { + "account_number": "245.1", + "account_name": "ƏDV gözləmədə (satış)", + "parent_number": "245", + "parent_name": "Digər qısamüddətli aktivlər", + "root_type": "Asset", + "account_type": "Tax", + "description": "Kassa metodu: Satış fakturasında ƏDV burada saxlanılır, ödəniş alındıqda 521.3-ə köçürülür", + "needed_for": "Kassa metodu", + }, + { + "account_number": "245.2", + "account_name": "ƏDV gözləmədə (alış)", + "parent_number": "245", + "parent_name": "Digər qısamüddətli aktivlər", + "root_type": "Asset", + "account_type": "Tax", + "description": "Kassa metodu: Alış fakturasında ƏDV burada saxlanılır, ödəniş edildikdə 226-ya köçürülür", + "needed_for": "Kassa metodu", + }, +] + + +def check_and_create_accounts(): + """Check required accounts for all companies and create if missing.""" + companies = frappe.get_all("Company", filters={"is_group": 0}, fields=["name", "abbr"]) + + for company in companies: + missing = [] + for acc_def in REQUIRED_ACCOUNTS: + exists = frappe.db.exists("Account", { + "company": company.name, + "account_number": acc_def["account_number"], + }) + if not exists: + missing.append(acc_def) + + if not missing: + continue + + # Check accounting method — only create if Kassa + method = frappe.db.get_value("Company", company.name, "accounting_method") or "" + if "Kassa" not in method: + continue + + for acc_def in missing: + _create_account(company.name, company.abbr, acc_def) + + +def _create_account(company, abbr, acc_def): + """Create a single account if parent exists.""" + # Ensure parent is group + parent_acc = frappe.db.get_value("Account", { + "company": company, + "account_number": acc_def["parent_number"], + }, ["name", "is_group"], as_dict=True) + + if not parent_acc: + frappe.log_error( + f"taxes_az: Cannot create account {acc_def['account_number']} — " + f"parent {acc_def['parent_number']} not found for {company}", + "Account Setup" + ) + return + + # Convert parent to group if needed + if not parent_acc.is_group: + gl_count = frappe.db.count("GL Entry", { + "account": parent_acc.name, "is_cancelled": 0 + }) + if gl_count == 0: + frappe.db.set_value("Account", parent_acc.name, "is_group", 1) + else: + frappe.log_error( + f"taxes_az: Cannot convert {parent_acc.name} to group — has GL entries", + "Account Setup" + ) + return + + try: + acc = frappe.get_doc({ + "doctype": "Account", + "account_name": acc_def["account_name"], + "account_number": acc_def["account_number"], + "parent_account": parent_acc.name, + "company": company, + "account_type": acc_def.get("account_type", ""), + "is_group": 0, + }) + acc.flags.ignore_permissions = True + acc.flags.ignore_mandatory = True + acc.insert(ignore_permissions=True) + frappe.db.commit() + print(f" ✓ Created {acc_def['account_number']} — {acc_def['account_name']} for {company}") + except Exception as e: + frappe.log_error( + f"taxes_az: Failed to create {acc_def['account_number']} for {company}: {e}", + "Account Setup" + ) + + +def update_tax_templates_for_method(company): + """Switch Sales/Purchase Tax Templates based on accounting method.""" + method = frappe.db.get_value("Company", company, "accounting_method") or "" + abbr = frappe.db.get_value("Company", company, "abbr") + + if "Kassa" in method: + # Kassa: 521.3 → 245.1 (sales), 226 → 245.2 (purchase) + sales_from = f"521.3 - ƏDV - {abbr}" + sales_to = frappe.db.get_value("Account", {"company": company, "account_number": "245.1"}, "name") + purchase_from = f"226 - ƏDV sub-uçot hesabı - {abbr}" + purchase_to = frappe.db.get_value("Account", {"company": company, "account_number": "245.2"}, "name") + else: + # Hesablama: 245.1 → 521.3 (sales), 245.2 → 226 (purchase) + sales_from = frappe.db.get_value("Account", {"company": company, "account_number": "245.1"}, "name") + sales_to = f"521.3 - ƏDV - {abbr}" + purchase_from = frappe.db.get_value("Account", {"company": company, "account_number": "245.2"}, "name") + purchase_to = f"226 - ƏDV sub-uçot hesabı - {abbr}" + + if not sales_to or not sales_from: + return + + # Update Sales Templates + updated = 0 + for t_name in frappe.get_all("Sales Taxes and Charges Template", + filters={"company": company}, pluck="name"): + doc = frappe.get_doc("Sales Taxes and Charges Template", t_name) + changed = False + for tax in doc.taxes: + if tax.account_head == sales_from: + tax.account_head = sales_to + changed = True + if changed: + doc.save(ignore_permissions=True) + updated += 1 + + # Update Purchase Templates + if purchase_from and purchase_to: + for t_name in frappe.get_all("Purchase Taxes and Charges Template", + filters={"company": company}, pluck="name"): + doc = frappe.get_doc("Purchase Taxes and Charges Template", t_name) + changed = False + for tax in doc.taxes: + if tax.account_head == purchase_from: + tax.account_head = purchase_to + changed = True + if changed: + doc.save(ignore_permissions=True) + updated += 1 + + if updated: + frappe.db.commit() + + +def on_company_update(doc, method): + """Hook: when Company is saved, update templates if accounting method changed.""" + old_method = doc.get_db_value("accounting_method") if not doc.is_new() else "" + new_method = doc.accounting_method or "" + + if old_method != new_method and new_method: + # Create accounts if switching to Kassa + if "Kassa" in new_method: + check_and_create_accounts() + # Update templates + update_tax_templates_for_method(doc.name) + + +@frappe.whitelist() +def check_required_accounts(company): + """API: Check which accounts are missing for a company.""" + if not company: + return {"accounts": [], "missing": []} + + abbr = frappe.db.get_value("Company", company, "abbr") + method = frappe.db.get_value("Company", company, "accounting_method") or "" + + accounts = [] + missing = [] + + for acc_def in REQUIRED_ACCOUNTS: + acc = frappe.db.get_value("Account", { + "company": company, + "account_number": acc_def["account_number"], + }, ["name", "account_name"], as_dict=True) + + status = { + "number": acc_def["account_number"], + "name": acc_def["account_name"], + "description": acc_def["description"], + "needed_for": acc_def["needed_for"], + "exists": bool(acc), + "account": acc.name if acc else None, + } + accounts.append(status) + if not acc: + missing.append(status) + + return { + "accounts": accounts, + "missing": missing, + "method": method, + "needs_kassa_accounts": "Kassa" in method, + } + + +@frappe.whitelist() +def create_missing_accounts(company): + """API: Create all missing required accounts for a company.""" + if not company: + return {"created": []} + + abbr = frappe.db.get_value("Company", company, "abbr") + created = [] + + for acc_def in REQUIRED_ACCOUNTS: + exists = frappe.db.exists("Account", { + "company": company, + "account_number": acc_def["account_number"], + }) + if exists: + continue + + _create_account(company, abbr, acc_def) + created.append(acc_def["account_number"]) + + return {"created": created} diff --git a/taxes_az/taxes_az/doctype/tax_closing_account_map/__init__.py b/taxes_az/taxes_az/doctype/tax_closing_account_map/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/tax_closing_account_map/tax_closing_account_map.json b/taxes_az/taxes_az/doctype/tax_closing_account_map/tax_closing_account_map.json new file mode 100644 index 0000000..1c28fca --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_account_map/tax_closing_account_map.json @@ -0,0 +1,75 @@ +{ + "actions": [], + "creation": "2026-04-01 12:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "tax_type", + "column_break_1", + "debit_account", + "column_break_2", + "credit_account", + "column_break_3", + "cost_center" + ], + "fields": [ + { + "fieldname": "tax_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Tax Type", + "options": "VAT (ƏDV)\nProfit Tax (Mənfəət)\nSimplified Tax (Sadələşdirilmiş)\nProperty Tax (Əmlak)\nLand Tax (Torpaq)\nExcise Tax (Aksiz)\nMining Tax (Mədən)\nRoad Tax (Yol)\nWithholding Tax (ÖMV)", + "reqd": 1 + }, + { + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, + { + "description": "Expense/Asset account to debit (e.g., 901 for profit tax, 226 for VAT input)", + "fieldname": "debit_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Debit Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "description": "Liability account to credit (e.g., 521.1 for profit tax payable, 521.3 for VAT)", + "fieldname": "credit_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Credit Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "description": "Optional: override default cost center for this tax type. Leave blank to use the default.", + "fieldname": "cost_center", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Cost Center", + "options": "Cost Center" + } + ], + "index_web_pages_for_search": 0, + "istable": 1, + "links": [], + "modified": "2026-04-01 14:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Tax Closing Account Map", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/taxes_az/taxes_az/doctype/tax_closing_account_map/tax_closing_account_map.py b/taxes_az/taxes_az/doctype/tax_closing_account_map/tax_closing_account_map.py new file mode 100644 index 0000000..c813334 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_account_map/tax_closing_account_map.py @@ -0,0 +1,5 @@ +from frappe.model.document import Document + + +class TaxClosingAccountMap(Document): + pass diff --git a/taxes_az/taxes_az/doctype/tax_closing_benefit/__init__.py b/taxes_az/taxes_az/doctype/tax_closing_benefit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/tax_closing_benefit/tax_closing_benefit.json b/taxes_az/taxes_az/doctype/tax_closing_benefit/tax_closing_benefit.json new file mode 100644 index 0000000..ce7278f --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_benefit/tax_closing_benefit.json @@ -0,0 +1,118 @@ +{ + "actions": [], + "creation": "2026-04-01 18:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "enabled", + "article", + "column_break_1", + "benefit_name", + "applies_to", + "column_break_2", + "exemption_percent", + "condition_met", + "column_break_3", + "condition_details", + "law_url", + "tax_reduction" + ], + "fields": [ + { + "default": "1", + "fieldname": "enabled", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Apply" + }, + { + "fieldname": "article", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Article (Maddə)", + "read_only": 1 + }, + { + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, + { + "fieldname": "benefit_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Benefit", + "read_only": 1 + }, + { + "fieldname": "applies_to", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Applies To", + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "exemption_percent", + "fieldtype": "Percent", + "in_list_view": 1, + "label": "Exemption %", + "read_only": 1 + }, + { + "fieldname": "condition_met", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Condition", + "options": "Yes\nNo\nPartial\nManual Check", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "condition_details", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Details", + "read_only": 1, + "columns": 3 + }, + { + "fieldname": "law_reference", + "fieldtype": "Small Text", + "label": "Law Reference", + "read_only": 1 + }, + { + "fieldname": "law_url", + "fieldtype": "Data", + "label": "Law URL", + "read_only": 1, + "hidden": 1 + }, + { + "fieldname": "tax_reduction", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Tax Reduction", + "precision": "2", + "read_only": 1 + } + ], + "index_web_pages_for_search": 0, + "istable": 1, + "links": [], + "modified": "2026-04-01 18:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Tax Closing Benefit", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/taxes_az/taxes_az/doctype/tax_closing_benefit/tax_closing_benefit.py b/taxes_az/taxes_az/doctype/tax_closing_benefit/tax_closing_benefit.py new file mode 100644 index 0000000..3ddcd28 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_benefit/tax_closing_benefit.py @@ -0,0 +1,5 @@ +from frappe.model.document import Document + + +class TaxClosingBenefit(Document): + pass diff --git a/taxes_az/taxes_az/doctype/tax_closing_entry_preview/__init__.py b/taxes_az/taxes_az/doctype/tax_closing_entry_preview/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/tax_closing_entry_preview/tax_closing_entry_preview.json b/taxes_az/taxes_az/doctype/tax_closing_entry_preview/tax_closing_entry_preview.json new file mode 100644 index 0000000..07b66a0 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_entry_preview/tax_closing_entry_preview.json @@ -0,0 +1,125 @@ +{ + "actions": [], + "creation": "2026-04-01 12:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "tax_type", + "sub_period", + "column_break_1", + "posting_date", + "debit_account", + "credit_account", + "column_break_2", + "amount", + "remark", + "column_break_3", + "status", + "existing_entry", + "existing_amount", + "difference" + ], + "fields": [ + { + "fieldname": "tax_type", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Tax Type", + "read_only": 1 + }, + { + "fieldname": "sub_period", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Sub-Period", + "read_only": 1 + }, + { + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, + { + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "read_only": 1 + }, + { + "fieldname": "debit_account", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Debit Account", + "read_only": 1 + }, + { + "fieldname": "credit_account", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Credit Account", + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Calculated Amount", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "remark", + "fieldtype": "Small Text", + "label": "Remark", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Pending\nExisting\nDifference\nCreated\nFailed\nSkipped", + "read_only": 1 + }, + { + "fieldname": "existing_entry", + "fieldtype": "Small Text", + "label": "Existing Entry", + "read_only": 1 + }, + { + "fieldname": "existing_amount", + "fieldtype": "Currency", + "label": "Existing Amount", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "difference", + "fieldtype": "Currency", + "label": "Difference", + "precision": "2", + "read_only": 1 + } + ], + "index_web_pages_for_search": 0, + "istable": 1, + "links": [], + "modified": "2026-04-01 16:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Tax Closing Entry Preview", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/taxes_az/taxes_az/doctype/tax_closing_entry_preview/tax_closing_entry_preview.py b/taxes_az/taxes_az/doctype/tax_closing_entry_preview/tax_closing_entry_preview.py new file mode 100644 index 0000000..2eb7685 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_entry_preview/tax_closing_entry_preview.py @@ -0,0 +1,5 @@ +from frappe.model.document import Document + + +class TaxClosingEntryPreview(Document): + pass diff --git a/taxes_az/taxes_az/doctype/tax_closing_journal_entry/__init__.py b/taxes_az/taxes_az/doctype/tax_closing_journal_entry/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/tax_closing_journal_entry/tax_closing_journal_entry.json b/taxes_az/taxes_az/doctype/tax_closing_journal_entry/tax_closing_journal_entry.json new file mode 100644 index 0000000..cc71a39 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_journal_entry/tax_closing_journal_entry.json @@ -0,0 +1,74 @@ +{ + "actions": [], + "creation": "2026-04-01 12:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "journal_entry", + "column_break_1", + "tax_type", + "sub_period", + "column_break_2", + "amount", + "posting_date" + ], + "fields": [ + { + "fieldname": "journal_entry", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Journal Entry", + "options": "Journal Entry", + "read_only": 1 + }, + { + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, + { + "fieldname": "tax_type", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Tax Type", + "read_only": 1 + }, + { + "fieldname": "sub_period", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Sub-Period", + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "read_only": 1 + } + ], + "index_web_pages_for_search": 0, + "istable": 1, + "links": [], + "modified": "2026-04-01 12:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Tax Closing Journal Entry", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/taxes_az/taxes_az/doctype/tax_closing_journal_entry/tax_closing_journal_entry.py b/taxes_az/taxes_az/doctype/tax_closing_journal_entry/tax_closing_journal_entry.py new file mode 100644 index 0000000..64f2969 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_journal_entry/tax_closing_journal_entry.py @@ -0,0 +1,5 @@ +from frappe.model.document import Document + + +class TaxClosingJournalEntry(Document): + pass diff --git a/taxes_az/taxes_az/doctype/tax_closing_log_entry/__init__.py b/taxes_az/taxes_az/doctype/tax_closing_log_entry/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/tax_closing_log_entry/tax_closing_log_entry.json b/taxes_az/taxes_az/doctype/tax_closing_log_entry/tax_closing_log_entry.json new file mode 100644 index 0000000..72ff468 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_log_entry/tax_closing_log_entry.json @@ -0,0 +1,61 @@ +{ + "actions": [], + "creation": "2026-04-01 12:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "timestamp", + "phase", + "column_break_1", + "level", + "message" + ], + "fields": [ + { + "fieldname": "timestamp", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Timestamp", + "read_only": 1 + }, + { + "fieldname": "phase", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Phase", + "options": "Initialization\nTax Detection\nData Collection\nCalculation\nEntry Preview\nEntry Creation\nPeriod Closing\nSummary\nSetup\nStep Complete\nStep Skipped\nBenefit Detection", + "read_only": 1 + }, + { + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, + { + "fieldname": "level", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Level", + "options": "Info\nSuccess\nWarning\nError\nHeader", + "read_only": 1 + }, + { + "fieldname": "message", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Message", + "read_only": 1 + } + ], + "index_web_pages_for_search": 0, + "istable": 1, + "links": [], + "modified": "2026-04-01 12:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Tax Closing Log Entry", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/taxes_az/taxes_az/doctype/tax_closing_log_entry/tax_closing_log_entry.py b/taxes_az/taxes_az/doctype/tax_closing_log_entry/tax_closing_log_entry.py new file mode 100644 index 0000000..aa64dbc --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_log_entry/tax_closing_log_entry.py @@ -0,0 +1,5 @@ +from frappe.model.document import Document + + +class TaxClosingLogEntry(Document): + pass diff --git a/taxes_az/taxes_az/doctype/tax_closing_payroll_summary/__init__.py b/taxes_az/taxes_az/doctype/tax_closing_payroll_summary/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/tax_closing_payroll_summary/tax_closing_payroll_summary.json b/taxes_az/taxes_az/doctype/tax_closing_payroll_summary/tax_closing_payroll_summary.json new file mode 100644 index 0000000..e336999 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_payroll_summary/tax_closing_payroll_summary.json @@ -0,0 +1,65 @@ +{ + "actions": [], + "creation": "2026-04-01 12:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "component", + "column_break_1", + "amount", + "column_break_2", + "account", + "source" + ], + "fields": [ + { + "fieldname": "component", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Component", + "read_only": 1 + }, + { + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "account", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Account", + "read_only": 1 + }, + { + "fieldname": "source", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Source", + "read_only": 1 + } + ], + "index_web_pages_for_search": 0, + "istable": 1, + "links": [], + "modified": "2026-04-01 12:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Tax Closing Payroll Summary", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/taxes_az/taxes_az/doctype/tax_closing_payroll_summary/tax_closing_payroll_summary.py b/taxes_az/taxes_az/doctype/tax_closing_payroll_summary/tax_closing_payroll_summary.py new file mode 100644 index 0000000..7143f7b --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_payroll_summary/tax_closing_payroll_summary.py @@ -0,0 +1,5 @@ +from frappe.model.document import Document + + +class TaxClosingPayrollSummary(Document): + pass diff --git a/taxes_az/taxes_az/doctype/tax_closing_tax_item/__init__.py b/taxes_az/taxes_az/doctype/tax_closing_tax_item/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/tax_closing_tax_item/tax_closing_tax_item.json b/taxes_az/taxes_az/doctype/tax_closing_tax_item/tax_closing_tax_item.json new file mode 100644 index 0000000..f3e0374 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_tax_item/tax_closing_tax_item.json @@ -0,0 +1,119 @@ +{ + "actions": [], + "creation": "2026-04-01 12:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "enabled", + "tax_type", + "column_break_1", + "period_type", + "sub_period", + "column_break_2", + "source_amount", + "rate", + "tax_amount", + "column_break_3", + "status", + "declaration_deadline", + "payment_deadline" + ], + "fields": [ + { + "default": "1", + "fieldname": "enabled", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Enabled" + }, + { + "fieldname": "tax_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Tax Type", + "options": "VAT (ƏDV)\nProfit Tax (Mənfəət)\nSimplified Tax (Sadələşdirilmiş)\nProperty Tax (Əmlak)\nLand Tax (Torpaq)\nExcise Tax (Aksiz)\nMining Tax (Mədən)\nRoad Tax (Yol)\nWithholding Tax (ÖMV)\nUnemployment Insurance (İşsizlik)\nDSMF\nMedical Insurance (Tibbi sığorta)", + "reqd": 1 + }, + { + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, + { + "fieldname": "period_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Period Type", + "options": "Monthly\nQuarterly\nAnnually", + "read_only": 1 + }, + { + "fieldname": "sub_period", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Sub-Period", + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "source_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Source Amount", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "rate", + "fieldtype": "Percent", + "label": "Rate (%)", + "read_only": 1 + }, + { + "fieldname": "tax_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Tax Amount", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Pending\nCalculated\nEntry Created\nData Only\nSkipped\nError", + "read_only": 1 + }, + { + "fieldname": "declaration_deadline", + "fieldtype": "Date", + "label": "Declaration Deadline", + "read_only": 1 + }, + { + "fieldname": "payment_deadline", + "fieldtype": "Date", + "label": "Payment Deadline", + "read_only": 1 + } + ], + "index_web_pages_for_search": 0, + "istable": 1, + "links": [], + "modified": "2026-04-01 12:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Tax Closing Tax Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/taxes_az/taxes_az/doctype/tax_closing_tax_item/tax_closing_tax_item.py b/taxes_az/taxes_az/doctype/tax_closing_tax_item/tax_closing_tax_item.py new file mode 100644 index 0000000..d7eb4eb --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_closing_tax_item/tax_closing_tax_item.py @@ -0,0 +1,5 @@ +from frappe.model.document import Document + + +class TaxClosingTaxItem(Document): + pass diff --git a/taxes_az/taxes_az/doctype/tax_period_closing_settings/__init__.py b/taxes_az/taxes_az/doctype/tax_period_closing_settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/tax_period_closing_settings/tax_period_closing_settings.js b/taxes_az/taxes_az/doctype/tax_period_closing_settings/tax_period_closing_settings.js new file mode 100644 index 0000000..6b97c71 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_period_closing_settings/tax_period_closing_settings.js @@ -0,0 +1,395 @@ +frappe.ui.form.on("Tax Period Closing Settings", { + refresh(frm) { + frm.trigger("render_detection_status"); + frm.trigger("render_regime_explanation"); + frm.trigger("setup_buttons"); + }, + + setup_buttons(frm) { + // Auto-detect button — always available + if (frm.doc.company) { + frm.add_custom_button(__("Auto-detect from Company"), () => { + frm.set_value("auto_detect_on_save", 1); + frm.save(); + }, __("Setup")); + + frm.add_custom_button(__("Auto-detect Accounts"), () => { + frm.trigger("detect_accounts"); + }, __("Setup")); + } + + // Load defaults buttons + frm.add_custom_button(__("Load Default AZ Rates"), () => { + frm.trigger("load_default_rates"); + }, __("Setup")); + + frm.add_custom_button(__("Load Default Account Map"), () => { + if (!frm.doc.company) { + frappe.msgprint(__("Please select a Company first.")); + return; + } + frm.trigger("detect_accounts_and_map"); + }, __("Setup")); + }, + + company(frm) { + if (frm.doc.company && frm.doc.auto_detect_on_save) { + frappe.show_alert({ + message: __("Company changed. Save to run auto-detection."), + indicator: "blue" + }); + } + frm.trigger("render_detection_status"); + }, + + default_tax_regime(frm) { + frm.trigger("render_regime_explanation"); + frm.trigger("sync_regime_enables"); + }, + + // ── Sync regime with enables ────────────────────────── + + sync_regime_enables(frm) { + const regime = frm.doc.default_tax_regime; + if (regime === "General (VAT + Profit)") { + frm.set_value("enable_vat", 1); + frm.set_value("enable_profit_tax", 1); + frm.set_value("enable_simplified_tax", 0); + } else if (regime === "Simplified Tax" || regime === "Special Regime") { + frm.set_value("enable_vat", 0); + frm.set_value("enable_profit_tax", 0); + frm.set_value("enable_simplified_tax", 1); + } + }, + + // ── Contradiction warnings in real-time ─────────────── + + enable_vat(frm) { + if (frm.doc.enable_vat && frm.doc.default_tax_regime !== "General (VAT + Profit)") { + frappe.show_alert({ + message: __("VAT is typically not applicable in Simplified/Special regime."), + indicator: "orange" + }); + } + }, + + enable_profit_tax(frm) { + if (frm.doc.enable_profit_tax && frm.doc.default_tax_regime !== "General (VAT + Profit)") { + frappe.show_alert({ + message: __("Profit Tax is replaced by Simplified Tax in non-General regimes."), + indicator: "orange" + }); + } + }, + + enable_simplified_tax(frm) { + if (frm.doc.enable_simplified_tax && frm.doc.default_tax_regime === "General (VAT + Profit)") { + frappe.show_alert({ + message: __("Simplified Tax is not applicable in General (VAT + Profit) regime."), + indicator: "orange" + }); + } + }, + + auto_submit_entries(frm) { + if (frm.doc.auto_submit_entries) { + frappe.confirm( + __("Auto-submit means journal entries will be posted immediately and will affect account balances. Submitted entries require cancellation to undo. Are you sure?"), + () => {}, + () => { frm.set_value("auto_submit_entries", 0); } + ); + } + }, + + enable_period_closing(frm) { + if (frm.doc.enable_period_closing && frm.doc.auto_submit_entries) { + frappe.show_alert({ + message: __("Warning: Both auto-submit and period closing are ON. Period Closing Voucher is irreversible once submitted."), + indicator: "red" + }); + } + }, + + vat_input_account(frm) { + if (frm.doc.vat_input_account && frm.doc.vat_output_account + && frm.doc.vat_input_account === frm.doc.vat_output_account) { + frappe.show_alert({ + message: __("VAT Input and Output accounts must be different!"), + indicator: "red" + }); + frm.set_value("vat_input_account", ""); + } + }, + + vat_output_account(frm) { + if (frm.doc.vat_input_account && frm.doc.vat_output_account + && frm.doc.vat_input_account === frm.doc.vat_output_account) { + frappe.show_alert({ + message: __("VAT Input and Output accounts must be different!"), + indicator: "red" + }); + frm.set_value("vat_output_account", ""); + } + }, + + profit_tax_advance_method(frm) { + if (frm.doc.profit_tax_advance_method === "1/4 of Previous Year Tax" + && !frm.doc.profit_tax_previous_year_amount) { + frappe.show_alert({ + message: __("Please enter the previous year's total profit tax amount below."), + indicator: "orange" + }); + } + }, + + // ── Regime Explanation HTML ──────────────────────────── + + render_regime_explanation(frm) { + const regime = frm.doc.default_tax_regime || ""; + let html = ""; + + const explanations = { + "General (VAT + Profit)": { + color: "#28a745", + icon: "📊", + taxes: [ + __("VAT (ƏDV) — 18%, monthly, declaration by 20th of next month"), + __("Profit Tax (Mənfəət) — 20%, annual declaration (Mar 31), quarterly advances"), + __("Property Tax (Əmlak) — 1%, annual declaration, quarterly advances"), + ], + note: __("Most common regime for medium/large businesses with VAT registration.") + }, + "Simplified Tax": { + color: "#17a2b8", + icon: "📋", + taxes: [ + __("Simplified Tax — 2%/4%/8% of revenue, quarterly"), + __("Property Tax (Əmlak) — 1%, annual declaration, quarterly advances"), + ], + note: __("For micro/small businesses WITHOUT VAT registration. Replaces VAT + Profit Tax.") + }, + "Special Regime": { + color: "#6f42c1", + icon: "🏗", + taxes: [ + __("Special Simplified Tax — rates vary by activity type"), + __("Property Tax (Əmlak) — 1%, annual declaration, quarterly advances"), + ], + note: __("For construction, real estate, housing, cash withdrawals. Uses special declaration forms.") + } + }; + + const info = explanations[regime]; + if (info) { + html = ` +
+
+ ${info.icon} ${regime} +
+
+ ${__("Applicable taxes:")}
+ ${info.taxes.map(t => `  • ${t}`).join("
")} +
+
+ ${info.note} +
+
+ `; + } + + if (frm.fields_dict.regime_explanation_html) { + frm.fields_dict.regime_explanation_html.$wrapper.html(html); + } + }, + + // ── Detection Status HTML ───────────────────────────── + + render_detection_status(frm) { + if (!frm.doc.company) { + if (frm.fields_dict.detection_status_html) { + frm.fields_dict.detection_status_html.$wrapper.html( + `
${__("Select a Company to enable auto-detection.")}
` + ); + } + return; + } + + const checks = [ + { + label: __("Company"), + ok: !!frm.doc.company, + value: frm.doc.company || __("Not set") + }, + { + label: __("Tax Regime"), + ok: !!frm.doc.default_tax_regime, + value: frm.doc.default_tax_regime || __("Not detected") + }, + { + label: __("VAT Accounts"), + ok: frm.doc.default_tax_regime !== "General (VAT + Profit)" || (!!frm.doc.vat_input_account && !!frm.doc.vat_output_account), + value: frm.doc.vat_input_account && frm.doc.vat_output_account + ? `${frm.doc.vat_input_account} / ${frm.doc.vat_output_account}` + : (frm.doc.default_tax_regime !== "General (VAT + Profit)" ? __("N/A (Simplified)") : __("Not set")) + }, + { + label: __("Account Mapping"), + ok: (frm.doc.account_map || []).length > 0, + value: __("{0} mappings", [(frm.doc.account_map || []).length]) + }, + { + label: __("Tax Rates"), + ok: (frm.doc.tax_rates || []).length > 0, + value: __("{0} rates", [(frm.doc.tax_rates || []).length]) + }, + { + label: __("Cost Center"), + ok: !!frm.doc.default_cost_center, + value: frm.doc.default_cost_center || __("Will use company default") + } + ]; + + let html = '
'; + for (const c of checks) { + const color = c.ok ? "#28a745" : "#dc3545"; + const bg = c.ok ? "#d4edda" : "#f8d7da"; + const icon = c.ok ? "✓" : "✗"; + html += ` +
+ ${icon} ${c.label} +
+ `; + } + html += '
'; + + if (frm.fields_dict.detection_status_html) { + frm.fields_dict.detection_status_html.$wrapper.html(html); + } + }, + + // ── Detect Accounts ─────────────────────────────────── + + detect_accounts(frm) { + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_period_closing_settings.tax_period_closing_settings.auto_detect_accounts", + args: { company: frm.doc.company }, + callback(r) { + if (!r.message || !r.message.accounts) return; + + const accounts = r.message.accounts; + let found = []; + + if (accounts["226"]) { + frm.set_value("vat_input_account", accounts["226"].name); + found.push(`VAT Input → ${accounts["226"].name}`); + } + if (accounts["521.3"]) { + frm.set_value("vat_output_account", accounts["521.3"].name); + found.push(`VAT Output → ${accounts["521.3"].name}`); + } + if (accounts["341"]) { + frm.set_value("closing_account", accounts["341"].name); + found.push(`Closing Account → ${accounts["341"].name}`); + } + + if (found.length) { + frappe.show_alert({ + message: __("Found {0} accounts", [found.length]), + indicator: "green" + }); + } else { + frappe.show_alert({ + message: __("No standard AZ accounts found. Set up manually."), + indicator: "orange" + }); + } + + frm.trigger("render_detection_status"); + } + }); + }, + + detect_accounts_and_map(frm) { + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_period_closing_settings.tax_period_closing_settings.auto_detect_accounts", + args: { company: frm.doc.company }, + callback(r) { + if (!r.message || !r.message.accounts) return; + const accounts = r.message.accounts; + + // Set individual account fields + if (accounts["226"]) frm.set_value("vat_input_account", accounts["226"].name); + if (accounts["521.3"]) frm.set_value("vat_output_account", accounts["521.3"].name); + if (accounts["341"]) frm.set_value("closing_account", accounts["341"].name); + + // Build account mapping table + const map_defs = [ + { tax_type: "Profit Tax (Mənfəət)", debit: "901", credit: "521.1" }, + { tax_type: "Simplified Tax (Sadələşdirilmiş)", debit: "731", credit: "521.9" }, + { tax_type: "Property Tax (Əmlak)", debit: "731", credit: "521.5" }, + { tax_type: "Land Tax (Torpaq)", debit: "731", credit: "521.6" }, + { tax_type: "Excise Tax (Aksiz)", debit: "731", credit: "521.4" }, + { tax_type: "Mining Tax (Mədən)", debit: "731", credit: "521.8" }, + { tax_type: "Road Tax (Yol)", debit: "731", credit: "521.7" }, + ]; + + frm.doc.account_map = []; + let mapped = 0; + + for (const def of map_defs) { + const debit_acc = accounts[def.debit]; + const credit_acc = accounts[def.credit]; + + if (debit_acc && credit_acc) { + let row = frm.add_child("account_map"); + row.tax_type = def.tax_type; + row.debit_account = debit_acc.name; + row.credit_account = credit_acc.name; + mapped++; + } + } + + frm.refresh_field("account_map"); + frappe.show_alert({ + message: __("{0} account mappings created from Chart of Accounts", [mapped]), + indicator: mapped > 0 ? "green" : "orange" + }); + + frm.trigger("render_detection_status"); + } + }); + }, + + // ── Load Default Rates ──────────────────────────────── + + load_default_rates(frm) { + const defaults = [ + { tax_type: "VAT (ƏDV)", rate: 18, description: __("Standard VAT rate — Maddə 159 Tax Code") }, + { tax_type: "Profit Tax (Mənfəət)", rate: 20, description: __("Corporate profit tax — Maddə 104 Tax Code") }, + { tax_type: "Simplified Tax - Trade Baku (Sadələşdirilmiş - Ticarət Bakı)", rate: 2, description: __("Retail/wholesale trade in Baku — Maddə 220.1") }, + { tax_type: "Simplified Tax - Catering (Sadələşdirilmiş - İaşə)", rate: 8, description: __("Catering/public food services — Maddə 220.1") }, + { tax_type: "Simplified Tax - Other (Sadələşdirilmiş - Digər)", rate: 4, description: __("Other activities — Maddə 220.1") }, + { tax_type: "Property Tax (Əmlak)", rate: 1, description: __("Fixed assets — Maddə 199 Tax Code") }, + { tax_type: "Unemployment Insurance Employer (İşsizlik - İşəgötürən)", rate: 0.5, description: __("Employer contribution — İşsizlikdən sığorta haqqında qanun") }, + { tax_type: "Unemployment Insurance Employee (İşsizlik - İşçi)", rate: 0.5, description: __("Employee withholding — İşsizlikdən sığorta haqqında qanun") }, + { tax_type: "DSMF Employer (DSMF - İşəgötürən)", rate: 22, description: __("Social insurance employer — DSMF qanunu") }, + { tax_type: "DSMF Employee (DSMF - İşçi)", rate: 3, description: __("Social insurance employee — DSMF qanunu") }, + { tax_type: "Medical Insurance Employer (Tibbi sığorta - İşəgötürən)", rate: 2, description: __("Medical insurance employer — İcbari tibbi sığorta qanunu") }, + { tax_type: "Medical Insurance Employee (Tibbi sığorta - İşçi)", rate: 2, description: __("Medical insurance employee — İcbari tibbi sığorta qanunu") } + ]; + + frm.doc.tax_rates = []; + defaults.forEach(d => { + let row = frm.add_child("tax_rates"); + row.tax_type = d.tax_type; + row.rate = d.rate; + row.description = d.description; + }); + + frm.refresh_field("tax_rates"); + frappe.show_alert({ + message: __("{0} default AZ tax rates loaded", [defaults.length]), + indicator: "green" + }); + } +}); diff --git a/taxes_az/taxes_az/doctype/tax_period_closing_settings/tax_period_closing_settings.json b/taxes_az/taxes_az/doctype/tax_period_closing_settings/tax_period_closing_settings.json new file mode 100644 index 0000000..db1d942 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_period_closing_settings/tax_period_closing_settings.json @@ -0,0 +1,686 @@ +{ + "actions": [], + "creation": "2026-04-01 12:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "issingle": 1, + "field_order": [ + "company_section", + "company", + "column_break_comp1", + "auto_detect_on_save", + "detection_status_html", + "accounting_method_section", + "accounting_method", + "column_break_am1", + "deferred_vat_sales_account", + "deferred_vat_purchase_account", + "column_break_am2", + "deferred_revenue_account", + "regime_section", + "auto_detect_regime", + "default_tax_regime", + "column_break_reg1", + "regime_explanation_html", + "scan_section", + "scan_existing_entries", + "column_break_scan1", + "force_create_if_exists", + "column_break_scan2", + "offer_correction_entry", + "behavior_section", + "auto_submit_entries", + "column_break_beh1", + "allow_reclosing", + "column_break_beh2", + "duplicate_check_scope", + "pcv_section", + "enable_period_closing", + "column_break_pcv1", + "closing_account", + "declaration_section", + "create_declaration_draft", + "column_break_decl1", + "auto_fill_declaration", + "cost_center_section", + "default_cost_center", + "column_break_cc1", + "cost_center_per_tax", + "vat_section", + "enable_vat", + "vat_rate", + "column_break_vat1", + "vat_input_account", + "vat_output_account", + "column_break_vat2", + "use_vat_allocation", + "vat_data_source", + "profit_tax_section", + "enable_profit_tax", + "profit_tax_rate", + "column_break_pt1", + "profit_tax_advance_method", + "column_break_pt2", + "profit_tax_previous_year_amount", + "simplified_tax_section", + "enable_simplified_tax", + "simplified_tax_auto_rate", + "column_break_st1", + "default_simplified_rate", + "column_break_st2", + "simplified_tax_revenue_source", + "property_tax_section", + "enable_property_tax", + "property_tax_rate", + "column_break_prop1", + "property_tax_threshold", + "column_break_prop2", + "property_tax_calc_method", + "land_tax_section", + "enable_land_tax", + "column_break_lt1", + "land_tax_source", + "other_taxes_section", + "enable_excise_tax", + "enable_mining_tax", + "column_break_ot1", + "enable_road_tax", + "payroll_section", + "enable_payroll_collection", + "column_break_pay1", + "payroll_data_source", + "tax_rates_section", + "tax_rates", + "account_mapping_section", + "account_map" + ], + "fields": [ + { + "fieldname": "company_section", + "fieldtype": "Section Break", + "label": "Company & Auto-Detection" + }, + { + "description": "Select the company for which these settings apply. All auto-detection (regime, accounts, rates) will use data from this company. If you have multiple companies, the wizard will let you override per-run.", + "fieldname": "company", + "fieldtype": "Link", + "label": "Default Company", + "options": "Company" + }, + { + "fieldname": "column_break_comp1", + "fieldtype": "Column Break" + }, + { + "default": "1", + "description": "When enabled, saving this form will automatically detect: tax regime, VAT payer status, OKVED code, available assets, employees, and set recommended values for all settings below. You can always override any auto-detected value manually.", + "fieldname": "auto_detect_on_save", + "fieldtype": "Check", + "label": "Auto-detect parameters on Save" + }, + { + "fieldname": "detection_status_html", + "fieldtype": "HTML", + "label": "Detection Status" + }, + { + "fieldname": "accounting_method_section", + "fieldtype": "Section Break", + "label": "Accounting Method" + }, + { + "description": "Read from Company settings. Kassa metodu: revenue, expenses, and VAT obligations are recognized only when payment is received/made. Hesablama metodu: recognized at invoice date.\n\nPer AZ Tax Code: Micro and Small entrepreneurs can use Kassa metodu. Medium and Big must use Hesablama metodu.", + "fieldname": "accounting_method", + "fieldtype": "Select", + "label": "Accounting Method", + "options": "\nKassa metodu\nHesablama metodu", + "read_only": 1 + }, + { + "fieldname": "column_break_am1", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.accounting_method === 'Kassa metodu'", + "description": "Account 245.1 — ƏDV gözləmədə (satış). Deferred output VAT — posted at Invoice, transferred to 521.3 at Payment.", + "fieldname": "deferred_vat_sales_account", + "fieldtype": "Link", + "label": "Deferred VAT (Sales)", + "options": "Account" + }, + { + "depends_on": "eval:doc.accounting_method === 'Kassa metodu'", + "description": "Account 245.2 — ƏDV gözləmədə (alış). Deferred input VAT — posted at Invoice, transferred to 226 at Payment.", + "fieldname": "deferred_vat_purchase_account", + "fieldtype": "Link", + "label": "Deferred VAT (Purchases)", + "options": "Account" + }, + { + "fieldname": "column_break_am2", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.accounting_method === 'Kassa metodu'", + "description": "Account 542 — Gələcək hesabat dövrünün gəlirləri. Deferred revenue — posted at Invoice, transferred to 601 at Payment.", + "fieldname": "deferred_revenue_account", + "fieldtype": "Link", + "label": "Deferred Revenue (542)", + "options": "Account" + }, + { + "fieldname": "regime_section", + "fieldtype": "Section Break", + "label": "Tax Regime" + }, + { + "default": "1", + "description": "If ON: the tax regime is determined automatically from the company's VAT certificate and OKVED code. Companies with a VAT certificate → General regime. Companies without → Simplified. Special OKVED codes (construction, real estate) → Special regime. If OFF: the regime below is used as-is for every wizard run.", + "fieldname": "auto_detect_regime", + "fieldtype": "Check", + "label": "Auto-detect Tax Regime" + }, + { + "default": "General (VAT + Profit)", + "description": "This determines which taxes the wizard will calculate:\n\n• General (VAT + Profit) — VAT monthly + Profit Tax quarterly. Most common for medium/large businesses registered as VAT payers.\n• Simplified Tax — Quarterly simplified tax on revenue. For micro/small businesses NOT registered for VAT.\n• Special Regime — Construction, real estate, housing, cash withdrawals. Uses simplified tax with special declaration forms.\n\nIf 'Auto-detect' is ON above, this field shows the detected value but can still be changed.", + "fieldname": "default_tax_regime", + "fieldtype": "Select", + "label": "Default Tax Regime", + "options": "General (VAT + Profit)\nSimplified Tax\nSpecial Regime" + }, + { + "fieldname": "column_break_reg1", + "fieldtype": "Column Break" + }, + { + "fieldname": "regime_explanation_html", + "fieldtype": "HTML", + "label": "Regime Explanation" + }, + { + "fieldname": "scan_section", + "fieldtype": "Section Break", + "label": "Smart Scan — Existing Entry Detection" + }, + { + "default": "1", + "description": "If ON (recommended): before creating any journal entry, the wizard scans GL Entries to check if a matching entry already exists for the same tax type, accounts, and period.\n\nIf an existing entry is found:\n• The wizard marks it as 'Existing' (green) and shows the existing JE number and amount\n• No duplicate entry is created\n• If the calculated amount differs from the existing amount, a warning with the difference is shown\n\nThis is essential when tax entries come from external sources (e.g., e-taxes portal imports) or were created manually. The wizard automatically adapts — no configuration needed per period.", + "fieldname": "scan_existing_entries", + "fieldtype": "Check", + "label": "Scan for Existing Entries" + }, + { + "fieldname": "column_break_scan1", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "scan_existing_entries", + "description": "If ON: the wizard will create a new journal entry even if a matching one already exists. Use only if you intentionally need duplicate entries (very rare).\n\nIf OFF (default, safe): existing entries are respected and not duplicated.", + "fieldname": "force_create_if_exists", + "fieldtype": "Check", + "label": "Force Create Even if Exists" + }, + { + "fieldname": "column_break_scan2", + "fieldtype": "Column Break" + }, + { + "default": "1", + "depends_on": "scan_existing_entries", + "description": "If ON: when an existing entry is found but its amount differs from the wizard's calculation, the wizard will offer a correction entry for the difference.\n\nFor example: existing VAT entry = 1,296 AZN, wizard calculates 1,300 AZN → offers a correction entry of 4 AZN.\n\nThe correction entry is shown in Entry Preview with status 'Difference' (orange) and is optional — you can disable it per row.\n\nIf OFF: differences are only logged as warnings, no correction entries are offered.", + "fieldname": "offer_correction_entry", + "fieldtype": "Check", + "label": "Offer Correction Entry for Differences" + }, + { + "fieldname": "behavior_section", + "fieldtype": "Section Break", + "label": "Journal Entry Behavior" + }, + { + "default": "0", + "description": "If ON: journal entries created by the wizard will be immediately submitted (docstatus=1) and will affect account balances right away. Use this only if you trust the calculation and don't need manual review.\n\nIf OFF (recommended): entries are created as Draft. You can review each one in the Journal Entry list and submit manually or in bulk. This is safer — you can edit or delete drafts if something looks wrong.", + "fieldname": "auto_submit_entries", + "fieldtype": "Check", + "label": "Auto-submit Journal Entries" + }, + { + "fieldname": "column_break_beh1", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Controls what happens when you run the wizard for a period that was already closed.\n\nIf OFF (default, safe): the wizard will refuse to run and show which previous wizard closed this period. This prevents accidental double-posting.\n\nIf ON: the wizard will cancel all journal entries from the previous run and create new ones. The old wizard document will be marked as superseded. Use this when you need to correct previously posted tax entries (e.g., after fixing invoices or asset values).", + "fieldname": "allow_reclosing", + "fieldtype": "Check", + "label": "Allow Re-closing Periods" + }, + { + "fieldname": "column_break_beh2", + "fieldtype": "Column Break" + }, + { + "default": "Same Company + Same Period", + "depends_on": "eval:!doc.allow_reclosing", + "description": "How the wizard checks for duplicate closings:\n\n• Same Company + Same Period — blocks if the exact same company and date range was already closed. Most restrictive.\n• Same Company + Overlapping Dates — blocks if any part of the period overlaps with a previous closing. Catches partial overlaps.\n\nOnly relevant when 'Allow Re-closing' is OFF.", + "fieldname": "duplicate_check_scope", + "fieldtype": "Select", + "label": "Duplicate Check Scope", + "options": "Same Company + Same Period\nSame Company + Overlapping Dates" + }, + { + "fieldname": "pcv_section", + "fieldtype": "Section Break", + "label": "Period Closing Voucher (P&L Close)" + }, + { + "default": "0", + "description": "If ON: after creating tax journal entries, the wizard will also create a standard ERPNext Period Closing Voucher. This closes all Income and Expense accounts and transfers the net result to the Closing Account below.\n\nIf OFF (default): the wizard only creates tax accrual entries. You close P&L manually via Accounting → Period Closing Voucher. Recommended to keep OFF if your accountant closes periods separately.\n\nIMPORTANT: Period Closing Voucher is irreversible after submission. The wizard will create it in Draft if 'Auto-submit' is OFF.", + "fieldname": "enable_period_closing", + "fieldtype": "Check", + "label": "Create Period Closing Voucher" + }, + { + "fieldname": "column_break_pcv1", + "fieldtype": "Column Break" + }, + { + "depends_on": "enable_period_closing", + "description": "The equity/liability account where net profit or loss will be transferred when P&L accounts are closed.\n\nStandard AZ Chart of Accounts:\n• 341 — Hesabat dövrünün xalis mənfəəti (Current period net profit)\n• 342 — Keçmiş illərin bölüşdürülməmiş mənfəəti (Retained earnings)\n\nThe account MUST be of type Liability or Equity, and in the same currency as the company.", + "fieldname": "closing_account", + "fieldtype": "Link", + "label": "Closing Account", + "options": "Account" + }, + { + "fieldname": "declaration_section", + "fieldtype": "Section Break", + "collapsible": 1, + "collapsible_depends_on": "eval:!doc.create_declaration_draft", + "label": "Declaration Integration (Future)" + }, + { + "default": "0", + "description": "If ON: the wizard will create a draft of the corresponding tax declaration (e.g., Declaration of Value Added Tax, Income Tax Return) pre-filled with calculated values.\n\nIf OFF (default): only journal entries are created. Declarations are filed separately via Tax Declaration doctypes or the E-Taxes portal.\n\nNote: This feature is under development. Currently only VAT and Simplified Tax declarations are supported.", + "fieldname": "create_declaration_draft", + "fieldtype": "Check", + "label": "Create Declaration Draft" + }, + { + "fieldname": "column_break_decl1", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "create_declaration_draft", + "description": "If ON: declaration fields (VOEN, activity code, tax authority, etc.) will be auto-filled from Company data and calculated amounts.\n\nIf OFF: only the amounts section will be filled; header fields (VOEN, period, etc.) are left blank for manual entry.\n\nRequires 'Create Declaration Draft' to be enabled.", + "fieldname": "auto_fill_declaration", + "fieldtype": "Check", + "label": "Auto-fill Declaration Header" + }, + { + "fieldname": "cost_center_section", + "fieldtype": "Section Break", + "collapsible": 1, + "label": "Cost Center" + }, + { + "description": "All tax journal entries will use this cost center by default. If left blank, the company's default cost center is used.\n\nYou can override this per wizard run.", + "fieldname": "default_cost_center", + "fieldtype": "Link", + "label": "Default Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "column_break_cc1", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "If ON: each tax type can have its own cost center (configured in the Account Mapping table below). For example, VAT entries go to 'Sales' cost center, while property tax goes to 'Admin'.\n\nIf OFF (default): all tax entries use the single default cost center above.", + "fieldname": "cost_center_per_tax", + "fieldtype": "Check", + "label": "Separate Cost Center per Tax Type" + }, + { + "fieldname": "vat_section", + "fieldtype": "Section Break", + "label": "VAT (Value Added Tax)", + "depends_on": "eval:doc.default_tax_regime === 'General (VAT + Profit)'" + }, + { + "default": "1", + "description": "Enable VAT calculation in the wizard. The wizard will calculate the difference between output VAT (collected from customers) and input VAT (paid to suppliers) for each month in the period.\n\nDisable this only if you handle VAT entries manually or via a separate process.", + "fieldname": "enable_vat", + "fieldtype": "Check", + "label": "Enable VAT Calculation" + }, + { + "default": "18", + "depends_on": "enable_vat", + "description": "Standard VAT rate in Azerbaijan is 18% (Maddə 159 of the Tax Code). This is used for reference and reporting only — the actual VAT amounts are calculated from GL account balances, not by applying this rate.\n\nChange only if the government announces a new rate.", + "fieldname": "vat_rate", + "fieldtype": "Percent", + "label": "VAT Rate (%)" + }, + { + "fieldname": "column_break_vat1", + "fieldtype": "Column Break" + }, + { + "depends_on": "enable_vat", + "description": "The asset account where input VAT (VAT paid to suppliers on purchases) is recorded.\n\nStandard AZ: Account 226 — ƏDV sub-uçot hesabı.\n\nThis account has a DEBIT balance. The wizard reads this balance to determine how much input VAT you can offset against output VAT.", + "fieldname": "vat_input_account", + "fieldtype": "Link", + "label": "VAT Input Account", + "options": "Account" + }, + { + "depends_on": "enable_vat", + "description": "The liability account where output VAT (VAT collected from customers on sales) is recorded.\n\nStandard AZ: Account 521.3 — ƏDV.\n\nThis account has a CREDIT balance. The wizard reads this balance and offsets it against input VAT to determine the net VAT payable to the budget.", + "fieldname": "vat_output_account", + "fieldtype": "Link", + "label": "VAT Output Account", + "options": "Account" + }, + { + "fieldname": "column_break_vat2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "enable_vat", + "description": "If ON: after calculating VAT from GL balances, the wizard will compare the result with the VAT Allocation doctype (if you use it). Any discrepancy will be logged as a warning.\n\nIf OFF: VAT is calculated purely from GL account balances. This is simpler and works for most companies.\n\nEnable only if you actively use the VAT Allocation system in taxes_az for distributing input VAT across taxable/exempt operations.", + "fieldname": "use_vat_allocation", + "fieldtype": "Check", + "label": "Cross-check with VAT Allocation" + }, + { + "default": "GL Account Balance", + "depends_on": "enable_vat", + "description": "How VAT amounts are determined:\n\n• GL Account Balance — reads debit/credit balances from VAT accounts (226 and 521.3) for each month. Simple, reliable, captures all transactions including manual adjustments.\n\n• Sales/Purchase Invoices — sums VAT from individual invoices. More granular but may miss manual journal entries or corrections.\n\nRecommended: GL Account Balance.", + "fieldname": "vat_data_source", + "fieldtype": "Select", + "label": "VAT Data Source", + "options": "GL Account Balance\nSales/Purchase Invoices" + }, + { + "fieldname": "profit_tax_section", + "fieldtype": "Section Break", + "label": "Profit Tax", + "depends_on": "eval:doc.default_tax_regime === 'General (VAT + Profit)'" + }, + { + "default": "1", + "description": "Enable profit tax calculation. The wizard will calculate quarterly advance payments based on the method selected below.\n\nPer AZ Tax Code: annual declaration is due by March 31 of the following year, but advance payments are due quarterly (by the 15th of the month after the quarter ends).\n\nDisable only if you handle profit tax entries manually.", + "fieldname": "enable_profit_tax", + "fieldtype": "Check", + "label": "Enable Profit Tax Calculation" + }, + { + "default": "20", + "depends_on": "enable_profit_tax", + "description": "Corporate profit tax rate. Standard rate in Azerbaijan is 20% (Maddə 104 of the Tax Code).\n\nApplied to: Net Profit = Total Income − Total Expenses for the period.\n\nChange only if your company qualifies for a reduced rate (e.g., certain agricultural or IT activities may have incentives).", + "fieldname": "profit_tax_rate", + "fieldtype": "Percent", + "label": "Profit Tax Rate (%)" + }, + { + "fieldname": "column_break_pt1", + "fieldtype": "Column Break" + }, + { + "default": "Actual Quarterly Profit", + "depends_on": "enable_profit_tax", + "description": "How quarterly advance payments are calculated:\n\n• Actual Quarterly Profit — calculates 20% of the actual profit for the current quarter (Income − Expenses from P&L). More accurate, reflects current performance. Recommended for growing or seasonal businesses.\n\n• 1/4 of Previous Year Tax — takes the total profit tax from the previous year and divides by 4. Simpler, more predictable payments. Better for stable businesses. Requires 'Previous Year Tax Amount' to be filled in.\n\nBoth methods are accepted by the AZ tax authorities. The annual declaration reconciles any over/underpayment.", + "fieldname": "profit_tax_advance_method", + "fieldtype": "Select", + "label": "Advance Payment Method", + "options": "Actual Quarterly Profit\n1/4 of Previous Year Tax" + }, + { + "fieldname": "column_break_pt2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:doc.enable_profit_tax && doc.profit_tax_advance_method === '1/4 of Previous Year Tax'", + "description": "Enter the total profit tax amount from the previous fiscal year. The wizard will divide this by 4 for each quarterly advance payment.\n\nYou can find this in last year's Income Tax Return (field: total calculated tax) or from the previous year's Tax Period Closing Wizard summary.\n\nLeave at 0 if using 'Actual Quarterly Profit' method.", + "fieldname": "profit_tax_previous_year_amount", + "fieldtype": "Currency", + "label": "Previous Year Tax Amount", + "precision": "2" + }, + { + "fieldname": "simplified_tax_section", + "fieldtype": "Section Break", + "label": "Simplified Tax", + "depends_on": "eval:doc.default_tax_regime === 'Simplified Tax' || doc.default_tax_regime === 'Special Regime'" + }, + { + "default": "1", + "description": "Enable simplified tax calculation. Applied quarterly to total revenue (not profit).\n\nPer AZ Tax Code (Maddə 218-227): simplified tax replaces VAT and profit tax for qualifying businesses. Declaration and payment are due by the 20th of the month after the quarter.\n\nDisable only if you handle simplified tax entries manually.", + "fieldname": "enable_simplified_tax", + "fieldtype": "Check", + "label": "Enable Simplified Tax" + }, + { + "default": "1", + "depends_on": "enable_simplified_tax", + "description": "If ON: the wizard determines the simplified tax rate automatically based on the company's OKVED (activity code):\n\n• Trade activities in Baku → 2%\n• Catering (public food) → 8%\n• All other activities → 4%\n\nIf OFF: the 'Default Rate' below is used for all calculations regardless of activity type.", + "fieldname": "simplified_tax_auto_rate", + "fieldtype": "Check", + "label": "Auto-detect Rate by OKVED" + }, + { + "fieldname": "column_break_st1", + "fieldtype": "Column Break" + }, + { + "default": "4", + "depends_on": "eval:doc.enable_simplified_tax && !doc.simplified_tax_auto_rate", + "description": "The flat simplified tax rate applied to total revenue.\n\nStandard rates per AZ Tax Code (Maddə 220.1):\n• 2% — Retail/wholesale trade in Baku\n• 4% — Services, production, and other activities\n• 8% — Catering/public food services\n\nThis field is only used when 'Auto-detect by OKVED' is OFF.", + "fieldname": "default_simplified_rate", + "fieldtype": "Percent", + "label": "Default Simplified Tax Rate (%)" + }, + { + "fieldname": "column_break_st2", + "fieldtype": "Column Break" + }, + { + "default": "Total Income (P&L)", + "depends_on": "enable_simplified_tax", + "description": "What counts as 'revenue' for simplified tax calculation:\n\n• Total Income (P&L) — sums all Income-type accounts from the P&L for the quarter. Includes sales revenue, other operating income, etc. Most common.\n\n• Sales Revenue Only — only account 601 (Satış gəlirləri). Excludes other income like interest, rent, etc. Use if your other income is not subject to simplified tax.\n\n• Cash Receipts — revenue recognized when cash is received (cash-basis). For companies using cash-basis accounting.", + "fieldname": "simplified_tax_revenue_source", + "fieldtype": "Select", + "label": "Revenue Source", + "options": "Total Income (P&L)\nSales Revenue Only\nCash Receipts" + }, + { + "fieldname": "property_tax_section", + "fieldtype": "Section Break", + "label": "Property Tax" + }, + { + "default": "1", + "description": "Enable property tax calculation on fixed assets. Calculated annually but paid in quarterly advances (1/4 each quarter).\n\nPer AZ Tax Code (Maddə 196-202): tax is levied on the average annual net book value of fixed assets (buildings, equipment, vehicles, etc.).\n\nDisable if the company has no fixed assets or all assets are tax-exempt under Maddə 199.4.", + "fieldname": "enable_property_tax", + "fieldtype": "Check", + "label": "Enable Property Tax" + }, + { + "default": "1", + "depends_on": "enable_property_tax", + "description": "Property tax rate on the average annual residual value of fixed assets.\n\nStandard rate: 1% (Maddə 199 of the Tax Code).\n\nApplied to: (Value at start of year + Value at end of year) / 2.\n\nThis rate applies uniformly to all taxable asset types (buildings, equipment, vehicles, etc.).", + "fieldname": "property_tax_rate", + "fieldtype": "Percent", + "label": "Property Tax Rate (%)" + }, + { + "fieldname": "column_break_prop1", + "fieldtype": "Column Break" + }, + { + "default": "1000000", + "depends_on": "enable_property_tax", + "description": "Net book value threshold for property tax reporting. Per Maddə 199 of the AZ Tax Code, enterprises with average annual asset value exceeding this threshold may have additional reporting requirements.\n\nStandard value: 1,000,000 AZN.\n\nThe wizard will show a warning in the log if your company's net book value exceeds this threshold.", + "fieldname": "property_tax_threshold", + "fieldtype": "Currency", + "label": "Reporting Threshold (AZN)", + "precision": "2" + }, + { + "fieldname": "column_break_prop2", + "fieldtype": "Column Break" + }, + { + "default": "Average Annual (Start + End) / 2", + "depends_on": "enable_property_tax", + "description": "How the taxable asset value is calculated:\n\n• Average Annual (Start + End) / 2 — standard method per Maddə 199. Takes the residual value at the start of the year and the end of the year, averages them. Used for companies operating the full year (Tam İl).\n\n• Pro-rata for New Companies — for companies established during the year (İl Ərzində). Formula: (Purchase value + Year-end value) / 24 × months of operation.\n\n• End of Period NBV — uses the net book value at the end of the closing period. Simpler but less accurate for annual tax.", + "fieldname": "property_tax_calc_method", + "fieldtype": "Select", + "label": "Calculation Method", + "options": "Average Annual (Start + End) / 2\nPro-rata for New Companies\nEnd of Period NBV" + }, + { + "fieldname": "land_tax_section", + "fieldtype": "Section Break", + "collapsible": 1, + "label": "Land Tax" + }, + { + "default": "0", + "description": "Enable land tax calculation. Uses existing calculation logic from Land Tax Declaration in taxes_az (cadastral scores, industrial land rates).\n\nPer AZ Tax Code (Maddə 206-212): land tax is calculated based on cadastral value and land purpose. Annual declaration, quarterly payments.\n\nEnable only if the company owns or uses land plots. The wizard will use rates from cadastral_points_data.json and industrial_land_data.json.", + "fieldname": "enable_land_tax", + "fieldtype": "Check", + "label": "Enable Land Tax" + }, + { + "fieldname": "column_break_lt1", + "fieldtype": "Column Break" + }, + { + "default": "Land Tax Declaration", + "depends_on": "enable_land_tax", + "description": "Where land tax amounts come from:\n\n• Land Tax Declaration — reads the calculated amount from an existing Land Tax Declaration document for the fiscal year. You must create and calculate the Land Tax Declaration first.\n\n• Manual Entry — you manually enter the annual land tax amount in the wizard. The wizard will divide by 4 for quarterly advances.\n\nRecommended: Land Tax Declaration (uses existing cadastral calculation logic).", + "fieldname": "land_tax_source", + "fieldtype": "Select", + "label": "Data Source", + "options": "Land Tax Declaration\nManual Entry" + }, + { + "fieldname": "other_taxes_section", + "fieldtype": "Section Break", + "collapsible": 1, + "label": "Other Taxes (Optional)" + }, + { + "default": "0", + "description": "Enable excise tax tracking. Relevant only for companies producing or importing excisable goods (alcohol, tobacco, fuel, vehicles).\n\nThe wizard will check if the company's OKVED code matches excise-related activities. If enabled but no excisable activities are found, it will be skipped with a note in the log.\n\nMonthly calculation and declaration.", + "fieldname": "enable_excise_tax", + "fieldtype": "Check", + "label": "Enable Excise Tax" + }, + { + "default": "0", + "description": "Enable mining tax tracking. Relevant only for companies extracting mineral resources (oil, gas, metals, stone, etc.).\n\nThe wizard will check if the company's OKVED code matches mining activities and if mining-related Item Groups exist. Monthly calculation.\n\nEnable only if the company has mining operations.", + "fieldname": "enable_mining_tax", + "fieldtype": "Check", + "label": "Enable Mining Tax" + }, + { + "fieldname": "column_break_ot1", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Enable road tax tracking. Relevant for companies owning vehicles registered in Azerbaijan.\n\nThe wizard will check if the company has transportation assets. Annual calculation.\n\nEnable only if the company owns registered vehicles.", + "fieldname": "enable_road_tax", + "fieldtype": "Check", + "label": "Enable Road Tax" + }, + { + "fieldname": "payroll_section", + "fieldtype": "Section Break", + "label": "Payroll Data Collection" + }, + { + "default": "1", + "description": "If ON: the wizard collects payroll data (gross pay, NDFL, DSMF, unemployment insurance, medical insurance) from Salary Slips for the period. This data is shown in the summary report for reference.\n\nNOTE: The wizard does NOT create payroll-related journal entries — those are already created by the Payroll Entry process. The wizard only COLLECTS and DISPLAYS the totals.\n\nIf OFF: payroll data is not included in the wizard summary.", + "fieldname": "enable_payroll_collection", + "fieldtype": "Check", + "label": "Collect Payroll Data" + }, + { + "fieldname": "column_break_pay1", + "fieldtype": "Column Break" + }, + { + "default": "Salary Slips", + "depends_on": "enable_payroll_collection", + "description": "Where payroll totals are collected from:\n\n• Salary Slips — reads submitted Salary Slip documents for the period. Provides detailed breakdown by salary component (deductions, contributions).\n\n• GL Account Balances — reads balances from payroll-related accounts (522, 522.1-4, 533). Faster but less detailed.\n\n• Both (with cross-check) — reads from both sources and flags discrepancies. Most thorough but slowest.\n\nRecommended: Salary Slips.", + "fieldname": "payroll_data_source", + "fieldtype": "Select", + "label": "Payroll Data Source", + "options": "Salary Slips\nGL Account Balances\nBoth (with cross-check)" + }, + { + "fieldname": "tax_rates_section", + "fieldtype": "Section Break", + "label": "Tax Rates Reference Table", + "description": "Reference table of all tax rates used by the wizard. Click 'Load Default Rates' to populate with standard AZ rates. You can modify rates here — changes affect all future wizard runs." + }, + { + "description": "Each row defines a tax type and its rate. These rates are used as reference for calculations and for taxes that use fixed rates (property, DSMF, unemployment insurance). VAT and profit tax have their own rate fields above for convenience.\n\nClick 'Load Default AZ Rates' button to populate with standard rates from the AZ Tax Code.", + "fieldname": "tax_rates", + "fieldtype": "Table", + "label": "Tax Rates", + "options": "Tax Rate Row" + }, + { + "fieldname": "account_mapping_section", + "fieldtype": "Section Break", + "label": "Account Mapping (Debit → Credit)", + "description": "Maps each tax type to the accounts used for journal entries. Debit = Expense account, Credit = Liability account. Click 'Auto-detect Accounts' to find matching accounts from your Chart of Accounts." + }, + { + "description": "Each row maps a tax type to a pair of accounts:\n• Debit (Expense) — where the tax cost is recorded (e.g., 901 for profit tax expense)\n• Credit (Liability) — where the tax payable is recorded (e.g., 521.1 for profit tax liability)\n\nIf the company uses the standard AZ Chart of Accounts, click 'Auto-detect Accounts' to find the correct accounts automatically.", + "fieldname": "account_map", + "fieldtype": "Table", + "label": "Account Mapping", + "options": "Tax Closing Account Map" + } + ], + "index_web_pages_for_search": 0, + "links": [], + "modified": "2026-04-01 14:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Tax Period Closing Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/taxes_az/taxes_az/doctype/tax_period_closing_settings/tax_period_closing_settings.py b/taxes_az/taxes_az/doctype/tax_period_closing_settings/tax_period_closing_settings.py new file mode 100644 index 0000000..fe015c1 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_period_closing_settings/tax_period_closing_settings.py @@ -0,0 +1,375 @@ +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import flt + + +class TaxPeriodClosingSettings(Document): + def validate(self): + self.validate_contradictions() + if self.auto_detect_on_save and self.company: + self.run_auto_detect() + + def validate_contradictions(self): + """Check for logical contradictions between settings.""" + + # 1. Regime vs tax enables + if self.default_tax_regime == "General (VAT + Profit)": + if self.enable_simplified_tax: + frappe.msgprint( + _("You have selected 'General (VAT + Profit)' regime but also enabled " + "Simplified Tax. In the General regime, Simplified Tax is not applicable. " + "The wizard will ignore Simplified Tax. Consider disabling it."), + indicator="orange", title=_("Setting Conflict")) + + if not self.enable_vat and not self.auto_detect_regime: + frappe.msgprint( + _("General regime typically requires VAT. VAT is disabled. " + "The wizard will not calculate VAT entries. Enable it if this company is a VAT payer."), + indicator="orange", title=_("Check Required")) + + if not self.enable_profit_tax and not self.auto_detect_regime: + frappe.msgprint( + _("General regime typically requires Profit Tax. It is disabled. " + "The wizard will not calculate profit tax entries."), + indicator="orange", title=_("Check Required")) + + elif self.default_tax_regime in ("Simplified Tax", "Special Regime"): + if self.enable_vat: + frappe.msgprint( + _("Simplified/Special regime does not use VAT. VAT is enabled. " + "Simplified tax payers are not VAT payers per AZ Tax Code. " + "The wizard will skip VAT calculation. Consider disabling it."), + indicator="orange", title=_("Setting Conflict")) + + if self.enable_profit_tax: + frappe.msgprint( + _("Simplified/Special regime does not use Profit Tax. It is enabled. " + "Simplified tax replaces profit tax per AZ Tax Code. " + "The wizard will skip profit tax. Consider disabling it."), + indicator="orange", title=_("Setting Conflict")) + + # 2. Profit tax advance method consistency + if (self.enable_profit_tax + and self.profit_tax_advance_method == "1/4 of Previous Year Tax" + and flt(self.profit_tax_previous_year_amount) <= 0): + frappe.msgprint( + _("Profit tax advance method is set to '1/4 of Previous Year Tax' " + "but the previous year amount is 0. The wizard will calculate 0 tax. " + "Please enter the previous year's total profit tax amount."), + indicator="red", title=_("Missing Data")) + + # 3. VAT accounts must be different + if self.enable_vat and self.vat_input_account and self.vat_output_account: + if self.vat_input_account == self.vat_output_account: + frappe.throw( + _("VAT Input Account and VAT Output Account must be different. " + "Input (226) is an Asset account for VAT paid on purchases. " + "Output (521.3) is a Liability account for VAT collected from sales.")) + + # 4. Period closing requires closing account + if self.enable_period_closing and not self.closing_account: + frappe.msgprint( + _("Period Closing Voucher is enabled but no Closing Account is set. " + "You must specify an Equity or Liability account (e.g., 341) where " + "net profit/loss will be transferred when P&L is closed."), + indicator="red", title=_("Missing Account")) + + # 5. Auto-submit + period closing = dangerous + if self.auto_submit_entries and self.enable_period_closing: + frappe.msgprint( + _("Both 'Auto-submit' and 'Period Closing Voucher' are enabled. " + "This means journal entries AND the Period Closing Voucher will be " + "submitted immediately without manual review. This is irreversible. " + "Consider disabling auto-submit for safety."), + indicator="red", title=_("High Risk Configuration")) + + # 6. Re-closing disabled but shown + if not self.allow_reclosing and self.duplicate_check_scope: + pass # This is fine, duplicate_check_scope only applies when reclosing is OFF + + # 7. Declaration draft without regime match + if self.create_declaration_draft and self.auto_fill_declaration: + if not self.company: + frappe.msgprint( + _("Auto-fill declarations requires a Company to be set. " + "VOEN, activity code, and tax authority are read from the Company document."), + indicator="orange", title=_("Company Required")) + + # 8. Property tax rate sanity + if flt(self.property_tax_rate) > 5: + frappe.msgprint( + _("Property tax rate is set to {}%. The standard AZ rate is 1%. " + "Are you sure this is correct?").format(self.property_tax_rate), + indicator="orange", title=_("Unusual Rate")) + + # 9. VAT rate sanity + if self.enable_vat and flt(self.vat_rate) != 18 and flt(self.vat_rate) > 0: + frappe.msgprint( + _("VAT rate is set to {}%. The standard AZ rate is 18%. " + "This is unusual — change only if a new rate has been enacted.").format( + self.vat_rate), + indicator="orange", title=_("Non-standard Rate")) + + # 10. Profit tax rate sanity + if self.enable_profit_tax and flt(self.profit_tax_rate) != 20 and flt(self.profit_tax_rate) > 0: + frappe.msgprint( + _("Profit tax rate is set to {}%. The standard AZ rate is 20%. " + "Change only if your company qualifies for a special rate.").format( + self.profit_tax_rate), + indicator="orange", title=_("Non-standard Rate")) + + # 11. Account mapping completeness + self._validate_account_map() + + def _validate_account_map(self): + """Check that enabled taxes have corresponding account mappings.""" + tax_map = {row.tax_type for row in self.account_map or []} + + missing = [] + if self.enable_vat and "VAT (ƏDV)" not in tax_map: + # VAT uses separate fields, so this is OK + if not self.vat_input_account or not self.vat_output_account: + missing.append("VAT (ƏDV) — set VAT Input/Output accounts above") + + if self.enable_profit_tax and "Profit Tax (Mənfəət)" not in tax_map: + missing.append("Profit Tax (Mənfəət)") + + if self.enable_simplified_tax and "Simplified Tax (Sadələşdirilmiş)" not in tax_map: + missing.append("Simplified Tax (Sadələşdirilmiş)") + + if self.enable_property_tax and "Property Tax (Əmlak)" not in tax_map: + missing.append("Property Tax (Əmlak)") + + if self.enable_land_tax and "Land Tax (Torpaq)" not in tax_map: + missing.append("Land Tax (Torpaq)") + + if missing: + frappe.msgprint( + _("The following enabled taxes have no account mapping. " + "The wizard won't know which accounts to debit/credit:

" + "{}").format("
".join(f"• {m}" for m in missing)), + indicator="orange", title=_("Missing Account Mapping")) + + def run_auto_detect(self): + """Auto-detect settings from company data.""" + company = frappe.get_doc("Company", self.company) + detected = [] + + # Cost center + if not self.default_cost_center and company.cost_center: + self.default_cost_center = company.cost_center + detected.append(f"Cost Center → {company.cost_center}") + + # VAT payer detection + has_vat_cert = (hasattr(company, "vat_certificate_number") + and company.vat_certificate_number) + + # Regime detection + if self.auto_detect_regime: + if has_vat_cert: + self.default_tax_regime = "General (VAT + Profit)" + self.enable_vat = 1 + self.enable_profit_tax = 1 + self.enable_simplified_tax = 0 + detected.append("Regime → General (VAT payer detected)") + else: + self.default_tax_regime = "Simplified Tax" + self.enable_vat = 0 + self.enable_profit_tax = 0 + self.enable_simplified_tax = 1 + detected.append("Regime → Simplified (no VAT certificate)") + + # Asset detection + asset_count = frappe.db.count("Asset", { + "company": self.company, + "docstatus": 1, + "status": ["not in", ["Scrapped", "Sold"]], + }) + if asset_count > 0: + self.enable_property_tax = 1 + detected.append(f"Property Tax → Enabled ({asset_count} assets found)") + else: + self.enable_property_tax = 0 + detected.append("Property Tax → Disabled (no active assets)") + + # Employee detection + emp_count = frappe.db.count("Employee", { + "company": self.company, + "status": "Active", + }) + if emp_count > 0: + self.enable_payroll_collection = 1 + detected.append(f"Payroll Collection → Enabled ({emp_count} active employees)") + else: + self.enable_payroll_collection = 0 + detected.append("Payroll Collection → Disabled (no active employees)") + + # VAT accounts auto-detect + if self.enable_vat and (not self.vat_input_account or not self.vat_output_account): + self._detect_vat_accounts(detected) + + # Account mapping auto-detect + if not self.account_map or len(self.account_map) == 0: + self._detect_account_mapping(detected) + + if detected: + frappe.msgprint( + _("Auto-detection results:

{}").format( + "
".join(f"✓ {d}" for d in detected)), + indicator="green", title=_("Parameters Detected")) + + def _detect_vat_accounts(self, detected): + """Try to find VAT accounts from Chart of Accounts.""" + # Look for account 226 (VAT input) + vat_input = frappe.db.get_value("Account", { + "company": self.company, + "account_number": "226", + "is_group": 0, + }, "name") + if vat_input: + self.vat_input_account = vat_input + detected.append(f"VAT Input Account → {vat_input}") + + # Look for account 521.3 (VAT output) + vat_output = frappe.db.get_value("Account", { + "company": self.company, + "account_number": "521.3", + "is_group": 0, + }, "name") + if vat_output: + self.vat_output_account = vat_output + detected.append(f"VAT Output Account → {vat_output}") + + def _detect_account_mapping(self, detected): + """Try to auto-detect account mappings from Chart of Accounts.""" + mapping_rules = [ + { + "tax_type": "Profit Tax (Mənfəət)", + "debit_numbers": ["901"], + "credit_numbers": ["521.1"], + }, + { + "tax_type": "Simplified Tax (Sadələşdirilmiş)", + "debit_numbers": ["731"], + "credit_numbers": ["521.9"], + }, + { + "tax_type": "Property Tax (Əmlak)", + "debit_numbers": ["731"], + "credit_numbers": ["521.5"], + }, + { + "tax_type": "Land Tax (Torpaq)", + "debit_numbers": ["731"], + "credit_numbers": ["521.6"], + }, + { + "tax_type": "Excise Tax (Aksiz)", + "debit_numbers": ["731"], + "credit_numbers": ["521.4"], + }, + { + "tax_type": "Mining Tax (Mədən)", + "debit_numbers": ["731"], + "credit_numbers": ["521.8"], + }, + { + "tax_type": "Road Tax (Yol)", + "debit_numbers": ["731"], + "credit_numbers": ["521.7"], + }, + ] + + found_count = 0 + for rule in mapping_rules: + debit_acc = None + credit_acc = None + + for num in rule["debit_numbers"]: + debit_acc = frappe.db.get_value("Account", { + "company": self.company, + "account_number": num, + "is_group": 0, + }, "name") + if debit_acc: + break + + for num in rule["credit_numbers"]: + credit_acc = frappe.db.get_value("Account", { + "company": self.company, + "account_number": num, + "is_group": 0, + }, "name") + if credit_acc: + break + + if debit_acc and credit_acc: + self.append("account_map", { + "tax_type": rule["tax_type"], + "debit_account": debit_acc, + "credit_account": credit_acc, + }) + found_count += 1 + + if found_count: + detected.append(f"Account Mapping → {found_count} mappings auto-detected") + + def on_update(self): + frappe.clear_cache(doctype="Tax Period Closing Settings") + + +def get_settings(): + return frappe.get_single("Tax Period Closing Settings") + + +def get_tax_rate(tax_type): + settings = get_settings() + for row in settings.tax_rates or []: + if row.tax_type == tax_type: + return row.rate + return 0 + + +def get_account_map(tax_type): + settings = get_settings() + for row in settings.account_map or []: + if row.tax_type == tax_type: + return {"debit": row.debit_account, "credit": row.credit_account} + return None + + +@frappe.whitelist() +def auto_detect_accounts(company): + """API endpoint to detect accounts for a specific company.""" + if not company: + return {"accounts": []} + + results = {} + + account_search = { + "226": "VAT Input (ƏDV sub-uçot)", + "521.3": "VAT Output (ƏDV)", + "521.1": "Profit Tax Liability", + "521.5": "Property Tax Liability", + "521.6": "Land Tax Liability", + "521.9": "Simplified Tax Liability", + "901": "Profit Tax Expense", + "731": "Other Operating Expenses", + "341": "Period Net Profit (Closing)", + } + + for acc_num, label in account_search.items(): + acc = frappe.db.get_value("Account", { + "company": company, + "account_number": acc_num, + "is_group": 0, + }, ["name", "account_name"], as_dict=True) + if acc: + results[acc_num] = { + "name": acc.name, + "account_name": acc.account_name, + "label": label, + } + + return {"accounts": results} diff --git a/taxes_az/taxes_az/doctype/tax_period_closing_wizard/__init__.py b/taxes_az/taxes_az/doctype/tax_period_closing_wizard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/tax_period_closing_wizard/tax_period_closing_wizard.js b/taxes_az/taxes_az/doctype/tax_period_closing_wizard/tax_period_closing_wizard.js new file mode 100644 index 0000000..6be71e1 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_period_closing_wizard/tax_period_closing_wizard.js @@ -0,0 +1,806 @@ +frappe.ui.form.on("Tax Period Closing Wizard", { + onload(frm) { + // Expand form to full width + frm.$wrapper.find(".form-column, .section-body, .form-section").css("max-width", "100%"); + $(``).appendTo("head"); + }, + + refresh(frm) { + frm.trigger("render_progress_bar"); + frm.trigger("render_benefits_html"); + frm.trigger("render_log_html"); + frm.trigger("render_summary_html"); + frm.trigger("setup_buttons"); + frm.trigger("color_tax_items"); + frm.trigger("bind_realtime"); + }, + + setup_buttons(frm) { + if (frm.doc.docstatus === 1 || frm.doc.docstatus === 2) return; + + if (frm.doc.wizard_status === "Draft" || frm.doc.wizard_status === "Error") { + frm.add_custom_button(__("Calculate Taxes"), () => { + frm.trigger("run_calculate"); + }, __("Actions")).addClass("btn-primary"); + } + + if (frm.doc.wizard_status === "Calculated") { + frm.add_custom_button(__("Create Journal Entries"), () => { + frappe.confirm( + __("This will create journal entries for all calculated taxes. Continue?"), + () => frm.trigger("run_create_entries") + ); + }, __("Actions")).addClass("btn-primary"); + + frm.add_custom_button(__("Re-calculate"), () => { + frm.trigger("run_calculate"); + }, __("Actions")); + } + + // Verify & Fix buttons — available after calculation + if (frm.doc.wizard_status !== "Draft") { + frm.add_custom_button(__("Verify Entries (Kassa)"), () => { + frm.trigger("run_verify"); + }, __("Actions")); + + frm.add_custom_button(__("Fix Cash Method Entries"), () => { + frappe.confirm( + __("This will create correction Journal Entries to move VAT from 521.3 to 245.1 and revenue from 601 to 542 for all unpaid Sales Invoices. Continue?"), + () => frm.trigger("run_fix_cash") + ); + }, __("Actions")); + } + + if (frm.doc.wizard_status === "Entries Created") { + frm.add_custom_button(__("Rollback All Entries"), () => { + frappe.confirm( + __("This will cancel/delete ALL journal entries created by this wizard. Are you sure?"), + () => frm.trigger("run_rollback") + ); + }, __("Actions")).addClass("btn-danger"); + } + }, + + // ── Realtime binding ────────────────────────────────── + + bind_realtime(frm) { + // Avoid duplicate bindings + if (frm._tpcw_realtime_bound) return; + frm._tpcw_realtime_bound = true; + + frappe.realtime.on("tpcw_progress", (data) => { + if (data.name !== frm.doc.name) return; + frm.trigger("update_live_progress", data); + }); + }, + + update_live_progress(frm, data) { + if (!data) return; + + const pct = data.percent || 0; + const phase = data.phase || ""; + const message = data.message || ""; + const level = data.level || "Info"; + + // Update the progress bar + const $wrapper = frm.fields_dict.progress_bar_html.$wrapper; + + const level_colors = { + "Info": { bar: "linear-gradient(90deg, #007bff, #6610f2)", text: "#007bff", pulse: "#007bff" }, + "Success": { bar: "linear-gradient(90deg, #28a745, #20c997)", text: "#28a745", pulse: "#28a745" }, + "Warning": { bar: "linear-gradient(90deg, #fd7e14, #ffc107)", text: "#e67e22", pulse: "#fd7e14" }, + "Error": { bar: "linear-gradient(90deg, #dc3545, #c82333)", text: "#dc3545", pulse: "#dc3545" }, + }; + const colors = level_colors[level] || level_colors["Info"]; + + const is_complete = pct >= 100 || phase === "Complete"; + const pulse_css = is_complete ? "" : "animation: tpcw-pulse 1.5s ease-in-out infinite;"; + + $wrapper.html(` + +
+
+
+ + ${is_complete ? "✓" : "⟳"} ${phase} + + + ${frappe.utils.escape_html(message)} + +
+ ${pct}% +
+
+
${pct > 8 ? pct + "%" : ""}
+
+
+ `); + + // If complete, show a success banner + if (is_complete && level === "Success") { + frappe.show_alert({ + message: message, + indicator: "green" + }, 7); + } + }, + + // ── Run actions ─────────────────────────────────────── + + run_calculate(frm) { + frm.save().then(() => { + frm.trigger("update_live_progress", { + percent: 2, + phase: "Starting", + message: "Initializing wizard...", + level: "Info" + }); + + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_period_closing_wizard.tax_period_closing_wizard.run_wizard", + args: { name: frm.doc.name }, + freeze: true, + freeze_message: __("Calculating taxes..."), + callback(r) { + if (r && r.message) { + frappe.show_alert({ + message: __("Calculation complete. Reloading..."), + indicator: "green" + }); + } + // Force full page reload to get all child table data + window.location.reload(); + }, + error(r) { + frappe.show_alert({ + message: __("Calculation failed. Check browser console."), + indicator: "red" + }); + window.location.reload(); + } + }); + }); + }, + + run_create_entries(frm) { + frm.trigger("update_live_progress", { + percent: 81, + phase: "Entry Creation", + message: "Preparing journal entries...", + level: "Info" + }); + + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_period_closing_wizard.tax_period_closing_wizard.create_entries", + args: { name: frm.doc.name }, + freeze: true, + freeze_message: __("Creating journal entries..."), + callback(r) { + if (r && r.message) { + frappe.show_alert({ + message: __("Entries created. Reloading..."), + indicator: "green" + }); + } + window.location.reload(); + }, + error() { + frappe.show_alert({ + message: __("Entry creation failed."), + indicator: "red" + }); + window.location.reload(); + } + }); + }, + + run_fix_cash(frm) { + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_period_closing_wizard.tax_period_closing_wizard.fix_cash_method_entries", + args: { name: frm.doc.name }, + freeze: true, + freeze_message: __("Re-submitting invoices with cash method accounts..."), + callback(r) { + if (!r || !r.message) return; + const data = r.message; + + if (data.status === "skip" || data.status === "error") { + frappe.msgprint({ message: data.message, indicator: "red" }); + return; + } + + const s = data.summary || {}; + const fmt = (v) => parseFloat(v || 0).toLocaleString("az-AZ", {minimumFractionDigits: 2}); + + let html = `
`; + + html += `
+ ${__("Cash method — invoices re-submitted")} +
`; + + html += ` + + + + + + + + + +
${__("Fixed invoices")}${s.invoices_fixed}ƏDV: ${fmt(s.total_vat_moved)} AZN → 245.1Gəlir: ${fmt(s.total_revenue_deferred)} AZN → 542
${__("Skipped")}${s.skipped}
${__("Errors")}${s.errors}
`; + + if ((data.fixed || []).length > 0) { + html += ` + + + `; + for (const f of data.fixed) { + html += ` + + + + + `; + } + html += `
${__("Invoice")}ƏDV → 245.1Gəlir → 542${__("Changes")}
${f.invoice}${fmt(f.vat)}${fmt(f.revenue)}${(f.changes || []).join("
")}
`; + } + + if ((data.errors || []).length > 0) { + for (const e of data.errors) { + html += `
+ ✗ ${e.invoice}: ${frappe.utils.escape_html(e.error)}
`; + } + } + + html += `
`; + frappe.msgprint({ + title: __("Cash Method — Fix Result"), + message: html, + indicator: (s.errors || 0) > 0 ? "orange" : "green", + wide: true, + }); + } + }); + }, + + run_verify(frm) { + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_period_closing_wizard.tax_period_closing_wizard.verify_entries", + args: { name: frm.doc.name }, + freeze: true, + freeze_message: __("Verifying entries..."), + callback(r) { + if (!r || !r.message) return; + const data = r.message; + const s = data.summary || {}; + + let html = `
`; + + // Header + html += `
+ ${__("Accounting method:")} ${data.method} | + ${__("Invoices:")} ${s.total_invoices || 0} | + ${__("Correct:")} ${s.correct || 0} | + ${__("Error:")} ${s.errors || 0} | + ${__("Warning:")} ${s.warnings || 0} +
`; + + // Issues + if ((data.issues || []).length > 0) { + html += `
`; + for (const issue of data.issues) { + const color = issue.type === "error" ? "#e74c3c" : "#f39c12"; + const icon = issue.type === "error" ? "✗" : "⚠"; + html += `
+ ${icon} + ${issue.invoice}: + ${frappe.utils.escape_html(issue.message)} +
+ ${__("Fix:")} ${frappe.utils.escape_html(issue.fix || "")} +
+
`; + } + if (s.total_wrong_vat > 0) { + html += `
+ ${__("Total incorrect VAT:")} ${parseFloat(s.total_wrong_vat).toLocaleString("az-AZ", {minimumFractionDigits: 2})} AZN +
`; + } + html += `
`; + } + + // OK entries + if ((data.ok || []).length > 0) { + html += `
+ ✓ ${__("Correct entries")} (${data.ok.length}) +
`; + for (const ok of data.ok) { + html += `
✓ ${frappe.utils.escape_html(ok)}
`; + } + html += `
`; + } + + html += `
`; + + frappe.msgprint({ + title: __("Cash Method — Verification Result"), + message: html, + indicator: (s.errors || 0) > 0 ? "red" : ((s.warnings || 0) > 0 ? "orange" : "green"), + wide: true, + }); + } + }); + }, + + run_rollback(frm) { + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_period_closing_wizard.tax_period_closing_wizard.rollback", + args: { name: frm.doc.name }, + freeze: true, + freeze_message: __("Rolling back entries..."), + callback(r) { + if (r.message) { + frm.reload_doc(); + frappe.show_alert({ + message: __("All entries rolled back"), + indicator: "orange" + }); + } + } + }); + }, + + company(frm) { + if (frm.doc.company) { + frappe.db.get_value("Company", frm.doc.company, [ + "cost_center", "tax_id", "default_currency" + ]).then(r => { + if (r.message) { + if (!frm.doc.cost_center) { + frm.set_value("cost_center", r.message.cost_center); + } + frm.set_value("company_voen", r.message.tax_id || ""); + } + }); + } + }, + + period_type(frm) { + if (frm.doc.period_type === "Monthly") { + frm.set_value("quarter", ""); + } else if (frm.doc.period_type === "Quarterly") { + frm.set_value("month", ""); + } else { + frm.set_value("quarter", ""); + frm.set_value("month", ""); + } + }, + + // ── Static Progress Bar (for saved state) ───────────── + + render_progress_bar(frm) { + const pct = frm.doc.progress_percent || 0; + const status = frm.doc.wizard_status || "Draft"; + + const status_config = { + "Draft": { color: "#6c757d", icon: "○", bar: "linear-gradient(90deg, #6c757d, #adb5bd)" }, + "Calculated": { color: "#fd7e14", icon: "◉", bar: "linear-gradient(90deg, #fd7e14, #ffc107)" }, + "Entries Created": { color: "#28a745", icon: "✓", bar: "linear-gradient(90deg, #28a745, #20c997)" }, + "Completed": { color: "#28a745", icon: "✓", bar: "linear-gradient(90deg, #28a745, #20c997)" }, + "Error": { color: "#dc3545", icon: "✗", bar: "linear-gradient(90deg, #dc3545, #c82333)" }, + "Cancelled": { color: "#6c757d", icon: "—", bar: "linear-gradient(90deg, #6c757d, #adb5bd)" } + }; + + const cfg = status_config[status] || status_config["Draft"]; + + const html = ` +
+
+ + ${cfg.icon} ${status} + + ${pct}% +
+
+
${pct > 8 ? pct + "%" : ""}
+
+
+ `; + frm.fields_dict.progress_bar_html.$wrapper.html(html); + }, + + // ── Benefits Display ────────────────────────────────── + + render_benefits_html(frm) { + const benefits = frm.doc.detected_benefits || []; + if (!benefits.length) { + if (frm.fields_dict.benefits_html) { + frm.fields_dict.benefits_html.$wrapper.html( + frm.doc.wizard_status === "Draft" + ? `
${__("Benefits will be detected after calculation.")}
` + : `
${__("No special tax benefits detected.")}
` + ); + } + return; + } + + let html = '
'; + + for (const b of benefits) { + const is_on = b.enabled; + const cond = b.condition_met || "No"; + + // Colors + let border_color, bg_color, icon, badge_bg, badge_text; + if (cond === "Yes" && is_on) { + border_color = "#28a745"; bg_color = "#f0fdf4"; icon = "✓"; badge_bg = "#28a745"; badge_text = __("Active"); + } else if (cond === "No") { + border_color = "#dc3545"; bg_color = "#fef2f2"; icon = "✗"; badge_bg = "#dc3545"; badge_text = __("Not Met"); + } else if (cond === "Manual Check") { + border_color = "#fd7e14"; bg_color = "#fffbeb"; icon = "?"; badge_bg = "#fd7e14"; badge_text = __("Manual Check"); + } else if (!is_on) { + border_color = "#6c757d"; bg_color = "#f8f9fa"; icon = "—"; badge_bg = "#6c757d"; badge_text = __("Disabled"); + } else { + border_color = "#17a2b8"; bg_color = "#f0f9ff"; icon = "~"; badge_bg = "#17a2b8"; badge_text = __("Partial"); + } + + const reduction_html = b.tax_reduction + ? `
+ ${__("Tax Reduction:")} ${format_currency(b.tax_reduction)} +
` + : ""; + + const law_text = b.law_url + ? `${frappe.utils.escape_html(b.law_reference)}` + : frappe.utils.escape_html(b.law_reference); + const law_html = b.law_reference + ? `
+ ${__("Law:")} ${law_text} +
` + : ""; + + html += ` +
+
+
+
+ ${icon} + + ${frappe.utils.escape_html(b.article)} + + + ${badge_text} + +
+
+ ${frappe.utils.escape_html(b.benefit_name)} +
+
+ ${__("Applies to:")} ${frappe.utils.escape_html(b.applies_to)} +  |  ${__("Exemption:")} ${b.exemption_percent}% +
+
+ ${frappe.utils.escape_html(b.condition_details || "")} +
+ ${reduction_html} + ${law_html} +
+
+
+ `; + } + + html += "
"; + if (frm.fields_dict.benefits_html) { + frm.fields_dict.benefits_html.$wrapper.html(html); + } + }, + + // ── Log Display ──────────────────────────────────────── + + render_log_html(frm) { + const logs = frm.doc.log_entries || []; + if (!logs.length) { + frm.fields_dict.log_html.$wrapper.html( + `
${__('No log entries yet. Click "Calculate Taxes" to begin.')}
` + ); + return; + } + + const level_styles = { + "Header": "background: #2c3e50; color: #ecf0f1; font-weight: bold; padding: 6px 10px; margin-top: 8px;", + "Info": "color: #2980b9; padding: 3px 10px;", + "Success": "color: #27ae60; padding: 3px 10px;", + "Warning": "color: #e67e22; padding: 3px 10px; font-weight: 500;", + "Error": "color: #e74c3c; padding: 3px 10px; font-weight: bold; background: #fdf0ef;" + }; + + const level_icons = { + "Header": "═══", + "Info": "ℹ", + "Success": "✓", + "Warning": "⚠", + "Error": "✗" + }; + + let html = '
'; + + for (const log of logs) { + const style = level_styles[log.level] || level_styles["Info"]; + const icon = level_icons[log.level] || "·"; + const ts = log.timestamp ? frappe.datetime.str_to_user(log.timestamp).split(" ")[1] || "" : ""; + + html += `
`; + if (log.level !== "Header") { + html += `[${ts}]`; + } + html += `${icon}`; + html += `${frappe.utils.escape_html(log.message)}`; + html += `
`; + } + + html += "
"; + frm.fields_dict.log_html.$wrapper.html(html); + }, + + // ── Summary Display ─────────────────────────────────── + + render_summary_html(frm) { + if (frm.doc.wizard_status !== "Entries Created" && frm.doc.wizard_status !== "Completed") { + frm.fields_dict.summary_html.$wrapper.html(""); + return; + } + + const items = frm.doc.tax_items || []; + const je_list = frm.doc.journal_entries || []; + const payroll = frm.doc.payroll_summary || []; + + let html = '
'; + + html += ` + + + + + + + + + + + + + `; + + for (const item of items) { + const status_badge = get_status_badge(item.status); + const amount_display = item.status === "Data Only" ? "—" : format_currency(item.tax_amount); + html += ` + + + + + + + + + `; + } + + html += ` + + + + + + `; + + if (frm.doc.total_payroll_taxes) { + html += ` + + + + + + `; + } + + html += ` + + + + + +
${__("Tax Type")}${__("Period")}${__("Amount (AZN)")}${__("Status")}${__("Declaration")}${__("Payment")}
${frappe.utils.escape_html(item.tax_type)}${frappe.utils.escape_html(item.sub_period || "")}${amount_display}${status_badge}${item.declaration_deadline || "—"}${item.payment_deadline || "—"}
${__("Total Tax Entries")}${format_currency(frm.doc.total_tax_amount)}
${__("Payroll Taxes (collected data)")}${format_currency(frm.doc.total_payroll_taxes)}
${__("GRAND TOTAL TAX BURDEN")}${format_currency(frm.doc.grand_total_tax_burden)}
+ `; + + if (je_list.length) { + html += ` +
${__("Created Documents")}
+ + + + + `; + for (const je of je_list) { + html += ` + + + + + + + + `; + } + html += "
${__("Journal Entry")}${__("Tax Type")}${__("Period")}${__("Amount")}${__("Date")}
${je.journal_entry}${frappe.utils.escape_html(je.tax_type || "")}${frappe.utils.escape_html(je.sub_period || "")}${format_currency(je.amount)}${je.posting_date || ""}
"; + } + + if (payroll.length) { + html += ` +
${__("Payroll Data (Reference Only)")}
+ + + + + `; + for (const p of payroll) { + html += ` + + + + + + `; + } + html += "
${__("Component")}${__("Amount")}${__("Source")}
${frappe.utils.escape_html(p.component || "")}${format_currency(p.amount)}${frappe.utils.escape_html(p.source || "")}
"; + } + + html += '
'; + html += `
${__("Upcoming Deadlines")}
`; + for (const item of items) { + if (item.enabled && item.declaration_deadline) { + const decl_date = new Date(item.declaration_deadline); + const today = new Date(); + const days_left = Math.ceil((decl_date - today) / (1000 * 60 * 60 * 24)); + const urgency = days_left <= 7 ? "color: #dc3545; font-weight: bold;" : + days_left <= 30 ? "color: #e67e22;" : "color: #28a745;"; + + html += `
`; + html += `${frappe.utils.escape_html(item.tax_type)} — `; + html += `Declaration: ${item.declaration_deadline} | `; + html += `Payment: ${item.payment_deadline || "N/A"}`; + if (days_left <= 7) html += ` (${days_left} days left!)`; + html += `
`; + } + } + html += "
"; + + frm.fields_dict.summary_html.$wrapper.html(html); + }, + + // ── Color Tax Items ─────────────────────────────────── + + color_tax_items(frm) { + setTimeout(() => { + const status_bg = { + "Entry Created": "#d4edda", + "Calculated": "#d4edda", + "Error": "#f8d7da", + "Skipped": "#e2e3e5", + "Data Only": "#d1ecf1", + "Pending": "#fff3cd" + }; + + frm.$wrapper.find('.frappe-control[data-fieldname="tax_items"] .rows .row').each(function(i) { + const item = (frm.doc.tax_items || [])[i]; + if (!item) return; + const bg = status_bg[item.status]; + if (bg) $(this).css({ "background-color": bg, "transition": "background-color 0.3s" }); + }); + + frm.$wrapper.find('.frappe-control[data-fieldname="entry_previews"] .rows .row').each(function(i) { + const item = (frm.doc.entry_previews || [])[i]; + if (!item) return; + const bg = { + "Created": "#d4edda", + "Failed": "#f8d7da", + "Pending": "#fff3cd", + "Existing": "#d4edda", + "Difference": "#ffe0b2", + "Skipped": "#e2e3e5" + }[item.status]; + if (bg) $(this).css("background-color", bg); + }); + + frm.$wrapper.find('.frappe-control[data-fieldname="journal_entries"] .rows .row').each(function() { + $(this).css("background-color", "#d4edda"); + }); + }, 300); + } +}); + + +// ── Helper Functions ────────────────────────────────────── + +function get_status_badge(status) { + const badges = { + "Pending": `${__("Pending")}`, + "Calculated": `${__("Calculated")}`, + "Entry Created": `${__("Created")}`, + "Existing": `${__("Existing")}`, + "Difference": `${__("Difference")}`, + "Data Only": `${__("Data Only")}`, + "Skipped": `${__("Skipped")}`, + "Error": `${__("Error")}` + }; + return badges[status] || `${status}`; +} + +function format_currency(val) { + const num = parseFloat(val) || 0; + return num.toLocaleString("az-AZ", { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }) + " AZN"; +} diff --git a/taxes_az/taxes_az/doctype/tax_period_closing_wizard/tax_period_closing_wizard.json b/taxes_az/taxes_az/doctype/tax_period_closing_wizard/tax_period_closing_wizard.json new file mode 100644 index 0000000..9b386ee --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_period_closing_wizard/tax_period_closing_wizard.json @@ -0,0 +1,582 @@ +{ + "actions": [], + "autoname": "TPCW-.YYYY.-.#####", + "creation": "2026-04-01 12:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "wizard_status_section", + "wizard_status", + "column_break_ws1", + "progress_percent", + "progress_bar_html", + "company_section", + "company", + "column_break_c1", + "tax_regime", + "column_break_c2", + "fiscal_year", + "period_section", + "period_type", + "column_break_p1", + "quarter", + "month", + "column_break_p2", + "from_date", + "to_date", + "options_section", + "cost_center", + "column_break_o1", + "auto_submit", + "column_break_o2", + "enable_period_closing_voucher", + "closing_account", + + "tab_dashboard", + "company_info_section", + "company_voen", + "company_activity_code", + "column_break_ci1", + "company_classification", + "company_tax_authority", + "column_break_ci2", + "is_vat_payer", + "has_assets", + "has_land", + "has_employees", + "dashboard_financials_section", + "total_income", + "column_break_fd1", + "total_expense", + "column_break_fd2", + "net_profit", + "dashboard_assets_section", + "total_asset_value", + "accumulated_depreciation", + "column_break_ad1", + "net_book_value", + "avg_annual_value", + "benefits_section", + "benefits_html", + "detected_benefits", + "dashboard_totals_section", + "total_tax_amount", + "column_break_tt1", + "total_payroll_taxes", + "column_break_tt2", + "grand_total_tax_burden", + + "tab_tax_details", + "taxes_section", + "tax_items", + "entry_preview_section", + "entry_previews", + + "tab_results", + "journal_entries_section", + "journal_entries", + "payroll_section", + "payroll_summary", + "summary_section", + "summary_html", + + "tab_log", + "log_section", + "log_html", + "log_entries", + + "amended_from" + ], + "fields": [ + { + "fieldname": "wizard_status_section", + "fieldtype": "Section Break", + "label": "Status" + }, + { + "default": "Draft", + "fieldname": "wizard_status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Wizard Status", + "options": "Draft\nCalculated\nEntries Created\nCompleted\nError\nCancelled", + "read_only": 1 + }, + { + "fieldname": "column_break_ws1", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "progress_percent", + "fieldtype": "Percent", + "label": "Progress", + "read_only": 1 + }, + { + "fieldname": "progress_bar_html", + "fieldtype": "HTML", + "label": "Progress Bar" + }, + { + "fieldname": "company_section", + "fieldtype": "Section Break", + "label": "Company" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "column_break_c1", + "fieldtype": "Column Break" + }, + { + "fieldname": "tax_regime", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Tax Regime", + "options": "\nGeneral (VAT + Profit)\nSimplified Tax\nSpecial Regime" + }, + { + "fieldname": "column_break_c2", + "fieldtype": "Column Break" + }, + { + "fieldname": "fiscal_year", + "fieldtype": "Link", + "label": "Fiscal Year", + "options": "Fiscal Year", + "reqd": 1 + }, + { + "fieldname": "period_section", + "fieldtype": "Section Break", + "label": "Period" + }, + { + "default": "Quarterly", + "fieldname": "period_type", + "fieldtype": "Select", + "label": "Period Type", + "options": "Monthly\nQuarterly\nAnnually", + "reqd": 1 + }, + { + "fieldname": "column_break_p1", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.period_type=='Quarterly'", + "fieldname": "quarter", + "fieldtype": "Select", + "label": "Quarter", + "options": "\nQ1 (Jan-Mar)\nQ2 (Apr-Jun)\nQ3 (Jul-Sep)\nQ4 (Oct-Dec)" + }, + { + "depends_on": "eval:doc.period_type=='Monthly'", + "fieldname": "month", + "fieldtype": "Select", + "label": "Month", + "options": "\nJanuary\nFebruary\nMarch\nApril\nMay\nJune\nJuly\nAugust\nSeptember\nOctober\nNovember\nDecember" + }, + { + "fieldname": "column_break_p2", + "fieldtype": "Column Break" + }, + { + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date", + "read_only": 1 + }, + { + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date", + "read_only": 1 + }, + { + "fieldname": "options_section", + "fieldtype": "Section Break", + "collapsible": 1, + "label": "Options" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "column_break_o1", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "auto_submit", + "fieldtype": "Check", + "label": "Auto-submit Journal Entries" + }, + { + "fieldname": "column_break_o2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "enable_period_closing_voucher", + "fieldtype": "Check", + "label": "Create Period Closing Voucher" + }, + { + "depends_on": "enable_period_closing_voucher", + "fieldname": "closing_account", + "fieldtype": "Link", + "label": "Closing Account", + "options": "Account" + }, + { + "fieldname": "tab_dashboard", + "fieldtype": "Tab Break", + "label": "Dashboard" + }, + { + "fieldname": "company_info_section", + "fieldtype": "Section Break", + "label": "Company Info" + }, + { + "fieldname": "company_voen", + "fieldtype": "Data", + "label": "VOEN", + "read_only": 1 + }, + { + "fieldname": "company_activity_code", + "fieldtype": "Data", + "label": "Activity Code (OKVED)", + "read_only": 1 + }, + { + "fieldname": "column_break_ci1", + "fieldtype": "Column Break" + }, + { + "fieldname": "company_classification", + "fieldtype": "Data", + "label": "Business Classification", + "read_only": 1 + }, + { + "fieldname": "company_tax_authority", + "fieldtype": "Data", + "label": "Tax Authority", + "read_only": 1 + }, + { + "fieldname": "column_break_ci2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "is_vat_payer", + "fieldtype": "Check", + "label": "VAT Payer", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "has_assets", + "fieldtype": "Check", + "label": "Has Fixed Assets", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "has_land", + "fieldtype": "Check", + "label": "Has Land", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "has_employees", + "fieldtype": "Check", + "label": "Has Employees", + "read_only": 1 + }, + { + "fieldname": "benefits_section", + "fieldtype": "Section Break", + "label": "Detected Tax Benefits & Exemptions" + }, + { + "fieldname": "benefits_html", + "fieldtype": "HTML", + "label": "Benefits Display" + }, + { + "description": "Auto-detected tax benefits. Uncheck 'Apply' to skip.", + "fieldname": "detected_benefits", + "fieldtype": "Table", + "label": "Benefits Data", + "options": "Tax Closing Benefit", + "hidden": 1 + }, + { + "fieldname": "tab_tax_details", + "fieldtype": "Tab Break", + "label": "Tax Details" + }, + { + "fieldname": "taxes_section", + "fieldtype": "Section Break", + "label": "Applicable Taxes" + }, + { + "fieldname": "tax_items", + "fieldtype": "Table", + "label": "Tax Items", + "options": "Tax Closing Tax Item" + }, + { + "fieldname": "dashboard_financials_section", + "fieldtype": "Section Break", + "label": "Financial Data" + }, + { + "fieldname": "total_income", + "fieldtype": "Currency", + "label": "Total Income", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "column_break_fd1", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_expense", + "fieldtype": "Currency", + "label": "Total Expense", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "column_break_fd2", + "fieldtype": "Column Break" + }, + { + "fieldname": "net_profit", + "fieldtype": "Currency", + "label": "Net Profit / Loss", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "dashboard_assets_section", + "fieldtype": "Section Break", + "collapsible": 1, + "label": "Asset Data" + }, + { + "fieldname": "total_asset_value", + "fieldtype": "Currency", + "label": "Total Asset Value (Gross)", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "accumulated_depreciation", + "fieldtype": "Currency", + "label": "Accumulated Depreciation", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "column_break_ad1", + "fieldtype": "Column Break" + }, + { + "fieldname": "net_book_value", + "fieldtype": "Currency", + "label": "Net Book Value", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "avg_annual_value", + "fieldtype": "Currency", + "label": "Avg Annual Value", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "payroll_section", + "fieldtype": "Section Break", + "label": "Payroll Summary (Data Only)" + }, + { + "fieldname": "payroll_summary", + "fieldtype": "Table", + "label": "Payroll Summary", + "options": "Tax Closing Payroll Summary", + "read_only": 1 + }, + { + "fieldname": "entry_preview_section", + "fieldtype": "Section Break", + "label": "Entry Preview" + }, + { + "fieldname": "entry_previews", + "fieldtype": "Table", + "label": "Entry Previews", + "options": "Tax Closing Entry Preview", + "read_only": 1 + }, + { + "fieldname": "tab_results", + "fieldtype": "Tab Break", + "label": "Results" + }, + { + "fieldname": "journal_entries_section", + "fieldtype": "Section Break", + "label": "Created Journal Entries" + }, + { + "fieldname": "journal_entries", + "fieldtype": "Table", + "label": "Journal Entries", + "options": "Tax Closing Journal Entry", + "read_only": 1 + }, + { + "fieldname": "tab_log", + "fieldtype": "Tab Break", + "label": "Log" + }, + { + "fieldname": "log_section", + "fieldtype": "Section Break", + "label": "Execution Log" + }, + { + "fieldname": "log_html", + "fieldtype": "HTML", + "label": "Log Display" + }, + { + "fieldname": "log_entries", + "fieldtype": "Table", + "label": "Log Entries", + "options": "Tax Closing Log Entry", + "read_only": 1 + }, + { + "fieldname": "summary_section", + "fieldtype": "Section Break", + "label": "Summary Report" + }, + { + "fieldname": "summary_html", + "fieldtype": "HTML", + "label": "Summary Display" + }, + { + "fieldname": "dashboard_totals_section", + "fieldtype": "Section Break", + "label": "Totals" + }, + { + "bold": 1, + "fieldname": "total_tax_amount", + "fieldtype": "Currency", + "label": "Total Tax Amount (Entries Created)", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "column_break_tt1", + "fieldtype": "Column Break" + }, + { + "bold": 1, + "fieldname": "total_payroll_taxes", + "fieldtype": "Currency", + "label": "Total Payroll Taxes (Data Only)", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "column_break_tt2", + "fieldtype": "Column Break" + }, + { + "bold": 1, + "fieldname": "grand_total_tax_burden", + "fieldtype": "Currency", + "label": "Grand Total Tax Burden", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Tax Period Closing Wizard", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2026-04-01 12:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Tax Period Closing Wizard", + "naming_rule": "Expression", + "owner": "Administrator", + "permissions": [ + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "creation", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} diff --git a/taxes_az/taxes_az/doctype/tax_period_closing_wizard/tax_period_closing_wizard.py b/taxes_az/taxes_az/doctype/tax_period_closing_wizard/tax_period_closing_wizard.py new file mode 100644 index 0000000..7cd64fe --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_period_closing_wizard/tax_period_closing_wizard.py @@ -0,0 +1,1757 @@ +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import now_datetime, getdate, flt, add_months, get_last_day, get_first_day, cint +from datetime import date +from calendar import monthrange + + +QUARTER_MAP = { + "Q1 (Jan-Mar)": (1, 3), + "Q2 (Apr-Jun)": (4, 6), + "Q3 (Jul-Sep)": (7, 9), + "Q4 (Oct-Dec)": (10, 12), +} + +MONTH_MAP = { + "January": 1, "February": 2, "March": 3, "April": 4, + "May": 5, "June": 6, "July": 7, "August": 8, + "September": 9, "October": 10, "November": 11, "December": 12, +} + +TAX_PERIOD_CONFIG = { + "VAT (ƏDV)": {"entry_period": "Monthly", "declaration_period": "Monthly"}, + "Profit Tax (Mənfəət)": {"entry_period": "Quarterly", "declaration_period": "Annually"}, + "Simplified Tax (Sadələşdirilmiş)": {"entry_period": "Quarterly", "declaration_period": "Quarterly"}, + "Property Tax (Əmlak)": {"entry_period": "Quarterly", "declaration_period": "Annually"}, + "Land Tax (Torpaq)": {"entry_period": "Quarterly", "declaration_period": "Annually"}, + "Excise Tax (Aksiz)": {"entry_period": "Monthly", "declaration_period": "Monthly"}, + "Mining Tax (Mədən)": {"entry_period": "Monthly", "declaration_period": "Monthly"}, + "Road Tax (Yol)": {"entry_period": "Annually", "declaration_period": "Annually"}, + "Withholding Tax (ÖMV)": {"entry_period": "Monthly", "declaration_period": "Monthly"}, + "Unemployment Insurance (İşsizlik)": {"entry_period": "Monthly", "declaration_period": "Quarterly"}, + "DSMF": {"entry_period": "Monthly", "declaration_period": "Monthly"}, + "Medical Insurance (Tibbi sığorta)": {"entry_period": "Monthly", "declaration_period": "Monthly"}, +} + +DATA_ONLY_TAXES = [ + "Withholding Tax (ÖMV)", + "Unemployment Insurance (İşsizlik)", + "DSMF", + "Medical Insurance (Tibbi sığorta)", +] + + +class TaxPeriodClosingWizard(Document): + def validate(self): + self.compute_dates() + if not self.cost_center: + self.cost_center = frappe.get_cached_value("Company", self.company, "cost_center") + + def compute_dates(self): + fy = frappe.get_doc("Fiscal Year", self.fiscal_year) + year = getdate(fy.year_start_date).year + + if self.period_type == "Monthly" and self.month: + m = MONTH_MAP.get(self.month, 1) + self.from_date = date(year, m, 1) + self.to_date = date(year, m, monthrange(year, m)[1]) + + elif self.period_type == "Quarterly" and self.quarter: + start_m, end_m = QUARTER_MAP.get(self.quarter, (1, 3)) + self.from_date = date(year, start_m, 1) + self.to_date = date(year, end_m, monthrange(year, end_m)[1]) + + elif self.period_type == "Annually": + self.from_date = getdate(fy.year_start_date) + self.to_date = getdate(fy.year_end_date) + + def before_submit(self): + if self.wizard_status not in ("Entries Created", "Completed"): + frappe.throw(_("Please run Calculate and Create Entries before submitting.")) + + def on_cancel(self): + self._cancel_all_journal_entries() + + def _cancel_all_journal_entries(self): + cancelled = [] + for row in self.journal_entries or []: + if row.journal_entry and frappe.db.exists("Journal Entry", row.journal_entry): + je = frappe.get_doc("Journal Entry", row.journal_entry) + if je.docstatus == 1: + je.cancel() + cancelled.append(row.journal_entry) + if cancelled: + self.add_log("Summary", "Warning", + f"Cancelled {len(cancelled)} journal entries: {', '.join(cancelled)}") + + # ── Logging ───────────────────────────────────────────── + + def add_log(self, phase, level, message): + self.append("log_entries", { + "timestamp": now_datetime(), + "phase": phase, + "level": level, + "message": message, + }) + + def _emit_progress(self, percent, phase, message, level="Info"): + """Push live progress to the browser via websocket.""" + self.progress_percent = percent + frappe.publish_realtime( + "tpcw_progress", + { + "percent": percent, + "phase": phase, + "message": message, + "level": level, + "name": self.name, + }, + doctype=self.doctype, + docname=self.name, + after_commit=False, + ) + + # ── Phase 1: Initialization ────────────────────────────── + + def detect_company_info(self): + self._emit_progress(5, "Initialization", "Loading company data...") + company = frappe.get_doc("Company", self.company) + self.company_voen = company.tax_id or "" + self.company_activity_code = "" + self.company_classification = "" + self.company_tax_authority = "" + + if hasattr(company, "main_activity") and company.main_activity: + self.company_activity_code = company.main_activity + if hasattr(company, "business_classification") and company.business_classification: + self.company_classification = company.business_classification + if hasattr(company, "tax_authority") and company.tax_authority: + self.company_tax_authority = company.tax_authority + + self.is_vat_payer = self._check_vat_payer() + self.has_assets = self._check_has_assets() + self.has_land = 0 # Land checking would need custom fields on Asset + self.has_employees = self._check_has_employees() + + self.add_log("Initialization", "Info", + f"Company: {self.company} | VOEN: {self.company_voen} | " + f"OKVED: {self.company_activity_code} | " + f"Classification: {self.company_classification}") + self.add_log("Initialization", "Info", + f"Period: {self.from_date} — {self.to_date} | " + f"Fiscal Year: {self.fiscal_year} | Currency: {company.default_currency}") + + def _check_vat_payer(self): + settings = self._get_settings() + if settings.vat_output_account: + return 1 + # Also check if company has VAT certificate + company = frappe.get_doc("Company", self.company) + if hasattr(company, "vat_certificate_number") and company.vat_certificate_number: + return 1 + return 0 + + def _check_has_assets(self): + count = frappe.db.count("Asset", { + "company": self.company, + "docstatus": 1, + "status": ["not in", ["Scrapped", "Sold"]], + }) + return 1 if count > 0 else 0 + + def _check_has_employees(self): + count = frappe.db.count("Employee", { + "company": self.company, + "status": "Active", + }) + return 1 if count > 0 else 0 + + # ── Phase 1.5: Benefit Detection ───────────────────────── + + def detect_benefits(self): + """Auto-detect applicable tax benefits based on AZ Tax Code.""" + self._emit_progress(8, "Benefit Detection", "Scanning tax benefits...") + self.add_log("Tax Detection", "Header", "Detecting tax benefits & exemptions...") + self.detected_benefits = [] + + classification = self.company_classification or "" + is_micro = "Micro" in classification + is_small = "Small" in classification or "Kiçik" in classification + is_micro_or_small = is_micro or is_small + + # Get actual data for condition checks + year = getdate(self.from_date).year + avg_employees = self._get_avg_monthly_employees(year) + annual_revenue = self._get_annual_revenue(year) + has_startup_cert = self._check_startup_certificate() + has_investment_cert = self._check_investment_certificate() + + self.add_log("Tax Detection", "Info", + f"Classification: {classification} | Avg employees: {avg_employees} | " + f"Annual revenue: {annual_revenue:,.2f} | Startup cert: {has_startup_cert}") + + # ── Maddə 106.1.20 — Micro: 75% profit exemption ── + if is_micro: + employees_ok = avg_employees >= 3 + # No DSMF debt check automated — mark as Manual Check + cond = "Yes" if employees_ok else "No" + details = (f"Avg employees: {avg_employees} (need ≥3). " + f"DSMF debt: requires manual verification.") + if not employees_ok: + details += " CONDITION NOT MET: less than 3 employees." + + self.append("detected_benefits", { + "enabled": 1 if employees_ok else 0, + "article": "Maddə 106.1.20", + "benefit_name": "Mikro sahibkar — 75% profit exemption", + "applies_to": "Profit Tax (Mənfəət)", + "exemption_percent": 75, + "condition_met": cond if employees_ok else "No", + "condition_details": details, + "law_reference": "Vergi Məcəlləsi, Maddə 106.1.20 — Mikro sahibkarlıq subyekti olan hüquqi şəxslərin müvafiq il üzrə orta aylıq muzdlu işçi sayı 3 nəfərdən az olmayan və məcburi dövlət sosial sığorta haqları üzrə borcu olmayanların sahibkarlıq fəaliyyətindən əldə etdikləri mənfəətin 75 faizi vergidən azaddır.", + "law_url": "https://e-qanun.az/framework/46947", + }) + level = "Success" if employees_ok else "Warning" + self.add_log("Tax Detection", level, + f"Maddə 106.1.20: Micro 75% profit exemption — " + f"{'APPLICABLE' if employees_ok else 'NOT APPLICABLE'} " + f"(employees: {avg_employees}/3)") + + # ── Maddə 106.1.23 — Startup (micro/small): 100% profit exemption ── + if is_micro_or_small and has_startup_cert: + self.append("detected_benefits", { + "enabled": 1, + "article": "Maddə 106.1.23", + "benefit_name": "Startap — 100% profit exemption", + "applies_to": "Profit Tax (Mənfəət)", + "exemption_percent": 100, + "condition_met": "Yes", + "condition_details": "Startup certificate detected. 100% exemption on innovation profit.", + "law_reference": "Vergi Məcəlləsi, Maddə 106.1.23 — Mikro və ya kiçik sahibkarlıq subyekti olan və hüquqi şəxs kimi fəaliyyət göstərən startap şəhadətnaməsinə malik müəssisələrin innovasiya fəaliyyətindən əldə etdikləri mənfəəti vergidən azaddır.", + "law_url": "https://e-qanun.az/framework/46947", + }) + self.add_log("Tax Detection", "Success", + "Maddə 106.1.23: Startup 100% profit exemption — APPLICABLE") + + # ── Maddə 221.1-1 — Micro simplified: 25% DSMF reduction ── + if is_micro: + regime = self.tax_regime or "" + if "Simplified" in regime or "Special" in regime: + self.append("detected_benefits", { + "enabled": 1, + "article": "Maddə 221.1-1", + "benefit_name": "Mikro — 25% quarterly DSMF offset against simplified tax", + "applies_to": "Simplified Tax (Sadələşdirilmiş)", + "exemption_percent": 25, + "condition_met": "Yes", + "condition_details": "Micro entrepreneur on simplified regime. 25% of quarterly DSMF contributions offset against simplified tax.", + "law_reference": "Vergi Məcəlləsi, Maddə 221.1-1 — Mikro sahibkarlıq subyekti olan fiziki şəxslərin sadələşdirilmiş vergi üzrə rüblük məcburi dövlət sosial sığorta haqqı məbləğinin 25 faizi sadələşdirilmiş vergidən çıxılır.", + "law_url": "https://e-qanun.az/framework/46947", + }) + self.add_log("Tax Detection", "Success", + "Maddə 221.1-1: Micro 25% DSMF offset — APPLICABLE") + + # ── Maddə 106.1.17 — Investment promotion: 50% profit exemption ── + if has_investment_cert: + self.append("detected_benefits", { + "enabled": 1, + "article": "Maddə 106.1.17", + "benefit_name": "Investment promotion — 50% profit exemption", + "applies_to": "Profit Tax (Mənfəət)", + "exemption_percent": 50, + "condition_met": "Yes", + "condition_details": "Investment promotion certificate detected.", + "law_reference": "Vergi Məcəlləsi, Maddə 106.1.17 — İnvestisiya təşviqi sənədini almış hüquqi şəxslərin həmin sənədi aldığı tarixdən əldə etdiyi mənfəətin 50 faizi vergidən azaddır.", + "law_url": "https://e-qanun.az/framework/46947", + }) + self.add_log("Tax Detection", "Success", + "Maddə 106.1.17: Investment 50% profit exemption — APPLICABLE") + + # ── Maddə 106.10 — Dividend exemption (revenue < 200,000) ── + if annual_revenue <= 200000 and not self.is_vat_payer: + self.append("detected_benefits", { + "enabled": 1, + "article": "Maddə 106.10", + "benefit_name": "Dividend income exemption (turnover ≤ 200,000)", + "applies_to": "Profit Tax (Mənfəət)", + "exemption_percent": 100, + "condition_met": "Yes", + "condition_details": f"Annual revenue {annual_revenue:,.2f} ≤ 200,000 and not VAT payer. Dividend income is 100% exempt.", + "law_reference": "Vergi Məcəlləsi, Maddə 106.10 — Əməliyyatlarının həcmi ardıcıl 12 aylıq dövrün istənilən ayında 200.000 manatadək olan rezident müəssisənin ƏDV-nin ödəyicisi kimi qeydiyyatda olmayan vergi ödəyicisinin dividend gəliri vergidən azaddır.", + "law_url": "https://e-qanun.az/framework/46947", + }) + self.add_log("Tax Detection", "Success", + f"Maddə 106.10: Dividend exemption — APPLICABLE (revenue {annual_revenue:,.2f} ≤ 200,000)") + + # ── Maddə 199.4 — Property tax exemptions (auto-detect from Asset) ── + if self.has_assets: + exempt_assets = frappe.db.sql(""" + SELECT COUNT(*) as cnt + FROM `tabAsset` + WHERE company = %s AND docstatus = 1 + AND status NOT IN ('Scrapped', 'Sold') + AND (tax_exempt_tax_article IS NOT NULL AND tax_exempt_tax_article != '') + """, self.company, as_dict=True) + exempt_count = exempt_assets[0].cnt if exempt_assets else 0 + if exempt_count > 0: + self.append("detected_benefits", { + "enabled": 1, + "article": "Maddə 199.4", + "benefit_name": f"Property tax exempt assets ({exempt_count} units)", + "applies_to": "Property Tax (Əmlak)", + "exemption_percent": 100, + "condition_met": "Yes", + "condition_details": f"{exempt_count} assets marked as tax-exempt in Asset register.", + "law_reference": "Vergi Məcəlləsi, Maddə 199.4 — Əmlak vergisindən azad olunan əsas vəsaitlərin siyahısı. Maddə 199.4.1–199.4.4 ilə müəyyən edilən kateqoriyalara aid əsas vəsaitlər vergidən azaddır.", + "law_url": "https://e-qanun.az/framework/46947", + }) + self.add_log("Tax Detection", "Success", + f"Maddə 199.4: {exempt_count} tax-exempt assets found") + + # ── DSMF rates for micro (15% instead of 22%) ── + if is_micro: + self.append("detected_benefits", { + "enabled": 1, + "article": "DSMF Micro Rate", + "benefit_name": "Mikro — DSMF employer rate 15% (instead of 22%)", + "applies_to": "DSMF", + "exemption_percent": 32, + "condition_met": "Yes", + "condition_details": "Micro entrepreneur DSMF employer rate is 15% instead of standard 22% (difference 7 p.p., ~32% reduction). Applied in payroll, shown here for reference — verify Payroll settings.", + "law_reference": "\"Sosial sığorta haqqında\" Azərbaycan Respublikasının Qanunu — Mikro sahibkarlıq subyektləri üçün işəgötürənin məcburi dövlət sosial sığorta haqqı dərəcəsi 15%-dir (standart 22% əvəzinə).", + "law_url": "https://e-qanun.az/framework/46854", + }) + self.add_log("Tax Detection", "Info", + "DSMF Micro Rate: 15% employer (32% reduction from standard 22%) — verify in Payroll") + + if not self.detected_benefits: + self.add_log("Tax Detection", "Info", + "No special tax benefits detected for this company.") + + def _get_avg_monthly_employees(self, year): + """Get average monthly employee count for the year.""" + # Count employees who were active during the year + total = frappe.db.sql(""" + SELECT COUNT(DISTINCT e.name) as cnt + FROM `tabEmployee` e + WHERE e.company = %s + AND e.status = 'Active' + AND (e.date_of_joining IS NULL OR e.date_of_joining <= %s) + AND (e.relieving_date IS NULL OR e.relieving_date >= %s) + """, (self.company, f"{year}-12-31", f"{year}-01-01"), as_dict=True) + count = total[0].cnt if total else 0 + + # For monthly average we need salary slips + monthly_counts = frappe.db.sql(""" + SELECT MONTH(posting_date) as month, COUNT(DISTINCT employee) as cnt + FROM `tabSalary Slip` + WHERE company = %s AND docstatus = 1 + AND posting_date BETWEEN %s AND %s + GROUP BY MONTH(posting_date) + """, (self.company, f"{year}-01-01", f"{year}-12-31"), as_dict=True) + + if monthly_counts: + avg = sum(m.cnt for m in monthly_counts) / len(monthly_counts) + return round(avg, 1) + return count + + def _get_annual_revenue(self, year): + """Get total revenue for the year from GL.""" + from erpnext.accounts.utils import get_balance_on + + income_accounts = frappe.get_all("Account", filters={ + "company": self.company, + "root_type": "Income", + "is_group": 0, + }, pluck="name") + + total = 0 + for acc in income_accounts: + bal = flt(get_balance_on(acc, f"{year}-12-31", company=self.company, + start_date=f"{year}-01-01")) + total += bal + return abs(total) + + def _check_startup_certificate(self): + """Check if company has a startup certificate.""" + company = frappe.get_doc("Company", self.company) + # Check for custom field or certificate data + if hasattr(company, "startup_certificate") and company.startup_certificate: + return True + # Check certificate_data child table + for cert in getattr(company, "certificate_data", []): + if "startap" in (cert.get("certificate_type") or "").lower(): + return True + if "startup" in (cert.get("certificate_type") or "").lower(): + return True + return False + + def _check_investment_certificate(self): + """Check if company has an investment promotion certificate.""" + company = frappe.get_doc("Company", self.company) + for cert in getattr(company, "certificate_data", []): + if "investisiya" in (cert.get("certificate_type") or "").lower(): + return True + if "investment" in (cert.get("certificate_type") or "").lower(): + return True + return False + + # ── Phase 2: Tax Detection ──────────────────────────────── + + def detect_taxes(self): + self._emit_progress(10, "Tax Detection", "Determining applicable taxes...") + settings = self._get_settings() + regime = self.tax_regime or settings.default_tax_regime or "General (VAT + Profit)" + + self.add_log("Tax Detection", "Header", + f"Tax regime: {regime}") + + self.tax_items = [] + + if regime == "General (VAT + Profit)": + if self.is_vat_payer: + self._add_tax_items_for_period("VAT (ƏDV)") + self._add_tax_items_for_period("Profit Tax (Mənfəət)") + elif regime == "Simplified Tax": + self._add_tax_items_for_period("Simplified Tax (Sadələşdirilmiş)") + elif regime == "Special Regime": + self._add_tax_items_for_period("Simplified Tax (Sadələşdirilmiş)") + + if self.has_assets: + self._add_tax_items_for_period("Property Tax (Əmlak)") + if self.has_land: + self._add_tax_items_for_period("Land Tax (Torpaq)") + + # Data-only taxes + if self.has_employees: + for tax in DATA_ONLY_TAXES: + self._add_tax_items_for_period(tax) + + def _add_tax_items_for_period(self, tax_type): + config = TAX_PERIOD_CONFIG.get(tax_type, {}) + entry_period = config.get("entry_period", "Quarterly") + + is_data_only = tax_type in DATA_ONLY_TAXES + + if entry_period == "Monthly": + months = self._get_months_in_range() + for m_num, m_name in months: + year = getdate(self.from_date).year + sub = f"{m_name} {year}" + deadlines = self._get_deadlines(tax_type, year, m_num) + status = "Data Only" if is_data_only else "Pending" + self.append("tax_items", { + "enabled": 1, + "tax_type": tax_type, + "period_type": "Monthly", + "sub_period": sub, + "status": status, + "declaration_deadline": deadlines.get("declaration"), + "payment_deadline": deadlines.get("payment"), + }) + action = "DATA ONLY" if is_data_only else "INCLUDED" + self.add_log("Tax Detection", "Success", + f"{tax_type} — {action} ({sub})") + elif entry_period == "Quarterly": + # Add one item for the whole selected period + year = getdate(self.from_date).year + sub = self._get_period_label() + deadlines = self._get_deadlines(tax_type, year, getdate(self.to_date).month) + self.append("tax_items", { + "enabled": 1, + "tax_type": tax_type, + "period_type": "Quarterly", + "sub_period": sub, + "status": "Pending", + "declaration_deadline": deadlines.get("declaration"), + "payment_deadline": deadlines.get("payment"), + }) + self.add_log("Tax Detection", "Success", + f"{tax_type} — INCLUDED ({sub})") + elif entry_period == "Annually": + year = getdate(self.from_date).year + sub = str(year) + deadlines = self._get_deadlines(tax_type, year, 12) + self.append("tax_items", { + "enabled": 1, + "tax_type": tax_type, + "period_type": "Annually", + "sub_period": sub, + "status": "Pending", + "declaration_deadline": deadlines.get("declaration"), + "payment_deadline": deadlines.get("payment"), + }) + self.add_log("Tax Detection", "Success", + f"{tax_type} — INCLUDED ({sub})") + + def _get_months_in_range(self): + from_d = getdate(self.from_date) + to_d = getdate(self.to_date) + months = [] + current = from_d + while current <= to_d: + m_name = list(MONTH_MAP.keys())[current.month - 1] + months.append((current.month, m_name)) + if current.month == 12: + break + next_month = current.month + 1 + current = date(current.year, next_month, 1) + return months + + def _get_period_label(self): + if self.period_type == "Monthly" and self.month: + year = getdate(self.from_date).year + return f"{self.month} {year}" + elif self.period_type == "Quarterly" and self.quarter: + year = getdate(self.from_date).year + return f"{self.quarter} {year}" + elif self.period_type == "Annually": + return str(getdate(self.from_date).year) + return str(self.from_date) + + def _get_deadlines(self, tax_type, year, month): + # Declaration and payment deadlines per AZ Tax Code + next_m = month + 1 + next_y = year + if next_m > 12: + next_m = next_m - 12 + next_y += 1 + + if tax_type == "VAT (ƏDV)": + return { + "declaration": date(next_y, next_m, min(20, monthrange(next_y, next_m)[1])), + "payment": date(next_y, next_m, min(20, monthrange(next_y, next_m)[1])), + } + elif tax_type == "Profit Tax (Mənfəət)": + return { + "declaration": date(year + 1, 3, 31), + "payment": date(next_y, next_m, min(15, monthrange(next_y, next_m)[1])), + } + elif tax_type == "Simplified Tax (Sadələşdirilmiş)": + return { + "declaration": date(next_y, next_m, min(20, monthrange(next_y, next_m)[1])), + "payment": date(next_y, next_m, min(20, monthrange(next_y, next_m)[1])), + } + elif tax_type in ("Property Tax (Əmlak)", "Land Tax (Torpaq)"): + return { + "declaration": date(year + 1, 3, 31), + "payment": date(next_y, next_m, min(15, monthrange(next_y, next_m)[1])), + } + elif tax_type in ("Withholding Tax (ÖMV)", "DSMF", "Medical Insurance (Tibbi sığorta)"): + return { + "declaration": date(next_y, next_m, min(20, monthrange(next_y, next_m)[1])), + "payment": date(next_y, next_m, min(20, monthrange(next_y, next_m)[1])), + } + elif tax_type == "Unemployment Insurance (İşsizlik)": + return { + "declaration": date(next_y, next_m, min(20, monthrange(next_y, next_m)[1])), + "payment": date(next_y, next_m, min(15, monthrange(next_y, next_m)[1])), + } + return {"declaration": None, "payment": None} + + # ── Phase 3: Data Collection ────────────────────────────── + + def collect_financial_data(self): + self.add_log("Data Collection", "Header", "Collecting financial data...") + + self._emit_progress(20, "Data Collection", "Reading P&L accounts...") + self._collect_pl_data() + + self._emit_progress(30, "Data Collection", "Reading VAT accounts...") + self._collect_vat_data() + + self._emit_progress(40, "Data Collection", "Reading asset register...") + self._collect_asset_data() + + self._emit_progress(48, "Data Collection", "Reading payroll data...") + self._collect_payroll_data() + + def _collect_pl_data(self): + from erpnext.accounts.utils import get_balance_on + + company = self.company + from_d = str(self.from_date) + to_d = str(self.to_date) + + # Get income totals + income_accounts = frappe.get_all("Account", filters={ + "company": company, + "root_type": "Income", + "is_group": 0, + }, pluck="name") + + total_income = 0 + for acc in income_accounts: + bal = flt(get_balance_on(acc, to_d, company=company, start_date=from_d)) + total_income += bal + + # Income accounts have credit balance, so it's negative in get_balance_on + self.total_income = abs(total_income) + + # Get expense totals + expense_accounts = frappe.get_all("Account", filters={ + "company": company, + "root_type": "Expense", + "is_group": 0, + }, pluck="name") + + total_expense = 0 + for acc in expense_accounts: + bal = flt(get_balance_on(acc, to_d, company=company, start_date=from_d)) + total_expense += bal + + self.total_expense = abs(total_expense) + self.net_profit = self.total_income - self.total_expense + + self.add_log("Data Collection", "Info", + f"Total Income: {self.total_income:,.2f} AZN") + self.add_log("Data Collection", "Info", + f"Total Expense: {self.total_expense:,.2f} AZN") + + profit_level = "Success" if self.net_profit > 0 else "Warning" + self.add_log("Data Collection", profit_level, + f"Net Profit/Loss: {self.net_profit:,.2f} AZN") + + def _collect_vat_data(self): + if not self.is_vat_payer: + return + + settings = self._get_settings() + vat_input = settings.vat_input_account + vat_output = settings.vat_output_account + + if not vat_input or not vat_output: + self.add_log("Data Collection", "Warning", + "VAT accounts not configured in Settings. Skipping VAT data collection.") + return + + from erpnext.accounts.utils import get_balance_on + + months = self._get_months_in_range() + for m_num, m_name in months: + year = getdate(self.from_date).year + m_start = date(year, m_num, 1) + m_end = date(year, m_num, monthrange(year, m_num)[1]) + + # Output VAT (credit balance = liability) + output_bal = abs(flt(get_balance_on( + vat_output, str(m_end), company=self.company, start_date=str(m_start)))) + # Input VAT (debit balance = asset) + input_bal = abs(flt(get_balance_on( + vat_input, str(m_end), company=self.company, start_date=str(m_start)))) + + self.add_log("Data Collection", "Info", + f"VAT {m_name} {year}: Output={output_bal:,.2f} | " + f"Input={input_bal:,.2f} | Net={output_bal - input_bal:,.2f}") + + # Asset categories exempt from property tax per Maddə 199.4 + EXEMPT_ASSET_CATEGORIES = [ + "Yüksək texnologiyalar məhsulu olan hesablama texnikası", # 199.4.4 + ] + + def _collect_asset_data(self): + if not self.has_assets: + self.add_log("Data Collection", "Info", "No fixed assets found.") + return + + assets = frappe.get_all("Asset", filters={ + "company": self.company, + "docstatus": 1, + "status": ["not in", ["Scrapped", "Sold"]], + }, fields=["name", "asset_name", "total_asset_cost", "value_after_depreciation", + "opening_accumulated_depreciation", "taxable_asset_type", + "tax_exempt_tax_article", "asset_category"]) + + total_gross = 0 + total_dep = 0 + taxable_nbv = 0 + exempt_nbv = 0 + exempt_count = 0 + + for asset in assets: + gross = flt(asset.total_asset_cost) + nbv = flt(asset.value_after_depreciation) + total_gross += gross + total_dep += (gross - nbv) + + # Check if asset is exempt + is_exempt = False + exempt_reason = "" + + # 1. Explicit exemption field + if asset.tax_exempt_tax_article: + is_exempt = True + exempt_reason = f"Maddə {asset.tax_exempt_tax_article}" + + # 2. Auto-detect by asset category (Maddə 199.4.4) + elif asset.asset_category in self.EXEMPT_ASSET_CATEGORIES: + is_exempt = True + exempt_reason = "Maddə 199.4.4 (auto: hesablama texnikası)" + + # 3. Auto-detect by taxable_asset_type + elif asset.taxable_asset_type in self.EXEMPT_ASSET_CATEGORIES: + is_exempt = True + exempt_reason = "Maddə 199.4.4 (auto: hesablama texnikası)" + + if is_exempt: + exempt_nbv += nbv + exempt_count += 1 + self.add_log("Data Collection", "Success", + f" EXEMPT: {asset.asset_name} | NBV: {nbv:,.2f} | {exempt_reason}") + else: + taxable_nbv += nbv + self.add_log("Data Collection", "Info", + f" TAXABLE: {asset.asset_name} | NBV: {nbv:,.2f}") + + self.total_asset_value = total_gross + self.accumulated_depreciation = total_dep + self.net_book_value = total_gross - total_dep + + # Store taxable NBV for property tax calculation (not total NBV) + self.avg_annual_value = taxable_nbv + + asset_count = len(assets) + self.add_log("Data Collection", "Info", + f"Fixed Assets: {asset_count} total | " + f"Gross: {total_gross:,.2f} | NBV: {self.net_book_value:,.2f}") + self.add_log("Data Collection", "Info", + f" Taxable NBV: {taxable_nbv:,.2f} ({asset_count - exempt_count} assets) | " + f"Exempt NBV: {exempt_nbv:,.2f} ({exempt_count} assets)") + + if exempt_count > 0: + self.add_log("Data Collection", "Success", + f" {exempt_count} asset(s) exempt from property tax per Maddə 199.4") + + if taxable_nbv == 0 and asset_count > 0: + self.add_log("Data Collection", "Success", + " All assets are TAX EXEMPT — Property Tax = 0") + + settings = self._get_settings() + threshold = flt(settings.property_tax_threshold) or 1000000 + if taxable_nbv > threshold: + self.add_log("Data Collection", "Warning", + f" Taxable NBV exceeds threshold {threshold:,.2f} AZN (Maddə 199)") + elif taxable_nbv > 0: + self.add_log("Data Collection", "Info", + f" Taxable NBV within threshold {threshold:,.2f} AZN") + + def _collect_payroll_data(self): + if not self.has_employees: + self.add_log("Data Collection", "Info", "No active employees found.") + return + + self.payroll_summary = [] + + # Get salary slips for the period + salary_slips = frappe.get_all("Salary Slip", filters={ + "company": self.company, + "posting_date": ["between", [str(self.from_date), str(self.to_date)]], + "docstatus": 1, + }, fields=["name", "gross_pay", "total_deduction", "net_pay"]) + + if not salary_slips: + self.add_log("Data Collection", "Warning", + "No submitted salary slips found for this period.") + return + + total_gross = sum(flt(s.gross_pay) for s in salary_slips) + total_deduction = sum(flt(s.total_deduction) for s in salary_slips) + total_net = sum(flt(s.net_pay) for s in salary_slips) + + self.append("payroll_summary", { + "component": _("Gross Pay"), + "amount": total_gross, + "source": f"{len(salary_slips)} Salary Slips", + }) + self.append("payroll_summary", { + "component": "Total Deductions", + "amount": total_deduction, + "source": "Salary Slips", + }) + self.append("payroll_summary", { + "component": "Net Pay", + "amount": total_net, + "source": "Salary Slips", + }) + + # Collect deduction components + deduction_totals = frappe.db.sql(""" + SELECT sd.salary_component, SUM(sd.amount) as total + FROM `tabSalary Detail` sd + JOIN `tabSalary Slip` ss ON ss.name = sd.parent + WHERE ss.company = %s + AND ss.posting_date BETWEEN %s AND %s + AND ss.docstatus = 1 + AND sd.parentfield = 'deductions' + GROUP BY sd.salary_component + """, (self.company, str(self.from_date), str(self.to_date)), as_dict=True) + + total_payroll_taxes = 0 + for d in deduction_totals: + self.append("payroll_summary", { + "component": d.salary_component, + "amount": flt(d.total), + "source": "Salary Detail", + }) + total_payroll_taxes += flt(d.total) + + self.total_payroll_taxes = total_payroll_taxes + + self.add_log("Data Collection", "Info", + f"Payroll: Gross={total_gross:,.2f} | " + f"Deductions={total_deduction:,.2f} | Net={total_net:,.2f}") + self.add_log("Data Collection", "Info", + f"Payroll tax components: {len(deduction_totals)}") + + # ── Phase 4: Calculate Taxes ────────────────────────────── + + def calculate_taxes(self): + self._emit_progress(52, "Calculation", "Calculating tax amounts...") + self.add_log("Calculation", "Header", "Calculating taxes...") + settings = self._get_settings() + + for item in self.tax_items: + if not item.enabled: + item.status = "Skipped" + self.add_log("Calculation", "Info", + f"{item.tax_type} ({item.sub_period}) — SKIPPED (disabled)") + continue + + if item.tax_type in DATA_ONLY_TAXES: + item.status = "Data Only" + continue + + try: + self._calculate_single_tax(item, settings) + if item.status != "Error": + item.status = "Calculated" + except Exception as e: + item.status = "Error" + self.add_log("Calculation", "Error", + f"{item.tax_type} ({item.sub_period}) — ERROR: {str(e)}") + frappe.log_error(f"Tax calculation error: {str(e)}") + raise + + def _calculate_single_tax(self, item, settings): + if item.tax_type == "VAT (ƏDV)": + self._calc_vat(item, settings) + elif item.tax_type == "Profit Tax (Mənfəət)": + self._calc_profit_tax(item, settings) + elif item.tax_type == "Simplified Tax (Sadələşdirilmiş)": + self._calc_simplified_tax(item, settings) + elif item.tax_type == "Property Tax (Əmlak)": + self._calc_property_tax(item, settings) + elif item.tax_type == "Land Tax (Torpaq)": + self._calc_land_tax(item, settings) + + def _calc_vat(self, item, settings): + from erpnext.accounts.utils import get_balance_on + + vat_input = settings.vat_input_account + vat_output = settings.vat_output_account + if not vat_input or not vat_output: + item.status = "Error" + self.add_log("Calculation", "Error", + "VAT accounts not configured. Set them in Tax Period Closing Settings.") + return + + # Parse month from sub_period + parts = item.sub_period.split() + if len(parts) >= 2: + m_name = parts[0] + year = int(parts[1]) + m_num = MONTH_MAP.get(m_name, 1) + else: + year = getdate(self.from_date).year + m_num = getdate(self.from_date).month + + m_start = date(year, m_num, 1) + m_end = date(year, m_num, monthrange(year, m_num)[1]) + + output_bal = abs(flt(get_balance_on( + vat_output, str(m_end), company=self.company, start_date=str(m_start)))) + input_bal = abs(flt(get_balance_on( + vat_input, str(m_end), company=self.company, start_date=str(m_start)))) + + item.source_amount = output_bal + item.rate = 0 # VAT is net calculation, not rate-based + item.tax_amount = input_bal # Amount to offset (input VAT to close) + + net = output_bal - input_bal + sign = _("payable") if net > 0 else _("refundable") + + self.add_log("Calculation", "Success", + f"VAT {item.sub_period}: Output={output_bal:,.2f} − Input={input_bal:,.2f} " + f"= {abs(net):,.2f} ({sign})") + + def _calc_profit_tax(self, item, settings): + method = settings.profit_tax_advance_method or "Actual Quarterly Profit" + rate = flt(settings.profit_tax_rate) or 20 + + if method == "Actual Quarterly Profit": + profit = flt(self.net_profit) + tax_before_benefits = max(0, profit * rate / 100) + else: + tax_before_benefits = max(0, flt(self.net_profit) * rate / 100) + + # Apply detected benefits + total_exemption_pct = 0 + applied_benefits = [] + for benefit in self.detected_benefits or []: + if (benefit.enabled + and benefit.applies_to == "Profit Tax (Mənfəət)" + and benefit.condition_met in ("Yes", "Partial")): + pct = flt(benefit.exemption_percent) + total_exemption_pct += pct + applied_benefits.append(f"{benefit.article}: {pct}%") + + # Cap at 100% + total_exemption_pct = min(total_exemption_pct, 100) + + tax_reduction = tax_before_benefits * total_exemption_pct / 100 + tax = max(0, tax_before_benefits - tax_reduction) + + # Update benefit rows with calculated reduction + for benefit in self.detected_benefits or []: + if (benefit.enabled + and benefit.applies_to == "Profit Tax (Mənfəət)" + and benefit.condition_met in ("Yes", "Partial")): + benefit.tax_reduction = tax_before_benefits * flt(benefit.exemption_percent) / 100 + + item.source_amount = flt(self.net_profit) + item.rate = rate + item.tax_amount = tax + + self.add_log("Calculation", "Success", + f"Profit Tax ({item.sub_period}): " + f"Profit={self.net_profit:,.2f} × {rate}% = {tax_before_benefits:,.2f}") + + if total_exemption_pct > 0: + self.add_log("Calculation", "Success", + f" → Benefits applied: {', '.join(applied_benefits)} " + f"= {total_exemption_pct}% exemption") + self.add_log("Calculation", "Success", + f" → Tax reduction: {tax_reduction:,.2f} | " + f"Final tax: {tax:,.2f}") + + def _calc_simplified_tax(self, item, settings): + rate = flt(settings.default_simplified_rate) or 4 + revenue = flt(self.total_income) + tax = revenue * rate / 100 + + item.source_amount = revenue + item.rate = rate + item.tax_amount = tax + + self.add_log("Calculation", "Success", + f"Simplified Tax ({item.sub_period}): " + f"Revenue={revenue:,.2f} × {rate}% = {tax:,.2f}") + + def _calc_property_tax(self, item, settings): + rate = flt(settings.property_tax_rate) or 1 + # avg_annual_value = taxable NBV only (exempt assets excluded) + taxable_nbv = flt(self.avg_annual_value) # Can be 0 if all exempt + + if taxable_nbv == 0: + item.source_amount = 0 + item.rate = rate + item.tax_amount = 0 + self.add_log("Calculation", "Success", + f"Property Tax ({item.sub_period}): " + f"Taxable NBV = 0.00 (all assets exempt per Maddə 199.4) → Tax = 0.00") + return + + annual_tax = taxable_nbv * rate / 100 + + # Quarterly advance = 1/4 of annual + if item.period_type == "Quarterly": + tax = annual_tax / 4 + else: + tax = annual_tax + + item.source_amount = taxable_nbv + item.rate = rate + item.tax_amount = tax + + period_label = "1/4 annual" if item.period_type == "Quarterly" else "annual" + self.add_log("Calculation", "Success", + f"Property Tax ({item.sub_period}): " + f"Taxable NBV={taxable_nbv:,.2f} × {rate}% = {annual_tax:,.2f} → " + f"{period_label} = {tax:,.2f}") + + def _calc_land_tax(self, item, settings): + # Land tax uses existing logic from land_tax_declaration.py + item.source_amount = 0 + item.rate = 0 + item.tax_amount = 0 + self.add_log("Calculation", "Info", + "Land Tax: Uses existing calculation from Land Tax Declaration. " + "Set amount manually if needed.") + + # ── Phase 5: Generate Entry Previews ────────────────────── + + def generate_previews(self): + self._emit_progress(70, "Entry Preview", "Scanning existing entries & generating previews...") + self.add_log("Entry Preview", "Header", "Generating entry previews...") + self.entry_previews = [] + settings = self._get_settings() + do_scan = getattr(settings, "scan_existing_entries", True) + offer_correction = getattr(settings, "offer_correction_entry", True) + force_create = getattr(settings, "force_create_if_exists", False) + + if do_scan: + self.add_log("Entry Preview", "Info", "Smart Scan enabled — checking for existing entries...") + + for item in self.tax_items: + if not item.enabled or item.status in ("Skipped", "Data Only", "Error"): + continue + if flt(item.tax_amount) == 0 and item.tax_type != "VAT (ƏDV)": + self.add_log("Entry Preview", "Warning", + f"{item.tax_type} ({item.sub_period}) — Amount is 0, skipping.") + continue + + accounts = self._get_accounts_for_tax(item.tax_type, settings) + if not accounts: + self.add_log("Entry Preview", "Error", + f"No account mapping for {item.tax_type}. Configure in Settings.") + item.status = "Error" + continue + + posting_date = self._get_posting_date(item) + amount = flt(item.tax_amount) + + debit_acc = accounts["debit"] + credit_acc = accounts["credit"] + + if item.tax_type == "VAT (ƏDV)": + debit_acc = settings.vat_output_account + credit_acc = settings.vat_input_account + if amount <= 0: + continue + + remark_prefix = _("VAT Input Offset") if item.tax_type == "VAT (ƏDV)" else _("Accrual {0}").format(item.tax_type) + + # ── Smart Scan ── + existing = None + if do_scan: + existing = self._find_existing_entry(debit_acc, credit_acc, posting_date, item) + + if existing and not force_create: + existing_amount = flt(existing["amount"]) + diff = flt(amount - existing_amount, 2) + + self.append("entry_previews", { + "tax_type": item.tax_type, + "sub_period": item.sub_period, + "posting_date": posting_date, + "debit_account": debit_acc, + "credit_account": credit_acc, + "amount": amount, + "existing_entry": existing["je_name"], + "existing_amount": existing_amount, + "difference": diff, + "remark": f"Existing: {existing['je_name']} ({existing_amount:,.2f} AZN)", + "status": "Existing" if abs(diff) < 0.01 else "Difference", + }) + + if abs(diff) < 0.01: + self.add_log("Entry Preview", "Success", + f"EXISTING: {item.tax_type} ({item.sub_period}) | " + f"{existing['je_name']} | {existing_amount:,.2f} AZN — matches calculation") + else: + level = "Warning" + self.add_log("Entry Preview", level, + f"DIFFERENCE: {item.tax_type} ({item.sub_period}) | " + f"Existing: {existing_amount:,.2f} | Calculated: {amount:,.2f} | " + f"Diff: {diff:+,.2f} AZN | JE: {existing['je_name']}") + + if offer_correction and abs(diff) >= 0.01: + self.append("entry_previews", { + "tax_type": item.tax_type, + "sub_period": item.sub_period, + "posting_date": posting_date, + "debit_account": debit_acc, + "credit_account": credit_acc, + "amount": abs(diff), + "existing_entry": existing["je_name"], + "existing_amount": existing_amount, + "difference": diff, + "remark": (f"Correction: {remark_prefix} for {item.sub_period}. " + f"Diff from {existing['je_name']}. " + f"Tax Period Closing Wizard {self.name}"), + "status": "Pending", + }) + self.add_log("Entry Preview", "Info", + f" → Correction entry offered: {abs(diff):,.2f} AZN") + else: + self.append("entry_previews", { + "tax_type": item.tax_type, + "sub_period": item.sub_period, + "posting_date": posting_date, + "debit_account": debit_acc, + "credit_account": credit_acc, + "amount": amount, + "remark": f"{remark_prefix} for {item.sub_period}. " + f"Tax Period Closing Wizard {self.name}", + "status": "Pending", + }) + + self.add_log("Entry Preview", "Success", + f"NEW: {item.tax_type} ({item.sub_period}) | " + f"Dr {debit_acc} → Cr {credit_acc} | " + f"{amount:,.2f} AZN | Date: {posting_date}") + + def _find_existing_entry(self, debit_account, credit_account, posting_date, item): + """Scan GL Entries to find an existing JE matching the tax type and period.""" + pd = getdate(posting_date) + + if item.period_type == "Monthly": + period_start = date(pd.year, pd.month, 1) + period_end = date(pd.year, pd.month, monthrange(pd.year, pd.month)[1]) + elif item.period_type == "Quarterly": + q_start_month = ((pd.month - 1) // 3) * 3 + 1 + q_end_month = q_start_month + 2 + period_start = date(pd.year, q_start_month, 1) + period_end = date(pd.year, q_end_month, monthrange(pd.year, q_end_month)[1]) + else: + period_start = getdate(self.from_date) + period_end = getdate(self.to_date) + + # Find ALL JEs that have both debit and credit lines matching our accounts + # Sum them up — there may be multiple entries per period (e.g. from e-taxes imports) + results = frappe.db.sql(""" + SELECT + je.name as je_name, + jea_d.debit_in_account_currency as amount + FROM `tabJournal Entry` je + INNER JOIN `tabJournal Entry Account` jea_d + ON jea_d.parent = je.name + AND jea_d.account = %(debit_account)s + AND jea_d.debit_in_account_currency > 0 + INNER JOIN `tabJournal Entry Account` jea_c + ON jea_c.parent = je.name + AND jea_c.account = %(credit_account)s + AND jea_c.credit_in_account_currency > 0 + WHERE je.company = %(company)s + AND je.posting_date BETWEEN %(start)s AND %(end)s + AND je.docstatus = 1 + AND je.is_opening = 'No' + ORDER BY je.posting_date + """, { + "debit_account": debit_account, + "credit_account": credit_account, + "company": self.company, + "start": str(period_start), + "end": str(period_end), + }, as_dict=True) + + if results: + total = sum(flt(r.amount) for r in results) + je_names = ", ".join(r.je_name for r in results[:3]) + if len(results) > 3: + je_names += f" (+{len(results) - 3} more)" + return { + "je_name": je_names, + "amount": total, + "count": len(results), + } + return None + + def _get_accounts_for_tax(self, tax_type, settings): + # First check account_map table + for row in settings.account_map or []: + if row.tax_type == tax_type: + return {"debit": row.debit_account, "credit": row.credit_account} + + # Fallback for VAT + if tax_type == "VAT (ƏDV)": + return { + "debit": settings.vat_output_account or "", + "credit": settings.vat_input_account or "", + } + return None + + def _get_posting_date(self, item): + # Posting date = last day of the sub-period + parts = item.sub_period.split() + if item.period_type == "Monthly" and len(parts) >= 2: + m_name = parts[0] + year = int(parts[1]) + m_num = MONTH_MAP.get(m_name, 1) + return date(year, m_num, monthrange(year, m_num)[1]) + return getdate(self.to_date) + + # ── Phase 6: Create Journal Entries ─────────────────────── + + def create_journal_entries(self): + self._emit_progress(82, "Entry Creation", "Creating journal entries...") + self.add_log("Entry Creation", "Header", "Creating journal entries...") + settings = self._get_settings() + + # Check for duplicate closing + if not settings.allow_reclosing: + existing = frappe.get_all("Tax Period Closing Wizard", filters={ + "company": self.company, + "from_date": self.from_date, + "to_date": self.to_date, + "wizard_status": ["in", ["Entries Created", "Completed"]], + "name": ["!=", self.name], + "docstatus": ["!=", 2], + }) + if existing: + frappe.throw( + _("Period {0} — {1} already closed by {2}. " + "Enable 'Allow re-closing' in Settings to override.").format( + self.from_date, self.to_date, existing[0].name)) + + self.journal_entries = [] + total_amount = 0 + created_count = 0 + + existing_count = sum(1 for p in self.entry_previews if p.status == "Existing") + if existing_count: + self.add_log("Entry Creation", "Info", + f"Skipping {existing_count} entries — already exist in the system.") + + for preview in self.entry_previews: + if preview.status not in ("Pending",): + continue + if flt(preview.amount) == 0: + continue + + try: + je = self._create_single_je(preview, settings) + preview.status = "Created" + + self.append("journal_entries", { + "journal_entry": je.name, + "tax_type": preview.tax_type, + "sub_period": preview.sub_period, + "amount": preview.amount, + "posting_date": preview.posting_date, + }) + + total_amount += flt(preview.amount) + created_count += 1 + + status_text = "submitted" if je.docstatus == 1 else "draft" + self.add_log("Entry Creation", "Success", + f"Journal Entry {je.name} created ({status_text}) | " + f"{preview.tax_type} ({preview.sub_period}) | " + f"{flt(preview.amount):,.2f} AZN") + + except Exception as e: + preview.status = "Failed" + self.add_log("Entry Creation", "Error", + f"FAILED: {preview.tax_type} ({preview.sub_period}) — {str(e)}") + frappe.log_error(f"JE creation error: {str(e)}") + raise + + self.total_tax_amount = total_amount + self.grand_total_tax_burden = total_amount + flt(self.total_payroll_taxes) + + self.add_log("Entry Creation", "Success", + f"Created {created_count} journal entries. " + f"Total: {total_amount:,.2f} AZN") + + def _create_single_je(self, preview, settings): + je = frappe.new_doc("Journal Entry") + je.company = self.company + je.posting_date = preview.posting_date + je.voucher_type = "Journal Entry" + je.user_remark = preview.remark + je.multi_currency = 0 + + je.append("accounts", { + "account": preview.debit_account, + "debit_in_account_currency": flt(preview.amount), + "cost_center": self.cost_center, + }) + + je.append("accounts", { + "account": preview.credit_account, + "credit_in_account_currency": flt(preview.amount), + "cost_center": self.cost_center, + }) + + je.insert(ignore_permissions=True) + + if self.auto_submit or settings.auto_submit_entries: + je.submit() + + return je + + # ── Phase 7: Period Closing Voucher ─────────────────────── + + def create_period_closing_voucher(self): + if not self.enable_period_closing_voucher: + return + + if not self.closing_account: + settings = self._get_settings() + self.closing_account = settings.closing_account + + if not self.closing_account: + self.add_log("Period Closing", "Error", + "Closing account not specified. Cannot create Period Closing Voucher.") + return + + self.add_log("Period Closing", "Header", "Creating Period Closing Voucher...") + + try: + pcv = frappe.new_doc("Period Closing Voucher") + pcv.company = self.company + pcv.posting_date = self.to_date + pcv.fiscal_year = self.fiscal_year + pcv.closing_account_head = self.closing_account + pcv.remarks = ( + f"Period Closing via Tax Period Closing Wizard {self.name}. " + f"Period: {self.from_date} — {self.to_date}" + ) + + # Set period dates + pcv.period_start_date = self.from_date + pcv.period_end_date = self.to_date + pcv.transaction_date = self.to_date + + pcv.insert(ignore_permissions=True) + pcv.submit() + + self.add_log("Period Closing", "Success", + f"Period Closing Voucher {pcv.name} created and submitted. " + f"Closing Account: {self.closing_account}") + + except Exception as e: + self.add_log("Period Closing", "Error", + f"Failed to create Period Closing Voucher: {str(e)}") + frappe.log_error(f"PCV creation error: {str(e)}") + + # ── Phase 8: Summary ───────────────────────────────────── + + def generate_summary(self): + self._emit_progress(95, "Summary", "Generating summary report...") + self.add_log("Summary", "Header", "=" * 50) + self.add_log("Summary", "Header", "SUMMARY REPORT") + self.add_log("Summary", "Header", "=" * 50) + + # Tax entries summary + for item in self.tax_items: + if item.status in ("Calculated", "Entry Created"): + self.add_log("Summary", "Success", + f"{item.tax_type} ({item.sub_period}): " + f"{flt(item.tax_amount):,.2f} AZN — {item.status}") + elif item.status == "Data Only": + self.add_log("Summary", "Info", + f"{item.tax_type} ({item.sub_period}): Data collected") + elif item.status == "Skipped": + self.add_log("Summary", "Info", + f"{item.tax_type} ({item.sub_period}): Skipped") + elif item.status == "Error": + self.add_log("Summary", "Error", + f"{item.tax_type} ({item.sub_period}): ERROR") + + self.add_log("Summary", "Header", "-" * 50) + self.add_log("Summary", "Success", + f"Total Tax Entries: {flt(self.total_tax_amount):,.2f} AZN") + self.add_log("Summary", "Info", + f"Total Payroll Taxes (collected): {flt(self.total_payroll_taxes):,.2f} AZN") + self.add_log("Summary", "Success", + f"Grand Total Tax Burden: {flt(self.grand_total_tax_burden):,.2f} AZN") + + # Journal entries list + self.add_log("Summary", "Header", "-" * 50) + self.add_log("Summary", "Header", "CREATED DOCUMENTS") + for je_row in self.journal_entries or []: + self.add_log("Summary", "Success", + f"Journal Entry: {je_row.journal_entry} | " + f"{je_row.tax_type} | {flt(je_row.amount):,.2f} AZN") + + # Deadlines + self.add_log("Summary", "Header", "-" * 50) + self.add_log("Summary", "Header", "DEADLINES") + for item in self.tax_items: + if item.enabled and item.status not in ("Skipped",): + if item.declaration_deadline: + self.add_log("Summary", "Warning", + f"{item.tax_type} — Declaration: {item.declaration_deadline} | " + f"Payment: {item.payment_deadline}") + + # ── Helpers ─────────────────────────────────────────────── + + def _get_settings(self): + return frappe.get_single("Tax Period Closing Settings") + + # ── Cancel All Entries (Rollback) ───────────────────────── + + def rollback_entries(self): + self.add_log("Summary", "Header", "ROLLBACK — Cancelling all journal entries...") + cancelled = [] + failed = [] + + for row in self.journal_entries or []: + if not row.journal_entry: + continue + try: + je = frappe.get_doc("Journal Entry", row.journal_entry) + if je.docstatus == 1: + je.cancel() + cancelled.append(row.journal_entry) + self.add_log("Summary", "Warning", + f"Cancelled: {row.journal_entry} | " + f"{row.tax_type} | {flt(row.amount):,.2f} AZN") + elif je.docstatus == 0: + je.delete() + cancelled.append(row.journal_entry) + self.add_log("Summary", "Warning", + f"Deleted (draft): {row.journal_entry}") + except Exception as e: + failed.append(row.journal_entry) + self.add_log("Summary", "Error", + f"Failed to cancel {row.journal_entry}: {str(e)}") + + self.journal_entries = [] + self.wizard_status = "Draft" + self.total_tax_amount = 0 + self.grand_total_tax_burden = flt(self.total_payroll_taxes) + + self.add_log("Summary", "Success" if not failed else "Warning", + f"Rollback complete. Cancelled: {len(cancelled)} | Failed: {len(failed)}") + + self.save() + + +# ── Whitelisted API Methods ────────────────────────────────── + +@frappe.whitelist() +def run_wizard(name): + doc = frappe.get_doc("Tax Period Closing Wizard", name) + doc.check_permission("write") + + doc.log_entries = [] + doc.progress_percent = 0 + doc.wizard_status = "Draft" + + # Phase 1 + doc.progress_percent = 5 + doc.detect_company_info() + + # Phase 1.5 + doc.detect_benefits() + + # Phase 2 + doc.progress_percent = 15 + doc.detect_taxes() + + # Phase 3 + doc.progress_percent = 30 + doc.collect_financial_data() + + # Phase 4 + doc.progress_percent = 50 + doc.calculate_taxes() + + doc.wizard_status = "Calculated" + + # Phase 5 + doc.generate_previews() + + doc._emit_progress(80, "Complete", "Calculation complete. Review results below.", "Success") + doc.progress_percent = 80 + + doc.save() + frappe.db.commit() + + return {"status": "calculated", "name": doc.name} + + +@frappe.whitelist() +def create_entries(name): + doc = frappe.get_doc("Tax Period Closing Wizard", name) + doc.check_permission("write") + + # Phase 6 + doc.create_journal_entries() + doc.progress_percent = 90 + + # Phase 7 + doc.create_period_closing_voucher() + doc.progress_percent = 95 + + # Phase 8 + doc.generate_summary() + + # Update tax item statuses + for item in doc.tax_items: + if item.status == "Calculated": + item.status = "Entry Created" + + doc.wizard_status = "Entries Created" + doc.progress_percent = 100 + + doc._emit_progress(100, "Complete", "All journal entries created successfully!", "Success") + + doc.save() + frappe.db.commit() + + return {"status": "entries_created", "name": doc.name} + + +@frappe.whitelist() +def rollback(name): + doc = frappe.get_doc("Tax Period Closing Wizard", name) + doc.check_permission("write") + doc.rollback_entries() + return {"status": "rolled_back", "name": doc.name} + + +@frappe.whitelist() +def verify_entries(name): + """Verify all journal entries for cash method compliance.""" + doc = frappe.get_doc("Tax Period Closing Wizard", name) + doc.check_permission("read") + + company = doc.company + accounting_method = frappe.db.get_value("Company", company, "accounting_method") + settings = frappe.get_single("Tax Period Closing Settings") + + results = { + "method": accounting_method or _("Accrual method"), + "issues": [], + "ok": [], + "summary": {}, + } + + if accounting_method != "Kassa metodu": + results["ok"].append(_("Company uses accrual method — no cash method verification needed.")) + return results + + vat_output = settings.vat_output_account + deferred_vat_sales = settings.deferred_vat_sales_account + + # Find all Sales Invoices in the period + invoices = frappe.get_all("Sales Invoice", filters={ + "company": company, + "posting_date": ["between", [str(doc.from_date), str(doc.to_date)]], + "docstatus": 1, + }, fields=["name", "grand_total", "net_total", "total_taxes_and_charges", + "outstanding_amount", "status"]) + + total_invoices = len(invoices) + paid_ok = 0 + unpaid_wrong = 0 + partial_issues = 0 + total_wrong_vat = 0 + + for si in invoices: + vat_amount = flt(si.total_taxes_and_charges) + if vat_amount == 0: + continue + + is_paid = flt(si.outstanding_amount) == 0 + is_partial = 0 < flt(si.outstanding_amount) < flt(si.grand_total) + is_unpaid = flt(si.outstanding_amount) == flt(si.grand_total) + + # Check: did VAT go to 521.3 directly (wrong for kassa) or 245.1 (correct)? + gl_vat_direct = frappe.db.sql(""" + SELECT SUM(credit) as total + FROM `tabGL Entry` + WHERE voucher_type = 'Sales Invoice' + AND voucher_no = %s + AND account = %s + AND is_cancelled = 0 + """, (si.name, vat_output), as_dict=True) + + vat_on_521 = flt(gl_vat_direct[0].total) if gl_vat_direct and gl_vat_direct[0].total else 0 + + gl_vat_deferred = frappe.db.sql(""" + SELECT SUM(credit) as total + FROM `tabGL Entry` + WHERE voucher_type = 'Sales Invoice' + AND voucher_no = %s + AND account = %s + AND is_cancelled = 0 + """, (si.name, deferred_vat_sales), as_dict=True) + + vat_on_245 = flt(gl_vat_deferred[0].total) if gl_vat_deferred and gl_vat_deferred[0].total else 0 + + if vat_on_521 > 0 and is_unpaid: + # WRONG: VAT on 521.3 but invoice not paid + unpaid_wrong += 1 + total_wrong_vat += vat_on_521 + results["issues"].append({ + "type": "error", + "invoice": si.name, + "message": (f"VAT {vat_on_521:,.2f} AZN posted to 521.3 (liability), " + f"but invoice is unpaid. Under cash method, VAT should only be " + f"recognized when payment is received."), + "amount": vat_on_521, + "fix": f"Move {vat_on_521:,.2f} from 521.3 to 245.1", + }) + elif vat_on_521 > 0 and is_partial: + # PARTIAL: some VAT on 521.3 but invoice partially paid + paid_ratio = 1 - (flt(si.outstanding_amount) / flt(si.grand_total)) + expected_vat = flt(vat_amount * paid_ratio, 2) + excess = flt(vat_on_521 - expected_vat, 2) + if excess > 0.01: + partial_issues += 1 + total_wrong_vat += excess + results["issues"].append({ + "type": "warning", + "invoice": si.name, + "message": (f"VAT {vat_on_521:,.2f} AZN on 521.3, but only " + f"{paid_ratio*100:.0f}% paid. Expected VAT: {expected_vat:,.2f}. " + f"Excess: {excess:,.2f} AZN."), + "amount": excess, + "fix": f"Move {excess:,.2f} from 521.3 to 245.1", + }) + else: + paid_ok += 1 + results["ok"].append(f"{si.name}: VAT correct ({paid_ratio*100:.0f}% paid)") + elif vat_on_521 > 0 and is_paid: + # OK: VAT on 521.3 and invoice is paid + paid_ok += 1 + results["ok"].append(f"{si.name}: VAT correct (fully paid, {vat_on_521:,.2f} AZN)") + elif vat_on_245 > 0: + # CORRECT: VAT on 245.1 (deferred) + paid_ok += 1 + results["ok"].append(f"{si.name}: VAT correct on 245.1 (deferred, {vat_on_245:,.2f} AZN)") + + results["summary"] = { + "total_invoices": total_invoices, + "correct": paid_ok, + "errors": unpaid_wrong, + "warnings": partial_issues, + "total_wrong_vat": total_wrong_vat, + } + + return results + + +@frappe.whitelist() +def fix_cash_method_entries(name): + """Fix Sales Invoices IN-PLACE for cash method. + Directly updates GL Entry accounts and Invoice tax/item accounts. + No new documents created. Same invoices, correct GL.""" + doc = frappe.get_doc("Tax Period Closing Wizard", name) + doc.check_permission("write") + + company = doc.company + accounting_method = frappe.db.get_value("Company", company, "accounting_method") + + if accounting_method != "Kassa metodu": + return {"status": "skip", "message": _("Company does not use cash method.")} + + settings = frappe.get_single("Tax Period Closing Settings") + vat_output = settings.vat_output_account + deferred_vat_sales = settings.deferred_vat_sales_account + deferred_revenue = settings.deferred_revenue_account + + if not deferred_vat_sales or not vat_output: + return {"status": "error", "message": _("Deferred VAT accounts not configured in Settings.")} + + invoices = frappe.get_all("Sales Invoice", filters={ + "company": company, + "posting_date": ["between", [str(doc.from_date), str(doc.to_date)]], + "docstatus": 1, + }, fields=["name", "grand_total", "net_total", "total_taxes_and_charges", + "outstanding_amount", "posting_date"]) + + fixed = [] + skipped = [] + errors = [] + + for si_data in invoices: + vat_amount = flt(si_data.total_taxes_and_charges) + if vat_amount == 0: + skipped.append({"invoice": si_data.name, "reason": _("No VAT")}) + continue + + # Check if already fixed + vat_on_245 = flt(frappe.db.sql(""" + SELECT SUM(credit) FROM `tabGL Entry` + WHERE voucher_type='Sales Invoice' AND voucher_no=%s + AND account=%s AND is_cancelled=0 + """, (si_data.name, deferred_vat_sales))[0][0] or 0) + + if vat_on_245 > 0: + skipped.append({"invoice": si_data.name, "reason": _("Already fixed")}) + continue + + try: + changes = [] + + # 1. Fix GL Entry: 521.3 → 245.1 (VAT) + vat_updated = frappe.db.sql(""" + UPDATE `tabGL Entry` + SET account = %s + WHERE voucher_type = 'Sales Invoice' AND voucher_no = %s + AND account = %s AND is_cancelled = 0 + """, (deferred_vat_sales, si_data.name, vat_output)) + vat_rows = frappe.db.sql("SELECT ROW_COUNT()")[0][0] + if vat_rows: + changes.append(f"GL: {vat_output} → {deferred_vat_sales} ({vat_rows} rows)") + + # 2. Fix GL Entry: 601 → 542 (Revenue) + rev_rows = 0 + if deferred_revenue: + # Find which income account this invoice uses + income_accounts = frappe.db.sql(""" + SELECT DISTINCT account FROM `tabGL Entry` + WHERE voucher_type = 'Sales Invoice' AND voucher_no = %s + AND is_cancelled = 0 AND credit > 0 + AND account NOT LIKE '521%%' AND account NOT LIKE '245%%' + """, si_data.name, as_dict=True) + + for acc in income_accounts: + frappe.db.sql(""" + UPDATE `tabGL Entry` + SET account = %s + WHERE voucher_type = 'Sales Invoice' AND voucher_no = %s + AND account = %s AND is_cancelled = 0 + """, (deferred_revenue, si_data.name, acc.account)) + rows = frappe.db.sql("SELECT ROW_COUNT()")[0][0] + if rows: + rev_rows += rows + changes.append(f"GL: {acc.account} → {deferred_revenue} ({rows} rows)") + + # 3. Update Invoice tax account_head (so re-opening shows correct account) + frappe.db.sql(""" + UPDATE `tabSales Taxes and Charges` + SET account_head = %s + WHERE parent = %s AND account_head = %s + """, (deferred_vat_sales, si_data.name, vat_output)) + + # 4. Update Invoice item income_account + if deferred_revenue: + frappe.db.sql(""" + UPDATE `tabSales Invoice Item` + SET income_account = %s + WHERE parent = %s AND income_account != %s + """, (deferred_revenue, si_data.name, deferred_revenue)) + + fixed.append({ + "invoice": si_data.name, + "vat": vat_amount, + "revenue": flt(si_data.net_total), + "changes": changes, + }) + + except Exception as e: + errors.append({"invoice": si_data.name, "error": str(e)}) + + frappe.db.commit() + + return { + "status": "done", + "fixed": fixed, + "skipped": skipped, + "errors": errors, + "summary": { + "invoices_fixed": len(fixed), + "total_vat_moved": sum(f["vat"] for f in fixed), + "total_revenue_deferred": sum(f["revenue"] for f in fixed), + "skipped": len(skipped), + "errors": len(errors), + } + } diff --git a/taxes_az/taxes_az/doctype/tax_rate_row/__init__.py b/taxes_az/taxes_az/doctype/tax_rate_row/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/tax_rate_row/tax_rate_row.json b/taxes_az/taxes_az/doctype/tax_rate_row/tax_rate_row.json new file mode 100644 index 0000000..a60a3b1 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_rate_row/tax_rate_row.json @@ -0,0 +1,56 @@ +{ + "actions": [], + "creation": "2026-04-01 12:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "tax_type", + "column_break_1", + "rate", + "column_break_2", + "description" + ], + "fields": [ + { + "fieldname": "tax_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Tax Type", + "options": "VAT (ƏDV)\nProfit Tax (Mənfəət)\nSimplified Tax - Trade Baku (Sadələşdirilmiş - Ticarət Bakı)\nSimplified Tax - Catering (Sadələşdirilmiş - İaşə)\nSimplified Tax - Other (Sadələşdirilmiş - Digər)\nProperty Tax (Əmlak)\nUnemployment Insurance Employer (İşsizlik - İşəgötürən)\nUnemployment Insurance Employee (İşsizlik - İşçi)\nDSMF Employer (DSMF - İşəgötürən)\nDSMF Employee (DSMF - İşçi)\nMedical Insurance Employer (Tibbi sığorta - İşəgötürən)\nMedical Insurance Employee (Tibbi sığorta - İşçi)", + "reqd": 1 + }, + { + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, + { + "fieldname": "rate", + "fieldtype": "Percent", + "in_list_view": 1, + "label": "Rate (%)", + "reqd": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description" + } + ], + "index_web_pages_for_search": 0, + "istable": 1, + "links": [], + "modified": "2026-04-01 12:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Tax Rate Row", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/taxes_az/taxes_az/doctype/tax_rate_row/tax_rate_row.py b/taxes_az/taxes_az/doctype/tax_rate_row/tax_rate_row.py new file mode 100644 index 0000000..c8ef55e --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_rate_row/tax_rate_row.py @@ -0,0 +1,5 @@ +from frappe.model.document import Document + + +class TaxRateRow(Document): + pass diff --git a/taxes_az/taxes_az/doctype/tax_year_closing_step/__init__.py b/taxes_az/taxes_az/doctype/tax_year_closing_step/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/tax_year_closing_step/tax_year_closing_step.json b/taxes_az/taxes_az/doctype/tax_year_closing_step/tax_year_closing_step.json new file mode 100644 index 0000000..9d69954 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_year_closing_step/tax_year_closing_step.json @@ -0,0 +1,149 @@ +{ + "actions": [], + "creation": "2026-04-01 20:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "step_order", + "step_type", + "period_label", + "column_break_1", + "month_start", + "month_end", + "quarter", + "column_break_2", + "taxes_included", + "column_break_3", + "status", + "tax_amount", + "journal_entries", + "column_break_4", + "deadline_declaration", + "deadline_payment", + "completed_date", + "details" + ], + "fields": [ + { + "fieldname": "step_order", + "fieldtype": "Int", + "in_list_view": 1, + "label": "#", + "read_only": 1 + }, + { + "fieldname": "step_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "options": "Monthly\nQuarterly\nAnnual", + "read_only": 1 + }, + { + "fieldname": "period_label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Period", + "read_only": 1 + }, + { + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, + { + "fieldname": "month_start", + "fieldtype": "Date", + "label": "From", + "read_only": 1 + }, + { + "fieldname": "month_end", + "fieldtype": "Date", + "label": "To", + "read_only": 1 + }, + { + "fieldname": "quarter", + "fieldtype": "Data", + "label": "Quarter", + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "taxes_included", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Taxes", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Pending\nActive\nCalculated\nCompleted\nSkipped", + "read_only": 1 + }, + { + "fieldname": "tax_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Tax Amount", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "journal_entries", + "fieldtype": "Small Text", + "label": "Journal Entries", + "read_only": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "deadline_declaration", + "fieldtype": "Date", + "label": "Declaration Deadline", + "read_only": 1 + }, + { + "fieldname": "deadline_payment", + "fieldtype": "Date", + "label": "Payment Deadline", + "read_only": 1 + }, + { + "fieldname": "completed_date", + "fieldtype": "Date", + "label": "Completed Date", + "read_only": 1 + }, + { + "fieldname": "details", + "fieldtype": "Small Text", + "label": "Details", + "read_only": 1 + } + ], + "index_web_pages_for_search": 0, + "istable": 1, + "links": [], + "modified": "2026-04-01 20:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Tax Year Closing Step", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/taxes_az/taxes_az/doctype/tax_year_closing_step/tax_year_closing_step.py b/taxes_az/taxes_az/doctype/tax_year_closing_step/tax_year_closing_step.py new file mode 100644 index 0000000..d4a21b4 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_year_closing_step/tax_year_closing_step.py @@ -0,0 +1,5 @@ +from frappe.model.document import Document + + +class TaxYearClosingStep(Document): + pass diff --git a/taxes_az/taxes_az/doctype/tax_year_closing_wizard/__init__.py b/taxes_az/taxes_az/doctype/tax_year_closing_wizard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/doctype/tax_year_closing_wizard/tax_year_closing_wizard.js b/taxes_az/taxes_az/doctype/tax_year_closing_wizard/tax_year_closing_wizard.js new file mode 100644 index 0000000..3b4ca60 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_year_closing_wizard/tax_year_closing_wizard.js @@ -0,0 +1,956 @@ +frappe.ui.form.on("Tax Year Closing Wizard", { + onload(frm) { + // Full width + $(``).appendTo("head"); + }, + + refresh(frm) { + frm.trigger("setup_buttons"); + frm.trigger("render_timeline"); + frm.trigger("render_current_step"); + frm.trigger("render_benefits"); + frm.trigger("render_summary"); + if (frm.doc.company && frm.doc.docstatus === 0) { + frm.trigger("check_accounts"); + } + }, + + check_accounts(frm) { + frappe.call({ + method: "taxes_az.setup_accounts.check_required_accounts", + args: { company: frm.doc.company }, + callback(r) { + if (!r || !r.message) return; + const data = r.message; + if (!data.needs_kassa_accounts || data.missing.length === 0) return; + + let msg = `

${__("Required accounts for cash method not found:")}

`; + + frappe.msgprint({ + title: __("Accounts not found"), + message: msg, + indicator: "orange", + primary_action: { + label: __("Create accounts"), + action() { + frappe.call({ + method: "taxes_az.setup_accounts.create_missing_accounts", + args: { company: frm.doc.company }, + freeze: true, + callback(r2) { + if (r2 && r2.message && r2.message.created.length > 0) { + frappe.show_alert({ + message: __("{0} accounts created", [r2.message.created.length]), + indicator: "green" + }); + } + frappe.hide_msgprint(); + } + }); + } + } + }); + } + }); + }, + + setup_buttons(frm) { + if (frm.doc.docstatus >= 1) return; + + if (!frm.doc.steps || frm.doc.steps.length === 0) { + frm.add_custom_button(__("Generate Timeline"), () => { + frm.save().then(() => { + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_year_closing_wizard.tax_year_closing_wizard.initialize_wizard", + args: { name: frm.doc.name }, + freeze: true, + freeze_message: __("Generating timeline..."), + callback() { window.location.reload(); } + }); + }); + }).addClass("btn-primary"); + } + + if ((frm.doc.steps || []).length > 0 && frm.doc.overall_status !== "Completed") { + frm.add_custom_button(__("Close all periods"), () => { + // First check SI accounts + check_and_fix_before_closing(frm, () => { + run_all_steps_sequentially(frm); + }); + }).addClass("btn-primary").css({"font-weight": "bold"}); + } + + if ((frm.doc.steps || []).length > 0) { + frm.add_custom_button(__("FIFO Allocate All"), () => { + frappe.confirm( + __("Run FIFO allocation for ALL months? This will recognize revenue and VAT for all received payments."), + () => { + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_year_closing_wizard.tax_year_closing_wizard.allocate_all", + args: { name: frm.doc.name }, + freeze: true, + freeze_message: __("Running FIFO allocation for all periods..."), + callback(r) { + if (r && r.message) { + const res = r.message; + if (res.total_jes === 0) { + frappe.msgprint({ + title: __("FIFO Allocation"), + message: __("FIFO allocation already done for all periods. No new allocations."), + indicator: "orange", + }); + } else { + frappe.msgprint({ + title: __("FIFO Allocation — All periods"), + message: `
+

${res.total_jes} ${__("Journal Entries created")}

+

${__("VAT recognition:")} ${format_azn(res.total_vat)} (245.1 → 521.3)

+

${__("Revenue recognition:")} ${format_azn(res.total_revenue)} (542 → 601)

+

${__("Periods:")} ${res.periods_processed}

+
`, + indicator: "green", + wide: true, + }); + } + } + window.location.reload(); + } + }); + } + ); + }).addClass("btn-primary"); + } + }, + + // ── Timeline Stepper ───────────────────────────────── + + render_timeline(frm) { + const steps = frm.doc.steps || []; + if (!steps.length) { + frm.fields_dict.timeline_html && frm.fields_dict.timeline_html.$wrapper.html( + `
${__('Click "Generate Timeline" to create the year plan.')}
` + ); + return; + } + + const cfg = { + "Completed": { bg: "#d4edda", border: "#28a745", icon: "✓", color: "#155724" }, + "Skipped": { bg: "#e2e3e5", border: "#6c757d", icon: "—", color: "#383d41" }, + "Active": { bg: "#fff3cd", border: "#ffc107", icon: "◉", color: "#856404" }, + "Calculated":{ bg: "#cce5ff", border: "#007bff", icon: "◉", color: "#004085" }, + "Pending": { bg: "#f8f9fa", border: "#dee2e6", icon: "○", color: "#6c757d" }, + }; + + let html = '
'; + + for (const step of steps) { + const c = cfg[step.status] || cfg["Pending"]; + const is_active = step.status === "Active" || step.status === "Calculated"; + const is_quarterly = step.step_type === "Quarterly"; + const is_annual = step.step_type === "Annual"; + const indent = is_quarterly ? "margin-left:20px;" : (is_annual ? "margin-left:0;border-width:2px;" : ""); + const font_weight = (is_quarterly || is_annual) ? "font-weight:bold;" : ""; + + const amount_html = flt(step.tax_amount) > 0 + ? `${format_azn(step.tax_amount)}` + : (step.status === "Completed" ? '0.00 AZN' : ""); + + const je_list = (step.journal_entries || "").split(",").map(j => j.trim()).filter(Boolean); + const je_html = je_list.length > 0 + ? je_list.map(j => `${j}`).join(", ") + : ""; + + const deadline_html = step.deadline_payment + ? (() => { + const d = new Date(step.deadline_payment); + const today = new Date(); + const days = Math.ceil((d - today) / 864e5); + const urg = days <= 0 ? "color:#dc3545;font-weight:bold;" : + days <= 14 ? "color:#e67e22;" : "color:#999;"; + return `⏰ ${step.deadline_payment}`; + })() + : ""; + + // Completed summary mini-card + const is_completed = step.status === "Completed"; + let completed_summary = ""; + if (is_completed) { + const details_lines = (step.details || "").split("\n").filter(l => l.trim()); + const details_short = details_lines.map(l => + `
${frappe.utils.escape_html(l)}
` + ).join(""); + + completed_summary = ` +
+
+ ${details_short} + ${je_list.length > 0 ? `
📄 ${je_list.map(j => { + if (j.startsWith("PCV:")) { + const pcv = j.replace("PCV:", ""); + return `📋${pcv}`; + } + return `${j}`; + }).join(", ")}
` : ""} + ${step.completed_date ? `
✓ ${step.completed_date}
` : ""} +
+ +
`; + } + + html += ` +
+
+
+ ${c.icon} +
+
+
+
+
+
+ + ${step.period_label} + + ${step.taxes_included || ""} +
+
+ ${amount_html} + ${!is_completed ? deadline_html : ""} +
+
+ ${completed_summary} + ${is_active && step.details ? `
${frappe.utils.escape_html(step.details)}
` : ""} + ${is_active ? `
` + + (step.status === "Active" ? `` : "") + + (step.status === "Active" || step.status === "Calculated" ? `` : "") + + (step.status === "Calculated" ? `` : "") + + (step.status === "Calculated" ? `` : "") + + ` + +
` : ""} + ${step.status === "Skipped" ? `
` : ""} +
+
`; + } + + html += '
'; + html += ``; + + const $wrapper = frm.fields_dict.timeline_html.$wrapper; + $wrapper.html(html); + + // Bind buttons + $wrapper.find(".tycw-calc").on("click", function(e) { + e.stopPropagation(); + call_step_action(frm, "calculate_step", $(this).data("step")); + }); + $wrapper.find(".tycw-confirm").on("click", function(e) { + e.stopPropagation(); + const step_order = $(this).data("step"); + frappe.confirm(__("Create journal entries for this step?"), () => { + call_step_action(frm, "complete_step", step_order); + }); + }); + $wrapper.find(".tycw-recalc").on("click", function(e) { + e.stopPropagation(); + call_step_action(frm, "calculate_step", $(this).data("step")); + }); + $wrapper.find(".tycw-skip").on("click", function(e) { + e.stopPropagation(); + const step_order = $(this).data("step"); + frappe.confirm(__("Skip this step? It will be marked as Skipped."), () => { + call_step_action(frm, "skip_step", step_order); + }); + }); + $wrapper.find(".tycw-allocate").on("click", function(e) { + e.stopPropagation(); + show_fifo_allocation(frm, $(this).data("step")); + }); + $wrapper.find(".tycw-report").on("click", function(e) { + e.stopPropagation(); + show_step_monthly_report(frm, $(this).data("step")); + }); + }, + + + // ── Current Step Detail ────────────────────────────── + + render_current_step(frm) { + const steps = frm.doc.steps || []; + const active = steps.find(s => s.status === "Active" || s.status === "Calculated"); + + if (!active) { + frm.fields_dict.current_step_html && frm.fields_dict.current_step_html.$wrapper.html( + frm.doc.overall_status === "Completed" + ? `
✓ ${__("All steps completed!")}
` + : `
${__("No active step. Go to Timeline tab.")}
` + ); + return; + } + + let html = ` +
+

◉ ${frappe.utils.escape_html(active.period_label)}

+
+
${__("Type:")} ${active.step_type}
+
${__("Period:")} ${active.month_start} — ${active.month_end}
+
${__("Taxes:")} ${frappe.utils.escape_html(active.taxes_included || "")}
+
${__("Declaration:")} ${active.deadline_declaration || "—"}
+
${__("Payment:")} ${active.deadline_payment || "—"}
+
+ `; + + if (active.details) { + html += `
+ ${frappe.utils.escape_html(active.details)}
`; + } + + if (flt(active.tax_amount) > 0 || active.status === "Calculated") { + html += `
+ ${__("Tax:")} ${format_azn(active.tax_amount)}
`; + } + + html += `
`; + frm.fields_dict.current_step_html && frm.fields_dict.current_step_html.$wrapper.html(html); + }, + + // ── Benefits ───────────────────────────────────────── + + render_benefits(frm) { + const benefits = frm.doc.detected_benefits || []; + if (!benefits.length || !frm.fields_dict.benefits_html) return; + + let html = '
'; + for (const b of benefits) { + html += ` +
+
✓ ${frappe.utils.escape_html(b.article)} — ${frappe.utils.escape_html(b.benefit_name)}
+
${frappe.utils.escape_html(b.condition_details || "")}
+ ${b.law_reference ? `
📖 ${b.law_url ? `${frappe.utils.escape_html(b.law_reference)}` : frappe.utils.escape_html(b.law_reference)}
` : ""} +
`; + } + html += '
'; + frm.fields_dict.benefits_html.$wrapper.html(html); + }, + + // ── Summary ────────────────────────────────────────── + + render_summary(frm) { + const steps = frm.doc.steps || []; + if (!steps.length || !frm.fields_dict.summary_html) return; + + const completed = steps.filter(s => s.status === "Completed"); + const skipped = steps.filter(s => s.status === "Skipped"); + const pending = steps.filter(s => s.status === "Pending" || s.status === "Active"); + + let html = ` +
+
+
${completed.length}
+
${__("Completed")}
+
+
+
${skipped.length}
+
${__("Skipped")}
+
+
+
${pending.length}
+
${__("Pending")}
+
+
+ `; + + if (completed.length > 0) { + html += ` + + + `; + for (const s of completed) { + const jes = (s.journal_entries || "").split(",").map(j => { + j = j.trim(); + return j ? `${j}` : ""; + }).filter(Boolean).join(", "); + html += ` + + + + + + `; + } + html += `
${__("Period")}${__("Taxes")}${__("Amount")}${__("JE")}${__("Date")}
${frappe.utils.escape_html(s.period_label)}${frappe.utils.escape_html(s.taxes_included || "")}${format_azn(s.tax_amount)}${jes}${s.completed_date || ""}
`; + } + + frm.fields_dict.summary_html.$wrapper.html(html); + } +}); + +function format_azn(val) { + return (parseFloat(val) || 0).toLocaleString("az-AZ", { + minimumFractionDigits: 2, maximumFractionDigits: 2 + }) + " AZN"; +} + +function flt(val) { + return parseFloat(val) || 0; +} + +function show_step_monthly_report(frm, step_order) { + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_year_closing_wizard.tax_year_closing_wizard.get_step_report", + args: { name: frm.doc.name, step_order: step_order }, + freeze: true, + freeze_message: __("Loading report..."), + callback(r) { + if (!r || !r.message) return; + const d = r.message; + const f = (v) => (parseFloat(v) || 0).toLocaleString("az-AZ", {minimumFractionDigits:2, maximumFractionDigits:2}); + + let html = `
`; + + // Header + html += `
+
${d.period}
${d.from} — ${d.to}
+
${__("Declaration:")} ${d.deadline_declaration || "—"}
${__("Payment:")} ${d.deadline_payment || "—"}
+
`; + + // Cash flow + html += `
+
+
${__("Cash in (PE+226)")}
+
${f(d.total_cash_in)} AZN
+
+
+
${__("Cash out")}
+
${f(d.payments_made)} AZN
+
+
+
ƏDV (226)
+
${f(d.vat_226_received)} AZN
+
+
`; + + // Income table + if ((d.income || []).length > 0) { + html += `
${__("Income")}
+ `; + for (const i of d.income) { + html += ``; + } + html += ``; + html += `
${__("Account")}${__("Name")}${__("Amount")}
${i.account}${i.name}${f(i.amount)}
${__("Total income")}${f(d.total_income)} AZN
`; + } + + // Expense table + if ((d.expenses || []).length > 0) { + html += `
${__("Expenses")}
+ `; + for (const e of d.expenses) { + html += ``; + } + html += ``; + html += `
${__("Account")}${__("Name")}${__("Amount")}
${e.account}${e.name}${f(e.amount)}
${__("Total expense")}${f(d.total_expense)} AZN
`; + } + + // Profit + const profit_color = d.net_profit >= 0 ? "#28a745" : "#dc3545"; + html += `
+ ${__("Net profit/loss:")} + ${f(d.net_profit)} AZN +
`; + + // VAT + html += `
ƏDV
+ + + +
ƏDV çıxış (521.3)${f(d.vat_output)}
ƏDV daxil (226)${f(d.vat_input)}
Nett ƏDV${f(d.vat_net)}
`; + + // Payroll + if (d.payroll) { + html += `
${__("Payroll")}
+ + + + +
${__("Employee count")}${d.payroll.count}
${__("Gross salary")}${f(d.payroll.gross)}
${__("Deductions")}${f(d.payroll.deductions)}
${__("Net salary")}${f(d.payroll.net)}
`; + } + + // FIFO status + if ((d.fifo_entries || []).length > 0) { + html += `
FIFO Allocations
`; + for (const j of d.fifo_entries) { + html += `${j.name} (${f(j.amount)}) `; + } + } else { + html += `
+ ${__("FIFO allocation not done yet. Click the FIFO Allocate button.")}
`; + } + + // Step JEs + PCV + if ((d.step_jes || []).length > 0) { + html += `
${__("Created documents")}
`; + for (const j of d.step_jes) { + if (j.startsWith("PCV:")) { + const pcv_name = j.replace("PCV:", ""); + html += `
📋 ${pcv_name} (Period Closing Voucher)
`; + } else { + html += `${j} `; + } + } + } + + html += `
`; + + new frappe.ui.Dialog({ + title: `📊 ${d.period} — ${__("Report")}`, + size: "extra-large", + fields: [{ fieldtype: "HTML", options: html }], + secondary_action_label: __("Close"), + }).show(); + } + }); +} + +function show_fifo_allocation(frm, step_order) { + // First preview + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_year_closing_wizard.tax_year_closing_wizard.preview_allocation", + args: { name: frm.doc.name, step_order: step_order }, + freeze: true, + freeze_message: __("Analyzing FIFO allocation..."), + callback(r) { + if (!r || !r.message) return; + const data = r.message; + + if (data.error) { + frappe.msgprint({ message: data.error, indicator: "red" }); + return; + } + + const allocs = data.allocations || []; + let total_vat = 0, total_rev = 0; + allocs.forEach(a => { total_vat += a.vat_transfer; total_rev += a.revenue_transfer; }); + + let html = `
`; + + // Summary + html += `
+
+
${allocs.length}
+
${__("Allocation")}
+
+
+
${format_azn(total_vat)}
+
ƏDV → 521.3
+
+
+
${format_azn(total_rev)}
+
Gəlir → 601
+
+
`; + + // Allocation table + if (allocs.length > 0) { + html += ` + + + + + `; + for (const a of allocs) { + html += ` + + + + + + + + `; + } + html += `
${__("Invoice")}${__("Customer")}${__("Payment date")}${__("Allocation")}%ƏDVGəlir
${a.invoice}${(a.customer || "").substring(0,25)}${a.payment_date}${format_azn(a.allocated)}${(a.ratio*100).toFixed(0)}%${format_azn(a.vat_transfer)}${format_azn(a.revenue_transfer)}
`; + } else { + html += `
+ ${__("No payments found for allocation in this period.")}
`; + } + + html += `
`; + + const dialog = new frappe.ui.Dialog({ + title: __("FIFO Cash Allocation — Preview"), + size: "extra-large", + fields: [{ fieldtype: "HTML", options: html }], + primary_action_label: allocs.length > 0 ? __("Confirm and create") : null, + primary_action() { + dialog.hide(); + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_year_closing_wizard.tax_year_closing_wizard.execute_allocation", + args: { name: frm.doc.name, step_order: step_order }, + freeze: true, + freeze_message: __("Creating allocation entries..."), + callback(r2) { + if (r2 && r2.message) { + const res = r2.message; + frappe.msgprint({ + title: __("FIFO Allocation completed"), + message: `
+

${res.count} ${__("Journal Entries created")}

+

${__("VAT recognition:")} ${format_azn(res.total_vat)} (245.1 → 521.3)

+

${__("Revenue recognition:")} ${format_azn(res.total_revenue)} (542 → 601)

+ ${(res.created_jes || []).map(j => `${j}`).join(", ")} +
`, + indicator: "green", + wide: true, + }); + } + window.location.reload(); + } + }); + }, + secondary_action_label: __("Close"), + }); + dialog.show(); + } + }); +} + +// ── Check SI Accounts Before Closing ───────────────── + +function check_and_fix_before_closing(frm, on_continue) { + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_year_closing_wizard.tax_year_closing_wizard.check_si_accounts", + args: { name: frm.doc.name }, + callback(r) { + if (!r || !r.message) { on_continue(); return; } + const data = r.message; + + if (!data.needs_fix) { + on_continue(); + return; + } + + // Show fix dialog + const wrong = data.wrong || []; + let html = `
+
+ ${__("{0} Sales Invoices still on old accounts (521.3/601). For cash method they must be moved to 245.1/542.", [wrong.length])} +
+ + + + `; + + for (const w of wrong.slice(0, 20)) { + html += ` + + + + + `; + } + if (wrong.length > 20) { + html += ``; + } + html += `
${__("Invoice")}${__("Date")}ƏDV${__("Total")}
${w.invoice}${w.date}${format_azn(w.vat)}${format_azn(w.total)}
... ${__("and more")} ${wrong.length - 20}
`; + + const dlg = new frappe.ui.Dialog({ + title: __("Invoice account correction required"), + size: "large", + fields: [{ fieldtype: "HTML", options: html }], + primary_action_label: __("Fix and continue"), + primary_action() { + dlg.hide(); + frappe.call({ + method: "taxes_az.taxes_az.doctype.tax_year_closing_wizard.tax_year_closing_wizard.fix_si_accounts", + args: { name: frm.doc.name }, + freeze: true, + freeze_message: __("Fixing invoices..."), + callback(r2) { + if (r2 && r2.message) { + frappe.show_alert({ + message: __("{0} invoices fixed", [r2.message.fixed]), + indicator: "green" + }); + } + on_continue(); + } + }); + }, + secondary_action_label: __("Continue without fixing"), + secondary_action() { + dlg.hide(); + on_continue(); + } + }); + dlg.show(); + } + }); +} + +// ── Run All Steps Sequentially ─────────────────────── + +function run_all_steps_sequentially(frm) { + const steps = (frm.doc.steps || []).filter(s => s.status !== "Completed" && s.status !== "Skipped"); + if (!steps.length) { + frappe.msgprint(__("All steps already completed.")); + return; + } + + // Create progress dialog + const dlg = new frappe.ui.Dialog({ + title: __("Close all periods"), + size: "large", + static: true, + fields: [{ fieldtype: "HTML", fieldname: "progress_area" }], + secondary_action_label: __("Stop"), + secondary_action() { + dlg._stopped = true; + dlg.hide(); + window.location.reload(); + } + }); + dlg._stopped = false; + dlg.show(); + + const $area = dlg.fields_dict.progress_area.$wrapper; + + function render_progress(current_idx, total, current_step, phase, error) { + const pct = Math.round((current_idx / total) * 100); + + let rows = ""; + const all_steps = frm.doc.steps || []; + for (const st of all_steps) { + let icon, bg, text_color; + if (st.status === "Completed") { + icon = "✓"; bg = "#d4edda"; text_color = "#155724"; + } else if (st.status === "Skipped") { + icon = "—"; bg = "#e2e3e5"; text_color = "#383d41"; + } else if (st.step_order === (current_step ? current_step.step_order : -1)) { + if (error) { + icon = "✗"; bg = "#f8d7da"; text_color = "#721c24"; + } else { + icon = "⟳"; bg = "#fff3cd"; text_color = "#856404"; + } + } else { + icon = "○"; bg = "#f8f9fa"; text_color = "#6c757d"; + } + + const is_current = current_step && st.step_order === current_step.step_order; + const phase_text = is_current && phase ? ` — ${phase}` : ""; + const amount_text = st.status === "Completed" && flt(st.tax_amount) > 0 + ? `${format_azn(st.tax_amount)}` : ""; + const error_text = is_current && error + ? `
${error}
` : ""; + + rows += `
+ ${icon} + ${st.period_label}${phase_text} + ${amount_text} +
${error_text}`; + } + + $area.html(` + +
+
+ ${current_step ? current_step.period_label : __("Completed")} + ${pct}% +
+
+
+
+
+
${rows}
+ `); + } + + // Sequential processing + let step_idx = 0; + const pending_steps = steps.slice(); + + async function process_next() { + if (dlg._stopped) return; + if (step_idx >= pending_steps.length) { + // All done! + render_progress(pending_steps.length, pending_steps.length, null, null, null); + $area.prepend(`
+ ✓ ${__("All steps completed successfully!")}
`); + dlg.set_secondary_action_label(__("Close")); + dlg.set_secondary_action(() => { dlg.hide(); window.location.reload(); }); + return; + } + + const step = pending_steps[step_idx]; + const total = pending_steps.length; + + try { + // Phase 1: Calculate + render_progress(step_idx, total, step, __("Calculating..."), null); + await call_step_async("calculate_step", frm.doc.name, step.step_order); + + if (dlg._stopped) return; + + // Phase 2: Complete (includes FIFO for kassa, VAT for hesablama) + render_progress(step_idx, total, step, __("Confirming..."), null); + const result = await call_step_async("complete_step", frm.doc.name, step.step_order); + + // Update local step data + step.status = "Completed"; + if (result && result.step_data) { + step.tax_amount = result.step_data.tax_amount; + step.details = result.step_data.details; + step.journal_entries = result.step_data.journal_entries; + } + + step_idx++; + // Small delay for visual effect + setTimeout(process_next, 300); + + } catch (err) { + const msg = (err && err.message) || String(err) || "Unknown error"; + render_progress(step_idx, total, step, null, msg); + $area.prepend(`
+ ✗ ${__("Error:")} ${step.period_label} — ${frappe.utils.escape_html(msg)}. + ${__("Previous steps have been saved.")}
`); + dlg.set_secondary_action_label(__("Close")); + dlg.set_secondary_action(() => { dlg.hide(); window.location.reload(); }); + } + } + + // Reload doc first to get fresh data + frm.reload_doc().then(() => { + process_next(); + }); +} + +function call_step_async(action, name, step_order) { + return new Promise((resolve, reject) => { + frappe.call({ + method: `taxes_az.taxes_az.doctype.tax_year_closing_wizard.tax_year_closing_wizard.${action}`, + args: { name: name, step_order: step_order }, + async: true, + callback(r) { + if (r && r.message) resolve(r.message); + else resolve({}); + }, + error(err) { + reject(err); + } + }); + }); +} + +function call_step_action(frm, action, step_order) { + const labels = { + "calculate_step": __("Calculating..."), + "complete_step": __("Creating journal entries..."), + "skip_step": __("Skipping..."), + }; + frappe.call({ + method: `taxes_az.taxes_az.doctype.tax_year_closing_wizard.tax_year_closing_wizard.${action}`, + args: { name: frm.doc.name, step_order: step_order }, + freeze: true, + freeze_message: __(labels[action] || "Processing..."), + callback(r) { + if (action === "complete_step" && r && r.message) { + show_step_report(r.message, () => window.location.reload()); + } else { + window.location.reload(); + } + }, + error() { window.location.reload(); } + }); +} + +function show_step_report(data, on_close) { + const s = data.step_data || {}; + const jes = (s.journal_entries || "").split(",").filter(j => j.trim()); + + let html = `
`; + + // Header + html += `
+
✓ ${frappe.utils.escape_html(s.period_label || "")}
+
${__("Step completed successfully")}
+
`; + + // Details + html += ` + + + + + + + + +
${__("Period")}${s.period_label || ""}
${__("Type")}${s.step_type || ""}
${__("Date range")}${s.month_start || ""} — ${s.month_end || ""}
${__("Taxes")}${frappe.utils.escape_html(s.taxes_included || "")}
${__("Declaration deadline")}${s.deadline_declaration || "—"}
${__("Payment deadline")}${s.deadline_payment || "—"}
${__("Tax amount")}${format_azn(s.tax_amount)}
`; + + // Calculation details + if (s.details) { + html += `
+ ${frappe.utils.escape_html(s.details)}
`; + } + + // Journal Entries + if (jes.length > 0) { + html += `
${__("Created documents")}
+ + + + `; + for (const je of jes) { + const name = je.trim(); + html += ` + + + `; + } + html += `
${__("Journal Entry")}${__("Link")}
${name} + ${__("Click to open")} →
`; + } else { + html += `
+ ${__("No journal entries created (amount = 0 or data collection only)")}
`; + } + + // Next step info + if (data.next_step) { + html += `
+ ${__("Next step:")} ${frappe.utils.escape_html(data.next_step)} +
`; + } + + html += `
`; + + const dialog = frappe.msgprint({ + title: __("Step completed"), + message: html, + indicator: "green", + wide: true, + primary_action: { + label: __("Continue"), + action() { dialog.hide(); if (on_close) on_close(); } + } + }); +} diff --git a/taxes_az/taxes_az/doctype/tax_year_closing_wizard/tax_year_closing_wizard.json b/taxes_az/taxes_az/doctype/tax_year_closing_wizard/tax_year_closing_wizard.json new file mode 100644 index 0000000..352bc7d --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_year_closing_wizard/tax_year_closing_wizard.json @@ -0,0 +1,317 @@ +{ + "actions": [], + "autoname": "TYCW-.YYYY.-.#####", + "creation": "2026-04-01 20:00:00.000000", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "header_section", + "company", + "column_break_h1", + "fiscal_year", + "year", + "column_break_h2", + "tax_regime", + "accounting_method", + "column_break_h3", + "overall_status", + "overall_progress", + + "company_info_section", + "company_voen", + "company_classification", + "column_break_ci1", + "company_activity_code", + "company_tax_authority", + "column_break_ci2", + "is_vat_payer", + "has_assets", + "has_employees", + "avg_employees", + + "benefits_section", + "benefits_html", + "detected_benefits", + + "timeline_tab", + "timeline_html", + "steps", + + "step_detail_tab", + "current_step_html", + + "summary_tab", + "summary_html", + "total_taxes_created", + "column_break_st1", + "total_payroll_collected", + "column_break_st2", + "grand_total", + + "log_tab", + "log_html", + "log_entries", + + "amended_from" + ], + "fields": [ + { + "fieldname": "header_section", + "fieldtype": "Section Break" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "column_break_h1", + "fieldtype": "Column Break" + }, + { + "fieldname": "fiscal_year", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Fiscal Year", + "options": "Fiscal Year", + "reqd": 1 + }, + { + "fieldname": "year", + "fieldtype": "Int", + "label": "Year", + "read_only": 1 + }, + { + "fieldname": "column_break_h2", + "fieldtype": "Column Break" + }, + { + "fieldname": "tax_regime", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Tax Regime", + "options": "\nGeneral (VAT + Profit)\nSimplified Tax\nSpecial Regime" + }, + { + "fieldname": "accounting_method", + "fieldtype": "Data", + "label": "Accounting Method", + "read_only": 1 + }, + { + "fieldname": "column_break_h3", + "fieldtype": "Column Break" + }, + { + "default": "Not Started", + "fieldname": "overall_status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Not Started\nIn Progress\nCompleted", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "overall_progress", + "fieldtype": "Percent", + "label": "Progress", + "read_only": 1 + }, + { + "fieldname": "company_info_section", + "fieldtype": "Section Break", + "collapsible": 1, + "label": "Company Info" + }, + { + "fieldname": "company_voen", + "fieldtype": "Data", + "label": "VOEN", + "read_only": 1 + }, + { + "fieldname": "company_classification", + "fieldtype": "Data", + "label": "Classification", + "read_only": 1 + }, + { + "fieldname": "column_break_ci1", + "fieldtype": "Column Break" + }, + { + "fieldname": "company_activity_code", + "fieldtype": "Data", + "label": "OKVED", + "read_only": 1 + }, + { + "fieldname": "company_tax_authority", + "fieldtype": "Data", + "label": "Tax Authority", + "read_only": 1 + }, + { + "fieldname": "column_break_ci2", + "fieldtype": "Column Break" + }, + { + "fieldname": "is_vat_payer", + "fieldtype": "Check", + "label": "VAT Payer", + "read_only": 1 + }, + { + "fieldname": "has_assets", + "fieldtype": "Check", + "label": "Has Assets", + "read_only": 1 + }, + { + "fieldname": "has_employees", + "fieldtype": "Check", + "label": "Has Employees", + "read_only": 1 + }, + { + "fieldname": "avg_employees", + "fieldtype": "Float", + "label": "Avg Monthly Employees", + "read_only": 1 + }, + { + "fieldname": "benefits_section", + "fieldtype": "Section Break", + "collapsible": 1, + "label": "Detected Benefits" + }, + { + "fieldname": "benefits_html", + "fieldtype": "HTML" + }, + { + "fieldname": "detected_benefits", + "fieldtype": "Table", + "options": "Tax Closing Benefit", + "hidden": 1 + }, + { + "fieldname": "timeline_tab", + "fieldtype": "Tab Break", + "label": "Timeline" + }, + { + "fieldname": "timeline_html", + "fieldtype": "HTML" + }, + { + "fieldname": "steps", + "fieldtype": "Table", + "options": "Tax Year Closing Step", + "hidden": 1 + }, + { + "fieldname": "step_detail_tab", + "fieldtype": "Tab Break", + "label": "Current Step" + }, + { + "fieldname": "current_step_html", + "fieldtype": "HTML" + }, + { + "fieldname": "summary_tab", + "fieldtype": "Tab Break", + "label": "Summary" + }, + { + "fieldname": "summary_html", + "fieldtype": "HTML" + }, + { + "fieldname": "total_taxes_created", + "fieldtype": "Currency", + "label": "Total Taxes (Entries Created)", + "precision": "2", + "read_only": 1, + "bold": 1 + }, + { + "fieldname": "column_break_st1", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_payroll_collected", + "fieldtype": "Currency", + "label": "Total Payroll (Data Only)", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "column_break_st2", + "fieldtype": "Column Break" + }, + { + "fieldname": "grand_total", + "fieldtype": "Currency", + "label": "Grand Total Tax Burden", + "precision": "2", + "read_only": 1, + "bold": 1 + }, + { + "fieldname": "log_tab", + "fieldtype": "Tab Break", + "label": "Log" + }, + { + "fieldname": "log_html", + "fieldtype": "HTML" + }, + { + "fieldname": "log_entries", + "fieldtype": "Table", + "options": "Tax Closing Log Entry", + "hidden": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Tax Year Closing Wizard", + "print_hide": 1, + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2026-04-01 20:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Tax Year Closing Wizard", + "naming_rule": "Expression", + "owner": "Administrator", + "permissions": [ + { + "cancel": 1, "create": 1, "delete": 1, "email": 1, "export": 1, + "print": 1, "read": 1, "report": 1, "role": "System Manager", + "share": 1, "submit": 1, "write": 1 + }, + { + "cancel": 1, "create": 1, "delete": 1, "email": 1, "export": 1, + "print": 1, "read": 1, "report": 1, "role": "Accounts Manager", + "share": 1, "submit": 1, "write": 1 + } + ], + "sort_field": "creation", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} diff --git a/taxes_az/taxes_az/doctype/tax_year_closing_wizard/tax_year_closing_wizard.py b/taxes_az/taxes_az/doctype/tax_year_closing_wizard/tax_year_closing_wizard.py new file mode 100644 index 0000000..e257c66 --- /dev/null +++ b/taxes_az/taxes_az/doctype/tax_year_closing_wizard/tax_year_closing_wizard.py @@ -0,0 +1,1345 @@ +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import now_datetime, getdate, flt, cint +from datetime import date +from calendar import monthrange + +MONTH_NAMES = [ + "Yanvar", "Fevral", "Mart", "Aprel", "May", "İyun", + "İyul", "Avqust", "Sentyabr", "Oktyabr", "Noyabr", "Dekabr" +] + +QUARTER_NAMES = ["Q1", "Q2", "Q3", "Q4"] +QUARTER_MONTHS = {1: (1, 3), 2: (4, 6), 3: (7, 9), 4: (10, 12)} + + +class TaxYearClosingWizard(Document): + + def validate(self): + if self.fiscal_year: + fy = frappe.get_doc("Fiscal Year", self.fiscal_year) + self.year = getdate(fy.year_start_date).year + + def before_submit(self): + if self.overall_status != "Completed": + frappe.throw(_("Complete all steps before submitting.")) + + def on_cancel(self): + self._cancel_all_step_entries() + + def _cancel_all_step_entries(self): + for step in self.steps or []: + for je_name in (step.journal_entries or "").split(","): + je_name = je_name.strip() + if je_name and frappe.db.exists("Journal Entry", je_name): + je = frappe.get_doc("Journal Entry", je_name) + if je.docstatus == 1: + je.cancel() + + def add_log(self, phase, level, message): + self.append("log_entries", { + "timestamp": now_datetime(), + "phase": phase, + "level": level, + "message": message, + }) + + # ── Step Generation ────────────────────────────────────── + + def generate_steps(self): + """Generate all timeline steps for the year based on tax regime.""" + self.steps = [] + self._detect_company() + self._detect_benefits_for_year() + + year = cint(self.year) + regime = self.tax_regime or "General (VAT + Profit)" + step_order = 0 + + for month in range(1, 13): + step_order += 1 + taxes = [] + + # Monthly taxes + if regime == "General (VAT + Profit)" and self.is_vat_payer: + taxes.append("ƏDV") + if self.has_employees: + taxes.append("Payroll (data)") + + m_start = date(year, month, 1) + m_end = date(year, month, monthrange(year, month)[1]) + next_m = month + 1 if month < 12 else 1 + next_y = year if month < 12 else year + 1 + decl_deadline = date(next_y, next_m, min(20, monthrange(next_y, next_m)[1])) + + self.append("steps", { + "step_order": step_order, + "step_type": "Monthly", + "period_label": f"{MONTH_NAMES[month-1]} {year}", + "month_start": str(m_start), + "month_end": str(m_end), + "quarter": f"Q{(month-1)//3 + 1}", + "taxes_included": ", ".join(taxes) if taxes else "—", + "status": "Pending", + "deadline_declaration": str(decl_deadline), + "deadline_payment": str(decl_deadline), + }) + + # Quarterly step after months 3, 6, 9, 12 + if month in (3, 6, 9, 12): + q_num = month // 3 + step_order += 1 + q_taxes = [] + + if regime == "General (VAT + Profit)": + q_taxes.append(_("Profit Tax (advance)")) + elif regime in ("Simplified Tax", "Special Regime"): + q_taxes.append(_("Simplified Tax")) + + if self.has_assets: + q_taxes.append(_("Property Tax (1/4)")) + + q_start_m = QUARTER_MONTHS[q_num][0] + q_end_m = QUARTER_MONTHS[q_num][1] + q_next_m = q_end_m + 1 if q_end_m < 12 else 1 + q_next_y = year if q_end_m < 12 else year + 1 + q_deadline = date(q_next_y, q_next_m, min(15, monthrange(q_next_y, q_next_m)[1])) + + self.append("steps", { + "step_order": step_order, + "step_type": "Quarterly", + "period_label": f"{QUARTER_NAMES[q_num-1]} {year}", + "month_start": str(date(year, q_start_m, 1)), + "month_end": str(date(year, q_end_m, monthrange(year, q_end_m)[1])), + "quarter": f"Q{q_num}", + "taxes_included": ", ".join(q_taxes) if q_taxes else "—", + "status": "Pending", + "deadline_declaration": str(q_deadline) if regime != "General (VAT + Profit)" else str(date(year + 1, 3, 31)), + "deadline_payment": str(q_deadline), + }) + + # Annual closing step + step_order += 1 + annual_taxes = [] + if regime == "General (VAT + Profit)": + annual_taxes.append(_("Profit Tax (annual)")) + if self.has_assets: + annual_taxes.append(_("Property Tax (annual)")) + annual_taxes.append("Period Closing") + + self.append("steps", { + "step_order": step_order, + "step_type": "Annual", + "period_label": _("Annual Closing {0}").format(year), + "month_start": str(date(year, 1, 1)), + "month_end": str(date(year, 12, 31)), + "quarter": "", + "taxes_included": ", ".join(annual_taxes), + "status": "Pending", + "deadline_declaration": str(date(year + 1, 3, 31)), + "deadline_payment": str(date(year + 1, 3, 31)), + }) + + # Set first step as Active + if self.steps: + self.steps[0].status = "Active" + + self.overall_status = "Not Started" + self.add_log("Setup", "Success", + f"Generated {len(self.steps)} steps for {year}") + + def _detect_company(self): + company = frappe.get_doc("Company", self.company) + self.company_voen = company.tax_id or "" + self.accounting_method = getattr(company, "accounting_method", "") or "" + self.company_classification = getattr(company, "business_classification", "") or "" + self.company_activity_code = getattr(company, "main_activity", "") or "" + self.company_tax_authority = getattr(company, "tax_authority", "") or "" + self.is_vat_payer = 1 if getattr(company, "vat_certificate_number", None) else 0 + self.has_assets = 1 if frappe.db.count("Asset", { + "company": self.company, "docstatus": 1, + "status": ["not in", ["Scrapped", "Sold"]]}) else 0 + self.has_employees = 1 if frappe.db.count("Employee", { + "company": self.company, "status": "Active"}) else 0 + + # Avg monthly employees + year = cint(self.year) + monthly = frappe.db.sql(""" + SELECT COUNT(DISTINCT employee) as cnt + FROM `tabSalary Slip` + WHERE company=%s AND docstatus=1 + AND posting_date BETWEEN %s AND %s + """, (self.company, f"{year}-01-01", f"{year}-12-31"), as_dict=True) + self.avg_employees = monthly[0].cnt if monthly else 0 + + # Auto-detect regime + if not self.tax_regime: + settings = frappe.get_single("Tax Period Closing Settings") + if settings.auto_detect_regime: + self.tax_regime = "General (VAT + Profit)" if self.is_vat_payer else "Simplified Tax" + else: + self.tax_regime = settings.default_tax_regime or "General (VAT + Profit)" + + def _detect_benefits_for_year(self): + """Reuse benefit detection from Tax Period Closing Wizard.""" + self.detected_benefits = [] + classification = self.company_classification or "" + is_micro = "Micro" in classification + + if is_micro and self.avg_employees >= 3: + self.append("detected_benefits", { + "enabled": 1, + "article": "Maddə 106.1.20", + "benefit_name": "Mikro sahibkar — 75% mənfəət azadolması", + "applies_to": "Profit Tax (Mənfəət)", + "exemption_percent": 75, + "condition_met": "Yes", + "condition_details": f"Orta aylıq işçi sayı: {self.avg_employees} (≥3). DSMF borcu: əl ilə yoxlanmalıdır.", + "law_reference": "Vergi Məcəlləsi, Maddə 106.1.20", + "law_url": "https://e-qanun.az/framework/46947", + }) + + if is_micro: + self.append("detected_benefits", { + "enabled": 1, + "article": "DSMF Micro", + "benefit_name": "Mikro — DSMF 15% (22% əvəzinə)", + "applies_to": "DSMF", + "exemption_percent": 32, + "condition_met": "Yes", + "condition_details": "Mikro sahibkar DSMF dərəcəsi 15%. Payroll-da tətbiq olunur.", + "law_reference": "Sosial sığorta haqqında Qanun", + "law_url": "https://e-qanun.az/framework/46854", + }) + + # ── Step Execution ─────────────────────────────────────── + + def get_active_step(self): + for step in self.steps or []: + if step.status == "Active": + return step + return None + + def get_step_by_order(self, order): + for step in self.steps or []: + if step.step_order == order: + return step + return None + + def calculate_step(self, step_order): + """Calculate taxes for a specific step.""" + step = self.get_step_by_order(step_order) + if not step: + frappe.throw(_("Step not found.")) + + # Check blocking + if step.step_order > 1: + prev = self.get_step_by_order(step.step_order - 1) + if prev and prev.status not in ("Completed", "Skipped"): + frappe.throw( + _("Previous step ({0}) is not completed. Complete or skip it first.").format( + prev.period_label)) + + step.status = "Active" + settings = frappe.get_single("Tax Period Closing Settings") + from_d = str(step.month_start) + to_d = str(step.month_end) + total_tax = 0 + details_parts = [] + + if step.step_type == "Monthly": + total_tax, details_parts = self._calc_monthly_step(step, settings, from_d, to_d) + elif step.step_type == "Quarterly": + total_tax, details_parts = self._calc_quarterly_step(step, settings, from_d, to_d) + elif step.step_type == "Annual": + total_tax, details_parts = self._calc_annual_step(step, settings, from_d, to_d) + + step.tax_amount = total_tax + step.details = "\n".join(details_parts) + step.status = "Calculated" + + self.add_log("Calculation", "Success", + f"Step {step.step_order} ({step.period_label}): {total_tax:,.2f} AZN") + + def _calc_monthly_step(self, step, settings, from_d, to_d): + """Calculate monthly taxes (VAT + payroll data).""" + from erpnext.accounts.utils import get_balance_on + total = 0 + details = [] + + # VAT + if "ƏDV" in (step.taxes_included or ""): + vat_in = settings.vat_input_account + vat_out = settings.vat_output_account + if vat_in and vat_out: + output_bal = abs(flt(get_balance_on(vat_out, to_d, company=self.company, start_date=from_d))) + input_bal = abs(flt(get_balance_on(vat_in, to_d, company=self.company, start_date=from_d))) + net_vat = input_bal # amount to offset + total += net_vat + details.append(f"VAT: Output={output_bal:,.2f} | Input={input_bal:,.2f} | Net={output_bal-input_bal:,.2f}") + + # Payroll data + if "Payroll" in (step.taxes_included or ""): + payroll = frappe.db.sql(""" + SELECT SUM(gross_pay) as gross, SUM(total_deduction) as ded + FROM `tabSalary Slip` + WHERE company=%s AND docstatus=1 + AND posting_date BETWEEN %s AND %s + """, (self.company, from_d, to_d), as_dict=True) + if payroll and payroll[0].gross: + details.append(f"Payroll: Gross={flt(payroll[0].gross):,.2f} | Deductions={flt(payroll[0].ded):,.2f}") + + return total, details + + def _calc_quarterly_step(self, step, settings, from_d, to_d): + """Calculate quarterly taxes (profit advance / simplified / property).""" + from erpnext.accounts.utils import get_balance_on + total = 0 + details = [] + + # Profit Tax advance + if "Mənfəət" in (step.taxes_included or ""): + income_accs = frappe.get_all("Account", filters={ + "company": self.company, "root_type": "Income", "is_group": 0 + }, pluck="name") + expense_accs = frappe.get_all("Account", filters={ + "company": self.company, "root_type": "Expense", "is_group": 0 + }, pluck="name") + + total_income = sum(abs(flt(get_balance_on(a, to_d, company=self.company, start_date=from_d))) for a in income_accs) + total_expense = sum(abs(flt(get_balance_on(a, to_d, company=self.company, start_date=from_d))) for a in expense_accs) + profit = total_income - total_expense + + rate = flt(settings.profit_tax_rate) or 20 + tax_before = max(0, profit * rate / 100) + + # Apply benefits + exemption_pct = 0 + for b in self.detected_benefits or []: + if b.enabled and b.applies_to == "Profit Tax (Mənfəət)" and b.condition_met == "Yes": + exemption_pct += flt(b.exemption_percent) + exemption_pct = min(exemption_pct, 100) + reduction = tax_before * exemption_pct / 100 + tax = max(0, tax_before - reduction) + + total += tax + details.append(f"Profit: Income={total_income:,.2f} | Expense={total_expense:,.2f} | Profit={profit:,.2f}") + details.append(f" Tax {rate}%={tax_before:,.2f} | Exemption {exemption_pct}%=-{reduction:,.2f} | Final={tax:,.2f}") + + # Simplified Tax + if "Sadələşdirilmiş" in (step.taxes_included or ""): + income_accs = frappe.get_all("Account", filters={ + "company": self.company, "root_type": "Income", "is_group": 0 + }, pluck="name") + revenue = sum(abs(flt(get_balance_on(a, to_d, company=self.company, start_date=from_d))) for a in income_accs) + rate = flt(settings.default_simplified_rate) or 4 + tax = revenue * rate / 100 + total += tax + details.append(f"Simplified: Revenue={revenue:,.2f} × {rate}% = {tax:,.2f}") + + # Property Tax (1/4) + if "Əmlak" in (step.taxes_included or ""): + from taxes_az.taxes_az.doctype.tax_period_closing_wizard.tax_period_closing_wizard import TaxPeriodClosingWizard + assets = frappe.get_all("Asset", filters={ + "company": self.company, "docstatus": 1, + "status": ["not in", ["Scrapped", "Sold"]] + }, fields=["total_asset_cost", "value_after_depreciation", + "asset_category", "taxable_asset_type"]) + + taxable_nbv = 0 + for a in assets: + if (a.asset_category in TaxPeriodClosingWizard.EXEMPT_ASSET_CATEGORIES + or a.taxable_asset_type in TaxPeriodClosingWizard.EXEMPT_ASSET_CATEGORIES): + continue + taxable_nbv += flt(a.value_after_depreciation) + + prop_rate = flt(settings.property_tax_rate) or 1 + annual_tax = taxable_nbv * prop_rate / 100 + quarterly_tax = annual_tax / 4 + total += quarterly_tax + details.append(f"Property: Taxable NBV={taxable_nbv:,.2f} × {prop_rate}% / 4 = {quarterly_tax:,.2f}") + + return total, details + + def _calc_annual_step(self, step, settings, from_d, to_d): + """Annual closing — summary + period closing voucher.""" + details = [] + total = 0 + + # Sum all completed steps + for s in self.steps or []: + if s.status == "Completed" and s != step: + total += flt(s.tax_amount) + + details.append(f"Sum of all steps: {total:,.2f} AZN") + details.append(_("Period Closing Voucher can be created")) + + return 0, details # Annual step doesn't add new tax, just summarizes + + def complete_step(self, step_order): + """Create journal entries for the step and mark it complete.""" + step = self.get_step_by_order(step_order) + if not step: + frappe.throw(_("Step not found.")) + if step.status not in ("Calculated",): + frappe.throw(_("Step must be calculated first.")) + + settings = frappe.get_single("Tax Period Closing Settings") + je_names = [] + accounting_method = frappe.db.get_value("Company", self.company, "accounting_method") or "" + + if step.step_type == "Monthly": + if "Kassa" in accounting_method: + # Kassa: FIFO allocation (542→601, 245.1→521.3) + # Only if not already done (duplicate protection built into execute_fifo) + fifo_result = self.execute_fifo_allocation( + str(step.month_start), str(step.month_end)) + if fifo_result.get("created_jes"): + je_names.extend(fifo_result["created_jes"]) + step.details = (step.details or "") + ( + f"\nFIFO: Revenue={fifo_result['total_revenue']:,.2f} → 601 | " + f"VAT={fifo_result['total_vat']:,.2f} → 521.3") + else: + # Hesablama: VAT offset — only if no existing JE for this period + if flt(step.tax_amount) > 0 and "ƏDV" in (step.taxes_included or ""): + existing = frappe.db.count("Journal Entry", { + "company": self.company, "docstatus": 1, + "user_remark": ["like", f"%VAT offset — {step.period_label}%"], + }) + if existing == 0: + je = self._create_je( + settings.vat_output_account, settings.vat_input_account, + step.tax_amount, step.month_end, + f"VAT offset — {step.period_label}. TYCW {self.name}", + settings) + je_names.append(je.name) + + elif step.step_type == "Quarterly": + taxes_in_step = (step.taxes_included or "").split(",") + for tax_label in taxes_in_step: + tax_label = tax_label.strip() + tax_type = None + if "Mənfəət" in tax_label: + tax_type = "Profit Tax (Mənfəət)" + elif "Sadələşdirilmiş" in tax_label: + tax_type = "Simplified Tax (Sadələşdirilmiş)" + elif "Əmlak" in tax_label: + tax_type = "Property Tax (Əmlak)" + + if not tax_type: + continue + + accs = None + for row in settings.account_map or []: + if row.tax_type == tax_type: + accs = {"debit": row.debit_account, "credit": row.credit_account} + break + if not accs: + continue + + # Duplicate check for this specific tax + period + existing = frappe.db.count("Journal Entry", { + "company": self.company, "docstatus": 1, + "user_remark": ["like", f"%{tax_label} — {step.period_label}. TYCW {self.name}%"], + }) + if existing > 0: + continue + + tax_amount = self._calc_single_quarterly_tax( + step, settings, tax_type, + str(step.month_start), str(step.month_end)) + + if flt(tax_amount) > 0: + je = self._create_je( + accs["debit"], accs["credit"], + tax_amount, step.month_end, + f"{tax_label} — {step.period_label}. TYCW {self.name}", + settings) + je_names.append(je.name) + + if step.step_type == "Annual": + # Annual closing: create Period Closing Voucher + pcv_result = self._create_pcv(step, settings, accounting_method) + if pcv_result.get("pcv_name"): + je_names.append(f"PCV:{pcv_result['pcv_name']}") + step.details = (step.details or "") + f"\n{pcv_result['details']}" + + step.journal_entries = ", ".join(je_names) + step.status = "Completed" + step.completed_date = frappe.utils.today() + + # Activate next step + next_step = self.get_step_by_order(step.step_order + 1) + if next_step and next_step.status == "Pending": + next_step.status = "Active" + + # Update progress + total_steps = len(self.steps) + completed = sum(1 for s in self.steps if s.status in ("Completed", "Skipped")) + self.overall_progress = (completed / total_steps * 100) if total_steps else 0 + self.overall_status = "Completed" if completed == total_steps else "In Progress" + + # Update totals + self.total_taxes_created = sum(flt(s.tax_amount) for s in self.steps + if s.status == "Completed") + self.grand_total = self.total_taxes_created + flt(self.total_payroll_collected) + + self.add_log("Step Complete", "Success", + f"Step {step.step_order} ({step.period_label}): {flt(step.tax_amount):,.2f} AZN | " + f"JE: {step.journal_entries or 'none'}") + + def skip_step(self, step_order): + """Skip a step with a note.""" + step = self.get_step_by_order(step_order) + if not step: + frappe.throw(_("Step not found.")) + + step.status = "Skipped" + step.completed_date = frappe.utils.today() + step.details = (step.details or "") + "\n[SKIPPED]" + + next_step = self.get_step_by_order(step.step_order + 1) + if next_step and next_step.status == "Pending": + next_step.status = "Active" + + total_steps = len(self.steps) + completed = sum(1 for s in self.steps if s.status in ("Completed", "Skipped")) + self.overall_progress = (completed / total_steps * 100) if total_steps else 0 + self.overall_status = "Completed" if completed == total_steps else "In Progress" + + self.add_log("Step Skipped", "Warning", + f"Step {step.step_order} ({step.period_label}): SKIPPED") + + # ── SI Account Check ───────────────────────────────────── + + def check_si_accounts(self): + """Check if Sales Invoices use correct accounts for the accounting method.""" + accounting_method = frappe.db.get_value("Company", self.company, "accounting_method") or "" + if "Kassa" not in accounting_method: + return {"needs_fix": False, "method": accounting_method, "wrong": [], "ok": 0} + + settings = frappe.get_single("Tax Period Closing Settings") + vat_output = settings.vat_output_account # 521.3 + deferred_vat = settings.deferred_vat_sales_account # 245.1 + + if not vat_output or not deferred_vat: + return {"needs_fix": False, "error": "Settings not configured"} + + year = cint(self.year) + invoices = frappe.get_all("Sales Invoice", filters={ + "company": self.company, "docstatus": 1, + "posting_date": ["between", [f"{year}-01-01", f"{year}-12-31"]], + }, fields=["name", "posting_date", "grand_total", "total_taxes_and_charges"]) + + wrong = [] + ok = 0 + for si in invoices: + if flt(si.total_taxes_and_charges) == 0: + continue + + vat_on_521 = flt(frappe.db.sql(""" + SELECT SUM(credit) FROM `tabGL Entry` + WHERE voucher_type='Sales Invoice' AND voucher_no=%s + AND account=%s AND is_cancelled=0 + """, (si.name, vat_output))[0][0] or 0) + + if vat_on_521 > 0: + wrong.append({ + "invoice": si.name, + "date": str(si.posting_date), + "vat": vat_on_521, + "total": flt(si.grand_total), + }) + else: + ok += 1 + + return { + "needs_fix": len(wrong) > 0, + "method": accounting_method, + "wrong": wrong, + "ok": ok, + "total": len(wrong) + ok, + } + + def fix_si_accounts(self): + """Fix Sales Invoices: 521.3→245.1 and 601→542 in GL.""" + settings = frappe.get_single("Tax Period Closing Settings") + vat_output = settings.vat_output_account + deferred_vat = settings.deferred_vat_sales_account + deferred_rev = settings.deferred_revenue_account + + if not vat_output or not deferred_vat: + return {"fixed": 0, "error": "Settings not configured"} + + year = cint(self.year) + invoices = frappe.get_all("Sales Invoice", filters={ + "company": self.company, "docstatus": 1, + "posting_date": ["between", [f"{year}-01-01", f"{year}-12-31"]], + }, pluck="name") + + fixed = 0 + for si_name in invoices: + # Fix VAT: 521.3 → 245.1 + updated = frappe.db.sql(""" + UPDATE `tabGL Entry` SET account=%s + WHERE voucher_type='Sales Invoice' AND voucher_no=%s + AND account=%s AND is_cancelled=0 + """, (deferred_vat, si_name, vat_output)) + vat_rows = frappe.db.sql("SELECT ROW_COUNT()")[0][0] + + # Fix Revenue: 601% → 542 + if deferred_rev: + income_accs = frappe.db.sql(""" + SELECT DISTINCT account FROM `tabGL Entry` + WHERE voucher_type='Sales Invoice' AND voucher_no=%s + AND is_cancelled=0 AND credit > 0 AND account LIKE '601%%' + """, si_name, as_dict=True) + + for acc in income_accs: + frappe.db.sql(""" + UPDATE `tabGL Entry` SET account=%s + WHERE voucher_type='Sales Invoice' AND voucher_no=%s + AND account=%s AND is_cancelled=0 + """, (deferred_rev, si_name, acc.account)) + + # Fix SI internal fields + frappe.db.sql(""" + UPDATE `tabSales Taxes and Charges` SET account_head=%s + WHERE parent=%s AND account_head=%s + """, (deferred_vat, si_name, vat_output)) + + if deferred_rev: + frappe.db.sql(""" + UPDATE `tabSales Invoice Item` SET income_account=%s + WHERE parent=%s AND income_account LIKE '601%%' + """, (deferred_rev, si_name)) + + if vat_rows > 0: + fixed += 1 + + frappe.db.commit() + return {"fixed": fixed, "total": len(invoices)} + + def _create_pcv(self, step, settings, accounting_method): + """Create Period Closing Voucher with smart logic based on method.""" + if not settings.enable_period_closing: + return {"details": _("PCV: Disabled in settings")} + + closing_account = settings.closing_account + if not closing_account: + return {"details": _("PCV: Closing account not set (341)")} + + # Check if PCV already exists for this period + existing_pcv = frappe.db.exists("Period Closing Voucher", { + "company": self.company, + "fiscal_year": self.fiscal_year, + "docstatus": 1, + }) + if existing_pcv: + return { + "pcv_name": existing_pcv, + "details": _("PCV: Already exists — {0}").format(existing_pcv), + } + + year = cint(self.year) + from_d = date(year, 1, 1) + to_d = date(year, 12, 31) + + # Pre-check: what will be closed + from erpnext.accounts.utils import get_balance_on + + # Income balance + income_accs = frappe.get_all("Account", filters={ + "company": self.company, "root_type": "Income", "is_group": 0 + }, pluck="name") + total_income = sum(abs(flt(get_balance_on(a, str(to_d), company=self.company, start_date=str(from_d)))) + for a in income_accs) + + # Expense balance + expense_accs = frappe.get_all("Account", filters={ + "company": self.company, "root_type": "Expense", "is_group": 0 + }, pluck="name") + total_expense = sum(abs(flt(get_balance_on(a, str(to_d), company=self.company, start_date=str(from_d)))) + for a in expense_accs) + + net_pl = total_income - total_expense + + # Method-specific checks + details_lines = [] + details_lines.append(f"Accounting method: {accounting_method or 'Accrual'}") + details_lines.append(f"Income: {total_income:,.2f} | Expense: {total_expense:,.2f}") + details_lines.append(f"Net profit/loss: {net_pl:,.2f}") + + if "Kassa" in (accounting_method or ""): + # For Kassa: check if all FIFO allocations done + bal_542 = abs(flt(frappe.db.sql(""" + SELECT COALESCE(SUM(credit)-SUM(debit), 0) + FROM `tabGL Entry` WHERE account LIKE '542%%' AND company=%s AND is_cancelled=0 + """, self.company)[0][0] or 0)) + + if bal_542 > 100: # More than 100 AZN still deferred + details_lines.append(f"⚠ Deferred revenue (542): {bal_542:,.2f} — FIFO not completed") + details_lines.append(_("PCV will be created, but 542 balance will carry over to next year")) + else: + details_lines.append(f"✓ 542 balance: {bal_542:,.2f} — FIFO completed") + + # Check 245.1 + bal_245 = abs(flt(frappe.db.sql(""" + SELECT COALESCE(SUM(credit)-SUM(debit), 0) + FROM `tabGL Entry` WHERE account LIKE '245.1%%' AND company=%s AND is_cancelled=0 + """, self.company)[0][0] or 0)) + + if bal_245 > 10: + details_lines.append(f"⚠ Deferred VAT (245.1): {bal_245:,.2f}") + else: + details_lines.append(_("Accrual method: standard PCV")) + + details_lines.append(f"Closing account: {closing_account}") + + try: + pcv = frappe.new_doc("Period Closing Voucher") + pcv.company = self.company + pcv.posting_date = str(to_d) + pcv.transaction_date = str(to_d) + pcv.fiscal_year = self.fiscal_year + pcv.closing_account_head = closing_account + pcv.period_start_date = str(from_d) + pcv.period_end_date = str(to_d) + pcv.remarks = ( + f"Annual closing — {self.fiscal_year}. " + f"Accounting method: {accounting_method or 'Accrual'}. " + f"TYCW: {self.name}" + ) + + pcv.insert(ignore_permissions=True) + pcv.submit() + + details_lines.append(f"✓ PCV created: {pcv.name}") + self.add_log("Period Closing", "Success", + f"Period Closing Voucher {pcv.name} created. " + f"Income: {total_income:,.2f} | Expense: {total_expense:,.2f} | " + f"Result: {net_pl:,.2f} → {closing_account}") + + return { + "pcv_name": pcv.name, + "details": "\n".join(details_lines), + } + + except Exception as e: + error_msg = str(e) + details_lines.append(f"✗ PCV error: {error_msg}") + self.add_log("Period Closing", "Error", f"PCV error: {error_msg}") + return {"details": "\n".join(details_lines)} + + def _create_je(self, debit_acc, credit_acc, amount, posting_date, remark, settings): + cost_center = getattr(settings, "default_cost_center", None) or frappe.db.get_value( + "Company", self.company, "cost_center") + + je = frappe.new_doc("Journal Entry") + je.company = self.company + je.posting_date = posting_date + je.voucher_type = "Journal Entry" + je.user_remark = remark + + je.append("accounts", { + "account": debit_acc, + "debit_in_account_currency": flt(amount), + "cost_center": cost_center, + }) + je.append("accounts", { + "account": credit_acc, + "credit_in_account_currency": flt(amount), + "cost_center": cost_center, + }) + + je.insert(ignore_permissions=True) + + if getattr(settings, "auto_submit_entries", False): + je.submit() + + return je + + def _calc_single_quarterly_tax(self, step, settings, tax_type, from_d, to_d): + """Calculate a single quarterly tax amount.""" + from erpnext.accounts.utils import get_balance_on + + if "Profit" in tax_type: + income_accs = frappe.get_all("Account", filters={ + "company": self.company, "root_type": "Income", "is_group": 0 + }, pluck="name") + expense_accs = frappe.get_all("Account", filters={ + "company": self.company, "root_type": "Expense", "is_group": 0 + }, pluck="name") + + # For kassa method: use cash receipts as income + accounting_method = frappe.db.get_value("Company", self.company, "accounting_method") or "" + if "Kassa" in accounting_method: + total_income = flt(frappe.db.sql(""" + SELECT COALESCE(SUM(paid_amount),0) FROM `tabPayment Entry` + WHERE company=%s AND docstatus=1 AND payment_type='Receive' + AND posting_date BETWEEN %s AND %s + """, (self.company, from_d, to_d))[0][0]) + total_income += flt(frappe.db.sql(""" + SELECT COALESCE(SUM(jea.debit),0) FROM `tabJournal Entry` je + JOIN `tabJournal Entry Account` jea ON jea.parent=je.name + WHERE je.company=%s AND je.docstatus=1 AND jea.account LIKE '226%%' AND jea.debit>0 + AND je.posting_date BETWEEN %s AND %s AND je.user_remark NOT LIKE '%%FIFO%%' + """, (self.company, from_d, to_d))[0][0]) + + total_expense = flt(frappe.db.sql(""" + SELECT COALESCE(SUM(paid_amount),0) FROM `tabPayment Entry` + WHERE company=%s AND docstatus=1 AND payment_type='Pay' + AND posting_date BETWEEN %s AND %s + """, (self.company, from_d, to_d))[0][0]) + # Add salary + total_expense += flt(frappe.db.sql(""" + SELECT COALESCE(SUM(net_pay),0) FROM `tabSalary Slip` + WHERE company=%s AND docstatus=1 AND posting_date BETWEEN %s AND %s + """, (self.company, from_d, to_d))[0][0]) + else: + total_income = sum(abs(flt(get_balance_on(a, to_d, company=self.company, start_date=from_d))) for a in income_accs) + total_expense = sum(abs(flt(get_balance_on(a, to_d, company=self.company, start_date=from_d))) for a in expense_accs) + + profit = total_income - total_expense + rate = flt(settings.profit_tax_rate) or 20 + tax_before = max(0, profit * rate / 100) + + # Apply benefits + exemption_pct = 0 + for b in self.detected_benefits or []: + if b.enabled and b.applies_to == "Profit Tax (Mənfəət)" and b.condition_met == "Yes": + exemption_pct += flt(b.exemption_percent) + exemption_pct = min(exemption_pct, 100) + return max(0, tax_before - (tax_before * exemption_pct / 100)) + + elif "Simplified" in tax_type: + rate = flt(settings.default_simplified_rate) or 4 + income_accs = frappe.get_all("Account", filters={ + "company": self.company, "root_type": "Income", "is_group": 0 + }, pluck="name") + revenue = sum(abs(flt(get_balance_on(a, to_d, company=self.company, start_date=from_d))) for a in income_accs) + return revenue * rate / 100 + + elif "Property" in tax_type: + from taxes_az.taxes_az.doctype.tax_period_closing_wizard.tax_period_closing_wizard import TaxPeriodClosingWizard + assets = frappe.get_all("Asset", filters={ + "company": self.company, "docstatus": 1, + "status": ["not in", ["Scrapped", "Sold"]] + }, fields=["total_asset_cost", "value_after_depreciation", + "asset_category", "taxable_asset_type"]) + + taxable_nbv = 0 + for a in assets: + if (a.asset_category in TaxPeriodClosingWizard.EXEMPT_ASSET_CATEGORIES + or a.taxable_asset_type in TaxPeriodClosingWizard.EXEMPT_ASSET_CATEGORIES): + continue + taxable_nbv += flt(a.value_after_depreciation) + + rate = flt(settings.property_tax_rate) or 1 + return taxable_nbv * rate / 100 / 4 + + return 0 + + # ── FIFO Allocation ────────────────────────────────────── + + def run_fifo_allocation(self, from_date, to_date): + """FIFO allocate payments to invoices for cash method recognition.""" + settings = frappe.get_single("Tax Period Closing Settings") + deferred_vat = settings.deferred_vat_sales_account + deferred_rev = settings.deferred_revenue_account + vat_output = settings.vat_output_account + + if not deferred_vat or not deferred_rev or not vat_output: + return {"error": _("Deferred accounts not configured in Settings.")} + + # 1. All SI with unrecognized revenue (has balance on 542/245.1), per customer FIFO + si_balances = [] + all_sis = frappe.get_all("Sales Invoice", filters={ + "company": self.company, "docstatus": 1 + }, fields=["name", "posting_date", "customer", "grand_total", + "net_total", "total_taxes_and_charges"], + order_by="posting_date, name") + + for si in all_sis: + # How much already recognized (transferred from 542→601)? + recognized = abs(flt(frappe.db.sql(""" + SELECT SUM(debit) FROM `tabGL Entry` + WHERE account=%s AND is_cancelled=0 + AND against_voucher_type='Sales Invoice' AND against_voucher=%s + """, (deferred_rev, si.name))[0][0] or 0)) + + remaining = flt(si.grand_total) - recognized + if remaining > 0.01: + si_balances.append({ + "name": si.name, + "date": str(si.posting_date), + "customer": si.customer, + "grand_total": flt(si.grand_total), + "net_total": flt(si.net_total), + "vat": flt(si.total_taxes_and_charges), + "recognized": recognized, + "remaining": remaining, + }) + + # 2. Payments in period — combine PE + JE per customer per date + combined = {} + + # PE Receive + pes = frappe.db.sql(""" + SELECT name, posting_date, paid_amount, party as customer + FROM `tabPayment Entry` + WHERE company=%s AND docstatus=1 AND payment_type='Receive' + AND party_type='Customer' + AND posting_date BETWEEN %s AND %s + """, (self.company, from_date, to_date), as_dict=True) + + for pe in pes: + key = f"{pe.customer}_{pe.posting_date}" + combined.setdefault(key, {"customer": pe.customer, "date": pe.posting_date, "total": 0, "sources": []}) + combined[key]["total"] += flt(pe.paid_amount) + combined[key]["sources"].append(f"PE: {pe.name}") + + # JE with Дт 226 + jes = frappe.db.sql(""" + SELECT je.name, je.posting_date, jea.debit as amount + FROM `tabJournal Entry` je + JOIN `tabJournal Entry Account` jea ON jea.parent = je.name + WHERE je.company=%s AND je.docstatus=1 + AND jea.account LIKE '226%%' AND jea.debit > 0 + AND je.posting_date BETWEEN %s AND %s + AND je.user_remark NOT LIKE '%%FIFO%%' + """, (self.company, from_date, to_date), as_dict=True) + + for je in jes: + party = frappe.db.sql(""" + SELECT party FROM `tabJournal Entry Account` + WHERE parent=%s AND account LIKE '211%%' AND credit > 0 LIMIT 1 + """, je.name) + customer = party[0][0] if party else None + if customer: + key = f"{customer}_{je.posting_date}" + combined.setdefault(key, {"customer": customer, "date": je.posting_date, "total": 0, "sources": []}) + combined[key]["total"] += flt(je.amount) + combined[key]["sources"].append(f"JE: {je.name}") + + payments = sorted(combined.values(), key=lambda x: str(x["date"])) + + # 3. FIFO allocation + allocations = [] + for payment in payments: + remaining_pay = payment["total"] + + for si in si_balances: + if remaining_pay <= 0.01: + break + if si["customer"] != payment["customer"]: + continue + if si["remaining"] <= 0.01: + continue + + allocate = min(remaining_pay, si["remaining"]) + ratio = allocate / si["grand_total"] if si["grand_total"] else 0 + vat_part = flt(si["vat"] * ratio, 2) + rev_part = flt(si["net_total"] * ratio, 2) + + allocations.append({ + "invoice": si["name"], + "invoice_date": si["date"], + "customer": si["customer"], + "payment_date": str(payment["date"]), + "sources": payment["sources"], + "allocated": allocate, + "ratio": ratio, + "vat_transfer": vat_part, + "revenue_transfer": rev_part, + }) + + si["remaining"] -= allocate + remaining_pay -= allocate + + return {"allocations": allocations, "payments": payments, "si_balances": si_balances} + + def execute_fifo_allocation(self, from_date, to_date): + """Execute FIFO: create JE for 245.1→521.3 and 542→601 transfers.""" + # Duplicate protection: check if FIFO already done for this period + wizard + existing = frappe.db.count("Journal Entry", { + "company": self.company, + "docstatus": 1, + "user_remark": ["like", f"%TYCW {self.name}%"], + "posting_date": ["between", [from_date, to_date]], + }) + if existing > 0: + return { + "status": "skip", + "created_jes": [], + "total_vat": 0, + "total_revenue": 0, + "count": 0, + "message": _("{0} FIFO journal entries already exist for this period.").format(existing), + } + + result = self.run_fifo_allocation(from_date, to_date) + if "error" in result: + frappe.throw(result["error"]) + + settings = frappe.get_single("Tax Period Closing Settings") + deferred_vat = settings.deferred_vat_sales_account + deferred_rev = settings.deferred_revenue_account + vat_output = settings.vat_output_account + cost_center = getattr(settings, "default_cost_center", None) or frappe.db.get_value( + "Company", self.company, "cost_center") + + income_account = frappe.db.get_value("Account", { + "company": self.company, "account_number": "601", "is_group": 0 + }, "name") + if not income_account: + income_account = frappe.db.get_value("Account", { + "company": self.company, "root_type": "Income", "is_group": 0 + }, "name") + + created_jes = [] + total_vat = 0 + total_rev = 0 + + for alloc in result.get("allocations", []): + if alloc["vat_transfer"] <= 0.01 and alloc["revenue_transfer"] <= 0.01: + continue + + je = frappe.new_doc("Journal Entry") + je.company = self.company + je.posting_date = alloc["payment_date"] + je.voucher_type = "Journal Entry" + je.user_remark = ( + f"FIFO Cash: {alloc['invoice']} ({alloc['ratio']*100:.0f}% recognized). " + f"TYCW {self.name}" + ) + + if alloc["vat_transfer"] > 0: + je.append("accounts", { + "account": deferred_vat, + "debit_in_account_currency": alloc["vat_transfer"], + "cost_center": cost_center, + "against_voucher_type": "Sales Invoice", + "against_voucher": alloc["invoice"], + }) + je.append("accounts", { + "account": vat_output, + "credit_in_account_currency": alloc["vat_transfer"], + "cost_center": cost_center, + }) + total_vat += alloc["vat_transfer"] + + if alloc["revenue_transfer"] > 0 and income_account: + je.append("accounts", { + "account": deferred_rev, + "debit_in_account_currency": alloc["revenue_transfer"], + "cost_center": cost_center, + "against_voucher_type": "Sales Invoice", + "against_voucher": alloc["invoice"], + }) + je.append("accounts", { + "account": income_account, + "credit_in_account_currency": alloc["revenue_transfer"], + "cost_center": cost_center, + }) + total_rev += alloc["revenue_transfer"] + + je.insert(ignore_permissions=True) + je.submit() + created_jes.append(je.name) + + return { + "status": "done", + "created_jes": created_jes, + "total_vat": total_vat, + "total_revenue": total_rev, + "count": len(created_jes), + "allocations": result.get("allocations", []), + } + + +# ── Whitelisted API ───────────────────────────────────────── + +@frappe.whitelist() +def check_si_accounts(name): + """Check if SI accounts need fixing for cash method.""" + doc = frappe.get_doc("Tax Year Closing Wizard", name) + doc.check_permission("read") + return doc.check_si_accounts() + + +@frappe.whitelist() +def fix_si_accounts(name): + """Fix SI accounts: 521.3→245.1, 601→542.""" + doc = frappe.get_doc("Tax Year Closing Wizard", name) + doc.check_permission("write") + return doc.fix_si_accounts() + + +@frappe.whitelist() +def initialize_wizard(name): + doc = frappe.get_doc("Tax Year Closing Wizard", name) + doc.check_permission("write") + doc.generate_steps() + doc.save() + frappe.db.commit() + return {"status": "ok", "steps": len(doc.steps)} + + +@frappe.whitelist() +def calculate_step(name, step_order): + doc = frappe.get_doc("Tax Year Closing Wizard", name) + doc.check_permission("write") + doc.calculate_step(cint(step_order)) + doc.save() + frappe.db.commit() + return {"status": "calculated", "step": cint(step_order)} + + +@frappe.whitelist() +def complete_step(name, step_order): + doc = frappe.get_doc("Tax Year Closing Wizard", name) + doc.check_permission("write") + doc.complete_step(cint(step_order)) + doc.save() + frappe.db.commit() + + # Return step data for report popup + step = doc.get_step_by_order(cint(step_order)) + next_step = doc.get_step_by_order(cint(step_order) + 1) + + return { + "status": "completed", + "step": cint(step_order), + "step_data": { + "period_label": step.period_label if step else "", + "step_type": step.step_type if step else "", + "month_start": str(step.month_start) if step else "", + "month_end": str(step.month_end) if step else "", + "taxes_included": step.taxes_included if step else "", + "tax_amount": step.tax_amount if step else 0, + "journal_entries": step.journal_entries if step else "", + "details": step.details if step else "", + "deadline_declaration": str(step.deadline_declaration) if step and step.deadline_declaration else "", + "deadline_payment": str(step.deadline_payment) if step and step.deadline_payment else "", + }, + "next_step": next_step.period_label if next_step else None, + } + + +@frappe.whitelist() +def skip_step(name, step_order): + doc = frappe.get_doc("Tax Year Closing Wizard", name) + doc.check_permission("write") + doc.skip_step(cint(step_order)) + doc.save() + frappe.db.commit() + return {"status": "skipped", "step": cint(step_order)} + + +@frappe.whitelist() +def allocate_all(name): + """Run FIFO allocation for all monthly steps.""" + doc = frappe.get_doc("Tax Year Closing Wizard", name) + doc.check_permission("write") + + total_jes = 0 + total_vat = 0 + total_revenue = 0 + periods = 0 + + for step in doc.steps: + if step.step_type != "Monthly": + continue + + result = doc.execute_fifo_allocation(str(step.month_start), str(step.month_end)) + if result.get("created_jes"): + existing_jes = step.journal_entries or "" + new_jes = ", ".join(result["created_jes"]) + step.journal_entries = f"{existing_jes}, {new_jes}".strip(", ") + step.details = (step.details or "") + ( + f"\nFIFO: VAT={result['total_vat']:,.2f} | " + f"Revenue={result['total_revenue']:,.2f} | " + f"JE: {len(result['created_jes'])}" + ) + total_jes += len(result["created_jes"]) + total_vat += result["total_vat"] + total_revenue += result["total_revenue"] + periods += 1 + + doc.save() + frappe.db.commit() + + return { + "status": "done", + "total_jes": total_jes, + "total_vat": total_vat, + "total_revenue": total_revenue, + "periods_processed": periods, + } + + +@frappe.whitelist() +def get_step_report(name, step_order): + """Get detailed report for a specific step/month.""" + doc = frappe.get_doc("Tax Year Closing Wizard", name) + doc.check_permission("read") + + step = doc.get_step_by_order(cint(step_order)) + if not step: + frappe.throw(_("Step not found.")) + + from erpnext.accounts.utils import get_balance_on + company = doc.company + from_d = str(step.month_start) + to_d = str(step.month_end) + settings = frappe.get_single("Tax Period Closing Settings") + + report = { + "period": step.period_label, + "type": step.step_type, + "from": from_d, + "to": to_d, + "status": step.status, + } + + # Income by account + income_accs = frappe.get_all("Account", filters={ + "company": company, "root_type": "Income", "is_group": 0 + }, fields=["name", "account_name", "account_number"]) + + income_lines = [] + total_income = 0 + for acc in income_accs: + bal = abs(flt(get_balance_on(acc.name, to_d, company=company, start_date=from_d))) + if bal > 0: + income_lines.append({"account": acc.account_number, "name": acc.account_name, "amount": bal}) + total_income += bal + report["income"] = income_lines + report["total_income"] = total_income + + # Expense by account + expense_accs = frappe.get_all("Account", filters={ + "company": company, "root_type": "Expense", "is_group": 0 + }, fields=["name", "account_name", "account_number"]) + + expense_lines = [] + total_expense = 0 + for acc in expense_accs: + bal = abs(flt(get_balance_on(acc.name, to_d, company=company, start_date=from_d))) + if bal > 0: + expense_lines.append({"account": acc.account_number, "name": acc.account_name, "amount": bal}) + total_expense += bal + report["expenses"] = expense_lines + report["total_expense"] = total_expense + report["net_profit"] = total_income - total_expense + + # VAT + vat_out = abs(flt(get_balance_on(settings.vat_output_account, to_d, company=company, start_date=from_d))) if settings.vat_output_account else 0 + vat_in = abs(flt(get_balance_on(settings.vat_input_account, to_d, company=company, start_date=from_d))) if settings.vat_input_account else 0 + report["vat_output"] = vat_out + report["vat_input"] = vat_in + report["vat_net"] = vat_out - vat_in + + # Payments received this month + pe_received = flt(frappe.db.sql(""" + SELECT SUM(paid_amount) FROM `tabPayment Entry` + WHERE company=%s AND docstatus=1 AND payment_type='Receive' + AND posting_date BETWEEN %s AND %s + """, (company, from_d, to_d))[0][0] or 0) + + je_226 = flt(frappe.db.sql(""" + SELECT SUM(jea.debit) FROM `tabJournal Entry` je + JOIN `tabJournal Entry Account` jea ON jea.parent=je.name + WHERE je.company=%s AND je.docstatus=1 + AND jea.account LIKE '226%%' AND jea.debit > 0 + AND je.posting_date BETWEEN %s AND %s + AND je.user_remark NOT LIKE '%%FIFO%%' + """, (company, from_d, to_d))[0][0] or 0) + + report["payments_received"] = pe_received + report["vat_226_received"] = je_226 + report["total_cash_in"] = pe_received + je_226 + + # Payments made this month + pe_paid = flt(frappe.db.sql(""" + SELECT SUM(paid_amount) FROM `tabPayment Entry` + WHERE company=%s AND docstatus=1 AND payment_type='Pay' + AND posting_date BETWEEN %s AND %s + """, (company, from_d, to_d))[0][0] or 0) + report["payments_made"] = pe_paid + + # Payroll + payroll = frappe.db.sql(""" + SELECT SUM(gross_pay) as gross, SUM(total_deduction) as ded, SUM(net_pay) as net, COUNT(*) as cnt + FROM `tabSalary Slip` + WHERE company=%s AND docstatus=1 AND posting_date BETWEEN %s AND %s + """, (company, from_d, to_d), as_dict=True) + if payroll and payroll[0].gross: + report["payroll"] = { + "gross": flt(payroll[0].gross), + "deductions": flt(payroll[0].ded), + "net": flt(payroll[0].net), + "count": payroll[0].cnt, + } + + # FIFO allocations done for this period + fifo_jes = frappe.db.sql(""" + SELECT name, total_debit, posting_date FROM `tabJournal Entry` + WHERE company=%s AND docstatus=1 + AND user_remark LIKE '%%FIFO%%' + AND posting_date BETWEEN %s AND %s + """, (company, from_d, to_d), as_dict=True) + report["fifo_entries"] = [{"name": j.name, "amount": flt(j.total_debit)} for j in fifo_jes] + + # Step JEs + report["step_jes"] = (step.journal_entries or "").split(",") + report["step_jes"] = [j.strip() for j in report["step_jes"] if j.strip()] + + # Deadlines + report["deadline_declaration"] = str(step.deadline_declaration) if step.deadline_declaration else "" + report["deadline_payment"] = str(step.deadline_payment) if step.deadline_payment else "" + + return report + + +@frappe.whitelist() +def preview_allocation(name, step_order): + """Preview FIFO allocation for a step without executing.""" + doc = frappe.get_doc("Tax Year Closing Wizard", name) + doc.check_permission("read") + step = doc.get_step_by_order(cint(step_order)) + if not step: + frappe.throw(_("Step not found.")) + return doc.run_fifo_allocation(str(step.month_start), str(step.month_end)) + + +@frappe.whitelist() +def execute_allocation(name, step_order): + """Execute FIFO allocation for a step — create JEs.""" + doc = frappe.get_doc("Tax Year Closing Wizard", name) + doc.check_permission("write") + step = doc.get_step_by_order(cint(step_order)) + if not step: + frappe.throw(_("Step not found.")) + + result = doc.execute_fifo_allocation(str(step.month_start), str(step.month_end)) + + # Update step details + if result.get("created_jes"): + existing_jes = step.journal_entries or "" + new_jes = ", ".join(result["created_jes"]) + step.journal_entries = f"{existing_jes}, {new_jes}".strip(", ") + step.details = (step.details or "") + ( + f"\nFIFO: VAT={result['total_vat']:,.2f} → 521.3 | " + f"Revenue={result['total_revenue']:,.2f} → 601 | " + f"JE: {len(result['created_jes'])}" + ) + doc.add_log("Step Complete", "Success", + f"FIFO allocation: {len(result['created_jes'])} JE | " + f"VAT: {result['total_vat']:,.2f} | Revenue: {result['total_revenue']:,.2f}") + + doc.save() + frappe.db.commit() + return result diff --git a/taxes_az/translations/az.csv b/taxes_az/translations/az.csv new file mode 100644 index 0000000..4a13e5e --- /dev/null +++ b/taxes_az/translations/az.csv @@ -0,0 +1,1101 @@ +Tax Year Closing Wizard,Vergi İli Bağlanış Sehrbazı +Tax Year Closing Step,Vergi İli Bağlanış Addımı +Tax Period Closing Wizard,Vergi Dövrü Bağlanış Sehrbazı +Tax Period Closing Settings,Vergi Dövrü Bağlanış Parametrləri +Tax Closing Benefit,Vergi Güzəşti +Tax Closing Account Map,Vergi Hesab Xəritəsi +Tax Rate Row,Vergi Dərəcəsi Sırası +Tax Closing Tax Item,Vergi Bağlanış Elementi +Tax Closing Entry Preview,Provodka Önbaxışı +Tax Closing Log Entry,Əməliyyat Jurnalı +Tax Closing Journal Entry,Journal Entry Qeydi +Tax Closing Payroll Summary,Əmək Haqqı Xülasəsi +Generate Timeline,Zaman xəttini yarat +Timeline,Zaman xətti +Current Step,Cari addım +Dashboard,İdarə paneli +Tax Details,Vergi təfərrüatları +Results,Nəticələr +Log,Jurnal +Summary,Xülasə +Company,Şirkət +Company Info,Şirkət məlumatı +Fiscal Year,Maliyyə ili +Year,İl +Tax Regime,Vergi rejimi +Accounting Method,Uçot metodu +Overall Status,Ümumi vəziyyət +Progress,İrəliləyiş +Status,Vəziyyət +VOEN,VÖEN +Activity Code (OKVED),Fəaliyyət kodu (OKED) +Business Classification,Sahibkarlıq təsnifatı +Tax Authority,Vergi orqanı +VAT Payer,ƏDV ödəyicisi +Has Fixed Assets,Əsas vəsaitlər var +Has Assets,Əsas vəsaitlər var +Has Land,Torpaq var +Has Employees,İşçilər var +Avg Monthly Employees,Orta aylıq işçi sayı +Detected Tax Benefits & Exemptions,Aşkar edilmiş vergi güzəştləri +Detected Benefits,Aşkar edilmiş güzəştlər +Benefits Display,Güzəştlər göstərişi +Benefits Data,Güzəşt məlumatları +Applicable Taxes,Tətbiq olunan vergilər +Tax Items,Vergi elementləri +Financial Data,Maliyyə məlumatları +Total Income,Ümumi gəlir +Total Expense,Ümumi xərc +Net Profit / Loss,Xalis mənfəət / zərər +Asset Data,Əsas vəsait məlumatları +Total Asset Value (Gross),Əsas vəsaitlərin ümumi dəyəri +Accumulated Depreciation,Yığılmış amortizasiya +Net Book Value,Qalıq dəyər +Avg Annual Value,Orta illik dəyər +Payroll Summary,Əmək haqqı xülasəsi +Payroll Summary (Data Only),Əmək haqqı xülasəsi (yalnız məlumat) +Payroll Data Collection,Əmək haqqı məlumatlarının toplanması +Payroll Data Source,Əmək haqqı məlumat mənbəyi +Entry Preview,Provodka önbaxışı +Entry Previews,Provodka önbaxışları +Created Journal Entries,Yaradılmış journal entry-lər +Journal Entries,Journal entry-lər +Journal Entry,Journal Entry +Execution Log,İcra jurnalı +Log Entries,Jurnal qeydləri +Log Display,Jurnal göstərişi +Summary Report,Xülasə hesabatı +Summary Display,Xülasə göstərişi +Total Tax Amount (Entries Created),Ümumi vergi məbləği (provodkalar yaradılıb) +Total Taxes (Entries Created),Ümumi vergilər (provodkalar yaradılıb) +Total Payroll (Data Only),Ümumi əmək haqqı (yalnız məlumat) +Total Payroll Taxes (Data Only),Ümumi əmək haqqı vergiləri (yalnız məlumat) +Grand Total Tax Burden,Ümumi vergi yükü +Wizard Status,Sehrbaz vəziyyəti +Progress Bar,İrəliləyiş paneli +Period Type,Dövr növü +Quarter,Rüb +Month,Ay +From Date,Başlanğıc tarix +To Date,Son tarix +Cost Center,Xərc mərkəzi +Options,Seçimlər +Amended From,Düzəliş edilib +General Settings,Ümumi parametrlər +Default Tax Regime,Standart vergi rejimi +Auto-detect Tax Regime,Vergi rejimini avtomatik müəyyən et +Auto-detect from Company,Şirkətdən avtomatik müəyyən et +Auto-detect parameters on Save,Saxlayarkən parametrləri avtomatik müəyyən et +Default Cost Center,Standart xərc mərkəzi +Journal Entry Behavior,Journal Entry davranışı +Auto-submit Journal Entries,Journal entry-ləri avtomatik təsdiqlə +Allow Re-closing Periods,Dövrlərin yenidən bağlanmasına icazə ver +Duplicate Check Scope,Dublikat yoxlama əhatəsi +Same Company + Same Period,Eyni şirkət + Eyni dövr +Same Company + Overlapping Dates,Eyni şirkət + Üst-üstə düşən tarixlər +Period Closing Voucher (P&L Close),Dövr bağlanış çeki (M/Z bağlanışı) +Create Period Closing Voucher,Dövr bağlanış çeki yarat +Closing Account,Bağlanış hesabı +Declaration Integration (Future),Bəyannamə inteqrasiyası (Gələcək) +Create Declaration Draft,Bəyannamə qaralamasi yarat +Auto-fill Declaration Header,Bəyannamə başlığını avtomatik doldur +Separate Cost Center per Tax Type,Hər vergi növü üçün ayrı xərc mərkəzi +VAT (ƏDV),ƏDV +VAT (ƏDV) — Əlavə Dəyər Vergisi,ƏDV — Əlavə Dəyər Vergisi +Enable VAT Calculation,ƏDV hesablamasını aktivləşdir +VAT Rate (%),ƏDV dərəcəsi (%) +VAT Input Account,ƏDV daxil hesabı +VAT Output Account,ƏDV çıxış hesabı +Cross-check with VAT Allocation,ƏDV bölüşdürmə ilə yoxla +VAT Data Source,ƏDV məlumat mənbəyi +GL Account Balance,Baş kitab hesab qalığı +Sales/Purchase Invoices,Satış/Alış fakturaları +Profit Tax (Mənfəət Vergisi),Mənfəət vergisi +Profit Tax (Mənfəət),Mənfəət vergisi +Enable Profit Tax Calculation,Mənfəət vergisi hesablamasını aktivləşdir +Profit Tax Rate (%),Mənfəət vergisi dərəcəsi (%) +Advance Payment Method,Avans ödəniş üsulu +Actual Quarterly Profit,Faktiki rüblük mənfəət +1/4 of Previous Year Tax,Əvvəlki ilin vergisinin 1/4-ü +Previous Year Tax Amount,Əvvəlki ilin vergi məbləği +Simplified Tax,Sadələşdirilmiş vergi +Simplified Tax (Sadələşdirilmiş Vergi),Sadələşdirilmiş vergi +Simplified Tax (Sadələşdirilmiş),Sadələşdirilmiş vergi +Enable Simplified Tax,Sadələşdirilmiş vergini aktivləşdir +Auto-detect Rate by OKVED,OKED-ə görə dərəcəni avtomatik müəyyən et +Default Simplified Tax Rate (%),Standart sadələşdirilmiş vergi dərəcəsi (%) +Revenue Source,Gəlir mənbəyi +Total Income (P&L),Ümumi gəlir (M/Z) +Sales Revenue Only,Yalnız satış gəliri +Cash Receipts,Nağd daxilolmalar +Property Tax (Əmlak Vergisi),Əmlak vergisi +Property Tax (Əmlak),Əmlak vergisi +Enable Property Tax,Əmlak vergisini aktivləşdir +Property Tax Rate (%),Əmlak vergisi dərəcəsi (%) +Reporting Threshold (AZN),Hesabat həddi (AZN) +Calculation Method,Hesablama metodu +Average Annual (Start + End) / 2,Orta illik (Əvvəl + Son) / 2 +Pro-rata for New Companies,Yeni şirkətlər üçün proporsional +End of Period NBV,Dövrün sonu qalıq dəyər +Land Tax (Torpaq Vergisi),Torpaq vergisi +Land Tax (Torpaq),Torpaq vergisi +Enable Land Tax,Torpaq vergisini aktivləşdir +Land Tax Declaration,Torpaq vergisi bəyannaməsi +Manual Entry,Əl ilə daxil etmə +Other Taxes (Optional),Digər vergilər (İstəyə bağlı) +Enable Excise Tax,Aksiz vergisini aktivləşdir +Enable Mining Tax,Mədən vergisini aktivləşdir +Enable Road Tax,Yol vergisini aktivləşdir +Excise Tax (Aksiz),Aksiz vergisi +Mining Tax (Mədən),Mədən vergisi +Road Tax (Yol),Yol vergisi +Withholding Tax (ÖMV),Ödəmə mənbəyində tutulan vergi +Unemployment Insurance (İşsizlik),İşsizlikdən sığorta +Medical Insurance (Tibbi sığorta),İcbari tibbi sığorta +DSMF,DSMF +Collect Payroll Data,Əmək haqqı məlumatlarını topla +Salary Slips,Əmək haqqı vərəqələri +GL Account Balances,Baş kitab hesab qalıqları +Both (with cross-check),Hər ikisi (qarşılıqlı yoxlama ilə) +Tax Rates Reference Table,Vergi dərəcələri istinad cədvəli +Tax Rates,Vergi dərəcələri +Account Mapping,Hesab xəritəsi +Account Mapping (Debit → Credit),Hesab xəritəsi (Debet → Kredit) +Tax Type,Vergi növü +Debit Account,Debet hesabı +Credit Account,Kredit hesabı +Rate (%),Dərəcə (%) +Description,Təsvir +Smart Scan — Existing Entry Detection,Ağıllı axtarış — Mövcud provodka aşkarlanması +Scan for Existing Entries,Mövcud provodkaları axtar +Force Create Even if Exists,Mövcud olsa belə məcburi yarat +Offer Correction Entry for Differences,Fərqlər üçün düzəliş provodkası təklif et +Accounting Method (Uçot Metodu),Uçot metodu +Company & Auto-Detection,Şirkət və avtomatik aşkarlama +Detection Status,Aşkarlama vəziyyəti +Regime Explanation,Rejim izahı +Default Company,Standart şirkət +Tax Regime,Vergi rejimi +General (VAT + Profit),Ümumi (ƏDV + Mənfəət) +Special Regime,Xüsusi rejim +Enabled,Aktiv +Period,Dövr +Sub-Period,Alt dövr +Source Amount,Mənbə məbləği +Tax Amount,Vergi məbləği +Declaration Deadline,Bəyannamə müddəti +Payment Deadline,Ödəniş müddəti +Calculated Amount,Hesablanmış məbləğ +Posting Date,Göndərmə tarixi +Remark,Qeyd +Existing Entry,Mövcud provodka +Existing Amount,Mövcud məbləğ +Difference,Fərq +Timestamp,Vaxt +Phase,Mərhələ +Level,Səviyyə +Message,Mesaj +Initialization,İlkin quraşdırma +Tax Detection,Vergi aşkarlanması +Data Collection,Məlumat toplanması +Calculation,Hesablama +Entry Preview,Provodka önbaxışı +Entry Creation,Provodka yaradılması +Period Closing,Dövr bağlanışı +Setup,Quraşdırma +Step Complete,Addım tamamlandı +Step Skipped,Addım keçildi +Benefit Detection,Güzəşt aşkarlanması +Info,Məlumat +Success,Uğurlu +Warning,Xəbərdarlıq +Error,Xəta +Header,Başlıq +Draft,Qaralama +Calculated,Hesablanıb +Entries Created,Provodkalar yaradılıb +Completed,Tamamlanıb +Cancelled,Ləğv edilib +Not Started,Başlanmayıb +In Progress,Davam edir +Active,Aktiv +Pending,Gözləmədə +Skipped,Keçildi +Data Only,Yalnız məlumat +Entry Created,Provodka yaradılıb +Failed,Uğursuz +Existing,Mövcud +Yes,Bəli +No,Xeyr +Partial,Qismən +Manual Check,Əl ilə yoxlama +Monthly,Aylıq +Quarterly,Rüblük +Annually,İllik +Annual,İllik +Article (Maddə),Maddə +Benefit,Güzəşt +Applies To,Tətbiq olunur +Exemption %,Azadolma % +Condition,Şərt +Details,Təfərrüatlar +Tax Reduction,Vergi azalması +Law Reference,Qanun istinadı +Apply,Tətbiq et +Component,Komponent +Amount,Məbləğ +Account,Hesab +Source,Mənbə +Calculate Taxes,Vergiləri hesabla +Calculating taxes...,Vergilər hesablanır... +Calculation complete. Reloading...,Hesablama tamamlandı. Yenidən yüklənir... +Calculation failed. Check browser console.,Hesablama uğursuz oldu. Brauzer konsolunu yoxlayın. +Create Journal Entries,Provodkalar yarat +Creating journal entries...,Provodkalar yaradılır... +Entries created. Reloading...,Provodkalar yaradıldı. Yenidən yüklənir... +Entry creation failed.,Provodka yaradılması uğursuz oldu. +Re-calculate,Yenidən hesabla +Rollback All Entries,Bütün provodkaları geri qaytar +Rolling back entries...,Provodkalar geri qaytarılır... +All entries rolled back,Bütün provodkalar geri qaytarıldı +This will create journal entries for all calculated taxes. Continue?,Bu bütün hesablanmış vergilər üçün provodkalar yaradacaq. Davam etmək istəyirsiniz? +This will cancel/delete ALL journal entries created by this wizard. Are you sure?,Bu sehrbaz tərəfindən yaradılmış BÜTÜN provodkaları ləğv edəcək/siləcək. Əminsiniz? +Verify Entries (Kassa),Provodkaları yoxla (Kassa) +Verifying entries...,Provodkalar yoxlanılır... +Fix Cash Method Entries,Kassa metodu provodkalarını düzəlt +Re-submitting invoices with cash method accounts...,Fakturalar kassa metodu hesabları ilə yenidən təsdiqlənir... +FIFO Allocate All,FIFO Hamısını bölüşdür +FIFO Allocation,FIFO Bölüşdürmə +Run FIFO allocation for ALL months? This will recognize revenue and VAT for all received payments.,Bütün aylar üçün FIFO bölüşdürməsi aparılsın? Bu alınmış bütün ödənişlər üçün gəlir və ƏDV-ni tanıyacaq. +Running FIFO allocation for all periods...,Bütün dövrlər üçün FIFO bölüşdürmə aparılır... +This will process ALL steps sequentially: Calculate → FIFO Allocate → Complete. Continue?,Bütün addımlar ardıcıl icra olunacaq: Hesabla → FIFO Bölüşdür → Tamamla. Davam edək? +Skip this step? It will be marked as Skipped.,Bu addım keçilsin? Keçildi kimi qeyd olunacaq. +Create journal entries for this step?,Bu addım üçün provodkalar yaradılsın? +Step not found.,Addım tapılmadı. +Step must be calculated first.,Əvvəlcə addım hesablanmalıdır. +Complete all steps before submitting.,Təsdiqləmədən əvvəl bütün addımları tamamlayın. +Please run Calculate and Create Entries before submitting.,Təsdiqləmədən əvvəl Hesabla və Provodkaları yarat düyməsini basın. +Please select a Company first.,Əvvəlcə şirkət seçin. +Setting Conflict,Parametr ziddiyyəti +Check Required,Yoxlama tələb olunur +Missing Data,Çatışmayan məlumat +Missing Account,Çatışmayan hesab +Missing Account Mapping,Çatışmayan hesab xəritəsi +High Risk Configuration,Yüksək riskli konfiqurasiya +Non-standard Rate,Qeyri-standart dərəcə +Unusual Rate,Qeyri-adi dərəcə +No standard AZ accounts found. Set up manually.,Standart AZ hesabları tapılmadı. Əl ilə quraşdırın. +Load Default AZ Rates,Standart AZ dərəcələrini yüklə +Load Default Account Map,Standart hesab xəritəsini yüklə +Company changed. Save to run auto-detection.,Şirkət dəyişdi. Avtomatik aşkarlama üçün saxlayın. +VAT Input and Output accounts must be different!,ƏDV daxil və çıxış hesabları fərqli olmalıdır! +Profit Tax is replaced by Simplified Tax in non-General regimes.,Ümumi olmayan rejimlərdə mənfəət vergisi sadələşdirilmiş vergi ilə əvəz olunur. +Simplified Tax is not applicable in General (VAT + Profit) regime.,Sadələşdirilmiş vergi Ümumi (ƏDV + Mənfəət) rejimində tətbiq olunmur. +VAT is typically not applicable in Simplified/Special regime.,ƏDV adətən sadələşdirilmiş/xüsusi rejimlərdə tətbiq olunmur. +Actions,Əməliyyatlar +Activity Group,Fəaliyyət qrupu +Activity code not found in system,Fəaliyyət kodu sistemdə tapılmadı +Actual Address,Faktiki ünvan +Actual Address (Full),Faktiki ünvan (tam) +Additional Activity Codes,Əlavə fəaliyyət kodları +Additional activities to be added,Əlavə ediləcək əlavə fəaliyyətlər +Address for Mail,Poçt ünvanı +Affiliate Organizations,Əlaqəli təşkilatlar +Affiliate Organizations TINs,Əlaqəli təşkilatların VÖEN-ləri +Affiliate organizations to be added,Əlavə ediləcək əlaqəli təşkilatlar +Analyzing FIFO allocation...,FIFO bölüşdürməsi təhlil edilir... +An error occurred while fetching profile data,Profil məlumatları alınarkən xəta baş verdi +An unknown error occurred,Naməlum xəta baş verdi +April,Aprel +Article Name,Maddə adı +At least one of Seller or Organizer must be checked for gambling activities.,Qumar fəaliyyətləri üçün Satıcı və ya Təşkilatçıdan ən azı biri seçilməlidir. +August,Avqust +Authentication Complete,Autentifikasiya tamamlandı +Authentication Error,Autentifikasiya xətası +Authentication Failed,Autentifikasiya uğursuz oldu +Authentication Successful,Autentifikasiya uğurlu oldu +Authentication Timeout,Autentifikasiya müddəti bitdi +Authentication cancelled,Autentifikasiya ləğv edildi +Authentication request failed,Autentifikasiya sorğusu uğursuz oldu +Auto-detect Accounts,Hesabları avtomatik aşkarla +Auto-detection results:

{},Avtomatik aşkarlama nəticələri:

{} +Auto-submit means journal entries will be posted immediately and will affect account balances. Submitted entries require cancellation to undo. Are you sure?,Avtomatik təsdiqləmə journal entry-lərin dərhal göndəriləcəyi və hesab qalıqlarına təsir edəcəyi deməkdir. Təsdiqlənmiş qeydlər ləğv tələb edir. Əminsiniz? +Cancel,Ləğv et +Certificate,Sertifikat +Certificates,Sertifikatlar +Chief,Rəhbər +Chief PIN,Rəhbərin PİN-i +Classification,Təsnifat +Close,Bağla +Company Information,Şirkət məlumatı +Company Name,Şirkət adı +Company Required,Şirkət tələb olunur +Company data successfully updated from E-Taxes profile,Şirkət məlumatları E-Taxes profilindən uğurla yeniləndi +Completed Date,Tamamlanma tarixi +Completing Authentication,Autentifikasiya tamamlanır +Contact Information,Əlaqə məlumatları +Continue?,Davam edilsin? +Could not authenticate with Asan Imza,Asan İmza ilə autentifikasiya mümkün olmadı +Created,Yaradılıb +Creating allocation entries...,Bölüşdürmə provodkaları yaradılır... +DSMF Employee (DSMF - İşçi),DSMF İşçi payı +DSMF Employer (DSMF - İşəgötürən),DSMF İşəgötürən payı +Data Source,Məlumat mənbəyi +Date From,Tarixdən +Date Range (Optional),Tarix aralığı (İstəyə bağlı) +Date To,Tarixə +Date To cannot be earlier than Date From,Son tarix başlanğıc tarixdən əvvəl ola bilməz +Date of Establishment,Təsis tarixi +December,Dekabr +Declaration,Bəyannamə +Director Name,Direktor adı +Director Name (E-Taxes),Direktor adı (E-Taxes) +Director PIN,Direktorun PİN-i +Document No,Sənəd nömrəsi +Domain,Domen +Duration,Müddət +E-Taxes,E-Taxes +E-Taxes Authentication,E-Taxes Autentifikasiyası +E-Taxes Company Profile,E-Taxes Şirkət Profili +E-Taxes authentication is required. Would you like to authenticate now?,E-Taxes autentifikasiyası tələb olunur. İndi autentifikasiya etmək istəyirsiniz? +E-Taxes service is temporarily unavailable. Please try again in a few minutes.,E-Taxes xidməti müvəqqəti olaraq əlçatmazdır. Bir neçə dəqiqədən sonra yenidən cəhd edin. +Email,E-poçt +Employee Count,İşçi sayı +Employer,İşəgötürən +Employer Name,İşəgötürənin adı +Employer Position,İşəgötürənin vəzifəsi +Error checking authentication status,Autentifikasiya vəziyyəti yoxlanarkən xəta +Error updating company data,Şirkət məlumatları yenilənərkən xəta +Export XML,XML ixrac et +FIN,FİN +February,Fevral +Filter Settings,Filtr parametrləri +Fixing invoices...,Fakturalar düzəldilir... +From,Haradan +Gambling activity detected. Seller and Organizer flags enabled.,Qumar fəaliyyəti aşkarlandı. Satıcı və Təşkilatçı bayraqları aktivləşdirildi. +Generating timeline...,Zaman xətti yaradılır... +Get E-Taxes Profile,E-Taxes Profilini əldə et +Getting Asan Login settings...,Asan Login parametrləri alınır... +Getting certificates and selecting taxpayer...,Sertifikatlar alınır və vergi ödəyicisi seçilir... +Has Access,Giriş icazəsi var +Has Active Production Object,Aktiv istehsal obyekti var +House Number,Ev nömrəsi +Important Dates,Vacib tarixlər +In Cancellation Process,Ləğv prosesindədir +Invalid Date Range,Yanlış tarix aralığı +Is Chief of Any Legal Entity,Hər hansı hüquqi şəxsin rəhbəridir +Is Group,Qrupdur +Is Risky Taxpayer,Riskli vergi ödəyicisidir +Is Taxpayer in Cancellation Process,Vergi ödəyicisi ləğv prosesindədir +Items List,Elementlər siyahısı +January,Yanvar +July,İyul +June,İyun +Landline Phone,Stasionar telefon +Left,Sol +Legal Address,Hüquqi ünvan +Legal Address (Full),Hüquqi ünvan (tam) +Legal Address Details,Hüquqi ünvan təfərrüatları +Legal Address House Number,Hüquqi ünvan ev nömrəsi +Legal Address Locality,Hüquqi ünvan yaşayış məntəqəsi +Legal Address Postcode,Hüquqi ünvan poçt indeksi +Legal Address Region,Hüquqi ünvan regionu +Legal Address Room Number,Hüquqi ünvan otaq nömrəsi +Legal Address Street,Hüquqi ünvan küçəsi +Legal Form Code,Hüquqi forma kodu +Liquidated,Ləğv edilmişdir +Liquidation Date,Ləğv tarixi +Loading report...,Hesabat yüklənir... +Locality,Yaşayış məntəqəsi +Main Activity,Əsas fəaliyyət +Main Activity Code,Əsas fəaliyyət kodu +Management Information,İdarəetmə məlumatı +March,Mart +May,May +Medical Insurance Employee (Tibbi sığorta - İşçi),Tibbi sığorta İşçi payı +Medical Insurance Employer (Tibbi sığorta - İşəgötürən),Tibbi sığorta İşəgötürən payı +Mobile Phone,Mobil telefon +Name,Ad +Network Error,Şəbəkə xətası +No Asan Login Settings,Asan Login parametrləri yoxdur +No Certificates,Sertifikat yoxdur +No certificates found for this account,Bu hesab üçün sertifikat tapılmadı +No new data to update,Yeniləmə üçün yeni məlumat yoxdur +Non-gambling activity. Seller and Organizer flags disabled.,Qumar olmayan fəaliyyət. Satıcı və Təşkilatçı bayraqları deaktivləşdirildi. +November,Noyabr +OKVED,OKED +October,Oktyabr +Old Parent,Köhnə ana element +Operation Date,Əməliyyat tarixi +Organizational Structure,Təşkilati struktur +PIN,PİN +Parameters Detected,Parametrlər aşkarlandı +Parent Organization,Ana təşkilat +Parent Organization TIN,Ana təşkilatın VÖEN-i +Parent Tax Article,Ana vergi maddəsi +Phone,Telefon +Please check your phone and confirm the authentication request,Telefonunuzu yoxlayın və autentifikasiya sorğusunu təsdiqləyin +Please configure Asan Login settings first,Əvvəlcə Asan Login parametrlərini konfiqurasiya edin +Please enter the previous year's total profit tax amount below.,Əvvəlki ilin ümumi mənfəət vergisi məbləğini aşağıda daxil edin. +Please select a Tax Article first,Əvvəlcə vergi maddəsini seçin +Please wait...,Xahiş edirik gözləyin... +Position,Vəzifə +Postcode,Poçt indeksi +Processing Error,Emal xətası +Progress,İrəliləyiş +Property Type,Əmlak növü +Q1 (Jan-Mar),R1 (Yan-Mar) +Q2 (Apr-Jun),R2 (Apr-İyn) +Q3 (Jul-Sep),R3 (İyl-Sen) +Q4 (Oct-Dec),R4 (Okt-Dek) +Refresh Results,Nəticələri yenilə +Region,Region +Registration Date,Qeydiyyat tarixi +Registration Details,Qeydiyyat təfərrüatları +Registration Number,Qeydiyyat nömrəsi +Right,Sağ +Room Number,Otaq nömrəsi +Saved,Saxlanıldı +Save failed,Saxlama uğursuz oldu +Select Main Activity,Əsas fəaliyyəti seçin +Selecting certificate,Sertifikat seçilir +Selecting taxpayer,Vergi ödəyicisi seçilir +Seller and Organizer flags can only be set for gambling activities.,Satıcı və Təşkilatçı bayraqları yalnız qumar fəaliyyətləri üçün seçilə bilər. +Sending authentication request...,Autentifikasiya sorğusu göndərilir... +Separate Cost Center per Tax Type,Hər vergi növü üçün ayrı xərc mərkəzi +September,Sentyabr +Series,Seriya +Server Error,Server xətası +Server error during update. Please check the error logs.,Yeniləmə zamanı server xətası. Xəta jurnallarını yoxlayın. +Set Main Activity,Əsas fəaliyyəti təyin et +Simplified Tax - Catering (Sadələşdirilmiş - İaşə),Sadələşdirilmiş vergi - İaşə +Simplified Tax - Other (Sadələşdirilmiş - Digər),Sadələşdirilmiş vergi - Digər +Simplified Tax - Trade Baku (Sadələşdirilmiş - Ticarət Bakı),Sadələşdirilmiş vergi - Ticarət Bakı +Special Tax Regime,Xüsusi vergi rejimi +Sport Betting Operator,İdman mərcləri operatoru +Starting Authentication,Autentifikasiya başlayır +State Registration Authority,Dövlət qeydiyyat orqanı +State Registration Document Issued Date,Dövlət qeydiyyat sənədinin verilmə tarixi +Street,Küçə +Submitted,Təsdiqlənib +Suspension End Date,Dayandırma bitmə tarixi +Suspension Start Date,Dayandırma başlama tarixi +TAX-REP-.YYYY.-,TAX-REP-.YYYY.- +TIN,VÖEN +TIN Type,VÖEN növü +Tax Article,Vergi maddəsi +Tax Article Items Report,Vergi maddəsi elementləri hesabatı +Tax Authority Code,Vergi orqanı kodu +Tax ID,Vergi İD-si +Tax Information,Vergi məlumatı +Tax Period Closing Settings,Vergi Dövrü Bağlanış Parametrləri +Tax Rate,Vergi dərəcəsi +Tax System Type,Vergi sistemi növü +Tax Systems,Vergi sistemləri +Tax Systems List,Vergi sistemləri siyahısı +Tax return withheld at source of payment,Ödəmə mənbəyindən tutulan vergi bəyannaməsi +Tax return withheld at source of payment Elave 1,Ödəmə mənbəyindən tutulan vergi bəyannaməsi Əlavə 1 +Tax return withheld at source of payment Elave 2 Hisse1,Ödəmə mənbəyindən tutulan vergi bəyannaməsi Əlavə 2 Hissə 1 +Tax return withheld at source of payment Elave 2 Hisse1 2ci,Ödəmə mənbəyindən tutulan vergi bəyannaməsi Əlavə 2 Hissə 1 (2-ci) +Tax return withheld at source of payment Elave 2 Hisse2,Ödəmə mənbəyindən tutulan vergi bəyannaməsi Əlavə 2 Hissə 2 +Tax return withheld at source of payment Elave 2 Hisse2 2ci,Ödəmə mənbəyindən tutulan vergi bəyannaməsi Əlavə 2 Hissə 2 (2-ci) +Tax return withheld at source of payment Elave 2 Hisse3,Ödəmə mənbəyindən tutulan vergi bəyannaməsi Əlavə 2 Hissə 3 +Tax return withheld at source of payment Elave 2 Hisse3 2ci,Ödəmə mənbəyindən tutulan vergi bəyannaməsi Əlavə 2 Hissə 3 (2-ci) +Tax return withheld at source of payment Elave1 2ci,Ödəmə mənbəyindən tutulan vergi bəyannaməsi Əlavə 1 (2-ci) +Tax return withheld at source of payment Vergi Hesab,Ödəmə mənbəyindən tutulan vergi bəyannaməsi Vergi Hesab +Taxation System,Vergitutma sistemi +Taxes,Vergilər +Taxpayer Activity Group,Vergi ödəyicisi fəaliyyət qrupu +The authentication request has timed out. Please try again.,Autentifikasiya sorğusunun müddəti bitdi. Yenidən cəhd edin. +The following company fields will be updated with data from E-Taxes:,Aşağıdakı şirkət sahələri E-Taxes məlumatları ilə yenilənəcək: +The following data will be updated:,Aşağıdakı məlumatlar yenilənəcək: +The operation may take some time,Əməliyyat bir müddət çəkə bilər +This will create correction Journal Entries to move VAT from 521.3 to 245.1 and revenue from 601 to 542 for all unpaid Sales Invoices. Continue?,Bu ödənilməmiş bütün satış fakturaları üçün ƏDV-ni 521.3-dən 245.1-ə və gəliri 601-dən 542-yə köçürmək üçün düzəliş provodkaları yaradacaq. Davam edilsin? +This will send a request to your Asan Imza mobile app,Bu Asan İmza mobil tətbiqinizə sorğu göndərəcək +To,Haraya +Total Items Found,Tapılmış elementlərin ümumi sayı +Totals,Yekunlar +Type,Növ +Unemployment Insurance Employee (İşsizlik - İşçi),İşsizlik sığortası İşçi payı +Unemployment Insurance Employer (İşsizlik - İşəgötürən),İşsizlik sığortası İşəgötürən payı +Update,Yenilə +Update Company Fields,Şirkət sahələrini yenilə +Update Error,Yeniləmə xətası +Update cancelled,Yeniləmə ləğv edildi +Updating company data...,Şirkət məlumatları yenilənir... +Using certificate: ,Sertifikat istifadə olunur: +Validity Period,Etibarlılıq müddəti +VAT Certificate Date,ƏDV sertifikat tarixi +VAT Certificate Number,ƏDV sertifikat nömrəsi +VAT Info,ƏDV məlumatı +VAT Registration Date,ƏDV qeydiyyat tarixi +Waiting for confirmation on your phone,Telefonunuzda təsdiq gözlənilir +Warning: Both auto-submit and period closing are ON. Period Closing Voucher is irreversible once submitted.,Xəbərdarlıq: Həm avtomatik təsdiqləmə həm də dövr bağlanışı aktivdir. Dövr bağlanış çeki təsdiqlədikdən sonra geri qaytarıla bilməz. +Wizard Status,Sehrbaz vəziyyəti +XML file generated and downloaded,XML faylı yaradıldı və yükləndi +You are now authenticated with Asan Imza,Asan İmza ilə autentifikasiya olunmusunuz +You can now access E-Taxes services,İndi E-Taxes xidmətlərindən istifadə edə bilərsiniz +Your E-Taxes session has expired. Would you like to authenticate again?,E-Taxes sessiyanızın müddəti bitib. Yenidən autentifikasiya etmək istəyirsiniz? +activities,fəaliyyətlər +organizations,təşkilatlar +Business Entity Criteria,Sahibkarlıq subyektinin meyarları +Business Information,İş məlumatı +Company data successfully updated from E-Taxes profile,Şirkət məlumatları E-Taxes profilindən uğurla yeniləndi +Failed to get authentication settings,Autentifikasiya parametrləri alına bilmədi +Failed to get certificates,Sertifikatlar alına bilmədi +Failed to select certificate,Sertifikat seçilə bilmədi +Failed to select taxpayer,Vergi ödəyicisi seçilə bilmədi +Failed to start authentication process,Autentifikasiya prosesi başladıla bilmədi +Failed to update main activity,Əsas fəaliyyət yenilənə bilmədi +Accounting method:,Uçot metodu: +Accounts not found,Hesablar tapılmadı +Accrual method,Hesablama metodu +Accrual method: standard PCV,Hesablama metodu: standart PCV +All steps already completed.,Bütün addımlar artıq tamamlanıb. +All steps completed successfully!,Bütün addımlar uğurla tamamlandı! +All steps completed!,Bütün addımlar tamamlandı! +Allocation,Bölüşdürmə +Already fixed,Artıq düzəldilib +Amount (AZN),Məbləğ (AZN) +Applicable taxes:,Tətbiq olunan vergilər: +Applies to:,Tətbiq olunur: +Benefits will be detected after calculation.,Güzəştlər hesablamadan sonra aşkar ediləcək. +Calculate,Hesabla +Calculating...,Hesablanır... +Cash Method — Fix Result,Kassa metodu — Düzəliş nəticəsi +Cash Method — Verification Result,Kassa metodu — Yoxlama nəticəsi +Cash in (PE+226),Daxil olan (PE+226) +Cash method — invoices re-submitted,Kassa metodu — fakturalar yenidən təsdiqləndi +Cash out,Xaric olan +Changes,Dəyişikliklər +Click to open,Açmaq üçün klikləyin +Close all periods,Bütün dövrləri bağla +Confirm and create,Təsdiqlə və yarat +Confirming...,Təsdiqlənir... +Continue,Davam et +Continue without fixing,Düzəltmədən davam et +Correct entries,Düzgün qeydlər +Correct:,Düzgün: +Create accounts,Hesabları yarat +Created Documents,Yaradılmış sənədlər +Created documents,Yaradılmış sənədlər +Customer,Müştəri +Date,Tarix +Date range,Tarix aralığı +Declaration deadline,Bəyannamə müddəti +Declaration:,Bəyannamə: +Deductions,Tutulmalar +Disabled,Deaktiv +Employee count,İşçi sayı +Error:,Xəta: +Errors,Xətalar +Exemption:,Azadolma: +Expenses,Xərclər +FIFO Allocation completed,FIFO bölüşdürməsi tamamlandı +FIFO Allocation — All periods,FIFO bölüşdürmə — Bütün dövrlər +FIFO Cash Allocation — Preview,FIFO Kassa bölüşdürmə — Önbaxış +FIFO allocation already done for all periods. No new allocations.,Bütün dövrlər üçün FIFO bölüşdürməsi artıq edilib. Yeni bölüşdürmə yoxdur. +FIFO allocation not done yet. Click the FIFO Allocate button.,FIFO bölüşdürmə hələ edilməyib. FIFO Bölüşdür düyməsini basın. +Fix and continue,Düzəlt və davam et +Fix:,Həll: +Fixed invoices,Düzəldilmiş fakturalar +GRAND TOTAL TAX BURDEN,ÜMUMİ VERGİ YÜKÜ +Gross Pay,Ümumi əmək haqqı +Gross salary,Ümumi əmək haqqı +Income,Gəlir +Invoice,Faktura +Invoice account correction required,Faktura hesabları düzəlişi lazımdır +Invoices:,Fakturalar: +JE,JE +Journal Entries created,Journal entry-lər yaradıldı +Law:,Qanun: +Link,Keçid +N/A (Simplified),Tətbiq olunmur (Sadələşdirilmiş) +Net profit/loss:,Xalis mənfəət/zərər: +Net salary,Xalis əmək haqqı +Next step:,Növbəti addım: +No active step. Go to Timeline tab.,Aktiv addım yoxdur. Zaman xətti tabına keçin. +No journal entries created (amount = 0 or data collection only),Provodka yaradılmadı (məbləğ = 0 və ya yalnız məlumat toplanması) +No payments found for allocation in this period.,Bu dövrdə bölüşdürüləcək ödəniş tapılmadı. +No special tax benefits detected.,Xüsusi vergi güzəştləri aşkar edilmədi. +Not Met,Şərt yerinə yetirilməyib +Not detected,Aşkar edilməyib +Not set,Təyin edilməyib +Other activities — Maddə 220.1,Digər fəaliyyətlər — Maddə 220.1 +Payment,Ödəniş +Payment date,Ödəniş tarixi +Payment deadline,Ödəniş müddəti +Payment:,Ödəniş: +Payroll,Əmək haqqı +Payroll Data (Reference Only),Əmək haqqı məlumatları (yalnız istinad) +Payroll Taxes (collected data),Əmək haqqı vergiləri (toplanmış məlumat) +Period:,Dövr: +Periods:,Dövrlər: +Previous steps have been saved.,Əvvəlki addımlar saxlanıldı. +Recalculate,Yenidən hesabla +Report,Hesabat +Required accounts for cash method not found:,Kassa metodu üçün lazım olan hesablar tapılmadı: +Revenue recognition:,Gəlir tanınması: +Select a Company to enable auto-detection.,Avtomatik aşkarlama üçün şirkət seçin. +Skip,Keç +Skipping...,Keçilir... +Standard VAT rate — Maddə 159 Tax Code,Standart ƏDV dərəcəsi — Vergi Məcəlləsi Maddə 159 +Step completed,Addım tamamlandı +Step completed successfully,Addım uğurla tamamlandı +Stop,Dayandır +Tax Reduction:,Vergi azalması: +Tax amount,Vergi məbləği +Tax:,Vergi: +Taxes:,Vergilər: +Total,Cəmi +Total Tax Entries,Ümumi vergi provodkaları +Total expense,Cəmi xərc +Total income,Cəmi gəlir +Total incorrect VAT:,Ümumi yanlış ƏDV: +Type:,Növ: +Upcoming Deadlines,Yaxınlaşan müddətlər +VAT Accounts,ƏDV hesabları +VAT recognition:,ƏDV tanınması: +Warning:,Xəbərdarlıq: +Will use company default,Şirkətin standart parametri istifadə ediləcək +and more,və daha +payable,ödəniləcək +refundable,geri qaytarılacaq +No VAT,ƏDV yoxdur +Profit Tax (advance),Mənfəət vergisi (avans) +Profit Tax (annual),Mənfəət vergisi (illik) +Property Tax (1/4),Əmlak vergisi (1/4) +Property Tax (annual),Əmlak vergisi (illik) +Simplified Tax — 2%/4%/8% of revenue quarterly,"Sadələşdirilmiş vergi — gəlirin 2%/4%/8%-i, rüblük" +VAT (ƏDV) — 18% monthly declaration by 20th of next month,"ƏDV — 18%, aylıq, növbəti ayın 20-dək bəyannamə" +Profit Tax (Mənfəət) — 20% annual declaration (Mar 31) quarterly advances,"Mənfəət vergisi — 20%, illik bəyannamə (Mar 31), rüblük avanslar" +Property Tax (Əmlak) — 1% annual declaration quarterly advances,"Əmlak vergisi — 1%, illik bəyannamə, rüblük avanslar" +Special Simplified Tax — rates vary by activity type,Xüsusi sadələşdirilmiş vergi — dərəcələr fəaliyyət növünə görə dəyişir +Most common regime for medium/large businesses with VAT registration.,ƏDV qeydiyyatlı orta/böyük biznes üçün ən geniş yayılmış rejim. +For micro/small businesses WITHOUT VAT registration. Replaces VAT + Profit Tax.,ƏDV qeydiyyatı OLMAYAN mikro/kiçik biznes üçün. ƏDV + Mənfəət vergisini əvəz edir. +For construction real estate housing cash withdrawals. Uses special declaration forms.,"Tikinti, daşınmaz əmlak, mənzil, nağd pul çıxarışları üçün. Xüsusi bəyannamə formaları istifadə olunur." +Corporate profit tax — Maddə 104 Tax Code,Korporativ mənfəət vergisi — Vergi Məcəlləsi Maddə 104 +Fixed assets — Maddə 199 Tax Code,Əsas vəsaitlər — Vergi Məcəlləsi Maddə 199 +Retail/wholesale trade in Baku — Maddə 220.1,Bakıda pərakəndə/topdan ticarət — Maddə 220.1 +Catering/public food services — Maddə 220.1,İctimai iaşə xidmətləri — Maddə 220.1 +Employer contribution — İşsizlikdən sığorta haqqında qanun,İşəgötürən payı — İşsizlikdən sığorta haqqında qanun +Employee withholding — İşsizlikdən sığorta haqqında qanun,İşçi tutulması — İşsizlikdən sığorta haqqında qanun +Social insurance employer — DSMF qanunu,Sosial sığorta işəgötürən — DSMF qanunu +Social insurance employee — DSMF qanunu,Sosial sığorta işçi — DSMF qanunu +Medical insurance employer — İcbari tibbi sığorta qanunu,Tibbi sığorta işəgötürən — İcbari tibbi sığorta qanunu +Medical insurance employee — İcbari tibbi sığorta qanunu,Tibbi sığorta işçi — İcbari tibbi sığorta qanunu +VAT Input Offset,ƏDV daxil əvəzləşdirmə +Deferred VAT (Sales),Gözləmədə ƏDV (satış) +Deferred VAT (Purchases),Gözləmədə ƏDV (alış) +Deferred Revenue (542),Gözləmədə gəlir (542) +VAT (Value Added Tax),ƏDV (Əlavə Dəyər Vergisi) +Profit Tax,Mənfəət vergisi +Property Tax,Əmlak vergisi +Land Tax,Torpaq vergisi +"Select the company for which these settings apply. All auto-detection (regime, accounts, rates) will use data from this company. If you have multiple companies, the wizard will let you override per-run.","Bu parametrlərin tətbiq olunacağı şirkəti seçin. Bütün avtomatik aşkarlama (rejim, hesablar, dərəcələr) bu şirkətin məlumatlarından istifadə edəcək. Əgər bir neçə şirkətiniz varsa, sehrbaz hər işə salma zamanı dəyişməyə imkan verəcək." +"When enabled, saving this form will automatically detect: tax regime, VAT payer status, OKVED code, available assets, employees, and set recommended values for all settings below. You can always override any auto-detected value manually.","Aktivləşdirildikdə, formanın yadda saxlanması avtomatik olaraq aşkarlayacaq: vergi rejimi, ƏDV ödəyicisi statusu, OKVED kodu, mövcud aktivlər, işçilər və aşağıdakı bütün parametrlər üçün tövsiyə olunan dəyərləri təyin edəcək. Siz hər zaman istənilən avtomatik aşkarlanmış dəyəri əl ilə dəyişə bilərsiniz." +"Read from Company settings. Kassa metodu: revenue, expenses, and VAT obligations are recognized only when payment is received/made. Hesablama metodu: recognized at invoice date. + +Per AZ Tax Code: Micro and Small entrepreneurs can use Kassa metodu. Medium and Big must use Hesablama metodu.","Şirkət parametrlərindən oxunur. Kassa metodu: gəlirlər, xərclər və ƏDV öhdəlikləri yalnız ödəniş alındığında/edildikdə tanınır. Hesablama metodu: faktura tarixində tanınır. + +AR Vergi Məcəlləsinə əsasən: mikro və kiçik sahibkarlar kassa metodundan istifadə edə bilərlər. Orta və böyüklər hesablama metodundan istifadə etməlidirlər." +"Account 245.1 — ƏDV gözləmədə (satış). Deferred output VAT — posted at Invoice, transferred to 521.3 at Payment.","Hesab 245.1 — ƏDV gözləmədə (satış). Təxirə salınmış çıxış ƏDV-si — fakturada əks olunur, ödənişdə 521.3-ə köçürülür." +"Account 245.2 — ƏDV gözləmədə (alış). Deferred input VAT — posted at Invoice, transferred to 226 at Payment.","Hesab 245.2 — ƏDV gözləmədə (alış). Təxirə salınmış daxil olan ƏDV — fakturada əks olunur, ödənişdə 226-ya köçürülür." +"Account 542 — Gələcək hesabat dövrünün gəlirləri. Deferred revenue — posted at Invoice, transferred to 601 at Payment.","Hesab 542 — Gələcək hesabat dövrünün gəlirləri. Təxirə salınmış gəlir — fakturada əks olunur, ödənişdə 601-ə köçürülür." +"If ON: the tax regime is determined automatically from the company's VAT certificate and OKVED code. Companies with a VAT certificate → General regime. Companies without → Simplified. Special OKVED codes (construction, real estate) → Special regime. If OFF: the regime below is used as-is for every wizard run.","Aktivdirsə: vergi rejimi şirkətin ƏDV şəhadətnaməsi və OKVED koduna əsasən avtomatik müəyyən edilir. ƏDV şəhadətnaməsi olan şirkətlər → Ümumi rejim. Olmayan şirkətlər → Sadələşdirilmiş. Xüsusi OKVED kodları (tikinti, daşınmaz əmlak) → Xüsusi rejim. Deaktivdirsə: aşağıdakı rejim hər sehrbaz işə salınması üçün olduğu kimi istifadə olunur." +"This determines which taxes the wizard will calculate: + +• General (VAT + Profit) — VAT monthly + Profit Tax quarterly. Most common for medium/large businesses registered as VAT payers. +• Simplified Tax — Quarterly simplified tax on revenue. For micro/small businesses NOT registered for VAT. +• Special Regime — Construction, real estate, housing, cash withdrawals. Uses simplified tax with special declaration forms. + +If 'Auto-detect' is ON above, this field shows the detected value but can still be changed.","Sehrbazın hansı vergiləri hesablayacağını müəyyən edir: + +• General (VAT + Profit) — ƏDV aylıq + Mənfəət vergisi rüblük. ƏDV ödəyicisi kimi qeydiyyatda olan orta/böyük müəssisələr üçün ən çox yayılmış. +• Simplified Tax — Gəlirə rüblük sadələşdirilmiş vergi. ƏDV üçün qeydiyyata alınMAMIŞ mikro/kiçik müəssisələr üçün. +• Special Regime — Tikinti, daşınmaz əmlak, mənzil təsərrüfatı, nağd pul çıxarışları. Xüsusi bəyannamə formaları ilə sadələşdirilmiş vergi. + +Yuxarıda «Avtomatik aşkarlama» aktivdirsə, bu sahə aşkarlanmış dəyəri göstərir, lakin dəyişdirilə bilər." +"If ON (recommended): before creating any journal entry, the wizard scans GL Entries to check if a matching entry already exists for the same tax type, accounts, and period. + +If an existing entry is found: +• The wizard marks it as 'Existing' (green) and shows the existing JE number and amount +• No duplicate entry is created +• If the calculated amount differs from the existing amount, a warning with the difference is shown + +This is essential when tax entries come from external sources (e.g., e-taxes portal imports) or were created manually. The wizard automatically adapts — no configuration needed per period.","Aktivdirsə (tövsiyə olunur): hər hansı provodka yaratmazdan əvvəl, sehrbaz Baş Kitab qeydlərini yoxlayaraq eyni vergi növü, hesablar və dövr üçün uyğun qeydin artıq mövcud olub-olmadığını aşkarlayır. + +Mövcud qeyd tapılarsa: +• Sehrbaz onu «Mövcud» (yaşıl) kimi işarələyir və mövcud provodka nömrəsini və məbləğini göstərir +• Dublikat qeyd yaradılmır +• Hesablanmış məbləğ mövcud məbləğdən fərqlənirsə, fərq ilə xəbərdarlıq göstərilir + +Bu, vergi provodkaları xarici mənbələrdən gəldikdə (məs., e-taxes portalından idxal) və ya əl ilə yaradıldıqda vacibdir. Sehrbaz avtomatik uyğunlaşır — hər dövr üçün konfiqurasiya tələb olunmur." +"If ON: the wizard will create a new journal entry even if a matching one already exists. Use only if you intentionally need duplicate entries (very rare). + +If OFF (default, safe): existing entries are respected and not duplicated.","Aktivdirsə: sehrbaz uyğun provodka artıq mövcud olsa belə yeni provodka yaradacaq. Yalnız bilərəkdən dublikat qeydlərə ehtiyacınız varsa istifadə edin (çox nadir). + +Deaktivdirsə (standart, təhlükəsiz): mövcud qeydlər nəzərə alınır və dublikat yaradılmır." +"If ON: when an existing entry is found but its amount differs from the wizard's calculation, the wizard will offer a correction entry for the difference. + +For example: existing VAT entry = 1,296 AZN, wizard calculates 1,300 AZN → offers a correction entry of 4 AZN. + +The correction entry is shown in Entry Preview with status 'Difference' (orange) and is optional — you can disable it per row. + +If OFF: differences are only logged as warnings, no correction entries are offered.","Aktivdirsə: mövcud qeyd tapılıb, lakin onun məbləği sehrbazın hesablamasından fərqləndikdə, sehrbaz fərq üçün düzəliş provodkası təklif edəcək. + +Məsələn: mövcud ƏDV provodkası = 1 296 AZN, sehrbaz 1 300 AZN hesablayır → 4 AZN-lik düzəliş provodkası təklif edir. + +Düzəliş provodkası Provodka Önbaxışında «Fərq» (narıncı) statusu ilə göstərilir və isteğe bağlıdır — hər sətir üçün söndürə bilərsiniz. + +Deaktivdirsə: fərqlər yalnız xəbərdarlıq kimi qeydə alınır, düzəliş provodkaları təklif olunmur." +"If ON: journal entries created by the wizard will be immediately submitted (docstatus=1) and will affect account balances right away. Use this only if you trust the calculation and don't need manual review. + +If OFF (recommended): entries are created as Draft. You can review each one in the Journal Entry list and submit manually or in bulk. This is safer — you can edit or delete drafts if something looks wrong.","Aktivdirsə: sehrbaz tərəfindən yaradılmış provodkalar dərhal təsdiqlənəcək (docstatus=1) və hesab qalıqlarına dərhal təsir edəcək. Bunu yalnız hesablamaya etibar edirsinizsə və əl ilə yoxlamaya ehtiyac yoxdursa istifadə edin. + +Deaktivdirsə (tövsiyə olunur): provodkalar Qaralama olaraq yaradılır. Hər birini provodka siyahısında nəzərdən keçirib əl ilə və ya toplu şəkildə təsdiqləyə bilərsiniz. Bu daha təhlükəsizdir — nəsə yanlış görünürsə, qaralmaları redaktə edə və ya silə bilərsiniz." +"Controls what happens when you run the wizard for a period that was already closed. + +If OFF (default, safe): the wizard will refuse to run and show which previous wizard closed this period. This prevents accidental double-posting. + +If ON: the wizard will cancel all journal entries from the previous run and create new ones. The old wizard document will be marked as superseded. Use this when you need to correct previously posted tax entries (e.g., after fixing invoices or asset values).","Artıq bağlanmış dövr üçün sehrbazı işə saldıqda nə baş verdiyini idarə edir. + +Deaktivdirsə (standart, təhlükəsiz): sehrbaz işə düşməkdən imtina edəcək və hansı əvvəlki sehrbazın bu dövrü bağladığını göstərəcək. Bu, təsadüfi ikiqat əks etdirmənin qarşısını alır. + +Aktivdirsə: sehrbaz əvvəlki işə salmadan bütün provodkaları ləğv edib yenilərini yaradacaq. Köhnə sehrbaz sənədi əvəz edilmiş kimi qeyd olunacaq. Bunu əvvəllər əks etdirilmiş vergi provodkalarını düzəltmək lazım olduqda istifadə edin (məs., fakturaları və ya aktiv dəyərlərini düzəltdikdən sonra)." +"How the wizard checks for duplicate closings: + +• Same Company + Same Period — blocks if the exact same company and date range was already closed. Most restrictive. +• Same Company + Overlapping Dates — blocks if any part of the period overlaps with a previous closing. Catches partial overlaps. + +Only relevant when 'Allow Re-closing' is OFF.","Sehrbaz dublikat bağlanışları necə yoxlayır: + +• Same Company + Same Period — eyni şirkət və tarix aralığı artıq bağlanıbsa blok edir. Ən sərt variant. +• Same Company + Overlapping Dates — dövrün hər hansı hissəsi əvvəlki bağlanışla üst-üstə düşürsə blok edir. Qismən üst-üstə düşmələri tutur. + +Yalnız «Yenidən bağlamaya icazə» deaktiv olduqda aktualdır." +"If ON: after creating tax journal entries, the wizard will also create a standard ERPNext Period Closing Voucher. This closes all Income and Expense accounts and transfers the net result to the Closing Account below. + +If OFF (default): the wizard only creates tax accrual entries. You close P&L manually via Accounting → Period Closing Voucher. Recommended to keep OFF if your accountant closes periods separately. + +IMPORTANT: Period Closing Voucher is irreversible after submission. The wizard will create it in Draft if 'Auto-submit' is OFF.","Aktivdirsə: vergi provodkalarını yaratdıqdan sonra sehrbaz həmçinin standart ERPNext Dövr Bağlama Sənədi yaradacaq. Bu, bütün Gəlir və Xərc hesablarını bağlayır və xalis nəticəni aşağıdakı Bağlama Hesabına köçürür. + +Deaktivdirsə (standart): sehrbaz yalnız vergi hesablama provodkaları yaradır. M/Z (mənfəət/zərər) hesablarını əl ilə Mühasibat → Dövr Bağlama Sənədi vasitəsilə bağlayırsınız. Mühasibin dövrləri ayrıca bağladığı halda deaktiv saxlamaq tövsiyə olunur. + +VACİB: Dövr Bağlama Sənədi təsdiqlənmədən sonra geri qaytarılmazdır. «Avtomatik təsdiqləmə» deaktivdirsə, sehrbaz onu Qaralama olaraq yaradacaq." +"The equity/liability account where net profit or loss will be transferred when P&L accounts are closed. + +Standard AZ Chart of Accounts: +• 341 — Hesabat dövrünün xalis mənfəəti (Current period net profit) +• 342 — Keçmiş illərin bölüşdürülməmiş mənfəəti (Retained earnings) + +The account MUST be of type Liability or Equity, and in the same currency as the company.","M/Z (mənfəət/zərər) hesabları bağlandıqda xalis mənfəət və ya zərərin köçürüləcəyi kapital/öhdəlik hesabı. + +Standart AZ Hesablar Planı: +• 341 — Hesabat dövrünün xalis mənfəəti +• 342 — Keçmiş illərin bölüşdürülməmiş mənfəəti + +Hesab mütləq Öhdəlik və ya Kapital növündə və şirkətlə eyni valyutada olmalıdır." +"If ON: the wizard will create a draft of the corresponding tax declaration (e.g., Declaration of Value Added Tax, Income Tax Return) pre-filled with calculated values. + +If OFF (default): only journal entries are created. Declarations are filed separately via Tax Declaration doctypes or the E-Taxes portal. + +Note: This feature is under development. Currently only VAT and Simplified Tax declarations are supported.","Aktivdirsə: sehrbaz hesablanmış dəyərlərlə əvvəlcədən doldurulmuş müvafiq vergi bəyannaməsinin (məs., ƏDV bəyannaməsi, Mənfəət vergisi bəyannaməsi) qaralama versiyasını yaradacaq. + +Deaktivdirsə (standart): yalnız provodkalar yaradılır. Bəyannamələr ayrıca Vergi Bəyannaməsi formları və ya E-Taxes portalı vasitəsilə təqdim olunur. + +Qeyd: bu funksiya inkişaf mərhələsindədir. Hazırda yalnız ƏDV və sadələşdirilmiş vergi bəyannamələri dəstəklənir." +"If ON: declaration fields (VOEN, activity code, tax authority, etc.) will be auto-filled from Company data and calculated amounts. + +If OFF: only the amounts section will be filled; header fields (VOEN, period, etc.) are left blank for manual entry. + +Requires 'Create Declaration Draft' to be enabled.","Aktivdirsə: bəyannamə sahələri (VÖEN, fəaliyyət kodu, vergi orqanı və s.) Şirkət məlumatları və hesablanmış məbləğlərdən avtomatik doldurulacaq. + +Deaktivdirsə: yalnız məbləğlər bölməsi doldurulacaq; başlıq sahələri (VÖEN, dövr və s.) əl ilə daxil etmək üçün boş qalacaq. + +«Bəyannamə qaralama yarat»ın aktiv olmasını tələb edir." +"All tax journal entries will use this cost center by default. If left blank, the company's default cost center is used. + +You can override this per wizard run.","Bütün vergi provodkaları standart olaraq bu məsrəf mərkəzindən istifadə edəcək. Boş qaldıqda şirkətin standart məsrəf mərkəzi istifadə olunur. + +Bunu hər sehrbaz işə salmasında dəyişə bilərsiniz." +"If ON: each tax type can have its own cost center (configured in the Account Mapping table below). For example, VAT entries go to 'Sales' cost center, while property tax goes to 'Admin'. + +If OFF (default): all tax entries use the single default cost center above.","Aktivdirsə: hər vergi növünün öz məsrəf mərkəzi ola bilər (aşağıdakı Hesab Xəritəsi cədvəlində konfiqurasiya edilir). Məsələn, ƏDV provodkaları «Satış» məsrəf mərkəzinə, əmlak vergisi isə «İnzibati» mərkəzə yönləndirilir. + +Deaktivdirsə (standart): bütün vergi provodkaları yuxarıdakı vahid standart məsrəf mərkəzindən istifadə edir." +"Enable VAT calculation in the wizard. The wizard will calculate the difference between output VAT (collected from customers) and input VAT (paid to suppliers) for each month in the period. + +Disable this only if you handle VAT entries manually or via a separate process.","Sehrbazda ƏDV hesablamasını aktivləşdirin. Sehrbaz dövrdəki hər ay üçün çıxış ƏDV-si (müştərilərdən toplanan) ilə daxil olan ƏDV (təchizatçılara ödənilən) arasındakı fərqi hesablayacaq. + +Bunu yalnız ƏDV provodkalarını əl ilə və ya ayrı proses vasitəsilə aparırsınızsa deaktiv edin." +"Standard VAT rate in Azerbaijan is 18% (Maddə 159 of the Tax Code). This is used for reference and reporting only — the actual VAT amounts are calculated from GL account balances, not by applying this rate. + +Change only if the government announces a new rate.","Azərbaycanda standart ƏDV dərəcəsi 18%-dir (Vergi Məcəlləsinin Maddə 159). Bu yalnız arayış və hesabat üçün istifadə olunur — faktiki ƏDV məbləğləri BK hesab qalıqlarından hesablanır, bu dərəcənin tətbiqi ilə deyil. + +Yalnız hökumət yeni dərəcə elan edərsə dəyişdirin." +"The asset account where input VAT (VAT paid to suppliers on purchases) is recorded. + +Standard AZ: Account 226 — ƏDV sub-uçot hesabı. + +This account has a DEBIT balance. The wizard reads this balance to determine how much input VAT you can offset against output VAT.","Daxil olan ƏDV-nin (alışlarda təchizatçılara ödənilən ƏDV) uçota alındığı aktiv hesabı. + +Standart AZ: Hesab 226 — ƏDV sub-uçot hesabı. + +Bu hesabın DEBİT qalığı var. Sehrbaz bu qalığı oxuyaraq çıxış ƏDV-si ilə nə qədər daxil olan ƏDV-ni əvəzləşdirə biləcəyinizi müəyyən edir." +"The liability account where output VAT (VAT collected from customers on sales) is recorded. + +Standard AZ: Account 521.3 — ƏDV. + +This account has a CREDIT balance. The wizard reads this balance and offsets it against input VAT to determine the net VAT payable to the budget.","Çıxış ƏDV-sinin (satışlarda müştərilərdən toplanan ƏDV) uçota alındığı öhdəlik hesabı. + +Standart AZ: Hesab 521.3 — ƏDV. + +Bu hesabın KREDİT qalığı var. Sehrbaz bu qalığı oxuyur və büdcəyə ödəniləcək xalis ƏDV-ni müəyyən etmək üçün daxil olan ƏDV ilə əvəzləşdirir." +"If ON: after calculating VAT from GL balances, the wizard will compare the result with the VAT Allocation doctype (if you use it). Any discrepancy will be logged as a warning. + +If OFF: VAT is calculated purely from GL account balances. This is simpler and works for most companies. + +Enable only if you actively use the VAT Allocation system in taxes_az for distributing input VAT across taxable/exempt operations.","Aktivdirsə: BK qalıqlarından ƏDV hesablandıqdan sonra sehrbaz nəticəni ƏDV Bölüşdürmə sənədi ilə (istifadə edirsinizsə) müqayisə edəcək. Hər hansı uyğunsuzluq xəbərdarlıq olaraq qeydə alınacaq. + +Deaktivdirsə: ƏDV yalnız BK hesab qalıqlarından hesablanır. Bu daha sadədir və əksər şirkətlər üçün işləyir. + +Yalnız taxes_az-da daxil olan ƏDV-ni vergiyə cəlb olunan/azad olunan əməliyyatlar arasında bölüşdürmək üçün ƏDV Bölüşdürmə sistemini aktiv istifadə edirsinizsə aktivləşdirin." +"How VAT amounts are determined: + +• GL Account Balance — reads debit/credit balances from VAT accounts (226 and 521.3) for each month. Simple, reliable, captures all transactions including manual adjustments. + +• Sales/Purchase Invoices — sums VAT from individual invoices. More granular but may miss manual journal entries or corrections. + +Recommended: GL Account Balance.","ƏDV məbləğləri necə müəyyən edilir: + +• GL Account Balance — hər ay üçün ƏDV hesablarından (226 və 521.3) debit/kredit qalıqlarını oxuyur. Sadə, etibarlı, əl ilə düzəlişlər daxil bütün əməliyyatları əhatə edir. + +• Sales/Purchase Invoices — ayrı-ayrı fakturalardan ƏDV-ni cəmləyir. Daha ətraflı, lakin əl ilə provodkaları və ya düzəlişləri qaçıra bilər. + +Tövsiyə olunur: GL Account Balance." +"Enable profit tax calculation. The wizard will calculate quarterly advance payments based on the method selected below. + +Per AZ Tax Code: annual declaration is due by March 31 of the following year, but advance payments are due quarterly (by the 15th of the month after the quarter ends). + +Disable only if you handle profit tax entries manually.","Mənfəət vergisi hesablamasını aktivləşdirin. Sehrbaz aşağıda seçilmiş metoda əsasən rüblük avans ödənişlərini hesablayacaq. + +AR Vergi Məcəlləsinə əsasən: illik bəyannamə növbəti ilin mart ayının 31-dək təqdim edilməlidir, lakin avans ödənişləri rüblük (rüb bitdikdən sonrakı ayın 15-dək) ödənilməlidir. + +Yalnız mənfəət vergisi provodkalarını əl ilə aparırsınızsa deaktiv edin." +"Corporate profit tax rate. Standard rate in Azerbaijan is 20% (Maddə 104 of the Tax Code). + +Applied to: Net Profit = Total Income − Total Expenses for the period. + +Change only if your company qualifies for a reduced rate (e.g., certain agricultural or IT activities may have incentives).","Mənfəət vergisi dərəcəsi. Azərbaycanda standart dərəcə 20%-dir (Vergi Məcəlləsinin Maddə 104). + +Tətbiq olunur: Xalis Mənfəət = Dövr üçün Ümumi Gəlir − Ümumi Xərclər. + +Yalnız şirkətiniz azaldılmış dərəcə üçün uyğundursa dəyişdirin (məs., müəyyən kənd təsərrüfatı və ya İT fəaliyyətləri üçün güzəştlər ola bilər)." +"How quarterly advance payments are calculated: + +• Actual Quarterly Profit — calculates 20% of the actual profit for the current quarter (Income − Expenses from P&L). More accurate, reflects current performance. Recommended for growing or seasonal businesses. + +• 1/4 of Previous Year Tax — takes the total profit tax from the previous year and divides by 4. Simpler, more predictable payments. Better for stable businesses. Requires 'Previous Year Tax Amount' to be filled in. + +Both methods are accepted by the AZ tax authorities. The annual declaration reconciles any over/underpayment.","Rüblük avans ödənişləri necə hesablanır: + +• Actual Quarterly Profit — cari rüb üçün faktiki mənfəətin 20%-ni hesablayır (M/Z-dən Gəlirlər − Xərclər). Daha dəqiq, cari fəaliyyəti əks etdirir. Böyüyən və ya mövsümi bizneslər üçün tövsiyə olunur. + +• 1/4 of Previous Year Tax — əvvəlki ildən ümumi mənfəət vergisini götürüb 4-ə bölür. Daha sadə, daha proqnozlaşdırılan ödənişlər. Sabit bizneslər üçün daha yaxşıdır. «Əvvəlki il vergi məbləği»nin doldurulmasını tələb edir. + +Hər iki metod AZ vergi orqanları tərəfindən qəbul edilir. İllik bəyannamə hər hansı artıq/az ödənişi uzlaşdırır." +"Enter the total profit tax amount from the previous fiscal year. The wizard will divide this by 4 for each quarterly advance payment. + +You can find this in last year's Income Tax Return (field: total calculated tax) or from the previous year's Tax Period Closing Wizard summary. + +Leave at 0 if using 'Actual Quarterly Profit' method.","Əvvəlki maliyyə ilindən ümumi mənfəət vergisi məbləğini daxil edin. Sehrbaz bunu hər rüblük avans ödənişi üçün 4-ə bölür. + +Bunu keçən ilin Mənfəət Vergisi Bəyannaməsində (sahə: ümumi hesablanmış vergi) və ya əvvəlki ilin Vergi Dövrü Bağlanış Sehrbazı xülasəsində tapa bilərsiniz. + +«Actual Quarterly Profit» metodundan istifadə edirsinizsə, 0 olaraq buraxın." +"Enable simplified tax calculation. Applied quarterly to total revenue (not profit). + +Per AZ Tax Code (Maddə 218-227): simplified tax replaces VAT and profit tax for qualifying businesses. Declaration and payment are due by the 20th of the month after the quarter. + +Disable only if you handle simplified tax entries manually.","Sadələşdirilmiş vergi hesablamasını aktivləşdirin. Ümumi gəlirə (mənfəətə deyil) rüblük tətbiq olunur. + +AR Vergi Məcəlləsinə əsasən (Maddə 218-227): sadələşdirilmiş vergi uyğun müəssisələr üçün ƏDV və mənfəət vergisini əvəz edir. Bəyannamə və ödəniş rübdən sonrakı ayın 20-dək. + +Yalnız sadələşdirilmiş vergi provodkalarını əl ilə aparırsınızsa deaktiv edin." +"If ON: the wizard determines the simplified tax rate automatically based on the company's OKVED (activity code): + +• Trade activities in Baku → 2% +• Catering (public food) → 8% +• All other activities → 4% + +If OFF: the 'Default Rate' below is used for all calculations regardless of activity type.","Aktivdirsə: sehrbaz sadələşdirilmiş vergi dərəcəsini şirkətin OKVED (fəaliyyət kodu) əsasında avtomatik müəyyən edir: + +• Bakıda ticarət fəaliyyətləri → 2% +• İctimai iaşə → 8% +• Bütün digər fəaliyyətlər → 4% + +Deaktivdirsə: fəaliyyət növündən asılı olmayaraq bütün hesablamalar üçün aşağıdakı «Standart dərəcə» istifadə olunur." +"The flat simplified tax rate applied to total revenue. + +Standard rates per AZ Tax Code (Maddə 220.1): +• 2% — Retail/wholesale trade in Baku +• 4% — Services, production, and other activities +• 8% — Catering/public food services + +This field is only used when 'Auto-detect by OKVED' is OFF.","Ümumi gəlirə tətbiq olunan sabit sadələşdirilmiş vergi dərəcəsi. + +AR Vergi Məcəlləsinə əsasən standart dərəcələr (Maddə 220.1): +• 2% — Bakıda pərakəndə/topdan ticarət +• 4% — Xidmətlər, istehsal və digər fəaliyyətlər +• 8% — İctimai iaşə xidmətləri + +Bu sahə yalnız «OKVED üzrə avtomatik aşkarlama» deaktiv olduqda istifadə olunur." +"What counts as 'revenue' for simplified tax calculation: + +• Total Income (P&L) — sums all Income-type accounts from the P&L for the quarter. Includes sales revenue, other operating income, etc. Most common. + +• Sales Revenue Only — only account 601 (Satış gəlirləri). Excludes other income like interest, rent, etc. Use if your other income is not subject to simplified tax. + +• Cash Receipts — revenue recognized when cash is received (cash-basis). For companies using cash-basis accounting.","Sadələşdirilmiş vergi hesablaması üçün nə «gəlir» sayılır: + +• Total Income (P&L) — rüb üçün M/Z-dən bütün Gəlir tipli hesabları cəmləyir. Satış gəliri, digər əməliyyat gəlirləri və s. daxildir. Ən çox yayılmış. + +• Sales Revenue Only — yalnız hesab 601 (Satış gəlirləri). Faiz, icarə və s. kimi digər gəlirləri istisna edir. Digər gəlirləriniz sadələşdirilmiş vergiyə cəlb olunmursa istifadə edin. + +• Cash Receipts — nağd pul alındıqda tanınan gəlir (kassa metodu). Kassa metodu ilə mühasibat uçotu aparan şirkətlər üçün." +"Enable property tax calculation on fixed assets. Calculated annually but paid in quarterly advances (1/4 each quarter). + +Per AZ Tax Code (Maddə 196-202): tax is levied on the average annual net book value of fixed assets (buildings, equipment, vehicles, etc.). + +Disable if the company has no fixed assets or all assets are tax-exempt under Maddə 199.4.","Əsas vəsaitlər üzrə əmlak vergisi hesablamasını aktivləşdirin. İllik hesablanır, lakin rüblük avanslarla ödənilir (hər rüb 1/4). + +AR Vergi Məcəlləsinə əsasən (Maddə 196-202): vergi əsas vəsaitlərin (binalar, avadanlıq, nəqliyyat vasitələri və s.) orta illik qalıq dəyərinə tətbiq olunur. + +Şirkətin əsas vəsaitləri yoxdursa və ya bütün aktivlər Maddə 199.4 üzrə vergidən azaddırsa deaktiv edin." +"Property tax rate on the average annual residual value of fixed assets. + +Standard rate: 1% (Maddə 199 of the Tax Code). + +Applied to: (Value at start of year + Value at end of year) / 2. + +This rate applies uniformly to all taxable asset types (buildings, equipment, vehicles, etc.).","Əsas vəsaitlərin orta illik qalıq dəyərinə əmlak vergisi dərəcəsi. + +Standart dərəcə: 1% (Vergi Məcəlləsinin Maddə 199). + +Tətbiq olunur: (İl əvvəlinə dəyər + İl sonuna dəyər) / 2. + +Bu dərəcə bütün vergiyə cəlb olunan aktiv növlərinə (binalar, avadanlıq, nəqliyyat vasitələri və s.) eyni şəkildə tətbiq olunur." +"Net book value threshold for property tax reporting. Per Maddə 199 of the AZ Tax Code, enterprises with average annual asset value exceeding this threshold may have additional reporting requirements. + +Standard value: 1,000,000 AZN. + +The wizard will show a warning in the log if your company's net book value exceeds this threshold.","Əmlak vergisi hesabatı üçün qalıq dəyər həddi. AZ Vergi Məcəlləsinin Maddə 199-a əsasən, orta illik aktiv dəyəri bu həddi aşan müəssisələrin əlavə hesabat tələbləri ola bilər. + +Standart dəyər: 1 000 000 AZN. + +Sehrbaz şirkətinizin qalıq dəyəri bu həddi aşarsa jurnalda xəbərdarlıq göstərəcək." +"How the taxable asset value is calculated: + +• Average Annual (Start + End) / 2 — standard method per Maddə 199. Takes the residual value at the start of the year and the end of the year, averages them. Used for companies operating the full year (Tam İl). + +• Pro-rata for New Companies — for companies established during the year (İl Ərzində). Formula: (Purchase value + Year-end value) / 24 × months of operation. + +• End of Period NBV — uses the net book value at the end of the closing period. Simpler but less accurate for annual tax.","Vergiyə cəlb olunan aktiv dəyəri necə hesablanır: + +• Average Annual (Start + End) / 2 — Maddə 199 üzrə standart metod. İl əvvəlinə və sonuna qalıq dəyəri götürüb ortalamasını hesablayır. Tam il fəaliyyət göstərən şirkətlər üçün (Tam İl). + +• Pro-rata for New Companies — il ərzində yaradılmış şirkətlər üçün (İl Ərzində). Düstur: (Alış dəyəri + İl sonuna dəyər) / 24 × fəaliyyət ayları. + +• End of Period NBV — bağlanış dövrünün sonundakı qalıq dəyərdən istifadə edir. Daha sadə, lakin illik vergi üçün daha az dəqiqdir." +"Enable land tax calculation. Uses existing calculation logic from Land Tax Declaration in taxes_az (cadastral scores, industrial land rates). + +Per AZ Tax Code (Maddə 206-212): land tax is calculated based on cadastral value and land purpose. Annual declaration, quarterly payments. + +Enable only if the company owns or uses land plots. The wizard will use rates from cadastral_points_data.json and industrial_land_data.json.","Torpaq vergisi hesablamasını aktivləşdirin. taxes_az-da Torpaq Vergisi Bəyannaməsindən mövcud hesablama məntiqindən istifadə edir (kadastr balları, sənaye torpaq dərəcələri). + +AR Vergi Məcəlləsinə əsasən (Maddə 206-212): torpaq vergisi kadastr dəyəri və torpağın təyinatı əsasında hesablanır. İllik bəyannamə, rüblük ödənişlər. + +Yalnız şirkət torpaq sahələrinə sahibdirsə və ya istifadə edirsə aktivləşdirin. Sehrbaz cadastral_points_data.json və industrial_land_data.json-dan dərəcələrdən istifadə edəcək." +"Where land tax amounts come from: + +• Land Tax Declaration — reads the calculated amount from an existing Land Tax Declaration document for the fiscal year. You must create and calculate the Land Tax Declaration first. + +• Manual Entry — you manually enter the annual land tax amount in the wizard. The wizard will divide by 4 for quarterly advances. + +Recommended: Land Tax Declaration (uses existing cadastral calculation logic).","Torpaq vergisi məbləğləri haradan gəlir: + +• Land Tax Declaration — maliyyə ili üçün mövcud Torpaq Vergisi Bəyannaməsi sənədindən hesablanmış məbləği oxuyur. Əvvəlcə Torpaq Vergisi Bəyannaməsini yaradıb hesablamalısınız. + +• Manual Entry — sehrbazda illik torpaq vergisi məbləğini əl ilə daxil edirsiniz. Sehrbaz rüblük avanslar üçün 4-ə bölür. + +Tövsiyə olunur: Land Tax Declaration (mövcud kadastr hesablama məntiqindən istifadə edir)." +"Enable excise tax tracking. Relevant only for companies producing or importing excisable goods (alcohol, tobacco, fuel, vehicles). + +The wizard will check if the company's OKVED code matches excise-related activities. If enabled but no excisable activities are found, it will be skipped with a note in the log. + +Monthly calculation and declaration.","Aksiz vergisi izləməsini aktivləşdirin. Yalnız aksizli mallar (spirtli içkilər, tütün, yanacaq, nəqliyyat vasitələri) istehsal edən və ya idxal edən şirkətlər üçün aktualdır. + +Sehrbaz şirkətin OKVED kodunun aksizlə əlaqəli fəaliyyətlərə uyğun olub-olmadığını yoxlayacaq. Aktivdirsə, lakin aksizli fəaliyyətlər tapılmırsa, jurnalda qeydlə keçiləcək. + +Aylıq hesablama və bəyannamə." +"Enable mining tax tracking. Relevant only for companies extracting mineral resources (oil, gas, metals, stone, etc.). + +The wizard will check if the company's OKVED code matches mining activities and if mining-related Item Groups exist. Monthly calculation. + +Enable only if the company has mining operations.","Mədən vergisi izləməsini aktivləşdirin. Yalnız mineral ehtiyatları (neft, qaz, metallar, daş və s.) çıxaran şirkətlər üçün aktualdır. + +Sehrbaz şirkətin OKVED kodunun mədənçilik fəaliyyətlərinə uyğun olub-olmadığını və mədənçiliklə əlaqəli Məhsul Qruplarının mövcud olub-olmadığını yoxlayacaq. Aylıq hesablama. + +Yalnız şirkətin mədənçilik əməliyyatları varsa aktivləşdirin." +"Enable road tax tracking. Relevant for companies owning vehicles registered in Azerbaijan. + +The wizard will check if the company has transportation assets. Annual calculation. + +Enable only if the company owns registered vehicles.","Yol vergisi izləməsini aktivləşdirin. Azərbaycanda qeydiyyata alınmış nəqliyyat vasitələrinə sahib şirkətlər üçün aktualdır. + +Sehrbaz şirkətin nəqliyyat aktivlərinin olub-olmadığını yoxlayacaq. İllik hesablama. + +Yalnız şirkətin qeydiyyatlı nəqliyyat vasitələri varsa aktivləşdirin." +"If ON: the wizard collects payroll data (gross pay, NDFL, DSMF, unemployment insurance, medical insurance) from Salary Slips for the period. This data is shown in the summary report for reference. + +NOTE: The wizard does NOT create payroll-related journal entries — those are already created by the Payroll Entry process. The wizard only COLLECTS and DISPLAYS the totals. + +If OFF: payroll data is not included in the wizard summary.","Aktivdirsə: sehrbaz dövr üçün Əmək Haqqı Vərəqələrindən əmək haqqı məlumatlarını (ümumi ödəniş, gəlir vergisi, DSMF, işsizlik sığortası, tibbi sığorta) toplayır. Bu məlumatlar arayış üçün xülasə hesabatında göstərilir. + +QEYD: Sehrbaz əmək haqqı ilə əlaqəli provodkalar YARATMIR — onlar artıq Əmək Haqqı Hesablaması prosesi tərəfindən yaradılıb. Sehrbaz yalnız cəmləri TOPLAYIR və GÖSTƏRİR. + +Deaktivdirsə: əmək haqqı məlumatları sehrbaz xülasəsinə daxil edilmir." +"Where payroll totals are collected from: + +• Salary Slips — reads submitted Salary Slip documents for the period. Provides detailed breakdown by salary component (deductions, contributions). + +• GL Account Balances — reads balances from payroll-related accounts (522, 522.1-4, 533). Faster but less detailed. + +• Both (with cross-check) — reads from both sources and flags discrepancies. Most thorough but slowest. + +Recommended: Salary Slips.","Əmək haqqı cəmləri haradan toplanır: + +• Salary Slips — dövr üçün təsdiqlənmiş Əmək Haqqı Vərəqəsi sənədlərini oxuyur. Əmək haqqı komponentləri üzrə ətraflı bölgü təqdim edir (tutulmalar, ayırmalar). + +• GL Account Balances — əmək haqqı ilə əlaqəli hesablardan (522, 522.1-4, 533) qalıqları oxuyur. Daha sürətli, lakin daha az ətraflıdır. + +• Both (with cross-check) — hər iki mənbədən oxuyur və uyğunsuzluqları işarələyir. Ən hərtərəfli, lakin ən yavaş. + +Tövsiyə olunur: Salary Slips." +Reference table of all tax rates used by the wizard. Click 'Load Default Rates' to populate with standard AZ rates. You can modify rates here — changes affect all future wizard runs.,Sehrbaz tərəfindən istifadə olunan bütün vergi dərəcələrinin arayış cədvəli. Standart AZ dərəcələri ilə doldurmaq üçün «Standart dərəcələri yüklə» düyməsini sıxın. Dərəcələri burada dəyişə bilərsiniz — dəyişikliklər bütün gələcək sehrbaz işə salmalarına təsir edəcək. +"Each row defines a tax type and its rate. These rates are used as reference for calculations and for taxes that use fixed rates (property, DSMF, unemployment insurance). VAT and profit tax have their own rate fields above for convenience. + +Click 'Load Default AZ Rates' button to populate with standard rates from the AZ Tax Code.","Hər sətir vergi növünü və onun dərəcəsini müəyyən edir. Bu dərəcələr hesablamalar üçün arayış kimi və sabit dərəcələr istifadə edən vergilər üçün (əmlak, DSMF, işsizlik sığortası) istifadə olunur. ƏDV və mənfəət vergisinin rahatlıq üçün yuxarıda öz dərəcə sahələri var. + +AR Vergi Məcəlləsindən standart dərəcələrlə doldurmaq üçün «Standart AZ dərəcələrini yüklə» düyməsini sıxın." +"Maps each tax type to the accounts used for journal entries. Debit = Expense account, Credit = Liability account. Click 'Auto-detect Accounts' to find matching accounts from your Chart of Accounts.","Hər vergi növünü provodkalar üçün istifadə olunan hesablarla əlaqələndirir. Debit = Xərc hesabı, Kredit = Öhdəlik hesabı. Hesablar Planınızdan uyğun hesabları tapmaq üçün «Hesabları avtomatik aşkarla» düyməsini sıxın." +"Each row maps a tax type to a pair of accounts: +• Debit (Expense) — where the tax cost is recorded (e.g., 901 for profit tax expense) +• Credit (Liability) — where the tax payable is recorded (e.g., 521.1 for profit tax liability) + +If the company uses the standard AZ Chart of Accounts, click 'Auto-detect Accounts' to find the correct accounts automatically.","Hər sətir vergi növünü bir cüt hesabla əlaqələndirir: +• Debit (Xərc) — vergi xərcinin uçota alındığı yer (məs., mənfəət vergisi xərci üçün 901) +• Kredit (Öhdəlik) — ödəniləcək verginin uçota alındığı yer (məs., mənfəət vergisi öhdəliyi üçün 521.1) + +Şirkət standart AZ Hesablar Planından istifadə edirsə, düzgün hesabları avtomatik tapmaq üçün «Hesabları avtomatik aşkarla» düyməsini sıxın." diff --git a/taxes_az/translations/ru.csv b/taxes_az/translations/ru.csv new file mode 100644 index 0000000..0cc6342 --- /dev/null +++ b/taxes_az/translations/ru.csv @@ -0,0 +1,1207 @@ +Tax Year Closing Wizard,Мастер закрытия налогового года +Tax Year Closing Step,Шаг закрытия налогового года +Tax Period Closing Wizard,Мастер закрытия налогового периода +Tax Period Closing Settings,Настройки закрытия налогового периода +Tax Closing Benefit,Налоговая льгота +Tax Closing Account Map,Карта налоговых счетов +Tax Rate Row,Строка налоговой ставки +Tax Closing Tax Item,Элемент налогового закрытия +Tax Closing Entry Preview,Предпросмотр проводки +Tax Closing Log Entry,Запись журнала операций +Tax Closing Journal Entry,Запись журнальной проводки +Tax Closing Payroll Summary,Сводка по зарплате +Generate Timeline,Создать временную шкалу +Timeline,Временная шкала +Current Step,Текущий шаг +Dashboard,Панель управления +Tax Details,Детали налогов +Results,Результаты +Log,Журнал +Summary,Сводка +Company,Компания +Company Info,Информация о компании +Fiscal Year,Финансовый год +Year,Год +Tax Regime,Налоговый режим +Accounting Method,Метод учёта +Overall Status,Общий статус +Progress,Прогресс +Status,Статус +VOEN,VÖEN +Activity Code (OKVED),Код деятельности (ОКВЭД) +Business Classification,Классификация бизнеса +Tax Authority,Налоговый орган +VAT Payer,Плательщик НДС +Has Fixed Assets,Есть основные средства +Has Assets,Есть активы +Has Land,Есть земельные участки +Has Employees,Есть сотрудники +Avg Monthly Employees,Среднемесячное кол-во сотрудников +Detected Tax Benefits & Exemptions,Обнаруженные налоговые льготы +Detected Benefits,Обнаруженные льготы +Benefits Display,Отображение льгот +Benefits Data,Данные о льготах +Applicable Taxes,Применимые налоги +Tax Items,Налоговые элементы +Financial Data,Финансовые данные +Total Income,Общий доход +Total Expense,Общий расход +Net Profit / Loss,Чистая прибыль / убыток +Asset Data,Данные об активах +Total Asset Value (Gross),Общая стоимость активов (валовая) +Accumulated Depreciation,Накопленная амортизация +Net Book Value,Остаточная стоимость +Avg Annual Value,Среднегодовая стоимость +Payroll Summary,Сводка по зарплате +Payroll Summary (Data Only),Сводка по зарплате (только данные) +Payroll Data Collection,Сбор данных по зарплате +Payroll Data Source,Источник данных по зарплате +Entry Preview,Предпросмотр проводки +Entry Previews,Предпросмотры проводок +Created Journal Entries,Созданные проводки +Journal Entries,Журнальные проводки +Journal Entry,Журнальная проводка +Execution Log,Журнал выполнения +Log Entries,Записи журнала +Log Display,Отображение журнала +Summary Report,Сводный отчёт +Summary Display,Отображение сводки +Total Tax Amount (Entries Created),Общая сумма налогов (проводки созданы) +Total Taxes (Entries Created),Всего налогов (проводки созданы) +Total Payroll (Data Only),Всего по зарплате (только данные) +Total Payroll Taxes (Data Only),Всего налогов на зарплату (только данные) +Grand Total Tax Burden,Общая налоговая нагрузка +Wizard Status,Статус мастера +Progress Bar,Индикатор прогресса +Period Type,Тип периода +Quarter,Квартал +Month,Месяц +From Date,Дата начала +To Date,Дата окончания +Cost Center,Центр затрат +Options,Настройки +Amended From,Изменено из +General Settings,Общие настройки +Default Tax Regime,Налоговый режим по умолчанию +Auto-detect Tax Regime,Автоопределение налогового режима +Auto-detect from Company,Автоопределение из компании +Auto-detect parameters on Save,Автоопределение параметров при сохранении +Default Cost Center,Центр затрат по умолчанию +Journal Entry Behavior,Поведение журнальных проводок +Auto-submit Journal Entries,Автоматическая проводка записей +Allow Re-closing Periods,Разрешить повторное закрытие периодов +Duplicate Check Scope,Область проверки дубликатов +Same Company + Same Period,Та же компания + тот же период +Same Company + Overlapping Dates,Та же компания + пересекающиеся даты +Period Closing Voucher (P&L Close),Документ закрытия периода (закрытие ОПУ) +Create Period Closing Voucher,Создать документ закрытия периода +Closing Account,Счёт закрытия +Declaration Integration (Future),Интеграция с декларациями (будущее) +Create Declaration Draft,Создать черновик декларации +Auto-fill Declaration Header,Автозаполнение заголовка декларации +Separate Cost Center per Tax Type,Отдельный центр затрат для каждого налога +VAT (ƏDV),НДС (ƏDV) +VAT (ƏDV) — Əlavə Dəyər Vergisi,НДС — Налог на добавленную стоимость +Enable VAT Calculation,Включить расчёт НДС +VAT Rate (%),Ставка НДС (%) +VAT Input Account,Счёт входящего НДС +VAT Output Account,Счёт исходящего НДС +Cross-check with VAT Allocation,Перекрёстная проверка с распределением НДС +VAT Data Source,Источник данных НДС +GL Account Balance,Сальдо счёта ГК +Sales/Purchase Invoices,Счета продаж/покупок +Profit Tax (Mənfəət Vergisi),Налог на прибыль +Profit Tax (Mənfəət),Налог на прибыль +Enable Profit Tax Calculation,Включить расчёт налога на прибыль +Profit Tax Rate (%),Ставка налога на прибыль (%) +Advance Payment Method,Метод авансовых платежей +Actual Quarterly Profit,Фактическая квартальная прибыль +1/4 of Previous Year Tax,1/4 налога прошлого года +Previous Year Tax Amount,Сумма налога прошлого года +Simplified Tax,Упрощённый налог +Simplified Tax (Sadələşdirilmiş Vergi),Упрощённый налог +Simplified Tax (Sadələşdirilmiş),Упрощённый налог +Enable Simplified Tax,Включить упрощённый налог +Auto-detect Rate by OKVED,Автоопределение ставки по ОКВЭД +Default Simplified Tax Rate (%),Ставка упрощённого налога по умолчанию (%) +Revenue Source,Источник выручки +Total Income (P&L),Общий доход (ОПУ) +Sales Revenue Only,Только выручка от продаж +Cash Receipts,Денежные поступления +Property Tax (Əmlak Vergisi),Налог на имущество +Property Tax (Əmlak),Налог на имущество +Enable Property Tax,Включить налог на имущество +Property Tax Rate (%),Ставка налога на имущество (%) +Reporting Threshold (AZN),Порог отчётности (AZN) +Calculation Method,Метод расчёта +Average Annual (Start + End) / 2,Среднегодовая (начало + конец) / 2 +Pro-rata for New Companies,Пропорционально для новых компаний +End of Period NBV,Остаточная стоимость на конец периода +Land Tax (Torpaq Vergisi),Земельный налог +Land Tax (Torpaq),Земельный налог +Enable Land Tax,Включить земельный налог +Land Tax Declaration,Декларация по земельному налогу +Manual Entry,Ручной ввод +Other Taxes (Optional),Другие налоги (необязательно) +Enable Excise Tax,Включить акцизный налог +Enable Mining Tax,Включить горный налог +Enable Road Tax,Включить дорожный налог +Excise Tax (Aksiz),Акцизный налог +Mining Tax (Mədən),Горный налог +Road Tax (Yol),Дорожный налог +Withholding Tax (ÖMV),Налог удерживаемый у источника +Unemployment Insurance (İşsizlik),Страхование от безработицы +Medical Insurance (Tibbi sığorta),Обязательное медицинское страхование +DSMF,ГФСЗ +Collect Payroll Data,Собрать данные по зарплате +Salary Slips,Расчётные листки +GL Account Balances,Сальдо счетов ГК +Both (with cross-check),Оба (с перекрёстной проверкой) +Tax Rates Reference Table,Справочная таблица налоговых ставок +Tax Rates,Налоговые ставки +Account Mapping,Сопоставление счетов +Account Mapping (Debit → Credit),Сопоставление счетов (Дебет → Кредит) +Tax Type,Тип налога +Debit Account,Дебетовый счёт +Credit Account,Кредитовый счёт +Rate (%),Ставка (%) +Description,Описание +Smart Scan — Existing Entry Detection,Умный поиск — обнаружение существующих проводок +Scan for Existing Entries,Искать существующие проводки +Force Create Even if Exists,Создать принудительно даже если существует +Offer Correction Entry for Differences,Предложить корректировочную проводку при расхождении +Accounting Method (Uçot Metodu),Метод учёта +Company & Auto-Detection,Компания и автообнаружение +Detection Status,Статус обнаружения +Regime Explanation,Описание режима +Default Company,Компания по умолчанию +General (VAT + Profit),Общий (НДС + Прибыль) +Special Regime,Специальный режим +Enabled,Включено +Period,Период +Sub-Period,Подпериод +Source Amount,Исходная сумма +Tax Amount,Сумма налога +Declaration Deadline,Срок сдачи декларации +Payment Deadline,Срок оплаты +Calculated Amount,Рассчитанная сумма +Posting Date,Дата проводки +Remark,Примечание +Existing Entry,Существующая проводка +Existing Amount,Существующая сумма +Difference,Разница +Timestamp,Метка времени +Phase,Фаза +Level,Уровень +Message,Сообщение +Initialization,Инициализация +Tax Detection,Определение налогов +Data Collection,Сбор данных +Calculation,Расчёт +Entry Preview,Предпросмотр проводки +Entry Creation,Создание проводок +Period Closing,Закрытие периода +Setup,Настройка +Step Complete,Шаг завершён +Step Skipped,Шаг пропущен +Benefit Detection,Определение льгот +Info,Информация +Success,Успешно +Warning,Предупреждение +Error,Ошибка +Header,Заголовок +Draft,Черновик +Calculated,Рассчитано +Entries Created,Проводки созданы +Completed,Завершено +Cancelled,Отменено +Not Started,Не начато +In Progress,В процессе +Active,Активно +Pending,Ожидание +Skipped,Пропущено +Data Only,Только данные +Entry Created,Проводка создана +Failed,Ошибка +Existing,Существует +Yes,Да +No,Нет +Partial,Частично +Manual Check,Ручная проверка +Monthly,Ежемесячно +Quarterly,Ежеквартально +Annually,Ежегодно +Annual,Годовое +Article (Maddə),Статья (Маддя) +Benefit,Льгота +Applies To,Применяется к +Exemption %,Освобождение % +Condition,Условие +Details,Детали +Tax Reduction,Уменьшение налога +Law Reference,Ссылка на закон +Apply,Применить +Component,Компонент +Amount,Сумма +Account,Счёт +Source,Источник +Calculate Taxes,Рассчитать налоги +Calculating taxes...,Расчёт налогов... +Calculation complete. Reloading...,Расчёт завершён. Перезагрузка... +Calculation failed. Check browser console.,Расчёт не удался. Проверьте консоль браузера. +Create Journal Entries,Создать проводки +Creating journal entries...,Создание проводок... +Entries created. Reloading...,Проводки созданы. Перезагрузка... +Entry creation failed.,Не удалось создать проводку. +Re-calculate,Пересчитать +Rollback All Entries,Откатить все проводки +Rolling back entries...,Откат проводок... +All entries rolled back,Все проводки откачены +This will create journal entries for all calculated taxes. Continue?,Будут созданы проводки для всех рассчитанных налогов. Продолжить? +This will cancel/delete ALL journal entries created by this wizard. Are you sure?,Будут отменены/удалены ВСЕ проводки созданные этим мастером. Вы уверены? +Verify Entries (Kassa),Проверить проводки (Кассовый) +Verifying entries...,Проверка проводок... +Fix Cash Method Entries,Исправить проводки кассового метода +Re-submitting invoices with cash method accounts...,Переотправка счетов с кассовыми счетами... +FIFO Allocate All,FIFO Распределить всё +FIFO Allocation,FIFO Распределение +Run FIFO allocation for ALL months? This will recognize revenue and VAT for all received payments.,Запустить FIFO распределение для ВСЕХ месяцев? Будут признаны доход и НДС по всем полученным оплатам. +Running FIFO allocation for all periods...,FIFO распределение для всех периодов... +This will process ALL steps sequentially: Calculate → FIFO Allocate → Complete. Continue?,Все шаги будут выполнены последовательно: Расчёт → FIFO → Завершение. Продолжить? +Skip this step? It will be marked as Skipped.,Пропустить этот шаг? Будет отмечен как Пропущен. +Create journal entries for this step?,Создать проводки для этого шага? +Step not found.,Шаг не найден. +Step must be calculated first.,Сначала необходимо выполнить расчёт шага. +Complete all steps before submitting.,Завершите все шаги перед утверждением. +Please run Calculate and Create Entries before submitting.,Выполните расчёт и создайте проводки перед утверждением. +Please select a Company first.,Сначала выберите компанию. +Setting Conflict,Конфликт настроек +Check Required,Требуется проверка +Missing Data,Отсутствуют данные +Missing Account,Отсутствует счёт +Missing Account Mapping,Отсутствует сопоставление счетов +High Risk Configuration,Конфигурация высокого риска +Non-standard Rate,Нестандартная ставка +Unusual Rate,Необычная ставка +No standard AZ accounts found. Set up manually.,Стандартные счета AZ не найдены. Настройте вручную. +Load Default AZ Rates,Загрузить стандартные ставки AZ +Load Default Account Map,Загрузить стандартную карту счетов +Company changed. Save to run auto-detection.,Компания изменена. Сохраните для автоопределения. +VAT Input and Output accounts must be different!,Счета входящего и исходящего НДС должны отличаться! +Profit Tax is replaced by Simplified Tax in non-General regimes.,Налог на прибыль заменяется упрощённым налогом в не-Общем режиме. +Simplified Tax is not applicable in General (VAT + Profit) regime.,Упрощённый налог не применяется в Общем (НДС + Прибыль) режиме. +VAT is typically not applicable in Simplified/Special regime.,НДС обычно не применяется в упрощённом/специальном режиме. +Actions,Действия +Activity Group,Группа деятельности +Activity code not found in system,Код деятельности не найден в системе +Actual Address,Фактический адрес +Actual Address (Full),Фактический адрес (полный) +Additional Activity Codes,Дополнительные коды деятельности +Additional activities to be added,Дополнительные виды деятельности для добавления +Address for Mail,Почтовый адрес +Affiliate Organizations,Аффилированные организации +Affiliate Organizations TINs,ИНН аффилированных организаций +Affiliate organizations to be added,Аффилированные организации для добавления +Analyzing FIFO allocation...,Анализ FIFO распределения... +An error occurred while fetching profile data,Произошла ошибка при получении данных профиля +An unknown error occurred,Произошла неизвестная ошибка +April,Апрель +Article Name,Название статьи +At least one of Seller or Organizer must be checked for gambling activities.,Для игорной деятельности необходимо отметить Продавца или Организатора. +August,Август +Authentication Complete,Аутентификация завершена +Authentication Error,Ошибка аутентификации +Authentication Failed,Аутентификация не удалась +Authentication Successful,Аутентификация успешна +Authentication Timeout,Тайм-аут аутентификации +Authentication cancelled,Аутентификация отменена +Authentication request failed,Запрос аутентификации не удался +Auto-detect Accounts,Автоопределение счетов +Auto-detection results:

{},Результаты автоопределения:

{} +Auto-submit means journal entries will be posted immediately and will affect account balances. Submitted entries require cancellation to undo. Are you sure?,Автоматическая проводка означает что записи будут проведены немедленно и повлияют на сальдо счетов. Для отмены проведённых записей потребуется аннулирование. Вы уверены? +Cancel,Отменить +Certificate,Сертификат +Certificates,Сертификаты +Chief,Руководитель +Chief PIN,ПИН руководителя +Classification,Классификация +Close,Закрыть +Company Information,Информация о компании +Company Name,Наименование компании +Company Required,Требуется компания +Company data successfully updated from E-Taxes profile,Данные компании успешно обновлены из профиля E-Taxes +Completed Date,Дата завершения +Completing Authentication,Завершение аутентификации +Contact Information,Контактная информация +Continue?,Продолжить? +Could not authenticate with Asan Imza,Не удалось аутентифицироваться через Asan İmza +Created,Создано +Creating allocation entries...,Создание записей распределения... +DSMF Employee (DSMF - İşçi),ГФСЗ Доля работника +DSMF Employer (DSMF - İşəgötürən),ГФСЗ Доля работодателя +Data Source,Источник данных +Date From,Дата от +Date Range (Optional),Диапазон дат (необязательно) +Date To,Дата до +Date To cannot be earlier than Date From,Дата окончания не может быть раньше даты начала +Date of Establishment,Дата учреждения +December,Декабрь +Declaration,Декларация +Director Name,Имя директора +Director Name (E-Taxes),Имя директора (E-Taxes) +Director PIN,ПИН директора +Document No,Номер документа +Domain,Домен +Duration,Продолжительность +E-Taxes,E-Taxes +E-Taxes Authentication,Аутентификация E-Taxes +E-Taxes Company Profile,Профиль компании E-Taxes +E-Taxes authentication is required. Would you like to authenticate now?,Требуется аутентификация E-Taxes. Хотите аутентифицироваться сейчас? +E-Taxes service is temporarily unavailable. Please try again in a few minutes.,Сервис E-Taxes временно недоступен. Повторите попытку через несколько минут. +Email,Электронная почта +Employee Count,Количество сотрудников +Employer,Работодатель +Employer Name,Наименование работодателя +Employer Position,Должность работодателя +Error checking authentication status,Ошибка при проверке статуса аутентификации +Error updating company data,Ошибка при обновлении данных компании +Export XML,Экспорт XML +FIN,ФИН +February,Февраль +Filter Settings,Настройки фильтра +Fixing invoices...,Исправление счетов... +From,От +Gambling activity detected. Seller and Organizer flags enabled.,Обнаружена игорная деятельность. Флаги Продавца и Организатора включены. +Generating timeline...,Создание временной шкалы... +Get E-Taxes Profile,Получить профиль E-Taxes +Getting Asan Login settings...,Получение настроек Asan Login... +Getting certificates and selecting taxpayer...,Получение сертификатов и выбор налогоплательщика... +Has Access,Есть доступ +Has Active Production Object,Есть действующий производственный объект +House Number,Номер дома +Important Dates,Важные даты +In Cancellation Process,В процессе ликвидации +Invalid Date Range,Неверный диапазон дат +Is Chief of Any Legal Entity,Является руководителем юридического лица +Is Group,Является группой +Is Risky Taxpayer,Является рисковым налогоплательщиком +Is Taxpayer in Cancellation Process,Налогоплательщик в процессе ликвидации +Items List,Список элементов +January,Январь +July,Июль +June,Июнь +Landline Phone,Стационарный телефон +Left,Лево +Legal Address,Юридический адрес +Legal Address (Full),Юридический адрес (полный) +Legal Address Details,Детали юридического адреса +Legal Address House Number,Номер дома юридического адреса +Legal Address Locality,Населённый пункт юридического адреса +Legal Address Postcode,Почтовый индекс юридического адреса +Legal Address Region,Регион юридического адреса +Legal Address Room Number,Номер помещения юридического адреса +Legal Address Street,Улица юридического адреса +Legal Form Code,Код правовой формы +Liquidated,Ликвидировано +Liquidation Date,Дата ликвидации +Loading report...,Загрузка отчёта... +Locality,Населённый пункт +Main Activity,Основная деятельность +Main Activity Code,Код основной деятельности +Management Information,Информация об управлении +March,Март +May,Май +Medical Insurance Employee (Tibbi sığorta - İşçi),Медстрахование Доля работника +Medical Insurance Employer (Tibbi sığorta - İşəgötürən),Медстрахование Доля работодателя +Mobile Phone,Мобильный телефон +Name,Наименование +Network Error,Ошибка сети +No Asan Login Settings,Нет настроек Asan Login +No Certificates,Нет сертификатов +No certificates found for this account,Сертификаты для этой учётной записи не найдены +No new data to update,Нет новых данных для обновления +Non-gambling activity. Seller and Organizer flags disabled.,Не игорная деятельность. Флаги Продавца и Организатора отключены. +November,Ноябрь +OKVED,ОКВЭД +October,Октябрь +Old Parent,Предыдущий родитель +Operation Date,Дата операции +Organizational Structure,Организационная структура +PIN,ПИН +Parameters Detected,Параметры определены +Parent Organization,Головная организация +Parent Organization TIN,ИНН головной организации +Parent Tax Article,Родительская налоговая статья +Phone,Телефон +Please check your phone and confirm the authentication request,Проверьте телефон и подтвердите запрос аутентификации +Please configure Asan Login settings first,Сначала настройте параметры Asan Login +Please enter the previous year's total profit tax amount below.,Введите ниже общую сумму налога на прибыль за прошлый год. +Please select a Tax Article first,Сначала выберите налоговую статью +Please wait...,Пожалуйста подождите... +Position,Должность +Postcode,Почтовый индекс +Processing Error,Ошибка обработки +Progress,Прогресс +Property Type,Тип имущества +Q1 (Jan-Mar),К1 (Янв-Мар) +Q2 (Apr-Jun),К2 (Апр-Июн) +Q3 (Jul-Sep),К3 (Июл-Сен) +Q4 (Oct-Dec),К4 (Окт-Дек) +Refresh Results,Обновить результаты +Region,Регион +Registration Date,Дата регистрации +Registration Details,Данные регистрации +Registration Number,Регистрационный номер +Right,Право +Room Number,Номер помещения +Saved,Сохранено +Save failed,Ошибка сохранения +Select Main Activity,Выберите основную деятельность +Selecting certificate,Выбор сертификата +Selecting taxpayer,Выбор налогоплательщика +Seller and Organizer flags can only be set for gambling activities.,Флаги Продавца и Организатора можно установить только для игорной деятельности. +Sending authentication request...,Отправка запроса аутентификации... +Separate Cost Center per Tax Type,Отдельный центр затрат для каждого налога +September,Сентябрь +Series,Серия +Server Error,Ошибка сервера +Server error during update. Please check the error logs.,Ошибка сервера при обновлении. Проверьте журнал ошибок. +Set Main Activity,Установить основную деятельность +Simplified Tax - Catering (Sadələşdirilmiş - İaşə),Упрощённый налог - Общепит +Simplified Tax - Other (Sadələşdirilmiş - Digər),Упрощённый налог - Прочее +Simplified Tax - Trade Baku (Sadələşdirilmiş - Ticarət Bakı),Упрощённый налог - Торговля Баку +Special Tax Regime,Специальный налоговый режим +Sport Betting Operator,Оператор спортивных ставок +Starting Authentication,Начало аутентификации +State Registration Authority,Орган государственной регистрации +State Registration Document Issued Date,Дата выдачи документа государственной регистрации +Street,Улица +Submitted,Проведено +Suspension End Date,Дата окончания приостановки +Suspension Start Date,Дата начала приостановки +TAX-REP-.YYYY.-,TAX-REP-.YYYY.- +TIN,ИНН +TIN Type,Тип ИНН +Tax Article,Налоговая статья +Tax Article Items Report,Отчёт по элементам налоговой статьи +Tax Authority Code,Код налогового органа +Tax ID,Налоговый идентификатор +Tax Information,Налоговая информация +Tax Period Closing Settings,Настройки закрытия налогового периода +Tax Rate,Налоговая ставка +Tax System Type,Тип налоговой системы +Tax Systems,Налоговые системы +Tax Systems List,Список налоговых систем +Tax return withheld at source of payment,Декларация по налогу удержанному у источника выплаты +Tax return withheld at source of payment Elave 1,Декларация по налогу у источника выплаты Приложение 1 +Tax return withheld at source of payment Elave 2 Hisse1,Декларация по налогу у источника выплаты Приложение 2 Часть 1 +Tax return withheld at source of payment Elave 2 Hisse1 2ci,Декларация по налогу у источника выплаты Приложение 2 Часть 1 (2-я) +Tax return withheld at source of payment Elave 2 Hisse2,Декларация по налогу у источника выплаты Приложение 2 Часть 2 +Tax return withheld at source of payment Elave 2 Hisse2 2ci,Декларация по налогу у источника выплаты Приложение 2 Часть 2 (2-я) +Tax return withheld at source of payment Elave 2 Hisse3,Декларация по налогу у источника выплаты Приложение 2 Часть 3 +Tax return withheld at source of payment Elave 2 Hisse3 2ci,Декларация по налогу у источника выплаты Приложение 2 Часть 3 (2-я) +Tax return withheld at source of payment Elave1 2ci,Декларация по налогу у источника выплаты Приложение 1 (2-я) +Tax return withheld at source of payment Vergi Hesab,Декларация по налогу у источника выплаты Налоговый расчёт +Taxation System,Система налогообложения +Taxes,Налоги +Taxpayer Activity Group,Группа деятельности налогоплательщика +The authentication request has timed out. Please try again.,Тайм-аут запроса аутентификации. Повторите попытку. +The following company fields will be updated with data from E-Taxes:,Следующие поля компании будут обновлены данными из E-Taxes: +The following data will be updated:,Следующие данные будут обновлены: +The operation may take some time,Операция может занять некоторое время +This will create correction Journal Entries to move VAT from 521.3 to 245.1 and revenue from 601 to 542 for all unpaid Sales Invoices. Continue?,Будут созданы корректировочные проводки для переноса НДС с 521.3 на 245.1 и выручки с 601 на 542 для всех неоплаченных счетов продаж. Продолжить? +This will send a request to your Asan Imza mobile app,Будет отправлен запрос в мобильное приложение Asan İmza +To,До +Total Items Found,Всего найдено элементов +Totals,Итого +Type,Тип +Unemployment Insurance Employee (İşsizlik - İşçi),Страхование от безработицы Доля работника +Unemployment Insurance Employer (İşsizlik - İşəgötürən),Страхование от безработицы Доля работодателя +Update,Обновить +Update Company Fields,Обновить поля компании +Update Error,Ошибка обновления +Update cancelled,Обновление отменено +Updating company data...,Обновление данных компании... +Using certificate: ,Используемый сертификат: +Validity Period,Срок действия +VAT Certificate Date,Дата сертификата НДС +VAT Certificate Number,Номер сертификата НДС +VAT Info,Информация о НДС +VAT Registration Date,Дата регистрации НДС +Waiting for confirmation on your phone,Ожидание подтверждения на телефоне +Warning: Both auto-submit and period closing are ON. Period Closing Voucher is irreversible once submitted.,Внимание: Автоматическая проводка и закрытие периода включены. Документ закрытия периода необратим после проведения. +Wizard Status,Статус мастера +XML file generated and downloaded,XML файл создан и загружен +You are now authenticated with Asan Imza,Вы аутентифицированы через Asan İmza +You can now access E-Taxes services,Теперь вы можете использовать сервисы E-Taxes +Your E-Taxes session has expired. Would you like to authenticate again?,Сессия E-Taxes истекла. Хотите аутентифицироваться снова? +activities,виды деятельности +organizations,организации +Business Entity Criteria,Критерии субъекта предпринимательства +Business Information,Деловая информация +Company data successfully updated from E-Taxes profile,Данные компании успешно обновлены из профиля E-Taxes +Failed to get authentication settings,Не удалось получить настройки аутентификации +Failed to get certificates,Не удалось получить сертификаты +Failed to select certificate,Не удалось выбрать сертификат +Failed to select taxpayer,Не удалось выбрать налогоплательщика +Failed to start authentication process,Не удалось начать процесс аутентификации +Failed to update main activity,Не удалось обновить основную деятельность +Addım tamamlandı,Шаг завершён +Adı,Имя +Analoji,Аналогичный +Artıq bütün dövrlər üçün FIFO allocation edilmişdir. Yeni bölüşdürmə yoxdur.,FIFO распределение уже выполнено для всех периодов. Новых распределений нет. +Ay,Месяц +Bağla,Закрыть +Bütün addımlar artıq tamamlanmışdır.,Все шаги уже завершены. +Bütün dövrləri bağla,Закрыть все периоды +Bəyan ediləcək məlumatım yoxdur,У меня нет данных для декларирования +Bəyannamənin növü,Тип декларации +Cari,Текущий +Davam et,Продолжить +Dayandır,Приостановить +Daşınan,Движимое +Daşınmaz,Недвижимое +Doğum tarixi,Дата рождения +Düzəlt və davam et,Исправить и продолжить +Düzəltmədən davam et,Продолжить без исправления +Dəqiqləşdirilmiş,Уточнённый +Dəqiqləşdirilmiş bəyannamənin təqdim edilməsi barədə bildirişin nömrəsi,Номер уведомления о подаче уточнённой декларации +Faktura hesabları düzəlişi lazımdır,Требуется корректировка счетов по счетам-фактурам +Fiziki,Физическое лицо +Fiziki şəxsin adı soyadı atasının adı,ФИО физического лица +Fiziki şəxsin doğum tarixi,Дата рождения физического лица +Fərdi sahibkar,Индивидуальный предприниматель +Göstəricilər,Показатели +Güzəştli vergi tutulan ölkə və ya ərazi ilə əlaqəsi,Связь со страной или территорией с льготным налогообложением +Güzəştli vergi tutulan ölkənin və ya ərazinin adı,Название страны или территории с льготным налогообложением +Gəlir məbləği,Сумма дохода +"Gəlir məbləği, manatla","Сумма дохода, в манатах" +Gələcək dövr gəlirləri (542),Доходы будущих периодов (542) +Hesablama metodu,Метод расчёта +Hesablar tapılmadı,Счета не найдены +Hesabları yarat,Создать счета +Hissə 1,Часть 1 +Hissə 2,Часть 2 +Hissə 3,Часть 3 +Hüquqi,Юридическое лицо +Hüquqi status,Правовой статус +Hüquqi və ya fiziki şəxsin adı,Наименование юридического или физического лица +Hüquqi şəxs,Юридическое лицо +Hüquqi şəxsin adı,Наименование юридического лица +Kassa Metodu — Düzəliş Nəticəsi,Кассовый метод — Результат корректировки +Kassa Metodu — Yoxlama Nəticəsi,Кассовый метод — Результат проверки +Kassa metodu,Кассовый метод +Könüllü açıqlama,Добровольное раскрытие +Ləğv olma,Ликвидация +Ləğvin dəqiqləşdirilmişi,Уточнение ликвидации +Mob nömrə 1,Моб. номер 1 +Mob nömrə 2,Моб. номер 2 +Pin nömrəsi,ПИН-код +Qeydiyyat ünvanı,Адрес регистрации +Rüblük,Квартальный +Soyadı,Фамилия +Sətrin kodu,Код строки +Təqdim olunmuş əlavələrin sayı,Количество поданных приложений +Təsdiqlə və yarat,Подтвердить и создать +Təsis edildiyi (qeydiyyatdan keçdiyi) ölkədə VÖEN-i,ИНН в стране учреждения (регистрации) +Təsis edildiyi (qeydiyyatdan keçdiyi) ölkədə qeydiyyat ünvanı,Адрес регистрации в стране учреждения (регистрации) +Təsnifatı verilən sətrin kodu,Код строки для которой указана классификация +Uçot metodu,Метод учёта +Vergi dövrü,Налоговый период +Vergi məbləği,Сумма налога +Vergi məbləği Cəmi,Итого сумма налога +"Vergi məbləği, manatla","Сумма налога, в манатах" +Vergi orqanı,Налоговый орган +Verginin Hesablanması,Расчёт налога +VÖEN,ИНН +VÖEN-i və ya öz ölkəsindəki VÖEN-i,ИНН или ИНН в своей стране +Vətandaş,Гражданин +Xarici pasportun seriya və nömrəsi,Серия и номер иностранного паспорта +Xarici pasportun verilmə tarixi,Дата выдачи иностранного паспорта +Yekunları Hesabla,Рассчитать итоги +Ödəniş məbləği,Сумма оплаты +Ödəniş məbləği Cəmi,Итого сумма оплаты +"Ödəniş məbləği, manatla","Сумма оплаты, в манатах" +Ödəniş tarixi,Дата оплаты +Ödənişin aid olduğu dövr,Период к которому относится оплата +Ödəyici tipi,Тип плательщика +Ümumi məlumat,Общая информация +İcarə haqqı ödənilən əmlakın növü,Тип имущества по которому уплачена арендная плата +İdarə,Управление +İl,Год +Şəhər nömrəsi 1,Городской номер 1 +Şəhər nömrəsi 2,Городской номер 2 +Şəxsi,Личный +Şəxsin adı,Имя лица +ƏDV gözləmədə (alış),НДС в ожидании (покупки) +ƏDV gözləmədə (satış),НДС в ожидании (продажи) +Əlavə 1,Приложение 1 +Əlavə 2,Приложение 2 +Əsas fəaliyyət növünün kodu,Код основного вида деятельности +Əvvəlki addım ({0}) tamamlanmayıb. Əvvəlcə onu tamamlayın və ya keçin.,Предыдущий шаг ({0}) не завершён. Сначала завершите его или пропустите. +FIFO Allocation tamamlandı,FIFO Распределение завершено +FIFO Allocation — Bütün dövrlər,FIFO Распределение — Все периоды +FIFO Kassa Allocation — Önbaxış,FIFO Кассовое распределение — Предпросмотр +Atasının adı,Отчество +1. Rüb,1-й Квартал +2. Rüb,2-й Квартал +3. Rüb,3-й Квартал +4. Rüb,4-й Квартал +Təsis edilib (qeydiyyatdan keçib),Учреждено (зарегистрировано) +Təsis edilmiş (qeydiyyatdan keçmiş) şəxsin filialı və ya nümayəndəsidir,Является филиалом или представителем учреждённого (зарегистрированного) лица +Bank hesabı mövcuddur,Имеется банковский счёт +Hesabla,Рассчитать +Keç,Пропустить +Yenidən hesabla,Пересчитать +Accounting method:,Метод учёта: +Accounts not found,Счета не найдены +Accrual method,Метод начисления +Accrual method: standard PCV,Метод начисления: стандартный PCV +All steps already completed.,Все шаги уже завершены. +All steps completed successfully!,Все шаги успешно завершены! +All steps completed!,Все шаги завершены! +Allocation,Распределение +Already fixed,Уже исправлено +Amount (AZN),Сумма (AZN) +Applicable taxes:,Применимые налоги: +Applies to:,Применяется к: +Benefits will be detected after calculation.,Льготы будут определены после расчёта. +Calculate,Рассчитать +Calculating...,Расчёт... +Cash Method — Fix Result,Кассовый метод — Результат исправления +Cash Method — Verification Result,Кассовый метод — Результат проверки +Cash in (PE+226),Поступления (PE+226) +Cash method — invoices re-submitted,Кассовый метод — счета пере-проведены +Cash out,Выплаты +Changes,Изменения +Click to open,Нажмите для открытия +Close all periods,Закрыть все периоды +Confirm and create,Подтвердить и создать +Confirming...,Подтверждение... +Continue,Продолжить +Continue without fixing,Продолжить без исправления +Correct entries,Корректные записи +Correct:,Корректных: +Create accounts,Создать счета +Created Documents,Созданные документы +Created documents,Созданные документы +Customer,Клиент +Date,Дата +Date range,Диапазон дат +Declaration deadline,Срок декларации +Declaration:,Декларация: +Deductions,Удержания +Disabled,Отключено +Employee count,Кол-во сотрудников +Error:,Ошибка: +Errors,Ошибки +Exemption:,Освобождение: +Expenses,Расходы +FIFO Allocation completed,FIFO-распределение завершено +FIFO Allocation — All periods,FIFO-распределение — Все периоды +FIFO Cash Allocation — Preview,FIFO кассовое распределение — Предпросмотр +FIFO allocation already done for all periods. No new allocations.,FIFO-распределение уже выполнено для всех периодов. Новых распределений нет. +FIFO allocation not done yet. Click the FIFO Allocate button.,FIFO-распределение ещё не выполнено. Нажмите кнопку FIFO Распределить. +Fix and continue,Исправить и продолжить +Fix:,Решение: +Fixed invoices,Исправленные счета +GRAND TOTAL TAX BURDEN,ОБЩАЯ НАЛОГОВАЯ НАГРУЗКА +Gross Pay,Начисленная зарплата +Gross salary,Начисленная зарплата +Income,Доход +Invoice,Счёт-фактура +Invoice account correction required,Требуется корректировка счетов фактур +Invoices:,Счета: +JE,ЖО +Journal Entries created,Журнальные ордера созданы +Law:,Закон: +Link,Ссылка +N/A (Simplified),Н/Д (Упрощённый) +Net profit/loss:,Чистая прибыль/убыток: +Net salary,Чистая зарплата +Next step:,Следующий шаг: +No active step. Go to Timeline tab.,Нет активного шага. Перейдите на вкладку Хронология. +No journal entries created (amount = 0 or data collection only),Проводки не созданы (сумма = 0 или только сбор данных) +No payments found for allocation in this period.,Платежей для распределения в этом периоде не найдено. +No special tax benefits detected.,Специальные налоговые льготы не обнаружены. +Not Met,Условие не выполнено +Not detected,Не определено +Not set,Не задано +Other activities — Maddə 220.1,Прочие виды деятельности — Статья 220.1 +Payment,Платёж +Payment date,Дата платежа +Payment deadline,Срок платежа +Payment:,Платёж: +Payroll,Зарплата +Payroll Data (Reference Only),Данные по зарплате (только справочно) +Payroll Taxes (collected data),Зарплатные налоги (собранные данные) +Period:,Период: +Periods:,Периодов: +Previous steps have been saved.,Предыдущие шаги сохранены. +Recalculate,Пересчитать +Report,Отчёт +Required accounts for cash method not found:,Необходимые счета для кассового метода не найдены: +Revenue recognition:,Признание выручки: +Select a Company to enable auto-detection.,Выберите компанию для автоопределения. +Skip,Пропустить +Skipping...,Пропуск... +Standard VAT rate — Maddə 159 Tax Code,Стандартная ставка НДС — Статья 159 НК +Step completed,Шаг завершён +Step completed successfully,Шаг успешно завершён +Stop,Остановить +Tax Reduction:,Снижение налога: +Tax amount,Сумма налога +Tax:,Налог: +Taxes:,Налоги: +Total,Итого +Total Tax Entries,Всего налоговых проводок +Total expense,Итого расходы +Total income,Итого доходы +Total incorrect VAT:,Всего некорректный НДС: +Type:,Тип: +Upcoming Deadlines,Ближайшие сроки +VAT Accounts,Счета НДС +VAT recognition:,Признание НДС: +Warning:,Предупреждение: +Will use company default,Будет использован по умолчанию из компании +and more,и ещё +payable,к оплате +refundable,к возврату +No VAT,Без НДС +Profit Tax (advance),Налог на прибыль (аванс) +Profit Tax (annual),Налог на прибыль (годовой) +Property Tax (1/4),Налог на имущество (1/4) +Property Tax (annual),Налог на имущество (годовой) +Simplified Tax — 2%/4%/8% of revenue quarterly,"Упрощённый налог — 2%/4%/8% от выручки, ежеквартально" +VAT (ƏDV) — 18% monthly declaration by 20th of next month,"НДС — 18%, ежемесячно, декларация до 20-го числа" +Profit Tax (Mənfəət) — 20% annual declaration (Mar 31) quarterly advances,"Налог на прибыль — 20%, годовая декларация (31 мар), квартальные авансы" +Property Tax (Əmlak) — 1% annual declaration quarterly advances,"Налог на имущество — 1%, годовая декларация, квартальные авансы" +Special Simplified Tax — rates vary by activity type,Специальный упрощённый налог — ставки зависят от вида деятельности +Most common regime for medium/large businesses with VAT registration.,Наиболее распространённый режим для средних/крупных предприятий с регистрацией НДС. +For micro/small businesses WITHOUT VAT registration. Replaces VAT + Profit Tax.,Для микро/малого бизнеса БЕЗ регистрации НДС. Заменяет НДС + Налог на прибыль. +For construction real estate housing cash withdrawals. Uses special declaration forms.,"Для строительства, недвижимости, жилья, обналичивания. Используются специальные формы деклараций." +Corporate profit tax — Maddə 104 Tax Code,Корпоративный налог на прибыль — Статья 104 НК +Fixed assets — Maddə 199 Tax Code,Основные средства — Статья 199 НК +Retail/wholesale trade in Baku — Maddə 220.1,Розничная/оптовая торговля в Баку — Статья 220.1 +Catering/public food services — Maddə 220.1,Общественное питание — Статья 220.1 +Employer contribution — İşsizlikdən sığorta haqqında qanun,Взнос работодателя — Закон о страховании от безработицы +Employee withholding — İşsizlikdən sığorta haqqında qanun,Удержание с работника — Закон о страховании от безработицы +Social insurance employer — DSMF qanunu,Социальное страхование работодатель — Закон ГФСЗ +Social insurance employee — DSMF qanunu,Социальное страхование работник — Закон ГФСЗ +Medical insurance employer — İcbari tibbi sığorta qanunu,Медицинское страхование работодатель — Закон об ОМС +Medical insurance employee — İcbari tibbi sığorta qanunu,Медицинское страхование работник — Закон об ОМС +VAT Input Offset,Зачёт входного НДС +Deferred VAT (Sales),Отложенный НДС (продажи) +Deferred VAT (Purchases),Отложенный НДС (покупки) +Deferred Revenue (542),Отложенная выручка (542) +VAT (Value Added Tax),НДС (Налог на добавленную стоимость) +Profit Tax,Налог на прибыль +Property Tax,Налог на имущество +Land Tax,Земельный налог +"Select the company for which these settings apply. All auto-detection (regime, accounts, rates) will use data from this company. If you have multiple companies, the wizard will let you override per-run.","Выберите компанию, для которой применяются эти настройки. Все функции автоопределения (режим, счета, ставки) используют данные этой компании. Если у вас несколько компаний, мастер позволит переопределить настройки при каждом запуске." +"When enabled, saving this form will automatically detect: tax regime, VAT payer status, OKVED code, available assets, employees, and set recommended values for all settings below. You can always override any auto-detected value manually.","При включении сохранение формы автоматически определит: налоговый режим, статус плательщика НДС, код ОКВЭД, наличие активов, сотрудников и установит рекомендуемые значения для всех настроек ниже. Вы всегда можете изменить любое автоопределённое значение вручную." +"Read from Company settings. Kassa metodu: revenue, expenses, and VAT obligations are recognized only when payment is received/made. Hesablama metodu: recognized at invoice date. + +Per AZ Tax Code: Micro and Small entrepreneurs can use Kassa metodu. Medium and Big must use Hesablama metodu.","Читается из настроек компании. Кассовый метод: доходы, расходы и обязательства по НДС признаются только при получении/осуществлении оплаты. Метод начисления: признаются на дату выставления счёта. + +По Налоговому кодексу АР: микро- и малые предприниматели могут использовать кассовый метод. Средние и крупные обязаны использовать метод начисления." +"Account 245.1 — ƏDV gözləmədə (satış). Deferred output VAT — posted at Invoice, transferred to 521.3 at Payment.","Счёт 245.1 — ƏDV gözləmədə (satış). Отложенный НДС к уплате — проводка при выставлении счёта, перенос на 521.3 при оплате." +"Account 245.2 — ƏDV gözləmədə (alış). Deferred input VAT — posted at Invoice, transferred to 226 at Payment.","Счёт 245.2 — ƏDV gözləmədə (alış). Отложенный входящий НДС — проводка при выставлении счёта, перенос на 226 при оплате." +"Account 542 — Gələcək hesabat dövrünün gəlirləri. Deferred revenue — posted at Invoice, transferred to 601 at Payment.","Счёт 542 — Gələcək hesabat dövrünün gəlirləri. Отложенный доход — проводка при выставлении счёта, перенос на 601 при оплате." +"If ON: the tax regime is determined automatically from the company's VAT certificate and OKVED code. Companies with a VAT certificate → General regime. Companies without → Simplified. Special OKVED codes (construction, real estate) → Special regime. If OFF: the regime below is used as-is for every wizard run.","Если ВКЛ: налоговый режим определяется автоматически по НДС-свидетельству и коду ОКВЭД компании. Компании с НДС-свидетельством → Общий режим. Без свидетельства → Упрощённый. Специальные коды ОКВЭД (строительство, недвижимость) → Специальный режим. Если ВЫКЛ: используется режим, указанный ниже, для каждого запуска мастера." +"This determines which taxes the wizard will calculate: + +• General (VAT + Profit) — VAT monthly + Profit Tax quarterly. Most common for medium/large businesses registered as VAT payers. +• Simplified Tax — Quarterly simplified tax on revenue. For micro/small businesses NOT registered for VAT. +• Special Regime — Construction, real estate, housing, cash withdrawals. Uses simplified tax with special declaration forms. + +If 'Auto-detect' is ON above, this field shows the detected value but can still be changed.","Определяет, какие налоги будет рассчитывать мастер: + +• General (VAT + Profit) — НДС ежемесячно + Налог на прибыль ежеквартально. Наиболее распространён для средних/крупных предприятий, зарегистрированных как плательщики НДС. +• Simplified Tax — Ежеквартальный упрощённый налог с выручки. Для микро/малых предприятий, НЕ зарегистрированных по НДС. +• Special Regime — Строительство, недвижимость, ЖКХ, снятие наличных. Упрощённый налог со специальными формами деклараций. + +Если выше включено «Автоопределение», поле показывает обнаруженное значение, но его можно изменить." +"If ON (recommended): before creating any journal entry, the wizard scans GL Entries to check if a matching entry already exists for the same tax type, accounts, and period. + +If an existing entry is found: +• The wizard marks it as 'Existing' (green) and shows the existing JE number and amount +• No duplicate entry is created +• If the calculated amount differs from the existing amount, a warning with the difference is shown + +This is essential when tax entries come from external sources (e.g., e-taxes portal imports) or were created manually. The wizard automatically adapts — no configuration needed per period.","Если ВКЛ (рекомендуется): перед созданием любой проводки мастер сканирует записи ГК, чтобы проверить, существует ли уже аналогичная проводка для того же типа налога, счетов и периода. + +Если существующая запись найдена: +• Мастер отмечает её как «Существующая» (зелёная) и показывает номер и сумму существующей проводки +• Дублирующая запись не создаётся +• Если рассчитанная сумма отличается от существующей, показывается предупреждение с разницей + +Это важно, когда налоговые проводки поступают из внешних источников (например, импорт с портала e-taxes) или были созданы вручную. Мастер автоматически адаптируется — настройка не требуется для каждого периода." +"If ON: the wizard will create a new journal entry even if a matching one already exists. Use only if you intentionally need duplicate entries (very rare). + +If OFF (default, safe): existing entries are respected and not duplicated.","Если ВКЛ: мастер создаст новую проводку, даже если аналогичная уже существует. Используйте, только если вам намеренно нужны дублирующие записи (очень редко). + +Если ВЫКЛ (по умолчанию, безопасно): существующие записи учитываются и не дублируются." +"If ON: when an existing entry is found but its amount differs from the wizard's calculation, the wizard will offer a correction entry for the difference. + +For example: existing VAT entry = 1,296 AZN, wizard calculates 1,300 AZN → offers a correction entry of 4 AZN. + +The correction entry is shown in Entry Preview with status 'Difference' (orange) and is optional — you can disable it per row. + +If OFF: differences are only logged as warnings, no correction entries are offered.","Если ВКЛ: когда найдена существующая запись, но её сумма отличается от расчёта мастера, мастер предложит корректирующую проводку на разницу. + +Например: существующая проводка НДС = 1 296 AZN, мастер рассчитал 1 300 AZN → предлагает корректирующую проводку на 4 AZN. + +Корректирующая проводка отображается в предпросмотре со статусом «Разница» (оранжевая) и является необязательной — вы можете отключить её для каждой строки. + +Если ВЫКЛ: разницы только фиксируются как предупреждения, корректирующие проводки не предлагаются." +"If ON: journal entries created by the wizard will be immediately submitted (docstatus=1) and will affect account balances right away. Use this only if you trust the calculation and don't need manual review. + +If OFF (recommended): entries are created as Draft. You can review each one in the Journal Entry list and submit manually or in bulk. This is safer — you can edit or delete drafts if something looks wrong.","Если ВКЛ: проводки, созданные мастером, будут сразу проведены (docstatus=1) и немедленно повлияют на остатки по счетам. Используйте, только если вы доверяете расчёту и ручная проверка не нужна. + +Если ВЫКЛ (рекомендуется): проводки создаются как черновики. Вы можете просмотреть каждую в списке проводок и провести вручную или массово. Это безопаснее — черновики можно редактировать или удалить, если что-то выглядит неправильно." +"Controls what happens when you run the wizard for a period that was already closed. + +If OFF (default, safe): the wizard will refuse to run and show which previous wizard closed this period. This prevents accidental double-posting. + +If ON: the wizard will cancel all journal entries from the previous run and create new ones. The old wizard document will be marked as superseded. Use this when you need to correct previously posted tax entries (e.g., after fixing invoices or asset values).","Определяет поведение при запуске мастера для уже закрытого периода. + +Если ВЫКЛ (по умолчанию, безопасно): мастер откажется запускаться и покажет, какой предыдущий запуск закрыл этот период. Это предотвращает случайное двойное проведение. + +Если ВКЛ: мастер отменит все проводки от предыдущего запуска и создаст новые. Старый документ мастера будет отмечен как заменённый. Используйте, когда нужно исправить ранее проведённые налоговые проводки (например, после исправления счетов или стоимости активов)." +"How the wizard checks for duplicate closings: + +• Same Company + Same Period — blocks if the exact same company and date range was already closed. Most restrictive. +• Same Company + Overlapping Dates — blocks if any part of the period overlaps with a previous closing. Catches partial overlaps. + +Only relevant when 'Allow Re-closing' is OFF.","Как мастер проверяет наличие дублирующих закрытий: + +• Same Company + Same Period — блокирует, если точно такая же компания и диапазон дат уже были закрыты. Самый строгий вариант. +• Same Company + Overlapping Dates — блокирует, если любая часть периода пересекается с предыдущим закрытием. Улавливает частичные пересечения. + +Актуально только при выключенном «Разрешить повторное закрытие»." +"If ON: after creating tax journal entries, the wizard will also create a standard ERPNext Period Closing Voucher. This closes all Income and Expense accounts and transfers the net result to the Closing Account below. + +If OFF (default): the wizard only creates tax accrual entries. You close P&L manually via Accounting → Period Closing Voucher. Recommended to keep OFF if your accountant closes periods separately. + +IMPORTANT: Period Closing Voucher is irreversible after submission. The wizard will create it in Draft if 'Auto-submit' is OFF.","Если ВКЛ: после создания налоговых проводок мастер также создаст стандартный Документ закрытия периода ERPNext. Он закроет все счета доходов и расходов и перенесёт чистый результат на Закрывающий счёт ниже. + +Если ВЫКЛ (по умолчанию): мастер создаёт только проводки по начислению налогов. Вы закрываете ОПУ (отчёт о прибылях и убытках) вручную через Бухгалтерия → Документ закрытия периода. Рекомендуется оставить ВЫКЛ, если ваш бухгалтер закрывает периоды отдельно. + +ВАЖНО: Документ закрытия периода необратим после проведения. Мастер создаст его как черновик, если «Автопроведение» ВЫКЛ." +"The equity/liability account where net profit or loss will be transferred when P&L accounts are closed. + +Standard AZ Chart of Accounts: +• 341 — Hesabat dövrünün xalis mənfəəti (Current period net profit) +• 342 — Keçmiş illərin bölüşdürülməmiş mənfəəti (Retained earnings) + +The account MUST be of type Liability or Equity, and in the same currency as the company.","Счёт собственного капитала/обязательств, на который переносится чистая прибыль или убыток при закрытии счетов ОПУ (отчёт о прибылях и убытках). + +Стандартный План счетов АР: +• 341 — Hesabat dövrünün xalis mənfəəti (Чистая прибыль отчётного периода) +• 342 — Keçmiş illərin bölüşdürülməmiş mənfəəti (Нераспределённая прибыль прошлых лет) + +Счёт ДОЛЖЕН быть типа «Обязательства» или «Капитал» и в той же валюте, что и компания." +"If ON: the wizard will create a draft of the corresponding tax declaration (e.g., Declaration of Value Added Tax, Income Tax Return) pre-filled with calculated values. + +If OFF (default): only journal entries are created. Declarations are filed separately via Tax Declaration doctypes or the E-Taxes portal. + +Note: This feature is under development. Currently only VAT and Simplified Tax declarations are supported.","Если ВКЛ: мастер создаст черновик соответствующей налоговой декларации (например, Декларация по НДС, Декларация по налогу на прибыль), предзаполненный рассчитанными значениями. + +Если ВЫКЛ (по умолчанию): создаются только проводки. Декларации подаются отдельно через формы налоговых деклараций или портал E-Taxes. + +Примечание: эта функция в разработке. В настоящее время поддерживаются только декларации по НДС и упрощённому налогу." +"If ON: declaration fields (VOEN, activity code, tax authority, etc.) will be auto-filled from Company data and calculated amounts. + +If OFF: only the amounts section will be filled; header fields (VOEN, period, etc.) are left blank for manual entry. + +Requires 'Create Declaration Draft' to be enabled.","Если ВКЛ: поля декларации (VOEN, код деятельности, налоговый орган и т.д.) будут автоматически заполнены из данных компании и рассчитанных сумм. + +Если ВЫКЛ: заполняется только раздел сумм; поля заголовка (VOEN, период и т.д.) остаются пустыми для ручного ввода. + +Требуется включение «Создавать черновик декларации»." +"All tax journal entries will use this cost center by default. If left blank, the company's default cost center is used. + +You can override this per wizard run.","Все налоговые проводки будут использовать этот центр затрат по умолчанию. Если оставить пустым, используется центр затрат компании по умолчанию. + +Вы можете переопределить это при каждом запуске мастера." +"If ON: each tax type can have its own cost center (configured in the Account Mapping table below). For example, VAT entries go to 'Sales' cost center, while property tax goes to 'Admin'. + +If OFF (default): all tax entries use the single default cost center above.","Если ВКЛ: каждый тип налога может иметь свой центр затрат (настраивается в таблице «Карта счетов» ниже). Например, проводки НДС идут в центр затрат «Продажи», а налог на имущество — в «Администрация». + +Если ВЫКЛ (по умолчанию): все налоговые проводки используют единый центр затрат по умолчанию, указанный выше." +"Enable VAT calculation in the wizard. The wizard will calculate the difference between output VAT (collected from customers) and input VAT (paid to suppliers) for each month in the period. + +Disable this only if you handle VAT entries manually or via a separate process.","Включить расчёт НДС в мастере. Мастер рассчитает разницу между НДС к уплате (собранным с покупателей) и входящим НДС (уплаченным поставщикам) за каждый месяц периода. + +Отключайте только если вы ведёте проводки по НДС вручную или через отдельный процесс." +"Standard VAT rate in Azerbaijan is 18% (Maddə 159 of the Tax Code). This is used for reference and reporting only — the actual VAT amounts are calculated from GL account balances, not by applying this rate. + +Change only if the government announces a new rate.","Стандартная ставка НДС в Азербайджане — 18% (Maddə 159 Налогового кодекса). Используется только для справки и отчётности — фактические суммы НДС рассчитываются из остатков по счетам ГК, а не применением этой ставки. + +Изменяйте только при объявлении новой ставки правительством." +"The asset account where input VAT (VAT paid to suppliers on purchases) is recorded. + +Standard AZ: Account 226 — ƏDV sub-uçot hesabı. + +This account has a DEBIT balance. The wizard reads this balance to determine how much input VAT you can offset against output VAT.","Счёт актива, на котором учитывается входящий НДС (НДС, уплаченный поставщикам при покупках). + +Стандарт АР: счёт 226 — ƏDV sub-uçot hesabı. + +Этот счёт имеет ДЕБЕТОВОЕ сальдо. Мастер считывает это сальдо, чтобы определить, сколько входящего НДС можно зачесть против НДС к уплате." +"The liability account where output VAT (VAT collected from customers on sales) is recorded. + +Standard AZ: Account 521.3 — ƏDV. + +This account has a CREDIT balance. The wizard reads this balance and offsets it against input VAT to determine the net VAT payable to the budget.","Счёт обязательств, на котором учитывается НДС к уплате (НДС, собранный с покупателей при продажах). + +Стандарт АР: счёт 521.3 — ƏDV. + +Этот счёт имеет КРЕДИТОВОЕ сальдо. Мастер считывает это сальдо и зачитывает его против входящего НДС для определения чистого НДС к уплате в бюджет." +"If ON: after calculating VAT from GL balances, the wizard will compare the result with the VAT Allocation doctype (if you use it). Any discrepancy will be logged as a warning. + +If OFF: VAT is calculated purely from GL account balances. This is simpler and works for most companies. + +Enable only if you actively use the VAT Allocation system in taxes_az for distributing input VAT across taxable/exempt operations.","Если ВКЛ: после расчёта НДС из остатков ГК мастер сравнит результат с данными из документа «Распределение НДС» (если вы его используете). Любое расхождение будет зафиксировано как предупреждение. + +Если ВЫКЛ: НДС рассчитывается исключительно из остатков по счетам ГК. Это проще и подходит для большинства компаний. + +Включайте только если вы активно используете систему распределения НДС в taxes_az для распределения входящего НДС между облагаемыми/необлагаемыми операциями." +"How VAT amounts are determined: + +• GL Account Balance — reads debit/credit balances from VAT accounts (226 and 521.3) for each month. Simple, reliable, captures all transactions including manual adjustments. + +• Sales/Purchase Invoices — sums VAT from individual invoices. More granular but may miss manual journal entries or corrections. + +Recommended: GL Account Balance.","Как определяются суммы НДС: + +• GL Account Balance — считывает дебетовые/кредитовые сальдо со счетов НДС (226 и 521.3) за каждый месяц. Просто, надёжно, учитывает все операции, включая ручные корректировки. + +• Sales/Purchase Invoices — суммирует НДС из отдельных счетов-фактур. Более детально, но может пропустить ручные проводки или корректировки. + +Рекомендуется: GL Account Balance." +"Enable profit tax calculation. The wizard will calculate quarterly advance payments based on the method selected below. + +Per AZ Tax Code: annual declaration is due by March 31 of the following year, but advance payments are due quarterly (by the 15th of the month after the quarter ends). + +Disable only if you handle profit tax entries manually.","Включить расчёт налога на прибыль. Мастер рассчитает ежеквартальные авансовые платежи на основе выбранного ниже метода. + +По Налоговому кодексу АР: годовая декларация подаётся до 31 марта следующего года, но авансовые платежи уплачиваются ежеквартально (до 15-го числа месяца после окончания квартала). + +Отключайте, только если вы ведёте проводки по налогу на прибыль вручную." +"Corporate profit tax rate. Standard rate in Azerbaijan is 20% (Maddə 104 of the Tax Code). + +Applied to: Net Profit = Total Income − Total Expenses for the period. + +Change only if your company qualifies for a reduced rate (e.g., certain agricultural or IT activities may have incentives).","Ставка налога на прибыль. Стандартная ставка в Азербайджане — 20% (Maddə 104 Налогового кодекса). + +Применяется к: Чистая прибыль = Совокупный доход − Совокупные расходы за период. + +Изменяйте, только если ваша компания имеет право на пониженную ставку (например, определённые сельскохозяйственные или IT-виды деятельности могут иметь льготы)." +"How quarterly advance payments are calculated: + +• Actual Quarterly Profit — calculates 20% of the actual profit for the current quarter (Income − Expenses from P&L). More accurate, reflects current performance. Recommended for growing or seasonal businesses. + +• 1/4 of Previous Year Tax — takes the total profit tax from the previous year and divides by 4. Simpler, more predictable payments. Better for stable businesses. Requires 'Previous Year Tax Amount' to be filled in. + +Both methods are accepted by the AZ tax authorities. The annual declaration reconciles any over/underpayment.","Как рассчитываются квартальные авансовые платежи: + +• Actual Quarterly Profit — рассчитывает 20% от фактической прибыли за текущий квартал (Доходы − Расходы из ОПУ). Более точный, отражает текущую деятельность. Рекомендуется для растущих или сезонных бизнесов. + +• 1/4 of Previous Year Tax — берёт общую сумму налога на прибыль за прошлый год и делит на 4. Проще, более предсказуемые платежи. Подходит для стабильных бизнесов. Требует заполнения «Сумма налога за прошлый год». + +Оба метода принимаются налоговыми органами АР. Годовая декларация сверяет любую переплату/недоплату." +"Enter the total profit tax amount from the previous fiscal year. The wizard will divide this by 4 for each quarterly advance payment. + +You can find this in last year's Income Tax Return (field: total calculated tax) or from the previous year's Tax Period Closing Wizard summary. + +Leave at 0 if using 'Actual Quarterly Profit' method.","Введите общую сумму налога на прибыль за предыдущий финансовый год. Мастер разделит её на 4 для каждого квартального авансового платежа. + +Эту сумму можно найти в прошлогодней Декларации по налогу на прибыль (поле: итого исчисленный налог) или в сводке Мастера закрытия налогового периода за прошлый год. + +Оставьте 0, если используете метод «Actual Quarterly Profit»." +"Enable simplified tax calculation. Applied quarterly to total revenue (not profit). + +Per AZ Tax Code (Maddə 218-227): simplified tax replaces VAT and profit tax for qualifying businesses. Declaration and payment are due by the 20th of the month after the quarter. + +Disable only if you handle simplified tax entries manually.","Включить расчёт упрощённого налога. Применяется ежеквартально к общей выручке (не прибыли). + +По Налоговому кодексу АР (Maddə 218-227): упрощённый налог заменяет НДС и налог на прибыль для соответствующих предприятий. Декларация и оплата — до 20-го числа месяца после окончания квартала. + +Отключайте, только если вы ведёте проводки по упрощённому налогу вручную." +"If ON: the wizard determines the simplified tax rate automatically based on the company's OKVED (activity code): + +• Trade activities in Baku → 2% +• Catering (public food) → 8% +• All other activities → 4% + +If OFF: the 'Default Rate' below is used for all calculations regardless of activity type.","Если ВКЛ: мастер определяет ставку упрощённого налога автоматически на основе ОКВЭД (кода деятельности) компании: + +• Торговая деятельность в Баку → 2% +• Общественное питание → 8% +• Все прочие виды деятельности → 4% + +Если ВЫКЛ: для всех расчётов используется «Ставка по умолчанию» ниже, независимо от вида деятельности." +"The flat simplified tax rate applied to total revenue. + +Standard rates per AZ Tax Code (Maddə 220.1): +• 2% — Retail/wholesale trade in Baku +• 4% — Services, production, and other activities +• 8% — Catering/public food services + +This field is only used when 'Auto-detect by OKVED' is OFF.","Фиксированная ставка упрощённого налога, применяемая к общей выручке. + +Стандартные ставки по Налоговому кодексу АР (Maddə 220.1): +• 2% — Розничная/оптовая торговля в Баку +• 4% — Услуги, производство и прочие виды деятельности +• 8% — Общественное питание + +Это поле используется только при выключенном «Автоопределение по ОКВЭД»." +"What counts as 'revenue' for simplified tax calculation: + +• Total Income (P&L) — sums all Income-type accounts from the P&L for the quarter. Includes sales revenue, other operating income, etc. Most common. + +• Sales Revenue Only — only account 601 (Satış gəlirləri). Excludes other income like interest, rent, etc. Use if your other income is not subject to simplified tax. + +• Cash Receipts — revenue recognized when cash is received (cash-basis). For companies using cash-basis accounting.","Что считается «выручкой» для расчёта упрощённого налога: + +• Total Income (P&L) — суммирует все счета типа «Доход» из ОПУ за квартал. Включает выручку от продаж, прочие операционные доходы и т.д. Наиболее распространённый вариант. + +• Sales Revenue Only — только счёт 601 (Satış gəlirləri). Исключает прочие доходы: проценты, аренда и т.д. Используйте, если прочие доходы не облагаются упрощённым налогом. + +• Cash Receipts — выручка признаётся при получении денежных средств (кассовый метод). Для компаний, использующих кассовый метод учёта." +"Enable property tax calculation on fixed assets. Calculated annually but paid in quarterly advances (1/4 each quarter). + +Per AZ Tax Code (Maddə 196-202): tax is levied on the average annual net book value of fixed assets (buildings, equipment, vehicles, etc.). + +Disable if the company has no fixed assets or all assets are tax-exempt under Maddə 199.4.","Включить расчёт налога на имущество по основным средствам. Рассчитывается ежегодно, но уплачивается ежеквартальными авансами (1/4 каждый квартал). + +По Налоговому кодексу АР (Maddə 196-202): налог взимается со среднегодовой остаточной стоимости основных средств (здания, оборудование, транспорт и т.д.). + +Отключите, если у компании нет основных средств или все активы освобождены от налога по Maddə 199.4." +"Property tax rate on the average annual residual value of fixed assets. + +Standard rate: 1% (Maddə 199 of the Tax Code). + +Applied to: (Value at start of year + Value at end of year) / 2. + +This rate applies uniformly to all taxable asset types (buildings, equipment, vehicles, etc.).","Ставка налога на имущество от среднегодовой остаточной стоимости основных средств. + +Стандартная ставка: 1% (Maddə 199 Налогового кодекса). + +Применяется к: (Стоимость на начало года + Стоимость на конец года) / 2. + +Эта ставка применяется единообразно ко всем облагаемым видам активов (здания, оборудование, транспорт и т.д.)." +"Net book value threshold for property tax reporting. Per Maddə 199 of the AZ Tax Code, enterprises with average annual asset value exceeding this threshold may have additional reporting requirements. + +Standard value: 1,000,000 AZN. + +The wizard will show a warning in the log if your company's net book value exceeds this threshold.","Порог остаточной стоимости для отчётности по налогу на имущество. По Maddə 199 Налогового кодекса АР, предприятия со среднегодовой стоимостью активов, превышающей этот порог, могут иметь дополнительные требования к отчётности. + +Стандартное значение: 1 000 000 AZN. + +Мастер покажет предупреждение в журнале, если остаточная стоимость активов вашей компании превышает этот порог." +"How the taxable asset value is calculated: + +• Average Annual (Start + End) / 2 — standard method per Maddə 199. Takes the residual value at the start of the year and the end of the year, averages them. Used for companies operating the full year (Tam İl). + +• Pro-rata for New Companies — for companies established during the year (İl Ərzində). Formula: (Purchase value + Year-end value) / 24 × months of operation. + +• End of Period NBV — uses the net book value at the end of the closing period. Simpler but less accurate for annual tax.","Как рассчитывается налогооблагаемая стоимость активов: + +• Average Annual (Start + End) / 2 — стандартный метод по Maddə 199. Берёт остаточную стоимость на начало и конец года, усредняет. Используется для компаний, работающих полный год (Tam İl). + +• Pro-rata for New Companies — для компаний, созданных в течение года (İl Ərzində). Формула: (Стоимость приобретения + Стоимость на конец года) / 24 × месяцы работы. + +• End of Period NBV — использует остаточную стоимость на конец закрываемого периода. Проще, но менее точно для годового налога." +"Enable land tax calculation. Uses existing calculation logic from Land Tax Declaration in taxes_az (cadastral scores, industrial land rates). + +Per AZ Tax Code (Maddə 206-212): land tax is calculated based on cadastral value and land purpose. Annual declaration, quarterly payments. + +Enable only if the company owns or uses land plots. The wizard will use rates from cadastral_points_data.json and industrial_land_data.json.","Включить расчёт земельного налога. Использует существующую логику расчёта из Декларации по земельному налогу в taxes_az (кадастровые баллы, ставки для промышленных земель). + +По Налоговому кодексу АР (Maddə 206-212): земельный налог рассчитывается на основе кадастровой стоимости и назначения земли. Годовая декларация, ежеквартальные платежи. + +Включайте, только если компания владеет или использует земельные участки. Мастер будет использовать ставки из cadastral_points_data.json и industrial_land_data.json." +"Where land tax amounts come from: + +• Land Tax Declaration — reads the calculated amount from an existing Land Tax Declaration document for the fiscal year. You must create and calculate the Land Tax Declaration first. + +• Manual Entry — you manually enter the annual land tax amount in the wizard. The wizard will divide by 4 for quarterly advances. + +Recommended: Land Tax Declaration (uses existing cadastral calculation logic).","Откуда берутся суммы земельного налога: + +• Land Tax Declaration — считывает рассчитанную сумму из существующего документа Декларации по земельному налогу за финансовый год. Вы должны сначала создать и рассчитать Декларацию по земельному налогу. + +• Manual Entry — вы вручную вводите годовую сумму земельного налога в мастере. Мастер разделит на 4 для ежеквартальных авансов. + +Рекомендуется: Land Tax Declaration (использует существующую логику кадастрового расчёта)." +"Enable excise tax tracking. Relevant only for companies producing or importing excisable goods (alcohol, tobacco, fuel, vehicles). + +The wizard will check if the company's OKVED code matches excise-related activities. If enabled but no excisable activities are found, it will be skipped with a note in the log. + +Monthly calculation and declaration.","Включить учёт акцизного налога. Актуально только для компаний, производящих или импортирующих подакцизные товары (алкоголь, табак, топливо, транспортные средства). + +Мастер проверит, соответствует ли код ОКВЭД компании акцизным видам деятельности. Если включено, но акцизные виды деятельности не найдены, этап будет пропущен с записью в журнале. + +Ежемесячный расчёт и декларация." +"Enable mining tax tracking. Relevant only for companies extracting mineral resources (oil, gas, metals, stone, etc.). + +The wizard will check if the company's OKVED code matches mining activities and if mining-related Item Groups exist. Monthly calculation. + +Enable only if the company has mining operations.","Включить учёт налога на добычу полезных ископаемых. Актуально только для компаний, добывающих минеральные ресурсы (нефть, газ, металлы, камень и т.д.). + +Мастер проверит, соответствует ли код ОКВЭД компании добывающим видам деятельности и существуют ли связанные группы товаров. Ежемесячный расчёт. + +Включайте, только если у компании есть добывающие операции." +"Enable road tax tracking. Relevant for companies owning vehicles registered in Azerbaijan. + +The wizard will check if the company has transportation assets. Annual calculation. + +Enable only if the company owns registered vehicles.","Включить учёт дорожного налога. Актуально для компаний, владеющих транспортными средствами, зарегистрированными в Азербайджане. + +Мастер проверит наличие транспортных активов у компании. Годовой расчёт. + +Включайте, только если компания владеет зарегистрированными транспортными средствами." +"If ON: the wizard collects payroll data (gross pay, NDFL, DSMF, unemployment insurance, medical insurance) from Salary Slips for the period. This data is shown in the summary report for reference. + +NOTE: The wizard does NOT create payroll-related journal entries — those are already created by the Payroll Entry process. The wizard only COLLECTS and DISPLAYS the totals. + +If OFF: payroll data is not included in the wizard summary.","Если ВКЛ: мастер собирает данные по зарплате (начисления, НДФЛ, ДСМФ, страхование от безработицы, медицинское страхование) из расчётных листков за период. Эти данные отображаются в сводном отчёте для справки. + +ПРИМЕЧАНИЕ: мастер НЕ создаёт проводки по зарплате — они уже создаются процессом расчёта зарплаты. Мастер только СОБИРАЕТ и ОТОБРАЖАЕТ итоги. + +Если ВЫКЛ: данные по зарплате не включаются в сводку мастера." +"Where payroll totals are collected from: + +• Salary Slips — reads submitted Salary Slip documents for the period. Provides detailed breakdown by salary component (deductions, contributions). + +• GL Account Balances — reads balances from payroll-related accounts (522, 522.1-4, 533). Faster but less detailed. + +• Both (with cross-check) — reads from both sources and flags discrepancies. Most thorough but slowest. + +Recommended: Salary Slips.","Откуда собираются итоги по зарплате: + +• Salary Slips — считывает проведённые расчётные листки за период. Предоставляет детальную разбивку по компонентам зарплаты (удержания, взносы). + +• GL Account Balances — считывает сальдо со счетов, связанных с зарплатой (522, 522.1-4, 533). Быстрее, но менее детально. + +• Both (with cross-check) — считывает из обоих источников и выявляет расхождения. Наиболее тщательно, но медленнее. + +Рекомендуется: Salary Slips." +Reference table of all tax rates used by the wizard. Click 'Load Default Rates' to populate with standard AZ rates. You can modify rates here — changes affect all future wizard runs.,"Справочная таблица всех налоговых ставок, используемых мастером. Нажмите «Загрузить ставки по умолчанию», чтобы заполнить стандартными ставками АР. Вы можете изменять ставки здесь — изменения повлияют на все будущие запуски мастера." +"Each row defines a tax type and its rate. These rates are used as reference for calculations and for taxes that use fixed rates (property, DSMF, unemployment insurance). VAT and profit tax have their own rate fields above for convenience. + +Click 'Load Default AZ Rates' button to populate with standard rates from the AZ Tax Code.","Каждая строка определяет тип налога и его ставку. Эти ставки используются как справочные для расчётов и для налогов с фиксированными ставками (имущество, ДСМФ, страхование от безработицы). НДС и налог на прибыль имеют собственные поля ставок выше для удобства. + +Нажмите кнопку «Загрузить стандартные ставки АР», чтобы заполнить стандартными ставками из Налогового кодекса АР." +"Maps each tax type to the accounts used for journal entries. Debit = Expense account, Credit = Liability account. Click 'Auto-detect Accounts' to find matching accounts from your Chart of Accounts.","Сопоставляет каждый тип налога со счетами для проводок. Дебет = счёт расходов, Кредит = счёт обязательств. Нажмите «Автоопределение счетов», чтобы найти подходящие счета из вашего Плана счетов." +"Each row maps a tax type to a pair of accounts: +• Debit (Expense) — where the tax cost is recorded (e.g., 901 for profit tax expense) +• Credit (Liability) — where the tax payable is recorded (e.g., 521.1 for profit tax liability) + +If the company uses the standard AZ Chart of Accounts, click 'Auto-detect Accounts' to find the correct accounts automatically.","Каждая строка сопоставляет тип налога с парой счетов: +• Дебет (Расход) — где учитывается налоговый расход (например, 901 для расхода по налогу на прибыль) +• Кредит (Обязательство) — где учитывается налог к уплате (например, 521.1 для обязательства по налогу на прибыль) + +Если компания использует стандартный План счетов АР, нажмите «Автоопределение счетов», чтобы автоматически найти правильные счета."