From 94659173f60c63a6606fc055813584c968451adf Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Thu, 10 Jul 2025 14:49:30 +0400 Subject: [PATCH] fixed a lot of bugs, but some still unfixed --- invoice_az/api.py | 80 +++- invoice_az/client/sales_order.js | 64 ++- .../doctype/e_taxes_item/e_taxes_item.json | 3 +- invoice_az/sales_api.py | 431 +++++++++--------- 4 files changed, 355 insertions(+), 223 deletions(-) diff --git a/invoice_az/api.py b/invoice_az/api.py index 8b3b761..ef76e50 100644 --- a/invoice_az/api.py +++ b/invoice_az/api.py @@ -1042,6 +1042,20 @@ def create_purchase_invoice_from_order(purchase_order_name): # ДОБАВЛЕНО: Устанавливаем галочку is_taxes_doc для Purchase Invoice 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) @@ -1053,7 +1067,7 @@ def create_purchase_invoice_from_order(purchase_order_name): except Exception as e: frappe.log_error(f"Error creating Purchase Invoice from PO {purchase_order_name}: {str(e)}\n{frappe.get_traceback()}", "Purchase Invoice Creation Error") return None - + @frappe.whitelist() def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule_date=None, warehouse=None): """Import invoice taking into account mappings and processing unmapped elements""" @@ -1177,14 +1191,20 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule if invoice_data.get("items"): for item in invoice_data.get("items", []): # Подготавливаем данные товара - item_name = item.get("productName", "") - item_code = item.get("itemId", "") + item_name = item.get("productName", "").strip() + 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) - + + # Если не найдено, ищем без учета регистра + 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: # Если соответствие не найдено, добавляем в список несопоставленных 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 except: # Fallback на название из E-Taxes если не удалось получить из базы - po_item.item_name = item.get("productName", "") - po_item.description = item.get("productName", "") + po_item.item_name = item.get("productName", "").strip() + po_item.description = item.get("productName", "").strip() po_item.qty = item.get("quantity", 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' } + # ДОБАВЛЕНО: Создание строки Ə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 po.save() @@ -1357,7 +1411,7 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule 'success': False, 'message': "An unknown error occurred, please try again in a few minutes." } - + @frappe.whitelist() def create_unmapped_parties(settings_name): """Creating parties for unmapped elements from E-Taxes settings table""" @@ -2291,7 +2345,7 @@ def process_single_invoice_for_items(token, invoice_id): unique_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}" # Пропускаем пустые товары @@ -2413,7 +2467,7 @@ def process_single_invoice_for_units(token, invoice_id): continue # 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] = { 'etaxes_unit_name': unit_name, @@ -2786,4 +2840,4 @@ def load_combined_data_from_etaxes(date_from, date_to, load_items=False, load_pa return { 'success': False, 'message': "An unknown error occurred, please try again in a few minutes." - } \ No newline at end of file + } diff --git a/invoice_az/client/sales_order.js b/invoice_az/client/sales_order.js index a9d4df8..55079f2 100644 --- a/invoice_az/client/sales_order.js +++ b/invoice_az/client/sales_order.js @@ -968,11 +968,11 @@ SalesETaxes.import = { } else { if (allInvoices.length > 0) { frappe.show_alert({ - message: __('Processing ') + allInvoices.length + __(' sales invoices...'), + message: __('Processing ') + allInvoices.length + __(' invoices...'), indicator: 'blue' }, 3); - SalesETaxes.import.showInvoiceSelection(allInvoices, mainToken, warehouse); + SalesETaxes.import.filterDuplicates(allInvoices, mainToken, warehouse); } else { frappe.msgprint({ 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) { let invoiceTable = '
'; diff --git a/invoice_az/invoice_az/doctype/e_taxes_item/e_taxes_item.json b/invoice_az/invoice_az/doctype/e_taxes_item/e_taxes_item.json index 34b52cc..9b669f1 100644 --- a/invoice_az/invoice_az/doctype/e_taxes_item/e_taxes_item.json +++ b/invoice_az/invoice_az/doctype/e_taxes_item/e_taxes_item.json @@ -33,6 +33,7 @@ "fieldname": "etaxes_item_code", "fieldtype": "Data", "label": "Item Code", + "length": 500, "read_only": 1, "unique": 1 }, @@ -100,7 +101,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-07-04 15:57:33.806919", + "modified": "2025-07-09 20:19:56.319865", "modified_by": "Administrator", "module": "Invoice Az", "name": "E-Taxes Item", diff --git a/invoice_az/sales_api.py b/invoice_az/sales_api.py index ac0b2c6..db06e0b 100644 --- a/invoice_az/sales_api.py +++ b/invoice_az/sales_api.py @@ -3,6 +3,20 @@ import json import requests from datetime import datetime 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(): """Record E-Taxes API activity to optimize token renewal""" @@ -200,29 +214,29 @@ def get_sales_invoice_details(token, invoice_id): } @frappe.whitelist() -def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, schedule_date=None, warehouse=None): - """Import sales invoice taking into account mappings from E-Taxes Item/Party/Unit mapped fields only""" +def import_sales_invoice_with_mapping(invoice_data, schedule_date=None, warehouse=None): + """Import sales invoice taking into account mappings and processing unmapped elements""" # Записываем активность record_etaxes_activity() try: + import re + # Deserialize if necessary if isinstance(invoice_data, str): invoice_data = json.loads(invoice_data) - # Get active settings (импортируем функцию из api.py) - from invoice_az.api import get_active_settings + # Get active settings settings = get_active_settings() if not settings: return { 'success': False, 'message': 'No active E-Taxes settings found' } - + # Create mapping dictionaries 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: 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.' } - # Get or create Sales Order - if sales_order_name: - so = frappe.get_doc('Sales Order', sales_order_name) - else: - so = frappe.new_doc('Sales Order') - - # Set customer - поиск ТОЛЬКО через E-Taxes Parties mapped_party - receiver = invoice_data.get('receiver', {}) - receiver_name = (receiver.get('name') or '').strip() # ИСПРАВЛЕНО: безопасная обработка None - receiver_tin = (receiver.get('tin') or '').strip() # ИСПРАВЛЕНО: безопасная обработка None - - # Дополнительная нормализация для имени получателя - receiver_name = ' '.join(receiver_name.split()) # Убираем лишние пробелы - - # Ищем E-Taxes Parties документ по имени и TIN - customer_found = False - mapped_customer = None - - if receiver_name and receiver_tin: - # Ищем точное соответствие по имени и TIN - etaxes_party = frappe.db.get_value('E-Taxes Parties', - 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: + # Create new Sales Invoice + si = frappe.new_doc('Sales Invoice') + + # Set customer + receiver = invoice_data.get('receiver', {}) + receiver_key = f"{receiver.get('name', '').lower()}|{receiver.get('tin', '').lower()}" + + if receiver_key in party_mappings and party_mappings[receiver_key][0]: + # Check if it's a customer + if party_mappings[receiver_key][1] == 'Customer': + si.customer = party_mappings[receiver_key][0] + else: return { 'success': False, - 'message': f'Customer mapping not found for: {receiver_name} (TIN: {receiver_tin})', - 'unmatched_parties': [{ - 'name': receiver_name, - 'tin': receiver_tin, - 'type': 'customer' - }] + 'message': f'Receiver is mapped as {party_mappings[receiver_key][1]}, but Sales Invoice requires a Customer' } - - so.customer = mapped_customer - so.transaction_date = schedule_date or frappe.utils.nowdate() - so.delivery_date = frappe.utils.add_days(so.transaction_date, 7) # 7 days for delivery - - # Set title - so.title = f"Sales Order from E-Taxes {invoice_data.get('serialNumber', '')}" - - # Set company - so.company = frappe.defaults.get_user_default('Company') or frappe.db.get_single_value('Global Defaults', 'default_company') - - # ДОБАВЛЕНО: Устанавливаем галочку is_taxes_doc - so.is_taxes_doc = 1 + else: + return { + 'success': False, + 'unmatched_parties': [{ + 'name': receiver.get('name', ''), + 'tin': receiver.get('tin', ''), + 'type': 'Receiver' + }], + 'message': f'No mapping found for customer: {receiver.get("name", "")}' + } - # Clear existing items if need to update them completely - if sales_order_name and invoice_data.get("items"): - so.items = [] + # Set dates + si.posting_date = frappe.utils.today() + + # Get date from invoice or parameter if specified + 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) + + si.company = frappe.defaults.get_user_default('Company') + + # Add additional fields from invoice + si.title = f"Sales Invoice {invoice_data.get('serialNumber', '')}" + si.invoice_serial_number = invoice_data.get("serialNumber") or invoice_data.get("number", "") date_to_use = None if schedule_date: @@ -342,95 +332,89 @@ def import_sales_invoice_with_mapping(invoice_data, sales_order_name=None, sched unmatched_items = [] unmatched_units = [] - # Process items from invoice - ИСПРАВЛЕНО: правильная обработка структуры данных - items_data = invoice_data.get('items', []) - - 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) - - if not mapped_item: - # Если соответствие не найдено, добавляем в список несопоставленных - unmatched_items.append({ - 'name': item_name, - 'code': item_code - }) - continue - - # Для единиц измерения ищем в E-Taxes Unit - unit_name = item_data.get('unit', '').strip() # ИСПРАВЛЕНО: правильное поле - mapped_uom = None - - if unit_name: - # Ищем существующую запись E-Taxes Unit и берем mapped_unit - try: - mapped_uom = frappe.db.get_value('E-Taxes Unit', - filters={'etaxes_unit_name': unit_name}, - fieldname='mapped_unit') - except Exception: - pass - - # Если соответствие единицы не найдено, используем UOM из товара или настроек - if not mapped_uom: - if unit_name: - # Добавляем в список несопоставленных единиц - unmatched_units.append({ - 'name': unit_name - }) + # Add items from invoice + 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', '')}" - # Используем UOM из настроек товара или default + # ИСПРАВЛЕНО: Ищем соответствие товара с учетом регистра и без + 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: + # Если соответствие не найдено, добавляем в список несопоставленных + unmatched_items.append({ + 'name': item_name, + 'code': item_code + }) + continue + + # Для единиц измерения ищем в E-Taxes Unit + unit_name = item.get("unit", "") + mapped_uom = None + + if unit_name: + # Ищем существующую запись E-Taxes Unit и берем mapped_unit + try: + mapped_uom = frappe.db.get_value('E-Taxes Unit', + filters={'etaxes_unit_name': unit_name}, + fieldname='mapped_unit') + except Exception: + pass + + # Если соответствие единицы не найдено, используем UOM из товара или настроек + if not mapped_uom: + if unit_name: + # Добавляем в список несопоставленных единиц + unmatched_units.append({ + 'name': unit_name + }) + + # Используем UOM из настроек товара или default + try: + item_doc = frappe.get_doc('Item', mapped_item) + mapped_uom = item_doc.stock_uom + except: + # Fallback на default_uom из настроек + mapped_uom = settings.default_uom if settings.default_uom else "Nos" + + # Add position to Sales Invoice + si_item = frappe.new_doc("Sales Invoice Item") + si_item.parent = si.name + si_item.parenttype = "Sales Invoice" + si_item.parentfield = "items" + + si_item.item_code = mapped_item + + # Получаем название и описание из базы данных Item try: item_doc = frappe.get_doc('Item', mapped_item) - mapped_uom = item_doc.stock_uom + si_item.item_name = item_doc.item_name + si_item.description = item_doc.description or item_doc.item_name except: - # Fallback на default_uom из настроек - mapped_uom = settings.default_uom if settings.default_uom else "Nos" - - # Add position to SO - so_item = frappe.new_doc("Sales Order Item") - so_item.parent = so.name - so_item.parenttype = "Sales Order" - so_item.parentfield = "items" + # Fallback на название из E-Taxes если не удалось получить из базы + si_item.item_name = item.get("productName", "") + si_item.description = item.get("productName", "") - so_item.item_code = mapped_item + si_item.qty = item.get("quantity", 0) + si_item.rate = item.get("pricePerUnit", 0) + si_item.amount = item.get("cost", 0) + si_item.uom = mapped_uom - # ИСПРАВЛЕНИЕ: Получаем название и описание из базы данных Item - try: - item_doc = frappe.get_doc('Item', mapped_item) - so_item.item_name = item_doc.item_name - so_item.description = item_doc.description or item_doc.item_name - except: - # Fallback на название из E-Taxes если не удалось получить из базы - so_item.item_name = item_name - so_item.description = item_name - - so_item.qty = item_data.get('quantity', 1) - so_item.rate = item_data.get('pricePerUnit', 0) # ИСПРАВЛЕНО: правильное поле - so_item.amount = item_data.get('cost', 0) - so_item.uom = mapped_uom - - # IMPORTANT: Set delivery_date for each row - so_item.delivery_date = date_to_use - - # IMPORTANT: Set default warehouse - so_item.warehouse = default_warehouse - - so.append("items", so_item) - added_items_count += 1 + # IMPORTANT: Set default warehouse + si_item.warehouse = default_warehouse + + si.append("items", si_item) + added_items_count += 1 # Check if there are unmapped items if unmatched_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 if added_items_count == 0: 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" ) return { '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 - so.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() + si.save() # ИЗМЕНЕНИЕ: Создаем E-Taxes Sales и устанавливаем связь ДО submit'а invoice_id = invoice_data.get('id', '') @@ -482,60 +490,35 @@ 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) if etaxes_sales_result and etaxes_sales_result.get('success'): # Устанавливаем поля E-Taxes ДО submit'а - so.is_taxes_doc = 1 - so.taxes_doc = etaxes_sales_result.get('name') + si.is_taxes_doc = 1 + si.taxes_doc = etaxes_sales_result.get('name') # Сохраняем изменения - so.save() + si.save() - # ИЗМЕНЕНИЕ: Делаем Submit для Sales Order + # ИЗМЕНЕНИЕ: Делаем Submit для Sales Invoice try: - so.submit() - frappe.log_error(f"Sales Order {so.name} submitted successfully", "Import Sales Invoice Success") + si.submit() + frappe.log_error(f"Sales Invoice {si.name} submitted successfully", "Import Sales Invoice Success") 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 { '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 { - 'success': True, - 'message': 'Sales invoice data imported successfully. Sales Order and Sales Invoice created.', - 'sales_order': so.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 - } + return { + 'success': True, + 'message': 'Sales invoice data imported successfully.', + 'sales_invoice': si.name + } 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 { 'success': False, 'message': "An unknown error occurred, please try again in a few minutes." } - + @frappe.whitelist() def create_sales_invoice_from_order(sales_order_name): """Создает Sales Invoice из Sales Order используя стандартную функцию ERPNext""" @@ -548,11 +531,24 @@ def create_sales_invoice_from_order(sales_order_name): # Устанавливаем даты 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 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) @@ -612,4 +608,25 @@ def create_etaxes_sales(etaxes_id, date, party, total): return { 'success': False, 'message': "An unknown error occurred, please try again in a few minutes." - } \ No newline at end of file + } + +@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." + } \ No newline at end of file