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