feat: switch all imports to Socket.IO background jobs for better performance
Replace sequential AJAX calls with frappe.enqueue + publish_realtime for: - Sales Order import (sales_api.py + sales_order.js) - Purchase Order import (api.py + purchase_order.js) - Sales Invoice import (api.py + sales_invoice.js) - VAT Operations import (vat_api.py + journal_entry.js) - AMAS Employee import (amas_api.py + employee.js) Also remove "Certificates retrieved successfully" msgprint from asan_login.js Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cdb6739308
commit
64961e4108
|
|
@ -2610,3 +2610,82 @@ def on_delete_employee(doc, method):
|
||||||
)
|
)
|
||||||
for rec in amas_records:
|
for rec in amas_records:
|
||||||
frappe.delete_doc("Amas Employees", rec.name, ignore_permissions=True)
|
frappe.delete_doc("Amas Employees", rec.name, ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def import_bulk_employees(asan_login_name, employees_data, company, create_designation=0):
|
||||||
|
"""Enqueue bulk employee import as a background job with realtime progress via Socket.IO."""
|
||||||
|
if isinstance(employees_data, str):
|
||||||
|
employees_data = json.loads(employees_data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
create_designation = int(create_designation)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
create_designation = 0
|
||||||
|
|
||||||
|
user = frappe.session.user
|
||||||
|
|
||||||
|
frappe.enqueue(
|
||||||
|
_process_bulk_employees_import,
|
||||||
|
asan_login_name=asan_login_name,
|
||||||
|
employees=employees_data,
|
||||||
|
company=company,
|
||||||
|
create_designation=create_designation,
|
||||||
|
user=user,
|
||||||
|
queue="default",
|
||||||
|
timeout=1200,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"success": True, "enqueued": True, "total": len(employees_data)}
|
||||||
|
|
||||||
|
|
||||||
|
def _process_bulk_employees_import(asan_login_name, employees, company, create_designation, user):
|
||||||
|
"""Background job: import each employee with realtime progress."""
|
||||||
|
frappe.set_user(user)
|
||||||
|
|
||||||
|
total = len(employees)
|
||||||
|
created_count = 0
|
||||||
|
updated_count = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for idx, emp_data in enumerate(employees):
|
||||||
|
emp_name = emp_data.get("full_name") or emp_data.get("identification_number") or "Employee"
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = create_single_employee_from_amas(
|
||||||
|
asan_login_name=asan_login_name,
|
||||||
|
emp_data=emp_data,
|
||||||
|
company=company,
|
||||||
|
create_designation=create_designation,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result and result.get("success"):
|
||||||
|
if result.get("action") == "created":
|
||||||
|
created_count += 1
|
||||||
|
elif result.get("action") == "updated":
|
||||||
|
updated_count += 1
|
||||||
|
else:
|
||||||
|
error_msg = result.get("message", "Unknown error") if result else "Empty result"
|
||||||
|
errors.append({"employee": emp_name, "error": error_msg})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
errors.append({"employee": emp_name, "error": str(e)})
|
||||||
|
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.publish_realtime(
|
||||||
|
"amas_import_progress",
|
||||||
|
{"current": idx + 1, "total": total, "employee_name": emp_name},
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.publish_realtime(
|
||||||
|
"amas_import_complete",
|
||||||
|
{
|
||||||
|
"total": total,
|
||||||
|
"created": created_count,
|
||||||
|
"updated": updated_count,
|
||||||
|
"errors": errors,
|
||||||
|
},
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -4307,3 +4307,109 @@ def refresh_token_from_asan_login():
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': str(e)
|
'message': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def import_bulk_purchase_invoices(invoice_ids, token, warehouse=None):
|
||||||
|
"""Enqueue bulk purchase import as a background job with realtime progress via Socket.IO."""
|
||||||
|
if isinstance(invoice_ids, str):
|
||||||
|
invoice_ids = json.loads(invoice_ids)
|
||||||
|
|
||||||
|
user = frappe.session.user
|
||||||
|
|
||||||
|
frappe.enqueue(
|
||||||
|
_process_bulk_purchase_import,
|
||||||
|
invoice_ids=invoice_ids,
|
||||||
|
token=token,
|
||||||
|
warehouse=warehouse,
|
||||||
|
user=user,
|
||||||
|
queue="default",
|
||||||
|
timeout=1200,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"success": True, "enqueued": True, "total": len(invoice_ids)}
|
||||||
|
|
||||||
|
|
||||||
|
def _process_bulk_purchase_import(invoice_ids, token, warehouse, user):
|
||||||
|
"""Background job: fetch details and import each purchase invoice with realtime progress."""
|
||||||
|
frappe.set_user(user)
|
||||||
|
record_etaxes_activity()
|
||||||
|
|
||||||
|
total = len(invoice_ids)
|
||||||
|
imported_count = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for idx, invoice_id in enumerate(invoice_ids):
|
||||||
|
try:
|
||||||
|
details = get_invoice_details(token, invoice_id)
|
||||||
|
except Exception:
|
||||||
|
details = None
|
||||||
|
|
||||||
|
if not details or details.get("error"):
|
||||||
|
error_msg = details.get("message", "Failed to fetch invoice details") if details else "Empty response"
|
||||||
|
if details and details.get("error") == "unauthorized":
|
||||||
|
asan_login = get_default_asan_login()
|
||||||
|
if asan_login.get("found") and asan_login.get("main_token"):
|
||||||
|
token = asan_login["main_token"]
|
||||||
|
details = get_invoice_details(token, invoice_id)
|
||||||
|
if not details or details.get("error"):
|
||||||
|
error_msg = details.get("message", "Failed after token refresh") if details else "Empty response"
|
||||||
|
errors.append({"invoice_id": invoice_id, "error": error_msg})
|
||||||
|
frappe.db.commit()
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
errors.append({"invoice_id": invoice_id, "error": "Authentication expired"})
|
||||||
|
frappe.db.commit()
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
errors.append({"invoice_id": invoice_id, "error": error_msg})
|
||||||
|
frappe.db.commit()
|
||||||
|
continue
|
||||||
|
|
||||||
|
schedule_date = None
|
||||||
|
created_at = details.get("creationDate") or details.get("date")
|
||||||
|
if created_at:
|
||||||
|
try:
|
||||||
|
schedule_date = datetime.datetime.strptime(str(created_at)[:10], "%Y-%m-%d").strftime("%Y-%m-%d")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
result = import_invoice_with_mapping(
|
||||||
|
invoice_data=details,
|
||||||
|
purchase_order_name=None,
|
||||||
|
schedule_date=schedule_date,
|
||||||
|
warehouse=warehouse,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result and result.get("success"):
|
||||||
|
try:
|
||||||
|
sender_name = details.get("sender", {}).get("name", "") if details.get("sender") else ""
|
||||||
|
total_amount = details.get("totalAmount", 0) or details.get("amount", 0)
|
||||||
|
etaxes_result = create_etaxes_purchase(invoice_id, schedule_date, sender_name, total_amount)
|
||||||
|
if etaxes_result and etaxes_result.get("success"):
|
||||||
|
link_purchase_order_to_etaxes(result["purchase_order"], etaxes_result["name"])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
imported_count += 1
|
||||||
|
else:
|
||||||
|
error_msg = result.get("message", "Unknown import error") if result else "Empty result"
|
||||||
|
error_entry = {"invoice_id": invoice_id, "error": error_msg}
|
||||||
|
if result:
|
||||||
|
for key in ("unmatched_items", "unmatched_parties", "unmatched_units"):
|
||||||
|
if result.get(key):
|
||||||
|
error_entry[key] = result[key]
|
||||||
|
errors.append(error_entry)
|
||||||
|
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.publish_realtime(
|
||||||
|
"purchase_import_progress",
|
||||||
|
{"current": idx + 1, "total": total},
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.publish_realtime(
|
||||||
|
"purchase_import_complete",
|
||||||
|
{"total": total, "imported": imported_count, "errors": errors},
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -586,25 +586,71 @@ function show_employee_selection(listview, asan_login_name, organization_name, e
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Create employees via backend - ONE BY ONE like E-Taxes
|
// Step 5: Create employees via backend - Socket.IO bulk import
|
||||||
function create_employees(listview, asan_login_name, selected_employees, company, create_designation) {
|
function create_employees(listview, asan_login_name, selected_employees, company, create_designation) {
|
||||||
// Initialize counters
|
const total = selected_employees.length;
|
||||||
window.amas_employees_to_process = selected_employees.length;
|
|
||||||
window.amas_cancel_loading = false;
|
|
||||||
|
|
||||||
// Start processing
|
frappe.show_progress(
|
||||||
process_employees_one_by_one(
|
__('Loading from ƏMAS'), 0, total,
|
||||||
listview,
|
__('Starting import of {0} employees...', [total])
|
||||||
asan_login_name,
|
|
||||||
selected_employees,
|
|
||||||
company,
|
|
||||||
create_designation,
|
|
||||||
0, // created_count
|
|
||||||
0, // updated_count
|
|
||||||
0, // error_count
|
|
||||||
[], // errors
|
|
||||||
0 // current_index
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
frappe.realtime.off('amas_import_progress');
|
||||||
|
frappe.realtime.on('amas_import_progress', function(data) {
|
||||||
|
let detail = __('Processing employee {0} of {1}', [data.current, data.total]);
|
||||||
|
if (data.employee_name) {
|
||||||
|
detail = __('Processing: ') + data.employee_name;
|
||||||
|
}
|
||||||
|
frappe.show_progress(__('Loading from ƏMAS'), data.current, data.total, detail);
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.realtime.off('amas_import_complete');
|
||||||
|
frappe.realtime.on('amas_import_complete', function(data) {
|
||||||
|
frappe.realtime.off('amas_import_progress');
|
||||||
|
frappe.realtime.off('amas_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
|
||||||
|
const error_messages = (data.errors || []).map(function(err) {
|
||||||
|
return (err.employee || 'Unknown') + ': ' + (err.error || 'Unknown error');
|
||||||
|
});
|
||||||
|
|
||||||
|
const total_processed = data.created + data.updated + (data.errors || []).length;
|
||||||
|
show_import_summary(total_processed, data.created, data.updated, (data.errors || []).length, error_messages);
|
||||||
|
|
||||||
|
listview.refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.amas_api.import_bulk_employees',
|
||||||
|
args: {
|
||||||
|
asan_login_name: asan_login_name,
|
||||||
|
employees_data: JSON.stringify(selected_employees),
|
||||||
|
company: company,
|
||||||
|
create_designation: create_designation ? 1 : 0
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (!r.message || !r.message.enqueued) {
|
||||||
|
frappe.realtime.off('amas_import_progress');
|
||||||
|
frappe.realtime.off('amas_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Failed to start import job')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
frappe.realtime.off('amas_import_progress');
|
||||||
|
frappe.realtime.off('amas_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Network error starting import')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process employees one by one (like E-Taxes loadSelectedInvoices)
|
// Process employees one by one (like E-Taxes loadSelectedInvoices)
|
||||||
|
|
|
||||||
|
|
@ -1232,7 +1232,7 @@ VATETaxes.import = {
|
||||||
|
|
||||||
d.hide();
|
d.hide();
|
||||||
VATETaxes.cancelLoading = false;
|
VATETaxes.cancelLoading = false;
|
||||||
VATETaxes.import.loadSelectedOperations(selectedOperations, token, company, 0, 0, createAsDraftValue, autoCreateCustomers);
|
VATETaxes.import.loadSelectedOperationsSocketIO(selectedOperations, token, company, createAsDraftValue, autoCreateCustomers);
|
||||||
},
|
},
|
||||||
secondary_action_label: __('Cancel'),
|
secondary_action_label: __('Cancel'),
|
||||||
secondary_action: function() {
|
secondary_action: function() {
|
||||||
|
|
@ -1398,6 +1398,81 @@ VATETaxes.import = {
|
||||||
|
|
||||||
VATETaxes.import.loadSelectedOperations(operations, token, company, processedCount, currentIndex, createAsDraft, autoCreateCustomers);
|
VATETaxes.import.loadSelectedOperations(operations, token, company, processedCount, currentIndex, createAsDraft, autoCreateCustomers);
|
||||||
}, VATETaxes.PROGRESS_UPDATE_DELAY);
|
}, VATETaxes.PROGRESS_UPDATE_DELAY);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Socket.IO bulk import
|
||||||
|
loadSelectedOperationsSocketIO: function(operations, token, company, createAsDraft, autoCreateCustomers) {
|
||||||
|
VATETaxes.loadingErrors = [];
|
||||||
|
const total = operations.length;
|
||||||
|
|
||||||
|
frappe.show_progress(
|
||||||
|
__('Importing VAT Operations'), 0, total,
|
||||||
|
__('Starting import of {0} operations...', [total])
|
||||||
|
);
|
||||||
|
|
||||||
|
frappe.realtime.off('vat_import_progress');
|
||||||
|
frappe.realtime.on('vat_import_progress', function(data) {
|
||||||
|
frappe.show_progress(
|
||||||
|
__('Importing VAT Operations'), data.current, data.total,
|
||||||
|
__('Processing operation {0} of {1}', [data.current, data.total])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.realtime.off('vat_import_complete');
|
||||||
|
frappe.realtime.on('vat_import_complete', function(data) {
|
||||||
|
frappe.realtime.off('vat_import_progress');
|
||||||
|
frappe.realtime.off('vat_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
|
||||||
|
(data.errors || []).forEach(function(err) {
|
||||||
|
VATETaxes.loadingErrors.push({
|
||||||
|
operation_id: err.operation_id || 'Unknown',
|
||||||
|
error_type: err.error_type || 'Import Error',
|
||||||
|
error_message: err.error || 'Unknown error',
|
||||||
|
tin: err.tin,
|
||||||
|
customer_name: err.customer_name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorsCount = VATETaxes.loadingErrors.length;
|
||||||
|
VATETaxes.errors.showSummary(data.total, data.imported, errorsCount, data.create_as_draft);
|
||||||
|
|
||||||
|
if (cur_list) {
|
||||||
|
cur_list.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.vat_api.import_bulk_vat_operations',
|
||||||
|
args: {
|
||||||
|
operations_data: JSON.stringify(operations),
|
||||||
|
company: company,
|
||||||
|
create_as_draft: createAsDraft,
|
||||||
|
auto_create_customers: autoCreateCustomers
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (!r.message || !r.message.enqueued) {
|
||||||
|
frappe.realtime.off('vat_import_progress');
|
||||||
|
frappe.realtime.off('vat_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Failed to start import job')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
frappe.realtime.off('vat_import_progress');
|
||||||
|
frappe.realtime.off('vat_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Network error starting import')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1161,7 +1161,7 @@ ETaxes.import = {
|
||||||
|
|
||||||
d.hide();
|
d.hide();
|
||||||
ETaxes.cancelLoading = false;
|
ETaxes.cancelLoading = false;
|
||||||
ETaxes.import.loadSelectedInvoices(selectedInvoiceIds, token, warehouse);
|
ETaxes.import.loadSelectedInvoicesSocketIO(selectedInvoiceIds, token, warehouse);
|
||||||
},
|
},
|
||||||
secondary_action_label: __('Cancel'),
|
secondary_action_label: __('Cancel'),
|
||||||
secondary_action: function() {
|
secondary_action: function() {
|
||||||
|
|
@ -1426,9 +1426,87 @@ ETaxes.import = {
|
||||||
if (invoiceIds.length === 0) {
|
if (invoiceIds.length === 0) {
|
||||||
frappe.hide_progress();
|
frappe.hide_progress();
|
||||||
}
|
}
|
||||||
|
|
||||||
ETaxes.import.loadSelectedInvoices(invoiceIds, token, warehouse, processedCount, currentIndex);
|
ETaxes.import.loadSelectedInvoices(invoiceIds, token, warehouse, processedCount, currentIndex);
|
||||||
}, ETaxes.PROGRESS_UPDATE_DELAY);
|
}, ETaxes.PROGRESS_UPDATE_DELAY);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Socket.IO bulk import
|
||||||
|
loadSelectedInvoicesSocketIO: function(invoiceIds, token, warehouse) {
|
||||||
|
ETaxes.loadingErrors = [];
|
||||||
|
const total = invoiceIds.length;
|
||||||
|
|
||||||
|
frappe.show_progress(
|
||||||
|
__('Importing Invoices'), 0, total,
|
||||||
|
__('Starting import of {0} invoices...', [total])
|
||||||
|
);
|
||||||
|
|
||||||
|
frappe.realtime.off('purchase_import_progress');
|
||||||
|
frappe.realtime.on('purchase_import_progress', function(data) {
|
||||||
|
frappe.show_progress(
|
||||||
|
__('Importing Invoices'), data.current, data.total,
|
||||||
|
__('Importing invoice {0} of {1}', [data.current, data.total])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.realtime.off('purchase_import_complete');
|
||||||
|
frappe.realtime.on('purchase_import_complete', function(data) {
|
||||||
|
frappe.realtime.off('purchase_import_progress');
|
||||||
|
frappe.realtime.off('purchase_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
|
||||||
|
(data.errors || []).forEach(function(err) {
|
||||||
|
ETaxes.loadingErrors.push({
|
||||||
|
invoice_id: err.invoice_id || 'Unknown',
|
||||||
|
error_type: err.unmatched_items ? 'Unmapped Items'
|
||||||
|
: err.unmatched_parties ? 'Unmapped Parties'
|
||||||
|
: err.unmatched_units ? 'Unmapped Units'
|
||||||
|
: 'Import Error',
|
||||||
|
error_message: err.error || 'Unknown error',
|
||||||
|
unmatched_items: err.unmatched_items,
|
||||||
|
unmatched_parties: err.unmatched_parties,
|
||||||
|
unmatched_units: err.unmatched_units
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorsCount = ETaxes.loadingErrors.length;
|
||||||
|
ETaxes.errors.showSummary(data.total, data.imported, errorsCount);
|
||||||
|
|
||||||
|
if (cur_list) {
|
||||||
|
cur_list.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.api.import_bulk_purchase_invoices',
|
||||||
|
args: {
|
||||||
|
invoice_ids: JSON.stringify(invoiceIds),
|
||||||
|
token: token,
|
||||||
|
warehouse: warehouse
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (!r.message || !r.message.enqueued) {
|
||||||
|
frappe.realtime.off('purchase_import_progress');
|
||||||
|
frappe.realtime.off('purchase_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Failed to start import job')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
frappe.realtime.off('purchase_import_progress');
|
||||||
|
frappe.realtime.off('purchase_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Network error starting import')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1180,7 +1180,7 @@ ETaxes.import = {
|
||||||
|
|
||||||
d.hide();
|
d.hide();
|
||||||
ETaxes.cancelLoading = false;
|
ETaxes.cancelLoading = false;
|
||||||
ETaxes.import.loadSelectedInvoices(selectedInvoiceIds, token, warehouse);
|
ETaxes.import.loadSelectedInvoicesSocketIO(selectedInvoiceIds, token, warehouse);
|
||||||
},
|
},
|
||||||
secondary_action_label: __('Cancel'),
|
secondary_action_label: __('Cancel'),
|
||||||
secondary_action: function() {
|
secondary_action: function() {
|
||||||
|
|
@ -1445,9 +1445,87 @@ ETaxes.import = {
|
||||||
if (invoiceIds.length === 0) {
|
if (invoiceIds.length === 0) {
|
||||||
frappe.hide_progress();
|
frappe.hide_progress();
|
||||||
}
|
}
|
||||||
|
|
||||||
ETaxes.import.loadSelectedInvoices(invoiceIds, token, warehouse, processedCount, currentIndex);
|
ETaxes.import.loadSelectedInvoices(invoiceIds, token, warehouse, processedCount, currentIndex);
|
||||||
}, ETaxes.PROGRESS_UPDATE_DELAY);
|
}, ETaxes.PROGRESS_UPDATE_DELAY);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Socket.IO bulk import
|
||||||
|
loadSelectedInvoicesSocketIO: function(invoiceIds, token, warehouse) {
|
||||||
|
ETaxes.loadingErrors = [];
|
||||||
|
const total = invoiceIds.length;
|
||||||
|
|
||||||
|
frappe.show_progress(
|
||||||
|
__('Importing Invoices'), 0, total,
|
||||||
|
__('Starting import of {0} invoices...', [total])
|
||||||
|
);
|
||||||
|
|
||||||
|
frappe.realtime.off('purchase_import_progress');
|
||||||
|
frappe.realtime.on('purchase_import_progress', function(data) {
|
||||||
|
frappe.show_progress(
|
||||||
|
__('Importing Invoices'), data.current, data.total,
|
||||||
|
__('Importing invoice {0} of {1}', [data.current, data.total])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.realtime.off('purchase_import_complete');
|
||||||
|
frappe.realtime.on('purchase_import_complete', function(data) {
|
||||||
|
frappe.realtime.off('purchase_import_progress');
|
||||||
|
frappe.realtime.off('purchase_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
|
||||||
|
(data.errors || []).forEach(function(err) {
|
||||||
|
ETaxes.loadingErrors.push({
|
||||||
|
invoice_id: err.invoice_id || 'Unknown',
|
||||||
|
error_type: err.unmatched_items ? 'Unmapped Items'
|
||||||
|
: err.unmatched_parties ? 'Unmapped Parties'
|
||||||
|
: err.unmatched_units ? 'Unmapped Units'
|
||||||
|
: 'Import Error',
|
||||||
|
error_message: err.error || 'Unknown error',
|
||||||
|
unmatched_items: err.unmatched_items,
|
||||||
|
unmatched_parties: err.unmatched_parties,
|
||||||
|
unmatched_units: err.unmatched_units
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorsCount = ETaxes.loadingErrors.length;
|
||||||
|
ETaxes.errors.showSummary(data.total, data.imported, errorsCount);
|
||||||
|
|
||||||
|
if (cur_list) {
|
||||||
|
cur_list.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.api.import_bulk_purchase_invoices',
|
||||||
|
args: {
|
||||||
|
invoice_ids: JSON.stringify(invoiceIds),
|
||||||
|
token: token,
|
||||||
|
warehouse: warehouse
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (!r.message || !r.message.enqueued) {
|
||||||
|
frappe.realtime.off('purchase_import_progress');
|
||||||
|
frappe.realtime.off('purchase_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Failed to start import job')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
frappe.realtime.off('purchase_import_progress');
|
||||||
|
frappe.realtime.off('purchase_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Network error starting import')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1161,7 +1161,7 @@ SalesETaxes.import = {
|
||||||
|
|
||||||
d.hide();
|
d.hide();
|
||||||
SalesETaxes.cancelLoading = false;
|
SalesETaxes.cancelLoading = false;
|
||||||
SalesETaxes.import.loadSelectedInvoices(selectedInvoiceIds, token, warehouse);
|
SalesETaxes.import.loadSelectedInvoicesSocketIO(selectedInvoiceIds, token, warehouse);
|
||||||
},
|
},
|
||||||
secondary_action_label: __('Cancel'),
|
secondary_action_label: __('Cancel'),
|
||||||
secondary_action: function() {
|
secondary_action: function() {
|
||||||
|
|
@ -1475,9 +1475,91 @@ SalesETaxes.import = {
|
||||||
if (invoiceIds.length === 0) {
|
if (invoiceIds.length === 0) {
|
||||||
frappe.hide_progress();
|
frappe.hide_progress();
|
||||||
}
|
}
|
||||||
|
|
||||||
SalesETaxes.import.loadSelectedInvoices(invoiceIds, token, warehouse, processedCount, currentIndex);
|
SalesETaxes.import.loadSelectedInvoices(invoiceIds, token, warehouse, processedCount, currentIndex);
|
||||||
}, SalesETaxes.PROGRESS_UPDATE_DELAY);
|
}, SalesETaxes.PROGRESS_UPDATE_DELAY);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Socket.IO bulk import — all work happens on the server in a background job
|
||||||
|
loadSelectedInvoicesSocketIO: function(invoiceIds, token, warehouse) {
|
||||||
|
SalesETaxes.loadingErrors = [];
|
||||||
|
const total = invoiceIds.length;
|
||||||
|
|
||||||
|
frappe.show_progress(
|
||||||
|
__('Importing Sales Invoices'), 0, total,
|
||||||
|
__('Starting import of {0} invoices...', [total])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Listen for per-invoice progress
|
||||||
|
frappe.realtime.off('sales_import_progress');
|
||||||
|
frappe.realtime.on('sales_import_progress', function(data) {
|
||||||
|
frappe.show_progress(
|
||||||
|
__('Importing Sales Invoices'), data.current, data.total,
|
||||||
|
__('Importing invoice {0} of {1}', [data.current, data.total])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for completion
|
||||||
|
frappe.realtime.off('sales_import_complete');
|
||||||
|
frappe.realtime.on('sales_import_complete', function(data) {
|
||||||
|
frappe.realtime.off('sales_import_progress');
|
||||||
|
frappe.realtime.off('sales_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
|
||||||
|
// Collect errors for the error panel
|
||||||
|
(data.errors || []).forEach(function(err) {
|
||||||
|
SalesETaxes.loadingErrors.push({
|
||||||
|
invoice_id: err.invoice_id || 'Unknown',
|
||||||
|
error_type: err.unmatched_items ? 'Unmapped Items'
|
||||||
|
: err.unmatched_parties ? 'Unmapped Parties'
|
||||||
|
: err.unmatched_units ? 'Unmapped Units'
|
||||||
|
: 'Import Error',
|
||||||
|
error_message: err.error || 'Unknown error',
|
||||||
|
unmatched_items: err.unmatched_items,
|
||||||
|
unmatched_parties: err.unmatched_parties,
|
||||||
|
unmatched_units: err.unmatched_units
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorsCount = SalesETaxes.loadingErrors.length;
|
||||||
|
SalesETaxes.errors.showSummary(data.total, data.imported, errorsCount);
|
||||||
|
|
||||||
|
if (cur_list) {
|
||||||
|
cur_list.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enqueue the background job
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.sales_api.import_bulk_sales_invoices',
|
||||||
|
args: {
|
||||||
|
invoice_ids: JSON.stringify(invoiceIds),
|
||||||
|
token: token,
|
||||||
|
warehouse: warehouse
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (!r.message || !r.message.enqueued) {
|
||||||
|
frappe.realtime.off('sales_import_progress');
|
||||||
|
frappe.realtime.off('sales_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Failed to start import job')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
frappe.realtime.off('sales_import_progress');
|
||||||
|
frappe.realtime.off('sales_import_complete');
|
||||||
|
frappe.hide_progress();
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __('Error'),
|
||||||
|
indicator: 'red',
|
||||||
|
message: __('Network error starting import')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,13 +87,6 @@ frappe.ui.form.on('Asan Login', {
|
||||||
// Обновляем форму после успешного получения сертификатов
|
// Обновляем форму после успешного получения сертификатов
|
||||||
frm.reload_doc();
|
frm.reload_doc();
|
||||||
|
|
||||||
// Показываем сообщение об успешном получении сертификатов
|
|
||||||
frappe.msgprint({
|
|
||||||
title: __('Success'),
|
|
||||||
indicator: 'green',
|
|
||||||
message: __('Certificates retrieved successfully')
|
|
||||||
});
|
|
||||||
|
|
||||||
// Отображаем список сертификатов в диалоговом окне
|
// Отображаем список сертификатов в диалоговом окне
|
||||||
if (r.message.certificates) {
|
if (r.message.certificates) {
|
||||||
showCertificatesDialog(r.message.certificates, frm);
|
showCertificatesDialog(r.message.certificates, frm);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import frappe
|
||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import time
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|
@ -876,5 +875,105 @@ def on_delete_sales_order(doc, method):
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
frappe.logger().info(f"Successfully deleted E-Taxes Sales {doc.taxes_doc}")
|
frappe.logger().info(f"Successfully deleted E-Taxes Sales {doc.taxes_doc}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.log_error(f"Error deleting E-Taxes Sales {doc.taxes_doc}: {str(e)}\n{frappe.get_traceback()}",
|
frappe.log_error(f"Error deleting E-Taxes Sales {doc.taxes_doc}: {str(e)}\n{frappe.get_traceback()}",
|
||||||
"Sales Order Delete Error")
|
"Sales Order Delete Error")
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def import_bulk_sales_invoices(invoice_ids, token, warehouse=None):
|
||||||
|
"""Enqueue bulk sales import as a background job with realtime progress via Socket.IO."""
|
||||||
|
if isinstance(invoice_ids, str):
|
||||||
|
invoice_ids = json.loads(invoice_ids)
|
||||||
|
|
||||||
|
user = frappe.session.user
|
||||||
|
|
||||||
|
frappe.enqueue(
|
||||||
|
_process_bulk_sales_import,
|
||||||
|
invoice_ids=invoice_ids,
|
||||||
|
token=token,
|
||||||
|
warehouse=warehouse,
|
||||||
|
user=user,
|
||||||
|
queue="default",
|
||||||
|
timeout=1200,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"success": True, "enqueued": True, "total": len(invoice_ids)}
|
||||||
|
|
||||||
|
|
||||||
|
def _process_bulk_sales_import(invoice_ids, token, warehouse, user):
|
||||||
|
"""Background job: fetch details and import each sales invoice with realtime progress."""
|
||||||
|
frappe.set_user(user)
|
||||||
|
record_etaxes_activity()
|
||||||
|
|
||||||
|
total = len(invoice_ids)
|
||||||
|
imported_count = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for idx, invoice_id in enumerate(invoice_ids):
|
||||||
|
try:
|
||||||
|
details = get_sales_invoice_details(token, invoice_id)
|
||||||
|
except Exception:
|
||||||
|
details = None
|
||||||
|
|
||||||
|
if not details or details.get("error"):
|
||||||
|
error_msg = details.get("message", "Failed to fetch invoice details") if details else "Empty response"
|
||||||
|
if details and details.get("error") == "unauthorized":
|
||||||
|
from invoice_az.auth import get_default_asan_login
|
||||||
|
asan_login = get_default_asan_login()
|
||||||
|
if asan_login.get("found") and asan_login.get("main_token"):
|
||||||
|
token = asan_login["main_token"]
|
||||||
|
details = get_sales_invoice_details(token, invoice_id)
|
||||||
|
if not details or details.get("error"):
|
||||||
|
error_msg = details.get("message", "Failed after token refresh") if details else "Empty response"
|
||||||
|
errors.append({"invoice_id": invoice_id, "error": error_msg})
|
||||||
|
frappe.db.commit()
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
errors.append({"invoice_id": invoice_id, "error": "Authentication expired"})
|
||||||
|
frappe.db.commit()
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
errors.append({"invoice_id": invoice_id, "error": error_msg})
|
||||||
|
frappe.db.commit()
|
||||||
|
continue
|
||||||
|
|
||||||
|
schedule_date = None
|
||||||
|
created_at = details.get("creationDate") or details.get("date")
|
||||||
|
if created_at:
|
||||||
|
try:
|
||||||
|
from datetime import datetime as dt
|
||||||
|
schedule_date = dt.strptime(str(created_at)[:10], "%Y-%m-%d").strftime("%Y-%m-%d")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
result = import_sales_invoice_with_mapping(
|
||||||
|
invoice_data=details,
|
||||||
|
sales_order_name=None,
|
||||||
|
schedule_date=schedule_date,
|
||||||
|
warehouse=warehouse,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result and result.get("success"):
|
||||||
|
imported_count += 1
|
||||||
|
else:
|
||||||
|
error_msg = result.get("message", "Unknown import error") if result else "Empty result"
|
||||||
|
error_entry = {"invoice_id": invoice_id, "error": error_msg}
|
||||||
|
if result:
|
||||||
|
for key in ("unmatched_items", "unmatched_parties", "unmatched_units"):
|
||||||
|
if result.get(key):
|
||||||
|
error_entry[key] = result[key]
|
||||||
|
errors.append(error_entry)
|
||||||
|
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.publish_realtime(
|
||||||
|
"sales_import_progress",
|
||||||
|
{"current": idx + 1, "total": total},
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.publish_realtime(
|
||||||
|
"sales_import_complete",
|
||||||
|
{"total": total, "imported": imported_count, "errors": errors},
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|
@ -1067,3 +1067,92 @@ def load_vat_operations_from_etaxes(date_from, date_to, max_count=50, offset=0):
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': f'Load failed: {str(e)}'
|
'message': f'Load failed: {str(e)}'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def import_bulk_vat_operations(operations_data, company=None, create_as_draft=0, auto_create_customers=0):
|
||||||
|
"""Enqueue bulk VAT operations import as a background job with realtime progress via Socket.IO."""
|
||||||
|
if isinstance(operations_data, str):
|
||||||
|
operations_data = json.loads(operations_data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
create_as_draft = int(create_as_draft)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
create_as_draft = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
auto_create_customers = int(auto_create_customers)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
auto_create_customers = 0
|
||||||
|
|
||||||
|
user = frappe.session.user
|
||||||
|
|
||||||
|
frappe.enqueue(
|
||||||
|
_process_bulk_vat_import,
|
||||||
|
operations=operations_data,
|
||||||
|
company=company,
|
||||||
|
create_as_draft=create_as_draft,
|
||||||
|
auto_create_customers=auto_create_customers,
|
||||||
|
user=user,
|
||||||
|
queue="default",
|
||||||
|
timeout=1200,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"success": True, "enqueued": True, "total": len(operations_data)}
|
||||||
|
|
||||||
|
|
||||||
|
def _process_bulk_vat_import(operations, company, create_as_draft, auto_create_customers, user):
|
||||||
|
"""Background job: import each VAT operation with realtime progress."""
|
||||||
|
frappe.set_user(user)
|
||||||
|
record_etaxes_activity()
|
||||||
|
|
||||||
|
total = len(operations)
|
||||||
|
imported_count = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for idx, operation in enumerate(operations):
|
||||||
|
try:
|
||||||
|
result = import_vat_operations(
|
||||||
|
operations_data=json.dumps([operation]),
|
||||||
|
company=company,
|
||||||
|
create_as_draft=create_as_draft,
|
||||||
|
auto_create_customers=auto_create_customers,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result and result.get("success"):
|
||||||
|
if result.get("imported", 0) > 0:
|
||||||
|
imported_count += 1
|
||||||
|
elif result.get("failed", 0) > 0 and result.get("errors"):
|
||||||
|
err = result["errors"][0]
|
||||||
|
errors.append({
|
||||||
|
"operation_id": operation.get("id", "Unknown"),
|
||||||
|
"error": err.get("message", "Unknown error"),
|
||||||
|
"error_type": err.get("error_type", "Import Error"),
|
||||||
|
"tin": err.get("tin"),
|
||||||
|
"customer_name": err.get("customer_name"),
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
error_msg = result.get("message", "Unknown error") if result else "Empty result"
|
||||||
|
errors.append({"operation_id": operation.get("id", "Unknown"), "error": error_msg})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
errors.append({"operation_id": operation.get("id", "Unknown"), "error": str(e)})
|
||||||
|
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
frappe.publish_realtime(
|
||||||
|
"vat_import_progress",
|
||||||
|
{"current": idx + 1, "total": total},
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.publish_realtime(
|
||||||
|
"vat_import_complete",
|
||||||
|
{
|
||||||
|
"total": total,
|
||||||
|
"imported": imported_count,
|
||||||
|
"errors": errors,
|
||||||
|
"create_as_draft": create_as_draft,
|
||||||
|
},
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue