From 7969b96d8729a7b47df807d96d734480114c6388 Mon Sep 17 00:00:00 2001
From: Ali <010109ali@gmail.com>
Date: Fri, 13 Feb 2026 17:14:14 +0400
Subject: [PATCH] bug fixes
---
invoice_az/amas_api.py | 288 ++++++++++++++++++
invoice_az/client/employee.js | 279 ++++++++++++-----
invoice_az/hooks.py | 6 -
invoice_az/install.py | 177 -----------
.../doctype/amas_employees/amas_employees.js | 13 +-
5 files changed, 491 insertions(+), 272 deletions(-)
delete mode 100644 invoice_az/install.py
diff --git a/invoice_az/amas_api.py b/invoice_az/amas_api.py
index 9ceaae8..040b49f 100644
--- a/invoice_az/amas_api.py
+++ b/invoice_az/amas_api.py
@@ -1650,6 +1650,294 @@ def create_single_employee_from_amas(asan_login_name, employee_data, company, cr
}
+@frappe.whitelist()
+def create_single_employee_from_amas(asan_login_name, emp_data, company, create_designation=0):
+ """
+ Create/update a SINGLE Employee record from ƏMAS data.
+ This is called from frontend in a loop for progress bar updates.
+
+ Args:
+ asan_login_name: Asan Login name
+ emp_data: Single employee data dict from ƏMAS API
+ company: Company name
+ create_designation: 1/0 to create designation if not found
+
+ Returns:
+ {
+ "success": True/False,
+ "action": "created"/"updated"/"error",
+ "employee_name": "...",
+ "full_name": "...",
+ "message": "..."
+ }
+ """
+ try:
+ if isinstance(emp_data, str):
+ emp_data = json.loads(emp_data)
+
+ create_designation = int(create_designation)
+
+ if not company:
+ return {"success": False, "message": "Company is required"}
+
+ doc = frappe.get_doc("Asan Login", asan_login_name)
+ organization_name = doc.amas_account_name
+ organization_voen = doc.amas_account_number
+
+ # Status mapping
+ status_map = {
+ "qüvvədədir": "Active",
+ "aktiv": "Active",
+ "active": "Active",
+ "ləğv edilib": "Left",
+ "xitam": "Left",
+ "terminated": "Left",
+ "dayandırılıb": "Suspended",
+ "suspended": "Suspended",
+ }
+
+ doc_oid = emp_data.get("doc_oid")
+ doc_no = emp_data.get("doc_no")
+ doc_type = emp_data.get("doc_type", "docType_47")
+
+ # Fetch detail data for complete employee information
+ detail_data = None
+ full_name = str(emp_data.get("full_name") or "").strip()
+
+ if doc_oid:
+ detail_result = get_employee_detail(asan_login_name, doc_oid, doc_no, doc_type)
+ if detail_result.get('success'):
+ detail_data = detail_result.get('data')
+
+ # Get FIN from detail or list data
+ fin = None
+ if detail_data and 'EmasContractsEnt' in detail_data and isinstance(detail_data.get('EmasContractsEnt'), list) and len(detail_data['EmasContractsEnt']) > 0:
+ fin = detail_data['EmasContractsEnt'][0].get('pin')
+ # Also update full_name from detail data if available
+ if 'iamasBean' in detail_data and detail_data.get('iamasBean'):
+ person = detail_data['iamasBean']
+ name = person.get('name', '')
+ surname = person.get('surname', '')
+ full_name = f"{name} {surname}".strip()
+
+ if not fin:
+ fin = emp_data.get("identification_number")
+
+ if not fin:
+ return {
+ "success": False,
+ "action": "error",
+ "full_name": full_name,
+ "message": f"{full_name}: No identification_number"
+ }
+
+ fin = str(fin).strip()
+
+ # 1. Save/update Amas Employees record
+ amas_existing = None
+ if doc_oid:
+ amas_existing = frappe.db.exists("Amas Employees", {"doc_oid": str(doc_oid)})
+ if not amas_existing:
+ amas_existing = frappe.db.exists(
+ "Amas Employees",
+ {"identification_number": fin, "organization_voen": organization_voen}
+ )
+
+ if amas_existing:
+ amas_doc = frappe.get_doc("Amas Employees", amas_existing)
+ update_employee_from_data(amas_doc, emp_data)
+ if detail_data:
+ update_employee_from_detail_data(amas_doc, detail_data)
+ if not amas_doc.identification_number:
+ amas_doc.identification_number = fin
+ amas_doc.save(ignore_permissions=True)
+ else:
+ amas_doc = frappe.new_doc("Amas Employees")
+ amas_doc.asan_login = asan_login_name
+ amas_doc.organization_name = organization_name
+ amas_doc.organization_voen = organization_voen
+ amas_doc.identification_number = fin
+ update_employee_from_data(amas_doc, emp_data)
+ if detail_data:
+ update_employee_from_detail_data(amas_doc, detail_data)
+ if not amas_doc.identification_number:
+ amas_doc.identification_number = fin
+ amas_doc.insert(ignore_permissions=True)
+
+ # 2. Create/update Employee record
+ existing_employee = frappe.db.exists("Employee", {"passport_number": fin})
+
+ # Split full_name into first/last
+ name_parts = full_name.split() if full_name else []
+ first_name = name_parts[0] if name_parts else "?"
+ last_name = " ".join(name_parts[1:]) if len(name_parts) > 1 else ""
+
+ # Parse dates from detail data if available, otherwise from list data
+ dob = None
+ doj = None
+ contract_end = None
+
+ if detail_data:
+ if 'iamasBean' in detail_data and detail_data.get('iamasBean'):
+ dob = parse_amas_date(detail_data['iamasBean'].get('birthDate'))
+ if 'EmasContractDetailsEnt' in detail_data and isinstance(detail_data.get('EmasContractDetailsEnt'), list) and len(detail_data['EmasContractDetailsEnt']) > 0:
+ details = detail_data['EmasContractDetailsEnt'][0]
+ doj = parse_amas_date(details.get('beginDate'))
+ contract_end = parse_amas_date(details.get('endDate'))
+ else:
+ dob = parse_amas_date(emp_data.get("birthday"))
+ doj = parse_amas_date(emp_data.get("begin_date"))
+ contract_end = parse_amas_date(emp_data.get("end_date"))
+
+ # Map status
+ raw_status = ""
+ if detail_data and 'EmasContractsEnt' in detail_data and isinstance(detail_data.get('EmasContractsEnt'), list) and len(detail_data['EmasContractsEnt']) > 0:
+ raw_status = str(detail_data['EmasContractsEnt'][0].get('contractStatus') or "").strip().lower()
+ else:
+ raw_status = str(emp_data.get("contract_status") or "").strip().lower()
+ status = status_map.get(raw_status, "Active")
+
+ # Parse salary
+ salary = None
+ if detail_data and 'EmasContractDetailsEnt' in detail_data and isinstance(detail_data.get('EmasContractDetailsEnt'), list) and len(detail_data['EmasContractDetailsEnt']) > 0:
+ raw_salary = detail_data['EmasContractDetailsEnt'][0].get('monthlySalary')
+ else:
+ raw_salary = emp_data.get("monthly_salary")
+
+ if raw_salary:
+ try:
+ salary = float(str(raw_salary).replace(",", ".").replace(" ", ""))
+ except (ValueError, TypeError):
+ pass
+
+ # Resolve designation
+ position_text = None
+ if detail_data and 'staffData' in detail_data and detail_data.get('staffData'):
+ position_text = detail_data['staffData'].get('positionText')
+ else:
+ position_text = emp_data.get("work_position_text")
+
+ designation = resolve_designation(position_text, create_designation)
+
+ # Extract additional fields from amas_doc
+ middle_name = amas_doc.father_name if hasattr(amas_doc, 'father_name') else None
+ gender = map_gender(amas_doc.gender if hasattr(amas_doc, 'gender') else None)
+ cell_number = amas_doc.contact_phone if hasattr(amas_doc, 'contact_phone') else None
+ personal_email = amas_doc.contact_email if hasattr(amas_doc, 'contact_email') else None
+ marital_status_mapped = map_marital_status(amas_doc.marital_status if hasattr(amas_doc, 'marital_status') else None)
+ blood_group_mapped = map_blood_group(amas_doc.blood_type if hasattr(amas_doc, 'blood_type') else None)
+ date_of_issue = parse_amas_date(amas_doc.document_issue_date if hasattr(amas_doc, 'document_issue_date') else None)
+ valid_upto = parse_amas_date(amas_doc.document_expiry_date if hasattr(amas_doc, 'document_expiry_date') else None)
+ place_of_issue = amas_doc.document_issued_by if hasattr(amas_doc, 'document_issued_by') else None
+ current_address = build_full_address(
+ amas_doc.personal_address if hasattr(amas_doc, 'personal_address') else None,
+ amas_doc.personal_city if hasattr(amas_doc, 'personal_city') else None,
+ amas_doc.personal_district if hasattr(amas_doc, 'personal_district') else None
+ )
+
+ if existing_employee:
+ emp = frappe.get_doc("Employee", existing_employee)
+ emp.first_name = first_name
+ if middle_name:
+ emp.middle_name = middle_name
+ emp.last_name = last_name
+ emp.employee_name = full_name or first_name
+ emp.gender = gender
+ if dob:
+ emp.date_of_birth = dob
+ if doj:
+ emp.date_of_joining = doj
+ if contract_end:
+ emp.contract_end_date = contract_end
+ if salary is not None:
+ emp.ctc = salary
+ if designation:
+ emp.designation = designation
+ if cell_number:
+ emp.cell_number = cell_number
+ if personal_email:
+ emp.personal_email = personal_email
+ if marital_status_mapped:
+ emp.marital_status = marital_status_mapped
+ if blood_group_mapped:
+ emp.blood_group = blood_group_mapped
+ if date_of_issue:
+ emp.date_of_issue = date_of_issue
+ if valid_upto:
+ emp.valid_upto = valid_upto
+ if place_of_issue:
+ emp.place_of_issue = place_of_issue
+ if current_address:
+ emp.current_address = current_address
+ emp.save(ignore_permissions=True)
+ action = "updated"
+ else:
+ emp = frappe.new_doc("Employee")
+ emp.first_name = first_name
+ if middle_name:
+ emp.middle_name = middle_name
+ emp.last_name = last_name
+ emp.company = company
+ emp.passport_number = fin
+ emp.date_of_birth = dob or "2000-01-01"
+ emp.date_of_joining = doj or frappe.utils.today()
+ emp.gender = gender
+ emp.status = status
+ if contract_end:
+ emp.contract_end_date = contract_end
+ if salary is not None:
+ emp.ctc = salary
+ if designation:
+ emp.designation = designation
+ if cell_number:
+ emp.cell_number = cell_number
+ if personal_email:
+ emp.personal_email = personal_email
+ if marital_status_mapped:
+ emp.marital_status = marital_status_mapped
+ if blood_group_mapped:
+ emp.blood_group = blood_group_mapped
+ if date_of_issue:
+ emp.date_of_issue = date_of_issue
+ if valid_upto:
+ emp.valid_upto = valid_upto
+ if place_of_issue:
+ emp.place_of_issue = place_of_issue
+ if current_address:
+ emp.current_address = current_address
+ emp.insert(ignore_permissions=True)
+ action = "created"
+
+ # Link Amas Employees → Employee
+ amas_doc.employee = emp.name
+ amas_doc.save(ignore_permissions=True)
+
+ frappe.db.commit()
+
+ return {
+ "success": True,
+ "action": action,
+ "employee_name": emp.name,
+ "full_name": full_name,
+ "message": f"{action.capitalize()}: {full_name}"
+ }
+
+ except Exception as e:
+ fin_val = emp_data.get("identification_number", "?") if isinstance(emp_data, dict) else "?"
+ name_val = emp_data.get("full_name", "?") if isinstance(emp_data, dict) else "?"
+ error_msg = f"{name_val} ({fin_val}): {str(e)}"
+ frappe.log_error(
+ f"{error_msg}\n{frappe.get_traceback()}",
+ "EMAS Single Employee Create Error"
+ )
+ return {
+ "success": False,
+ "action": "error",
+ "full_name": name_val,
+ "message": error_msg
+ }
+
+
@frappe.whitelist()
def create_employees_from_amas(asan_login_name, employees, company, create_designation=0):
"""
diff --git a/invoice_az/client/employee.js b/invoice_az/client/employee.js
index 1efec6f..37cc260 100644
--- a/invoice_az/client/employee.js
+++ b/invoice_az/client/employee.js
@@ -520,14 +520,6 @@ function show_employee_selection(listview, asan_login_name, organization_name, e
fieldtype: 'Section Break',
label: __('Settings')
},
- {
- fieldname: 'company',
- fieldtype: 'Link',
- label: __('Company'),
- options: 'Company',
- reqd: 1,
- default: default_company
- },
{
fieldname: 'create_designation',
fieldtype: 'Check',
@@ -568,7 +560,7 @@ function show_employee_selection(listview, asan_login_name, organization_name, e
}
d.hide();
- create_employees(listview, asan_login_name, selected_employees, values.company, values.create_designation);
+ create_employees(listview, asan_login_name, selected_employees, default_company, values.create_designation);
},
secondary_action_label: __('Cancel'),
secondary_action: function() { d.hide(); }
@@ -594,95 +586,228 @@ function show_employee_selection(listview, asan_login_name, organization_name, e
});
}
-// Step 5: Create employees via backend
+// Step 5: Create employees via backend - ONE BY ONE like E-Taxes
function create_employees(listview, asan_login_name, selected_employees, company, create_designation) {
- // Show progress indicator
+ // Initialize counters
+ window.amas_employees_to_process = selected_employees.length;
+ window.amas_cancel_loading = false;
+
+ // 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
+ );
+}
+
+// Process employees one by one (like E-Taxes loadSelectedInvoices)
+function process_employees_one_by_one(listview, asan_login_name, employees_queue, company, create_designation,
+ created_count, updated_count, error_count, errors, current_index) {
+ // Check if finished
+ if (employees_queue.length === 0) {
+ frappe.hide_progress();
+ $(document).off('progress-cancel.loading_amas_employees');
+
+ const total = created_count + updated_count + error_count;
+ show_import_summary(total, created_count, updated_count, error_count, errors);
+
+ window.amas_employees_to_process = null;
+ listview.refresh();
+ return;
+ }
+
+ // Check if cancelled
+ if (window.amas_cancel_loading) {
+ employees_queue = [];
+ frappe.hide_progress();
+ $(document).off('progress-cancel.loading_amas_employees');
+
+ const total = created_count + updated_count + error_count;
+ frappe.show_alert({
+ message: __('Import cancelled. Processed ') + total + __(' employees.'),
+ indicator: 'orange'
+ }, 5);
+
+ listview.refresh();
+ return;
+ }
+
+ // Take first employee from queue
+ const emp_data = employees_queue.shift();
+ const is_last = employees_queue.length === 0;
+ const total_to_process = window.amas_employees_to_process || (created_count + updated_count + error_count + employees_queue.length + 1);
+
+ current_index++;
+
+ // Show progress
frappe.show_progress(
__('Loading from ƏMAS'),
- 0,
- selected_employees.length,
- __('Fetching employee details...')
+ current_index - 1,
+ total_to_process,
+ __('Processing employee ') + current_index + __(' of ') + total_to_process,
+ null,
+ true
);
+ // Setup cancel handler
+ $(document).off('progress-cancel.loading_amas_employees');
+ $(document).on('progress-cancel.loading_amas_employees', function() {
+ window.amas_cancel_loading = true;
+ frappe.show_alert({
+ message: __('Cancelling import... Finishing current employee.'),
+ indicator: 'orange'
+ }, 3);
+ frappe.hide_progress();
+ });
+
+ // Get employee name for progress message
+ const emp_name = emp_data.full_name || emp_data.identification_number || 'Employee';
+
+ // Update progress with employee name
+ frappe.show_progress(
+ __('Loading from ƏMAS'),
+ current_index - 1,
+ total_to_process,
+ __('Processing: ') + emp_name
+ );
+
+ // Create/update single employee
frappe.call({
- method: 'invoice_az.amas_api.create_employees_from_amas',
+ method: 'invoice_az.amas_api.create_single_employee_from_amas',
args: {
asan_login_name: asan_login_name,
- employees: selected_employees,
+ emp_data: emp_data,
company: company,
create_designation: create_designation ? 1 : 0
},
- freeze: true,
- freeze_message: __('Creating employees from ƏMAS...'),
callback: function(r) {
- frappe.hide_progress();
- if (!r.message || !r.message.success) {
- // Check for CSRF error - need to reconnect
- if (r.message && r.message.csrf_error) {
- show_amas_reconnect_dialog(asan_login_name, function() {
- // Retry after successful reconnection
- create_employees(listview, asan_login_name, selected_employees, company, create_designation);
- });
- return;
+ if (r.message && r.message.success) {
+ // Success - increment counters
+ if (r.message.action === 'created') {
+ created_count++;
+ frappe.show_alert({
+ message: __('Created: ') + r.message.full_name,
+ indicator: 'green'
+ }, 2);
+ } else if (r.message.action === 'updated') {
+ updated_count++;
+ frappe.show_alert({
+ message: __('Updated: ') + r.message.full_name,
+ indicator: 'blue'
+ }, 2);
}
-
- frappe.msgprint({
- title: __('Error'),
- indicator: 'red',
- message: r.message ? r.message.message : __('Failed to create employees')
- });
- return;
- }
-
- const res = r.message;
- const total = res.created_count + res.updated_count + res.error_count;
-
- if (res.error_count === 0) {
- let msg = __('All ') + total + __(' employees were processed successfully.');
- if (res.created_count > 0) {
- msg += '
' + __('Created: ') + '' + res.created_count + '';
- }
- if (res.updated_count > 0) {
- msg += '
' + __('Updated: ') + '' + res.updated_count + '';
- }
-
- frappe.msgprint({
- title: __('Import Completed Successfully'),
- indicator: 'green',
- message: msg
- });
} else {
- let msg = '
${frm.doc.raw_data}`,
- wide: true
- });
- });
- }
- }
+ // Raw data is hidden and can be viewed if needed in customization
}
});