fixed a lot of bugs, but some still unfixed
This commit is contained in:
parent
a198d2a59b
commit
94659173f6
|
|
@ -1042,6 +1042,20 @@ def create_purchase_invoice_from_order(purchase_order_name):
|
||||||
# ДОБАВЛЕНО: Устанавливаем галочку is_taxes_doc для Purchase Invoice
|
# ДОБАВЛЕНО: Устанавливаем галочку is_taxes_doc для Purchase Invoice
|
||||||
pi_doc.is_taxes_doc = 1
|
pi_doc.is_taxes_doc = 1
|
||||||
|
|
||||||
|
# ДОБАВЛЕНО: Копируем налоги из Purchase Order
|
||||||
|
po_doc = frappe.get_doc('Purchase Order', purchase_order_name)
|
||||||
|
if po_doc.taxes:
|
||||||
|
pi_doc.taxes = []
|
||||||
|
for tax_row in po_doc.taxes:
|
||||||
|
pi_doc.append("taxes", {
|
||||||
|
"account_head": tax_row.account_head,
|
||||||
|
"charge_type": tax_row.charge_type,
|
||||||
|
"rate": tax_row.rate,
|
||||||
|
"tax_amount": tax_row.tax_amount,
|
||||||
|
"description": tax_row.description,
|
||||||
|
"add_deduct_tax": tax_row.add_deduct_tax
|
||||||
|
})
|
||||||
|
|
||||||
# Сохраняем документ
|
# Сохраняем документ
|
||||||
pi_doc.insert(ignore_permissions=True)
|
pi_doc.insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
@ -1177,14 +1191,20 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
||||||
if invoice_data.get("items"):
|
if invoice_data.get("items"):
|
||||||
for item in invoice_data.get("items", []):
|
for item in invoice_data.get("items", []):
|
||||||
# Подготавливаем данные товара
|
# Подготавливаем данные товара
|
||||||
item_name = item.get("productName", "")
|
item_name = item.get("productName", "").strip()
|
||||||
item_code = item.get("itemId", "")
|
item_code = item.get("itemId", "") or f"CODE-{invoice_data.get('serialNumber', '')}"
|
||||||
|
|
||||||
# ИСПРАВЛЕНО: Ищем соответствие товара
|
# ИСПРАВЛЕНО: Ищем соответствие с учетом регистра и без
|
||||||
# Так как autoname = "field:etaxes_item_name", name документа E-Taxes Item = etaxes_item_name
|
|
||||||
# Поэтому проверяем напрямую по item_name в mappings
|
|
||||||
mapped_item = item_mappings.get(item_name)
|
mapped_item = item_mappings.get(item_name)
|
||||||
|
|
||||||
|
# Если не найдено, ищем без учета регистра
|
||||||
|
if not mapped_item:
|
||||||
|
item_name_lower = item_name.lower()
|
||||||
|
for mapping_key, mapping_value in item_mappings.items():
|
||||||
|
if mapping_key.lower() == item_name_lower:
|
||||||
|
mapped_item = mapping_value
|
||||||
|
break
|
||||||
|
|
||||||
if not mapped_item:
|
if not mapped_item:
|
||||||
# Если соответствие не найдено, добавляем в список несопоставленных
|
# Если соответствие не найдено, добавляем в список несопоставленных
|
||||||
unmatched_items.append({
|
unmatched_items.append({
|
||||||
|
|
@ -1237,8 +1257,8 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
||||||
po_item.description = item_doc.description or item_doc.item_name
|
po_item.description = item_doc.description or item_doc.item_name
|
||||||
except:
|
except:
|
||||||
# Fallback на название из E-Taxes если не удалось получить из базы
|
# Fallback на название из E-Taxes если не удалось получить из базы
|
||||||
po_item.item_name = item.get("productName", "")
|
po_item.item_name = item.get("productName", "").strip()
|
||||||
po_item.description = item.get("productName", "")
|
po_item.description = item.get("productName", "").strip()
|
||||||
|
|
||||||
po_item.qty = item.get("quantity", 0)
|
po_item.qty = item.get("quantity", 0)
|
||||||
po_item.rate = item.get("pricePerUnit", 0)
|
po_item.rate = item.get("pricePerUnit", 0)
|
||||||
|
|
@ -1281,6 +1301,40 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
||||||
'message': 'Could not add any items to order'
|
'message': 'Could not add any items to order'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ДОБАВЛЕНО: Создание строки ƏDV в Purchase Taxes and Charges ТОЛЬКО если есть vat в данных
|
||||||
|
try:
|
||||||
|
# Получаем VAT из E-Taxes данных
|
||||||
|
vat_amount = invoice_data.get('vat', 0)
|
||||||
|
|
||||||
|
# Создаем строку ƏDV ТОЛЬКО если есть vat в данных и он больше 0
|
||||||
|
if vat_amount and vat_amount > 0:
|
||||||
|
vat_account = frappe.db.get_value('Account',
|
||||||
|
filters={'account_number': '521.3'},
|
||||||
|
fieldname='name')
|
||||||
|
|
||||||
|
if vat_account:
|
||||||
|
# Рассчитываем net_total для определения ставки
|
||||||
|
net_total = sum(item.get('cost', 0) for item in invoice_data.get('items', []))
|
||||||
|
vat_rate = (vat_amount / net_total * 100) if net_total > 0 else 0
|
||||||
|
|
||||||
|
po.append("taxes", {
|
||||||
|
"account_head": vat_account,
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"rate": vat_rate,
|
||||||
|
"tax_amount": vat_amount,
|
||||||
|
"description": "ƏDV",
|
||||||
|
"add_deduct_tax": "Add"
|
||||||
|
})
|
||||||
|
|
||||||
|
frappe.log_error(f"Added ƏDV tax: Amount={vat_amount}, Rate={vat_rate}%", "Purchase Order Tax")
|
||||||
|
else:
|
||||||
|
frappe.log_error("ƏDV account (521.3) not found", "Purchase Order Tax Warning")
|
||||||
|
else:
|
||||||
|
frappe.log_error("No VAT amount in E-Taxes data, skipping ƏDV tax creation", "Purchase Order Tax Info")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error adding ƏDV tax: {str(e)}", "Purchase Order Tax Error")
|
||||||
|
|
||||||
# Save document
|
# Save document
|
||||||
po.save()
|
po.save()
|
||||||
|
|
||||||
|
|
@ -2291,7 +2345,7 @@ def process_single_invoice_for_items(token, invoice_id):
|
||||||
unique_items = {}
|
unique_items = {}
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
item_name = item.get('productName', '')
|
item_name = item.get('productName', '').strip()
|
||||||
item_code = item.get('itemId', '') or f"CODE-{serial_number}"
|
item_code = item.get('itemId', '') or f"CODE-{serial_number}"
|
||||||
|
|
||||||
# Пропускаем пустые товары
|
# Пропускаем пустые товары
|
||||||
|
|
@ -2413,7 +2467,7 @@ def process_single_invoice_for_units(token, invoice_id):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Generate a consistent code for the unit
|
# Generate a consistent code for the unit
|
||||||
unit_code = "UNIT-" + ''.join(e for e in unit_name if e.isalnum()).upper()
|
unit_code = unit_name
|
||||||
|
|
||||||
unique_units[unit_name] = {
|
unique_units[unit_name] = {
|
||||||
'etaxes_unit_name': unit_name,
|
'etaxes_unit_name': unit_name,
|
||||||
|
|
|
||||||
|
|
@ -968,11 +968,11 @@ SalesETaxes.import = {
|
||||||
} else {
|
} else {
|
||||||
if (allInvoices.length > 0) {
|
if (allInvoices.length > 0) {
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message: __('Processing ') + allInvoices.length + __(' sales invoices...'),
|
message: __('Processing ') + allInvoices.length + __(' invoices...'),
|
||||||
indicator: 'blue'
|
indicator: 'blue'
|
||||||
}, 3);
|
}, 3);
|
||||||
|
|
||||||
SalesETaxes.import.showInvoiceSelection(allInvoices, mainToken, warehouse);
|
SalesETaxes.import.filterDuplicates(allInvoices, mainToken, warehouse);
|
||||||
} else {
|
} else {
|
||||||
frappe.msgprint({
|
frappe.msgprint({
|
||||||
title: __('Information'),
|
title: __('Information'),
|
||||||
|
|
@ -1023,6 +1023,66 @@ SalesETaxes.import = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Фильтрация дубликатов
|
||||||
|
filterDuplicates: function(allInvoices, token, warehouse) {
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.sales_api.get_etaxes_sales',
|
||||||
|
callback: function(etaxesR) {
|
||||||
|
try {
|
||||||
|
if (SalesETaxes.cancelLoading) return;
|
||||||
|
|
||||||
|
const importedInvoices = {};
|
||||||
|
|
||||||
|
if (etaxesR.message && etaxesR.message.success && etaxesR.message.sales) {
|
||||||
|
etaxesR.message.sales.forEach(function(sale) {
|
||||||
|
if (sale.etaxes_id) {
|
||||||
|
const normId = String(sale.etaxes_id).trim();
|
||||||
|
importedInvoices[normId] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredInvoices = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < allInvoices.length; i++) {
|
||||||
|
const invoice = allInvoices[i];
|
||||||
|
const invoiceId = String(invoice.id).trim();
|
||||||
|
|
||||||
|
if (!importedInvoices.hasOwnProperty(invoiceId)) {
|
||||||
|
filteredInvoices.push(invoice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filteredInvoices.length > 0) {
|
||||||
|
SalesETaxes.import.showInvoiceSelection(filteredInvoices, token, warehouse);
|
||||||
|
} else {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Information'),
|
||||||
|
indicator: 'blue',
|
||||||
|
message: __('No new sales invoices found for the specified period (all ' +
|
||||||
|
allInvoices.length + ' are already imported)')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error processing sales data:", e);
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('An error occurred while processing data: ') + e.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(err) {
|
||||||
|
console.error("Error fetching sales:", err);
|
||||||
|
SalesETaxes.import.showInvoiceSelection(allInvoices, token, warehouse);
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Failed to check for duplicates. Showing all sales invoices.'),
|
||||||
|
indicator: 'orange'
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Показать диалог выбора инвойсов
|
// Показать диалог выбора инвойсов
|
||||||
showInvoiceSelection: function(invoices, token, warehouse) {
|
showInvoiceSelection: function(invoices, token, warehouse) {
|
||||||
let invoiceTable = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered etaxes-sales-invoices-table" style="width: 100%; table-layout: fixed;">';
|
let invoiceTable = '<div style="max-height: 500px; overflow-y: auto;"><table class="table table-bordered etaxes-sales-invoices-table" style="width: 100%; table-layout: fixed;">';
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
"fieldname": "etaxes_item_code",
|
"fieldname": "etaxes_item_code",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Item Code",
|
"label": "Item Code",
|
||||||
|
"length": 500,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"unique": 1
|
"unique": 1
|
||||||
},
|
},
|
||||||
|
|
@ -100,7 +101,7 @@
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-07-04 15:57:33.806919",
|
"modified": "2025-07-09 20:19:56.319865",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Invoice Az",
|
"module": "Invoice Az",
|
||||||
"name": "E-Taxes Item",
|
"name": "E-Taxes Item",
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,20 @@ import json
|
||||||
import requests
|
import requests
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import time
|
import time
|
||||||
|
import re
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_active_settings():
|
||||||
|
"""Getting active E-Taxes settings"""
|
||||||
|
settings_list = frappe.get_all('E-Taxes Settings',
|
||||||
|
filters={'is_active': 1},
|
||||||
|
order_by='creation desc',
|
||||||
|
limit=1)
|
||||||
|
|
||||||
|
if not settings_list:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return frappe.get_doc('E-Taxes Settings', settings_list[0].name)
|
||||||
|
|
||||||
def record_etaxes_activity():
|
def record_etaxes_activity():
|
||||||
"""Record E-Taxes API activity to optimize token renewal"""
|
"""Record E-Taxes API activity to optimize token renewal"""
|
||||||
|
|
@ -200,18 +214,19 @@ def get_sales_invoice_details(token, invoice_id):
|
||||||
}
|
}
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, schedule_date=None, warehouse=None):
|
def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehouse=None):
|
||||||
"""Import sales invoice taking into account mappings from E-Taxes Item/Party/Unit mapped fields only"""
|
"""Import sales invoice taking into account mappings and processing unmapped elements"""
|
||||||
# Записываем активность
|
# Записываем активность
|
||||||
record_etaxes_activity()
|
record_etaxes_activity()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
import re
|
||||||
|
|
||||||
# Deserialize if necessary
|
# Deserialize if necessary
|
||||||
if isinstance(invoice_data, str):
|
if isinstance(invoice_data, str):
|
||||||
invoice_data = json.loads(invoice_data)
|
invoice_data = json.loads(invoice_data)
|
||||||
|
|
||||||
# Get active settings (импортируем функцию из api.py)
|
# Get active settings
|
||||||
from invoice_az.api import get_active_settings
|
|
||||||
settings = get_active_settings()
|
settings = get_active_settings()
|
||||||
if not settings:
|
if not settings:
|
||||||
return {
|
return {
|
||||||
|
|
@ -222,7 +237,6 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched
|
||||||
# Create mapping dictionaries
|
# Create mapping dictionaries
|
||||||
item_mappings = {}
|
item_mappings = {}
|
||||||
for mapping in settings.item_mappings:
|
for mapping in settings.item_mappings:
|
||||||
# mapping.etaxes_item_name содержит name документа E-Taxes Item
|
|
||||||
if mapping.etaxes_item_name and mapping.erp_item:
|
if mapping.etaxes_item_name and mapping.erp_item:
|
||||||
item_mappings[mapping.etaxes_item_name] = mapping.erp_item
|
item_mappings[mapping.etaxes_item_name] = mapping.erp_item
|
||||||
|
|
||||||
|
|
@ -261,73 +275,49 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched
|
||||||
'message': 'Default warehouse not found. Please specify warehouse in E-Taxes settings.'
|
'message': 'Default warehouse not found. Please specify warehouse in E-Taxes settings.'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get or create Sales Order
|
# Create new Sales Invoice
|
||||||
if sales_order_name:
|
si = frappe.new_doc('Sales Invoice')
|
||||||
so = frappe.get_doc('Sales Order', sales_order_name)
|
|
||||||
else:
|
|
||||||
so = frappe.new_doc('Sales Order')
|
|
||||||
|
|
||||||
# Set customer - поиск ТОЛЬКО через E-Taxes Parties mapped_party
|
# Set customer
|
||||||
receiver = invoice_data.get('receiver', {})
|
receiver = invoice_data.get('receiver', {})
|
||||||
receiver_name = (receiver.get('name') or '').strip() # ИСПРАВЛЕНО: безопасная обработка None
|
receiver_key = f"{receiver.get('name', '').lower()}|{receiver.get('tin', '').lower()}"
|
||||||
receiver_tin = (receiver.get('tin') or '').strip() # ИСПРАВЛЕНО: безопасная обработка None
|
|
||||||
|
|
||||||
# Дополнительная нормализация для имени получателя
|
if receiver_key in party_mappings and party_mappings[receiver_key][0]:
|
||||||
receiver_name = ' '.join(receiver_name.split()) # Убираем лишние пробелы
|
# Check if it's a customer
|
||||||
|
if party_mappings[receiver_key][1] == 'Customer':
|
||||||
# Ищем E-Taxes Parties документ по имени и TIN
|
si.customer = party_mappings[receiver_key][0]
|
||||||
customer_found = False
|
else:
|
||||||
mapped_customer = None
|
return {
|
||||||
|
'success': False,
|
||||||
if receiver_name and receiver_tin:
|
'message': f'Receiver is mapped as {party_mappings[receiver_key][1]}, but Sales Invoice requires a Customer'
|
||||||
# Ищем точное соответствие по имени и TIN
|
}
|
||||||
etaxes_party = frappe.db.get_value('E-Taxes Parties',
|
else:
|
||||||
filters={
|
|
||||||
'etaxes_party_name': receiver_name,
|
|
||||||
'etaxes_tax_id': receiver_tin
|
|
||||||
},
|
|
||||||
fieldname='mapped_party')
|
|
||||||
|
|
||||||
if etaxes_party:
|
|
||||||
mapped_customer = etaxes_party
|
|
||||||
customer_found = True
|
|
||||||
|
|
||||||
# Если не найдено по TIN и имени, пробуем только по имени
|
|
||||||
if not customer_found and receiver_name:
|
|
||||||
etaxes_party = frappe.db.get_value('E-Taxes Parties',
|
|
||||||
filters={'etaxes_party_name': receiver_name},
|
|
||||||
fieldname='mapped_party')
|
|
||||||
if etaxes_party:
|
|
||||||
mapped_customer = etaxes_party
|
|
||||||
customer_found = True
|
|
||||||
|
|
||||||
if not customer_found:
|
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'Customer mapping not found for: {receiver_name} (TIN: {receiver_tin})',
|
|
||||||
'unmatched_parties': [{
|
'unmatched_parties': [{
|
||||||
'name': receiver_name,
|
'name': receiver.get('name', ''),
|
||||||
'tin': receiver_tin,
|
'tin': receiver.get('tin', ''),
|
||||||
'type': 'customer'
|
'type': 'Receiver'
|
||||||
}]
|
}],
|
||||||
|
'message': f'No mapping found for customer: {receiver.get("name", "")}'
|
||||||
}
|
}
|
||||||
|
|
||||||
so.customer = mapped_customer
|
# Set dates
|
||||||
so.transaction_date = schedule_date or frappe.utils.nowdate()
|
si.posting_date = frappe.utils.today()
|
||||||
so.delivery_date = frappe.utils.add_days(so.transaction_date, 7) # 7 days for delivery
|
|
||||||
|
|
||||||
# Set title
|
# Get date from invoice or parameter if specified
|
||||||
so.title = f"Sales Order from E-Taxes {invoice_data.get('serialNumber', '')}"
|
if schedule_date:
|
||||||
|
si.due_date = schedule_date
|
||||||
|
elif invoice_data.get("creationDate"):
|
||||||
|
si.due_date = frappe.utils.getdate(invoice_data.get("creationDate"))
|
||||||
|
else:
|
||||||
|
si.due_date = frappe.utils.add_days(frappe.utils.today(), 30)
|
||||||
|
|
||||||
# Set company
|
si.company = frappe.defaults.get_user_default('Company')
|
||||||
so.company = frappe.defaults.get_user_default('Company') or frappe.db.get_single_value('Global Defaults', 'default_company')
|
|
||||||
|
|
||||||
# ДОБАВЛЕНО: Устанавливаем галочку is_taxes_doc
|
# Add additional fields from invoice
|
||||||
so.is_taxes_doc = 1
|
si.title = f"Sales Invoice {invoice_data.get('serialNumber', '')}"
|
||||||
|
si.invoice_serial_number = invoice_data.get("serialNumber") or invoice_data.get("number", "")
|
||||||
# Clear existing items if need to update them completely
|
|
||||||
if sales_order_name and invoice_data.get("items"):
|
|
||||||
so.items = []
|
|
||||||
|
|
||||||
date_to_use = None
|
date_to_use = None
|
||||||
if schedule_date:
|
if schedule_date:
|
||||||
|
|
@ -342,27 +332,24 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched
|
||||||
unmatched_items = []
|
unmatched_items = []
|
||||||
unmatched_units = []
|
unmatched_units = []
|
||||||
|
|
||||||
# Process items from invoice - ИСПРАВЛЕНО: правильная обработка структуры данных
|
# Add items from invoice
|
||||||
items_data = invoice_data.get('items', [])
|
if invoice_data.get("items"):
|
||||||
|
for item in invoice_data.get("items", []):
|
||||||
|
# Подготавливаем данные товара с очисткой от невидимых символов
|
||||||
|
item_name = re.sub(r'\s+', ' ', item.get("productName", "")).strip()
|
||||||
|
item_code = item.get("itemId", "") or f"CODE-{invoice_data.get('serialNumber', '')}"
|
||||||
|
|
||||||
for item_data in items_data:
|
# ИСПРАВЛЕНО: Ищем соответствие товара с учетом регистра и без
|
||||||
# ИСПРАВЛЕНО: Используем правильные поля из структуры E-Taxes
|
|
||||||
item_name = item_data.get('productName', '').strip() # Используем productName
|
|
||||||
item_code = item_data.get('itemId', '').strip() # Используем itemId
|
|
||||||
|
|
||||||
# Пропускаем пустые товары
|
|
||||||
if not item_name:
|
|
||||||
unmatched_items.append({
|
|
||||||
'name': 'Item with empty name',
|
|
||||||
'code': item_code
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
|
|
||||||
# ИСПРАВЛЕНО: Ищем соответствие товара по productName
|
|
||||||
# Так как autoname = "field:etaxes_item_name", name документа E-Taxes Item = etaxes_item_name
|
|
||||||
# Поэтому проверяем напрямую по item_name в mappings
|
|
||||||
mapped_item = item_mappings.get(item_name)
|
mapped_item = item_mappings.get(item_name)
|
||||||
|
|
||||||
|
# Если не найдено, ищем без учета регистра
|
||||||
|
if not mapped_item:
|
||||||
|
item_name_lower = item_name.lower()
|
||||||
|
for mapping_key, mapping_value in item_mappings.items():
|
||||||
|
if mapping_key.lower() == item_name_lower:
|
||||||
|
mapped_item = mapping_value
|
||||||
|
break
|
||||||
|
|
||||||
if not mapped_item:
|
if not mapped_item:
|
||||||
# Если соответствие не найдено, добавляем в список несопоставленных
|
# Если соответствие не найдено, добавляем в список несопоставленных
|
||||||
unmatched_items.append({
|
unmatched_items.append({
|
||||||
|
|
@ -372,7 +359,7 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Для единиц измерения ищем в E-Taxes Unit
|
# Для единиц измерения ищем в E-Taxes Unit
|
||||||
unit_name = item_data.get('unit', '').strip() # ИСПРАВЛЕНО: правильное поле
|
unit_name = item.get("unit", "")
|
||||||
mapped_uom = None
|
mapped_uom = None
|
||||||
|
|
||||||
if unit_name:
|
if unit_name:
|
||||||
|
|
@ -400,36 +387,33 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched
|
||||||
# Fallback на default_uom из настроек
|
# Fallback на default_uom из настроек
|
||||||
mapped_uom = settings.default_uom if settings.default_uom else "Nos"
|
mapped_uom = settings.default_uom if settings.default_uom else "Nos"
|
||||||
|
|
||||||
# Add position to SO
|
# Add position to Sales Invoice
|
||||||
so_item = frappe.new_doc("Sales Order Item")
|
si_item = frappe.new_doc("Sales Invoice Item")
|
||||||
so_item.parent = so.name
|
si_item.parent = si.name
|
||||||
so_item.parenttype = "Sales Order"
|
si_item.parenttype = "Sales Invoice"
|
||||||
so_item.parentfield = "items"
|
si_item.parentfield = "items"
|
||||||
|
|
||||||
so_item.item_code = mapped_item
|
si_item.item_code = mapped_item
|
||||||
|
|
||||||
# ИСПРАВЛЕНИЕ: Получаем название и описание из базы данных Item
|
# Получаем название и описание из базы данных Item
|
||||||
try:
|
try:
|
||||||
item_doc = frappe.get_doc('Item', mapped_item)
|
item_doc = frappe.get_doc('Item', mapped_item)
|
||||||
so_item.item_name = item_doc.item_name
|
si_item.item_name = item_doc.item_name
|
||||||
so_item.description = item_doc.description or item_doc.item_name
|
si_item.description = item_doc.description or item_doc.item_name
|
||||||
except:
|
except:
|
||||||
# Fallback на название из E-Taxes если не удалось получить из базы
|
# Fallback на название из E-Taxes если не удалось получить из базы
|
||||||
so_item.item_name = item_name
|
si_item.item_name = item.get("productName", "")
|
||||||
so_item.description = item_name
|
si_item.description = item.get("productName", "")
|
||||||
|
|
||||||
so_item.qty = item_data.get('quantity', 1)
|
si_item.qty = item.get("quantity", 0)
|
||||||
so_item.rate = item_data.get('pricePerUnit', 0) # ИСПРАВЛЕНО: правильное поле
|
si_item.rate = item.get("pricePerUnit", 0)
|
||||||
so_item.amount = item_data.get('cost', 0)
|
si_item.amount = item.get("cost", 0)
|
||||||
so_item.uom = mapped_uom
|
si_item.uom = mapped_uom
|
||||||
|
|
||||||
# IMPORTANT: Set delivery_date for each row
|
|
||||||
so_item.delivery_date = date_to_use
|
|
||||||
|
|
||||||
# IMPORTANT: Set default warehouse
|
# IMPORTANT: Set default warehouse
|
||||||
so_item.warehouse = default_warehouse
|
si_item.warehouse = default_warehouse
|
||||||
|
|
||||||
so.append("items", so_item)
|
si.append("items", si_item)
|
||||||
added_items_count += 1
|
added_items_count += 1
|
||||||
|
|
||||||
# Check if there are unmapped items
|
# Check if there are unmapped items
|
||||||
|
|
@ -451,26 +435,50 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched
|
||||||
# Check that there is at least one element in table
|
# Check that there is at least one element in table
|
||||||
if added_items_count == 0:
|
if added_items_count == 0:
|
||||||
frappe.log_error(
|
frappe.log_error(
|
||||||
f"No items were added to SO. Invoice data: {invoice_data}",
|
f"No items were added to Sales Invoice. Invoice data: {invoice_data}",
|
||||||
"Import Sales Invoice Error"
|
"Import Sales Invoice Error"
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': 'Could not add any items to order'
|
'message': 'Could not add any items to sales invoice'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ДОБАВЛЕНО: Создание строки ƏDV в Sales Taxes and Charges ТОЛЬКО если есть vat в данных
|
||||||
|
try:
|
||||||
|
# Получаем VAT из E-Taxes данных
|
||||||
|
vat_amount = invoice_data.get('vat', 0)
|
||||||
|
|
||||||
|
# Создаем строку ƏDV ТОЛЬКО если есть vat в данных и он больше 0
|
||||||
|
if vat_amount and vat_amount > 0:
|
||||||
|
vat_account = frappe.db.get_value('Account',
|
||||||
|
filters={'account_number': '531.1'},
|
||||||
|
fieldname='name')
|
||||||
|
|
||||||
|
if vat_account:
|
||||||
|
# Рассчитываем net_total для определения ставки
|
||||||
|
net_total = sum(item.get('cost', 0) for item in invoice_data.get('items', []))
|
||||||
|
vat_rate = (vat_amount / net_total * 100) if net_total > 0 else 0
|
||||||
|
|
||||||
|
si.append("taxes", {
|
||||||
|
"account_head": vat_account,
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"rate": vat_rate,
|
||||||
|
"tax_amount": vat_amount,
|
||||||
|
"description": "ƏDV",
|
||||||
|
"add_deduct_tax": "Add"
|
||||||
|
})
|
||||||
|
|
||||||
|
frappe.log_error(f"Added ƏDV tax: Amount={vat_amount}, Rate={vat_rate}%", "Sales Invoice Tax")
|
||||||
|
else:
|
||||||
|
frappe.log_error("ƏDV account (531.1) not found", "Sales Invoice Tax Warning")
|
||||||
|
else:
|
||||||
|
frappe.log_error("No VAT amount in E-Taxes data, skipping ƏDV tax creation", "Sales Invoice Tax Info")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error adding ƏDV tax: {str(e)}", "Sales Invoice Tax Error")
|
||||||
|
|
||||||
# Save document
|
# Save document
|
||||||
so.save()
|
si.save()
|
||||||
|
|
||||||
# Additional check that all delivery_date and warehouse are set
|
|
||||||
for item in so.items:
|
|
||||||
if not item.delivery_date:
|
|
||||||
item.delivery_date = date_to_use
|
|
||||||
if not item.warehouse:
|
|
||||||
item.warehouse = default_warehouse
|
|
||||||
|
|
||||||
# Save again to ensure changes are applied
|
|
||||||
so.save()
|
|
||||||
|
|
||||||
# ИЗМЕНЕНИЕ: Создаем E-Taxes Sales и устанавливаем связь ДО submit'а
|
# ИЗМЕНЕНИЕ: Создаем E-Taxes Sales и устанавливаем связь ДО submit'а
|
||||||
invoice_id = invoice_data.get('id', '')
|
invoice_id = invoice_data.get('id', '')
|
||||||
|
|
@ -482,55 +490,30 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched
|
||||||
etaxes_sales_result = create_etaxes_sales(invoice_id, date_to_use, receiver_name, total)
|
etaxes_sales_result = create_etaxes_sales(invoice_id, date_to_use, receiver_name, total)
|
||||||
if etaxes_sales_result and etaxes_sales_result.get('success'):
|
if etaxes_sales_result and etaxes_sales_result.get('success'):
|
||||||
# Устанавливаем поля E-Taxes ДО submit'а
|
# Устанавливаем поля E-Taxes ДО submit'а
|
||||||
so.is_taxes_doc = 1
|
si.is_taxes_doc = 1
|
||||||
so.taxes_doc = etaxes_sales_result.get('name')
|
si.taxes_doc = etaxes_sales_result.get('name')
|
||||||
# Сохраняем изменения
|
# Сохраняем изменения
|
||||||
so.save()
|
si.save()
|
||||||
|
|
||||||
# ИЗМЕНЕНИЕ: Делаем Submit для Sales Order
|
# ИЗМЕНЕНИЕ: Делаем Submit для Sales Invoice
|
||||||
try:
|
try:
|
||||||
so.submit()
|
si.submit()
|
||||||
frappe.log_error(f"Sales Order {so.name} submitted successfully", "Import Sales Invoice Success")
|
frappe.log_error(f"Sales Invoice {si.name} submitted successfully", "Import Sales Invoice Success")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.log_error(f"Error submitting Sales Order {so.name}: {str(e)}", "Submit SO Error")
|
frappe.log_error(f"Error submitting Sales Invoice {si.name}: {str(e)}", "Submit SI Error")
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'Failed to submit Sales Order: {str(e)}'
|
'message': f'Failed to submit Sales Invoice: {str(e)}'
|
||||||
}
|
}
|
||||||
|
|
||||||
# ИЗМЕНЕНИЕ: Создаем Sales Invoice
|
|
||||||
try:
|
|
||||||
si_name = create_sales_invoice_from_order(so.name)
|
|
||||||
if si_name:
|
|
||||||
# Делаем Submit для Sales Invoice
|
|
||||||
si = frappe.get_doc("Sales Invoice", si_name)
|
|
||||||
si.submit()
|
|
||||||
frappe.log_error(f"Sales Invoice {si_name} created and submitted successfully", "Import Sales Invoice Success")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': 'Sales invoice data imported successfully. Sales Order and Sales Invoice created.',
|
'message': 'Sales invoice data imported successfully.',
|
||||||
'sales_order': so.name,
|
'sales_invoice': si.name
|
||||||
'sales_invoice': si_name
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
# Если не удалось создать Sales Invoice, всё равно возвращаем успех с Sales Order
|
|
||||||
return {
|
|
||||||
'success': True,
|
|
||||||
'message': 'Sales invoice data imported successfully. Sales Order created, but Sales Invoice creation failed.',
|
|
||||||
'sales_order': so.name
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
frappe.log_error(f"Error creating or submitting Sales Invoice: {str(e)}", "Submit SI Error")
|
|
||||||
# Если не удалось создать Sales Invoice, всё равно возвращаем успех с Sales Order
|
|
||||||
return {
|
|
||||||
'success': True,
|
|
||||||
'message': 'Sales invoice data imported successfully. Sales Order created, but Sales Invoice creation failed.',
|
|
||||||
'sales_order': so.name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.log_error(f"Error in import_sales_invoice_with_mapping: {str(e)}\n{frappe.get_traceback()}", "Import Sales Error")
|
frappe.log_error(f"Error in import_sales_invoice_with_mapping: {str(e)}\n{frappe.get_traceback()}", "Import Sales Invoice Error")
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': "An unknown error occurred, please try again in a few minutes."
|
'message': "An unknown error occurred, please try again in a few minutes."
|
||||||
|
|
@ -548,11 +531,24 @@ def create_sales_invoice_from_order(sales_order_name):
|
||||||
|
|
||||||
# Устанавливаем даты
|
# Устанавливаем даты
|
||||||
si_doc.posting_date = frappe.utils.nowdate()
|
si_doc.posting_date = frappe.utils.nowdate()
|
||||||
si_doc.due_date = frappe.utils.add_days(frappe.utils.nowdate(), 30) # 30 дней для оплаты
|
si_doc.due_date = frappe.utils.add_days(frappe.utils.nowdate(), 30)
|
||||||
|
|
||||||
# ДОБАВЛЕНО: Устанавливаем галочку is_taxes_doc для Sales Invoice
|
# ДОБАВЛЕНО: Устанавливаем галочку is_taxes_doc для Sales Invoice
|
||||||
si_doc.is_taxes_doc = 1
|
si_doc.is_taxes_doc = 1
|
||||||
|
|
||||||
|
# ДОБАВЛЕНО: Копируем налоги из Sales Order
|
||||||
|
so_doc = frappe.get_doc('Sales Order', sales_order_name)
|
||||||
|
if so_doc.taxes:
|
||||||
|
si_doc.taxes = []
|
||||||
|
for tax_row in so_doc.taxes:
|
||||||
|
si_doc.append("taxes", {
|
||||||
|
"account_head": tax_row.account_head,
|
||||||
|
"charge_type": tax_row.charge_type,
|
||||||
|
"rate": tax_row.rate,
|
||||||
|
"tax_amount": tax_row.tax_amount,
|
||||||
|
"description": tax_row.description
|
||||||
|
})
|
||||||
|
|
||||||
# Сохраняем документ
|
# Сохраняем документ
|
||||||
si_doc.insert(ignore_permissions=True)
|
si_doc.insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
@ -613,3 +609,24 @@ def create_etaxes_sales(etaxes_id, date, party, total):
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': "An unknown error occurred, please try again in a few minutes."
|
'message': "An unknown error occurred, please try again in a few minutes."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_etaxes_sales():
|
||||||
|
"""Gets list of all E-Taxes Sales records"""
|
||||||
|
try:
|
||||||
|
sales = frappe.get_all('E-Taxes Sales',
|
||||||
|
fields=['name', 'etaxes_id', 'date', 'party', 'total'])
|
||||||
|
|
||||||
|
# Debug logging
|
||||||
|
frappe.logger().info(f"Retrieved {len(sales)} E-Taxes Sales records")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'sales': sales
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
frappe.log_error(f"Error in get_etaxes_sales: {str(e)}", "API Error")
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': "An unknown error occurred, please try again in a few minutes."
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue