From b7758589a9ec5ac69d81928ceb6882954352bf6d Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Thu, 15 Jan 2026 21:20:31 +0400 Subject: [PATCH] fixed vat issue --- jey_erp/custom/vat_calculations.py | 158 +++++++++++++++++++--------- jey_erp/public/js/vat_calculator.js | 91 ++++++++-------- 2 files changed, 151 insertions(+), 98 deletions(-) diff --git a/jey_erp/custom/vat_calculations.py b/jey_erp/custom/vat_calculations.py index 8ab9576..097943c 100644 --- a/jey_erp/custom/vat_calculations.py +++ b/jey_erp/custom/vat_calculations.py @@ -7,45 +7,71 @@ from frappe.utils import flt from erpnext.selling.doctype.sales_order.sales_order import SalesOrder from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice + +def get_vat_template_type(tax_template): + """ + Определяет тип VAT шаблона, игнорируя суффикс компании + Возвращает: '18%', 'daxil_18%', '0%', 'free', 'none' + """ + if not tax_template: + return 'none' + + # Убираем пробелы и проверяем начало строки + tax_template = str(tax_template).strip() + + # Порядок важен! Проверяем сначала более специфичные шаблоны + if tax_template.startswith("ƏDV daxil 18%"): + return 'daxil_18%' + elif tax_template.startswith("ƏDV 18%"): + return '18%' + elif tax_template.startswith("ƏDV 0%"): + return '0%' + elif tax_template.startswith("ƏDV-dən azadolma"): + return 'free' + else: + return 'none' + class CustomSalesInvoice(SalesInvoice): def on_update_after_submit(self): super().on_update_after_submit() - + def calculate_taxes_and_totals(self): # Сначала стандартные расчеты ERPNext super().calculate_taxes_and_totals() # Потом наши VAT расчеты self.calculate_vat_fields() - + def calculate_vat_fields(self): """Рассчитывает VAT поля для всех строк""" if not self.items: return - + for item in self.items: self.calculate_item_vat_fields(item) - + def calculate_item_vat_fields(self, item): """Рассчитывает VAT поля для одной строки""" if not item.item_code or flt(item.amount) <= 0: self.clear_item_vat_fields(item) return - + # Очищаем все VAT поля перед новым расчетом self.clear_item_vat_fields(item) - + # Блокируем tax_article если не разрешено self.handle_tax_article_permission(item) - + # Рассчитываем поля в зависимости от налогового шаблона amount = flt(item.amount) - tax_template = item.item_tax_template - - if tax_template == "ƏDV 18%": + vat_type = get_vat_template_type(item.item_tax_template) + + if vat_type == '18%': self.calculate_18_percent_vat(item, amount) - elif tax_template == "ƏDV 0%": + elif vat_type == 'daxil_18%': + self.calculate_18_percent_vat_inclusive(item, amount) + elif vat_type == '0%': self.calculate_0_percent_vat(item, amount) - elif tax_template == "ƏDV-dən azadolma": + elif vat_type == 'free': self.calculate_vat_free(item, amount) else: self.calculate_without_vat(item, amount) @@ -66,21 +92,30 @@ class CustomSalesInvoice(SalesInvoice): def handle_tax_article_permission(self, item): """Обрабатывает разрешения для поля tax_article""" - tax_template = item.item_tax_template - is_allowed = tax_template in ["ƏDV 0%", "ƏDV-dən azadolma"] - + vat_type = get_vat_template_type(item.item_tax_template) + is_allowed = vat_type in ['0%', 'free'] + # Очищаем tax_article если оно не должно быть доступно if not is_allowed and item.tax_article: item.tax_article = None def calculate_18_percent_vat(self, item, amount): - """Рассчитывает поля для ƏDV 18%""" + """Рассчитывает поля для ƏDV 18% (НДС сверху)""" vat_18_total = amount * 1.18 vat_amount = vat_18_total - amount - + item.vat_18_percent_with_amount = vat_18_total item.vat_amount = vat_amount - + + def calculate_18_percent_vat_inclusive(self, item, amount): + """Рассчитывает поля для ƏDV daxil 18% (НДС в том числе)""" + # amount уже включает НДС, выделяем его + amount_without_vat = amount / 1.18 + vat_amount = amount - amount_without_vat + + item.vat_18_percent_with_amount = amount + item.vat_amount = vat_amount + def calculate_0_percent_vat(self, item, amount): """Рассчитывает поля для ƏDV 0%""" item.vat_0_percent_with_amount = amount @@ -97,42 +132,44 @@ class CustomSalesInvoice(SalesInvoice): class CustomSalesOrder(SalesOrder): def on_update_after_submit(self): super().on_update_after_submit() - + def calculate_taxes_and_totals(self): # Сначала стандартные расчеты ERPNext super().calculate_taxes_and_totals() # Потом наши VAT расчеты self.calculate_vat_fields() - + def calculate_vat_fields(self): """Рассчитывает VAT поля для всех строк""" if not self.items: return - + for item in self.items: self.calculate_item_vat_fields(item) - + def calculate_item_vat_fields(self, item): """Рассчитывает VAT поля для одной строки""" if not item.item_code or flt(item.amount) <= 0: self.clear_item_vat_fields(item) return - + # Очищаем все VAT поля перед новым расчетом self.clear_item_vat_fields(item) - + # Блокируем tax_article если не разрешено self.handle_tax_article_permission(item) - + # Рассчитываем поля в зависимости от налогового шаблона amount = flt(item.amount) - tax_template = item.item_tax_template - - if tax_template == "ƏDV 18%": + vat_type = get_vat_template_type(item.item_tax_template) + + if vat_type == '18%': self.calculate_18_percent_vat(item, amount) - elif tax_template == "ƏDV 0%": + elif vat_type == 'daxil_18%': + self.calculate_18_percent_vat_inclusive(item, amount) + elif vat_type == '0%': self.calculate_0_percent_vat(item, amount) - elif tax_template == "ƏDV-dən azadolma": + elif vat_type == 'free': self.calculate_vat_free(item, amount) else: self.calculate_without_vat(item, amount) @@ -153,21 +190,30 @@ class CustomSalesOrder(SalesOrder): def handle_tax_article_permission(self, item): """Обрабатывает разрешения для поля tax_article""" - tax_template = item.item_tax_template - is_allowed = tax_template in ["ƏDV 0%", "ƏDV-dən azadolma"] - + vat_type = get_vat_template_type(item.item_tax_template) + is_allowed = vat_type in ['0%', 'free'] + # Очищаем tax_article если оно не должно быть доступно if not is_allowed and item.tax_article: item.tax_article = None def calculate_18_percent_vat(self, item, amount): - """Рассчитывает поля для ƏDV 18%""" + """Рассчитывает поля для ƏDV 18% (НДС сверху)""" vat_18_total = amount * 1.18 vat_amount = vat_18_total - amount - + item.vat_18_percent_with_amount = vat_18_total item.vat_amount = vat_amount - + + def calculate_18_percent_vat_inclusive(self, item, amount): + """Рассчитывает поля для ƏDV daxil 18% (НДС в том числе)""" + # amount уже включает НДС, выделяем его + amount_without_vat = amount / 1.18 + vat_amount = amount - amount_without_vat + + item.vat_18_percent_with_amount = amount + item.vat_amount = vat_amount + def calculate_0_percent_vat(self, item, amount): """Рассчитывает поля для ƏDV 0%""" item.vat_0_percent_with_amount = amount @@ -208,27 +254,29 @@ def calculate_vat_fields_for_doc(doc): """Универсальная функция для расчета VAT полей""" if not doc.items: return - + for item in doc.items: if not item.item_code or flt(item.amount) <= 0: clear_vat_fields(item) continue - + # Очищаем все VAT поля перед новым расчетом clear_vat_fields(item) - + # Блокируем tax_article если не разрешено handle_tax_article_permission(item) - + # Рассчитываем поля в зависимости от налогового шаблона amount = flt(item.amount) - tax_template = item.item_tax_template - - if tax_template == "ƏDV 18%": + vat_type = get_vat_template_type(item.item_tax_template) + + if vat_type == '18%': calculate_18_percent_vat(item, amount) - elif tax_template == "ƏDV 0%": + elif vat_type == 'daxil_18%': + calculate_18_percent_vat_inclusive(item, amount) + elif vat_type == '0%': calculate_0_percent_vat(item, amount) - elif tax_template == "ƏDV-dən azadolma": + elif vat_type == 'free': calculate_vat_free(item, amount) else: calculate_without_vat(item, amount) @@ -251,22 +299,32 @@ def clear_vat_fields(item): def handle_tax_article_permission(item): """Обрабатывает разрешения для поля tax_article""" - tax_template = item.item_tax_template - is_allowed = tax_template in ["ƏDV 0%", "ƏDV-dən azadolma"] - + vat_type = get_vat_template_type(item.item_tax_template) + is_allowed = vat_type in ['0%', 'free'] + if not is_allowed and item.tax_article: item.tax_article = None def calculate_18_percent_vat(item, amount): - """Рассчитывает поля для ƏDV 18%""" + """Рассчитывает поля для ƏDV 18% (НДС сверху)""" vat_18_total = amount * 1.18 vat_amount = vat_18_total - amount - + item.vat_18_percent_with_amount = vat_18_total item.vat_amount = vat_amount +def calculate_18_percent_vat_inclusive(item, amount): + """Рассчитывает поля для ƏDV daxil 18% (НДС в том числе)""" + # amount уже включает НДС, выделяем его + amount_without_vat = amount / 1.18 + vat_amount = amount - amount_without_vat + + item.vat_18_percent_with_amount = amount + item.vat_amount = vat_amount + + def calculate_0_percent_vat(item, amount): """Рассчитывает поля для ƏDV 0%""" item.vat_0_percent_with_amount = amount diff --git a/jey_erp/public/js/vat_calculator.js b/jey_erp/public/js/vat_calculator.js index a61cb09..6e6c2d9 100644 --- a/jey_erp/public/js/vat_calculator.js +++ b/jey_erp/public/js/vat_calculator.js @@ -182,22 +182,26 @@ jey_erp.vat_calculator = { // ОБНОВЛЕНО: tax_article разрешен только для ƏDV 0% и ƏDV-dən azadolma // Для обоих типов НДС 18% поле заблокировано is_tax_article_allowed: function(tax_template) { - return tax_template === "ƏDV 0%" || tax_template === "ƏDV-dən azadolma"; + if (!tax_template) return false; + const template = tax_template.trim(); + return template.startsWith("ƏDV 0%") || template.startsWith("ƏDV-dən azadolma"); }, // НОВАЯ функция: возвращает фильтр для tax_article в зависимости от налогового шаблона get_tax_article_filter: function(tax_template) { - switch (tax_template) { - case "ƏDV 0%": - return { - "parent_tax_article": "ƏDV-yə 0% dərəcə ilə tutulan əməliyyatlar" - }; - case "ƏDV-dən azadolma": - return { - "parent_tax_article": "ƏDV-dən azad olunan əməliyyatlar" - }; - default: - return null; + if (!tax_template) return null; + const template = tax_template.trim(); + + if (template.startsWith("ƏDV 0%")) { + return { + "parent_tax_article": "ƏDV-yə 0% dərəcə ilə tutulan əməliyyatlar" + }; + } else if (template.startsWith("ƏDV-dən azadolma")) { + return { + "parent_tax_article": "ƏDV-dən azad olunan əməliyyatlar" + }; + } else { + return null; } }, @@ -272,7 +276,7 @@ jey_erp.vat_calculator = { calculate_vat_fields: function(cdt, cdn, frm = null) { const item = locals[cdt][cdn]; - + // Получаем актуальные значения let amount; if (frm) { @@ -290,41 +294,32 @@ jey_erp.vat_calculator = { if (amount <= 0) return; - const tax_template = item.item_tax_template; - - switch (tax_template) { - case "ƏDV 18%": - // НДС 18% СВЕРХУ: к amount добавляется 18% - // amount = 100 -> vat_amount = 18, total = 118 - const vat_amount_top = amount * 0.18; - const total_with_vat_top = amount + vat_amount_top; - - frappe.model.set_value(cdt, cdn, 'vat_18_percent_with_amount', total_with_vat_top); - frappe.model.set_value(cdt, cdn, 'vat_amount', vat_amount_top); - break; - - case "ƏDV daxil 18%": - // НДС 18% В ТОМ ЧИСЛЕ: из amount выделяется НДС - // amount = 100 -> vat_amount = 15.25, amount без НДС = 84.75, total = 100 - const total_with_vat_included = amount; - const amount_without_vat = total_with_vat_included / 1.18; - const vat_amount_included = total_with_vat_included - amount_without_vat; - - frappe.model.set_value(cdt, cdn, 'vat_18_percent_with_amount', total_with_vat_included); - frappe.model.set_value(cdt, cdn, 'vat_amount', vat_amount_included); - break; - - case "ƏDV 0%": - frappe.model.set_value(cdt, cdn, 'vat_0_percent_with_amount', amount); - break; - - case "ƏDV-dən azadolma": - frappe.model.set_value(cdt, cdn, 'vat_free_amount', amount); - break; - - default: - frappe.model.set_value(cdt, cdn, 'amount_without_vat', amount); - break; + const tax_template = (item.item_tax_template || '').trim(); + + // Используем startsWith вместо точного сравнения + if (tax_template.startsWith("ƏDV daxil 18%")) { + // НДС 18% В ТОМ ЧИСЛЕ: из amount выделяется НДС + // amount = 100 -> vat_amount = 15.25, amount без НДС = 84.75, total = 100 + const total_with_vat_included = amount; + const amount_without_vat = total_with_vat_included / 1.18; + const vat_amount_included = total_with_vat_included - amount_without_vat; + + frappe.model.set_value(cdt, cdn, 'vat_18_percent_with_amount', total_with_vat_included); + frappe.model.set_value(cdt, cdn, 'vat_amount', vat_amount_included); + } else if (tax_template.startsWith("ƏDV 18%")) { + // НДС 18% СВЕРХУ: к amount добавляется 18% + // amount = 100 -> vat_amount = 18, total = 118 + const vat_amount_top = amount * 0.18; + const total_with_vat_top = amount + vat_amount_top; + + frappe.model.set_value(cdt, cdn, 'vat_18_percent_with_amount', total_with_vat_top); + frappe.model.set_value(cdt, cdn, 'vat_amount', vat_amount_top); + } else if (tax_template.startsWith("ƏDV 0%")) { + frappe.model.set_value(cdt, cdn, 'vat_0_percent_with_amount', amount); + } else if (tax_template.startsWith("ƏDV-dən azadolma")) { + frappe.model.set_value(cdt, cdn, 'vat_free_amount', amount); + } else { + frappe.model.set_value(cdt, cdn, 'amount_without_vat', amount); } },