jey_erp/jey_erp/custom/sales_invoice.py

238 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import frappe
import json
from frappe.utils import flt
def validate_won_lost_fields(doc, method=None):
"""
Ensure that exactly one of won/lost checkboxes is always selected for gambling companies
Automatically fixes the issue instead of throwing an error
"""
if doc.doctype != "Sales Invoice":
return
# Check if this is a gambling company
allowed_activities = ["92000", "9200003", "9200004", "9200001", "9200002", "9200005"]
company_main_activity = doc.get('company_main_activity')
if not company_main_activity or company_main_activity not in allowed_activities:
# Not a gambling company - skip validation
return
# Check if both fields exist
if not hasattr(doc, 'won') or not hasattr(doc, 'lost'):
return
# Для gambling компаний - автоматически исправляем ситуацию
if not doc.won and not doc.lost:
# Если обе выключены - включаем won по умолчанию
doc.won = 1
doc.lost = 0
elif doc.won and doc.lost:
# Если обе включены - оставляем только won
doc.lost = 0
def calculate_tax_free_amounts(doc, method=None):
"""
Calculate tax free amounts for Sales Invoice
Called after calculate_taxes_and_totals
"""
if doc.doctype != "Sales Invoice":
return
# Clear existing tax_free_amount values
for tax in doc.get("taxes", []):
if hasattr(tax, 'tax_free_amount'):
tax.tax_free_amount = 0.0
# Calculate tax free amounts
for item in doc.get("items", []):
if not item.get("item_code"):
continue
# Try to get tax template from item row first
item_tax_template = item.get("item_tax_template")
item_tax_rate = item.get("item_tax_rate")
# If no tax info in item row, get from Item master
if not item_tax_template and not item_tax_rate:
try:
item_doc = frappe.get_doc("Item", item.item_code)
item_tax_template = item_doc.get("item_tax_template")
except Exception:
continue
# Process item_tax_rate (JSON format)
if item_tax_rate:
try:
if isinstance(item_tax_rate, str):
tax_rates = json.loads(item_tax_rate)
else:
tax_rates = item_tax_rate
for account_head, rate in tax_rates.items():
if flt(rate) == 0.0:
# Find corresponding tax in sales invoice
for doc_tax in doc.get("taxes", []):
if doc_tax.account_head == account_head:
if hasattr(doc_tax, 'tax_free_amount'):
doc_tax.tax_free_amount = flt(doc_tax.tax_free_amount) + flt(item.net_amount)
break
except Exception:
pass
# Process item_tax_template
elif item_tax_template:
try:
tax_template = frappe.get_doc("Item Tax Template", item_tax_template)
for template_tax in tax_template.get("taxes", []):
if flt(template_tax.tax_rate) == 0.0:
# Find corresponding tax in sales invoice
for doc_tax in doc.get("taxes", []):
if doc_tax.account_head == template_tax.tax_type:
if hasattr(doc_tax, 'tax_free_amount'):
doc_tax.tax_free_amount = flt(doc_tax.tax_free_amount) + flt(item.net_amount)
break
except Exception:
pass
@frappe.whitelist()
def recalculate_tax_free_amounts(doc):
"""
API method to recalculate tax free amounts
Called from client side when items change
"""
if isinstance(doc, str):
doc = frappe.parse_json(doc)
# Convert to document object if it's a dict
if isinstance(doc, dict):
sales_invoice = frappe.get_doc(doc)
else:
sales_invoice = doc
# Calculate tax free amounts
calculate_tax_free_amounts(sales_invoice)
# Return updated taxes
taxes_data = []
for tax in sales_invoice.get("taxes", []):
tax_dict = tax.as_dict() if hasattr(tax, 'as_dict') else dict(tax)
taxes_data.append(tax_dict)
return taxes_data
@frappe.whitelist()
def sync_vat_to_taxes(doc):
"""
Синхронизирует фактические VAT amounts из items в taxes таблицу
Вызывается из клиентского JavaScript
"""
if isinstance(doc, str):
doc = frappe.parse_json(doc)
# Словарь для агрегации VAT amounts по account_head
vat_aggregates = {}
# Агрегируем vat_amount из items
for item in doc.get("items", []):
if not item.get("item_code"):
continue
# Округляем до 2 знаков при чтении
vat_amount = flt(item.get("vat_amount", 0), 2)
if vat_amount <= 0:
continue
item_tax_template = item.get("item_tax_template")
if not item_tax_template:
continue
# Получить account_head из налогового шаблона
try:
tax_template = frappe.get_doc("Item Tax Template", item_tax_template)
if tax_template.taxes and len(tax_template.taxes) > 0:
account_head = tax_template.taxes[0].tax_type
if account_head not in vat_aggregates:
vat_aggregates[account_head] = 0.0
vat_aggregates[account_head] += vat_amount
except Exception:
continue
# Обновляем taxes таблицу
updated_taxes = []
for tax in doc.get("taxes", []):
if tax.get("account_head") in vat_aggregates:
# Округляем итоговую сумму до 2 знаков
tax["tax_amount"] = flt(vat_aggregates[tax["account_head"]], 2)
tax["base_tax_amount"] = flt(vat_aggregates[tax["account_head"]], 2)
updated_taxes.append(tax)
return updated_taxes
def sync_actual_vat_amounts(doc, method=None):
"""
Перезаписывает tax_amount в taxes таблице фактическими суммами НДС из items.
Вызывается после стандартного calculate_taxes_and_totals.
"""
if doc.doctype not in ["Sales Invoice", "Sales Order"]:
return
# Словарь для агрегации сумм НДС по account_head
vat_aggregates = {}
# Агрегировать фактические суммы НДС из items
for item in doc.get("items", []):
if not item.get("item_code"):
continue
# Получить фактическую сумму НДС из строки товара
vat_amount = flt(item.get("vat_amount", 0), 2)
if vat_amount <= 0:
continue
# Получить налоговый шаблон товара
item_tax_template = item.get("item_tax_template")
if not item_tax_template:
# Если нет шаблона в строке, проверить Item master
try:
item_doc = frappe.get_doc("Item", item.item_code)
item_tax_template = item_doc.get("item_tax_template")
except Exception:
continue
if not item_tax_template:
continue
# Получить account_head из налогового шаблона
try:
tax_template = frappe.get_doc("Item Tax Template", item_tax_template)
# Взять первый tax_type (account_head) из шаблона
if tax_template.taxes and len(tax_template.taxes) > 0:
account_head = tax_template.taxes[0].tax_type
# Агрегировать сумму НДС с округлением
if account_head not in vat_aggregates:
vat_aggregates[account_head] = 0.0
vat_aggregates[account_head] += vat_amount
except Exception:
continue
# ПЕРЕЗАПИСАТЬ tax_amount в taxes таблице агрегированными суммами
for tax in doc.get("taxes", []):
account_head = tax.account_head
if account_head in vat_aggregates:
# Заменить стандартный расчет ERPNext нашей фактической суммой
# Округляем до 2 знаков
tax.tax_amount = flt(vat_aggregates[account_head], 2)
# Также обновить base_tax_amount для мультивалютных документов
tax.base_tax_amount = flt(vat_aggregates[account_head], 2)