fixed vat issue

This commit is contained in:
Ali 2026-01-15 21:20:31 +04:00
parent 22d22f4529
commit b7758589a9
2 changed files with 151 additions and 98 deletions

View File

@ -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

View File

@ -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);
}
},