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:
|
||||
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,
|
||||
'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) {
|
||||
// Initialize counters
|
||||
window.amas_employees_to_process = selected_employees.length;
|
||||
window.amas_cancel_loading = false;
|
||||
const total = selected_employees.length;
|
||||
|
||||
// Start processing
|
||||
process_employees_one_by_one(
|
||||
listview,
|
||||
asan_login_name,
|
||||
selected_employees,
|
||||
company,
|
||||
create_designation,
|
||||
0, // created_count
|
||||
0, // updated_count
|
||||
0, // error_count
|
||||
[], // errors
|
||||
0 // current_index
|
||||
frappe.show_progress(
|
||||
__('Loading from ƏMAS'), 0, total,
|
||||
__('Starting import of {0} employees...', [total])
|
||||
);
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1232,7 +1232,7 @@ VATETaxes.import = {
|
|||
|
||||
d.hide();
|
||||
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: function() {
|
||||
|
|
@ -1398,6 +1398,81 @@ VATETaxes.import = {
|
|||
|
||||
VATETaxes.import.loadSelectedOperations(operations, token, company, processedCount, currentIndex, createAsDraft, autoCreateCustomers);
|
||||
}, 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();
|
||||
ETaxes.cancelLoading = false;
|
||||
ETaxes.import.loadSelectedInvoices(selectedInvoiceIds, token, warehouse);
|
||||
ETaxes.import.loadSelectedInvoicesSocketIO(selectedInvoiceIds, token, warehouse);
|
||||
},
|
||||
secondary_action_label: __('Cancel'),
|
||||
secondary_action: function() {
|
||||
|
|
@ -1429,6 +1429,84 @@ ETaxes.import = {
|
|||
|
||||
ETaxes.import.loadSelectedInvoices(invoiceIds, token, warehouse, processedCount, currentIndex);
|
||||
}, 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();
|
||||
ETaxes.cancelLoading = false;
|
||||
ETaxes.import.loadSelectedInvoices(selectedInvoiceIds, token, warehouse);
|
||||
ETaxes.import.loadSelectedInvoicesSocketIO(selectedInvoiceIds, token, warehouse);
|
||||
},
|
||||
secondary_action_label: __('Cancel'),
|
||||
secondary_action: function() {
|
||||
|
|
@ -1448,6 +1448,84 @@ ETaxes.import = {
|
|||
|
||||
ETaxes.import.loadSelectedInvoices(invoiceIds, token, warehouse, processedCount, currentIndex);
|
||||
}, 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();
|
||||
SalesETaxes.cancelLoading = false;
|
||||
SalesETaxes.import.loadSelectedInvoices(selectedInvoiceIds, token, warehouse);
|
||||
SalesETaxes.import.loadSelectedInvoicesSocketIO(selectedInvoiceIds, token, warehouse);
|
||||
},
|
||||
secondary_action_label: __('Cancel'),
|
||||
secondary_action: function() {
|
||||
|
|
@ -1478,6 +1478,88 @@ SalesETaxes.import = {
|
|||
|
||||
SalesETaxes.import.loadSelectedInvoices(invoiceIds, token, warehouse, processedCount, currentIndex);
|
||||
}, 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();
|
||||
|
||||
// Показываем сообщение об успешном получении сертификатов
|
||||
frappe.msgprint({
|
||||
title: __('Success'),
|
||||
indicator: 'green',
|
||||
message: __('Certificates retrieved successfully')
|
||||
});
|
||||
|
||||
// Отображаем список сертификатов в диалоговом окне
|
||||
if (r.message.certificates) {
|
||||
showCertificatesDialog(r.message.certificates, frm);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import frappe
|
|||
import json
|
||||
import requests
|
||||
from datetime import datetime
|
||||
import time
|
||||
import re
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
@ -878,3 +877,103 @@ def on_delete_sales_order(doc, method):
|
|||
except Exception as e:
|
||||
frappe.log_error(f"Error deleting E-Taxes Sales {doc.taxes_doc}: {str(e)}\n{frappe.get_traceback()}",
|
||||
"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,
|
||||
'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