1865 lines
70 KiB
Python
1865 lines
70 KiB
Python
"""
|
||
ƏMAS (e-social.gov.az) Integration API
|
||
|
||
This module provides functions to interact with ƏMAS (Employment Management System)
|
||
of Azerbaijan's Ministry of Labour and Social Protection.
|
||
|
||
ƏMAS uses MyGovID/ASAN authentication. The flow is:
|
||
1. Get MyGovID JWT token via ASAN Sign (same as existing MyGovID Login)
|
||
2. Use JWT token to get authorization code for ƏMAS via MyGovID OAuth API
|
||
3. Exchange authorization code for ƏMAS session via SSO callback
|
||
4. Use session cookies for ƏMAS API calls
|
||
"""
|
||
|
||
import http.client
|
||
import json
|
||
import random
|
||
import string
|
||
from urllib.parse import unquote
|
||
|
||
import frappe
|
||
import requests
|
||
|
||
# Increase header limit - EMAS returns many Set-Cookie headers
|
||
http.client._MAXHEADERS = 1000
|
||
|
||
|
||
# EMAS Base URL
|
||
EMAS_BASE_URL = "https://eroom.e-social.gov.az"
|
||
|
||
# EMAS API Endpoints
|
||
EMAS_SSO_CALLBACK = "/ssoAsan/v2"
|
||
EMAS_CHANGE_ACCOUNT = "/service/aas.changeAccount"
|
||
EMAS_EXECUTE_REPORT = "/service/magusbi.executeReport"
|
||
EMAS_GET_DOMAINS = "/service/aas.domains"
|
||
EMAS_GET_ACCOUNTS = "/request/accounts"
|
||
|
||
# MyGovID OAuth API
|
||
MYGOVID_API_URL = "https://api.mygovid.gov.az"
|
||
MYGOVID_AUTH_CODES_URL = f"{MYGOVID_API_URL}/ssoauth/oauth2/auth/codes"
|
||
|
||
# EMAS OAuth Client ID (from HAR analysis)
|
||
EMAS_CLIENT_ID = "64942e33ec8d49059333a1e0ebad7fe2"
|
||
EMAS_REDIRECT_URI = f"{EMAS_BASE_URL}{EMAS_SSO_CALLBACK}"
|
||
|
||
# EMAS request headers
|
||
EMAS_HEADERS = {
|
||
"Accept": "text/plain, */*; q=0.01",
|
||
"Accept-Language": "en-US,en;q=0.9",
|
||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||
"X-Requested-With": "XMLHttpRequest",
|
||
"Origin": EMAS_BASE_URL,
|
||
"Referer": f"{EMAS_BASE_URL}/",
|
||
"X-MAGUS-RECAPTCHA": "null",
|
||
}
|
||
|
||
MYGOVID_HEADERS = {
|
||
"Accept": "application/json, text/plain, */*",
|
||
"Accept-Language": "az",
|
||
"Content-Type": "application/json",
|
||
"Origin": "https://mygovid.gov.az",
|
||
"Referer": "https://mygovid.gov.az/",
|
||
}
|
||
|
||
|
||
def generate_request_number():
|
||
"""Generate a unique request number for EMAS API calls"""
|
||
# Format: 1XXXXXXXXXXXXXXX (alphanumeric)
|
||
chars = string.ascii_uppercase + string.digits
|
||
return "1" + "".join(random.choices(chars, k=13))
|
||
|
||
|
||
def generate_state():
|
||
"""Generate a unique state for SSO flow"""
|
||
# Format: XXXXXXXXXXXXX:eroom:emas
|
||
chars = string.hexdigits.lower()[:16]
|
||
return "".join(random.choices(chars, k=13)) + ":eroom:emas"
|
||
|
||
|
||
def parse_cookies_from_response(response):
|
||
"""Extract cookies from response and return as dict"""
|
||
cookies = {}
|
||
for cookie in response.cookies:
|
||
cookies[cookie.name] = cookie.value
|
||
return cookies
|
||
|
||
|
||
@frappe.whitelist()
|
||
def connect_emas(asan_login_name):
|
||
"""
|
||
Connect to EMAS using MyGovID JWT token.
|
||
|
||
This is the main entry point for EMAS connection.
|
||
If MyGovID token exists - uses it to get EMAS session automatically.
|
||
If not - returns error asking user to login with MyGovID first.
|
||
"""
|
||
try:
|
||
doc = frappe.get_doc("Asan Login", asan_login_name)
|
||
|
||
# Check if we have MyGovID token
|
||
if not doc.mygovid_token:
|
||
return {
|
||
"success": False,
|
||
"need_mygovid_login": True,
|
||
"message": "MyGovID token not found. Please login with MyGovID first."
|
||
}
|
||
|
||
# Step 1: Get authorization code from MyGovID
|
||
auth_code_result = get_emas_auth_code(doc.mygovid_token)
|
||
|
||
if not auth_code_result.get("success"):
|
||
# Token might be expired
|
||
if "401" in str(auth_code_result.get("message", "")):
|
||
return {
|
||
"success": False,
|
||
"need_mygovid_login": True,
|
||
"message": "MyGovID token expired. Please login with MyGovID again."
|
||
}
|
||
return auth_code_result
|
||
|
||
auth_code = auth_code_result.get("code")
|
||
state = auth_code_result.get("state")
|
||
|
||
# Step 2: Exchange authorization code for EMAS session
|
||
session_result = exchange_code_for_session(auth_code, state)
|
||
|
||
if not session_result.get("success"):
|
||
return session_result
|
||
|
||
# Step 3: Save session data (but don't save account yet - user must select)
|
||
session_data = session_result.get("session_data")
|
||
|
||
doc.emas_session = json.dumps(session_data)
|
||
doc.emas_csrf_token = session_data.get("xsrf_token")
|
||
doc.emas_auth_status = "Connected"
|
||
doc.emas_last_activity = frappe.utils.now_datetime()
|
||
# Clear any previous account selection - user must choose
|
||
doc.emas_account_oid = None
|
||
doc.emas_account_name = None
|
||
doc.emas_account_number = None
|
||
|
||
doc.save(ignore_permissions=True)
|
||
frappe.db.commit()
|
||
|
||
# Step 4: Fetch available accounts from /request/accounts
|
||
accounts_result = get_emas_accounts(asan_login_name)
|
||
accounts = accounts_result.get("accounts", []) if accounts_result.get("success") else []
|
||
|
||
return {
|
||
"success": True,
|
||
"message": "ƏMAS connected! Please select an organization.",
|
||
"accounts": accounts,
|
||
"has_accounts": len(accounts) > 0,
|
||
"need_account_selection": True
|
||
}
|
||
|
||
except Exception as e:
|
||
frappe.log_error(
|
||
f"EMAS connect error: {str(e)}\n{frappe.get_traceback()}",
|
||
"ƏMAS API Error"
|
||
)
|
||
return {"success": False, "message": f"Error: {str(e)}"}
|
||
|
||
|
||
def get_emas_auth_code(mygovid_token):
|
||
"""
|
||
Get EMAS authorization code using MyGovID JWT token.
|
||
|
||
This calls the MyGovID OAuth API to get an authorization code
|
||
that can be exchanged for EMAS session.
|
||
"""
|
||
try:
|
||
state = generate_state()
|
||
|
||
payload = {
|
||
"client_id": EMAS_CLIENT_ID,
|
||
"state": state,
|
||
"response_type": "code",
|
||
"redirect_uri": EMAS_REDIRECT_URI
|
||
}
|
||
|
||
headers = MYGOVID_HEADERS.copy()
|
||
headers["Authorization"] = mygovid_token
|
||
|
||
response = requests.post(
|
||
MYGOVID_AUTH_CODES_URL,
|
||
json=payload,
|
||
headers=headers,
|
||
timeout=30
|
||
)
|
||
|
||
if response.status_code == 201:
|
||
data = response.json()
|
||
return {
|
||
"success": True,
|
||
"code": data.get("code"),
|
||
"state": data.get("state", state)
|
||
}
|
||
elif response.status_code == 401:
|
||
return {
|
||
"success": False,
|
||
"message": "MyGovID token expired (401)"
|
||
}
|
||
else:
|
||
return {
|
||
"success": False,
|
||
"message": f"Failed to get auth code: status {response.status_code}"
|
||
}
|
||
|
||
except requests.exceptions.RequestException as e:
|
||
return {"success": False, "message": f"Network error: {str(e)}"}
|
||
except Exception as e:
|
||
return {"success": False, "message": f"Error: {str(e)}"}
|
||
|
||
|
||
def parse_config_from_html(html_text):
|
||
"""
|
||
Parse the 'var config = {...}' JavaScript object from EMAS HTML page.
|
||
Returns a dict with account info.
|
||
"""
|
||
import re
|
||
|
||
config = {}
|
||
|
||
# Extract the var config = {...} block
|
||
# Pattern matches: var config = { ... };
|
||
config_match = re.search(r'var\s+config\s*=\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}', html_text)
|
||
|
||
if not config_match:
|
||
return config
|
||
|
||
config_text = config_match.group(1)
|
||
|
||
# Parse individual fields using pattern: 'key' : 'value' or 'key' : value
|
||
# Fields we want to extract
|
||
fields = [
|
||
'token', 'accountOid', 'accountName', 'accountNumber',
|
||
'firstName', 'lastName', 'middleName', 'userId',
|
||
'userLevel', 'userBranchId', 'userBranchName', 'userPosId',
|
||
'customerOid', 'domain', 'loginType', 'entityType'
|
||
]
|
||
|
||
for field in fields:
|
||
# Try 'field' : 'value' pattern (string value)
|
||
pattern = rf"'{field}'\s*:\s*'([^']*)'"
|
||
match = re.search(pattern, config_text)
|
||
if match:
|
||
config[field] = match.group(1)
|
||
continue
|
||
|
||
# Try 'field' : value pattern (non-string value)
|
||
pattern = rf"'{field}'\s*:\s*([^,\n]+)"
|
||
match = re.search(pattern, config_text)
|
||
if match:
|
||
value = match.group(1).strip().strip("'\"")
|
||
config[field] = value
|
||
|
||
return config
|
||
|
||
|
||
def exchange_code_for_session(code, state):
|
||
"""
|
||
Exchange authorization code for EMAS session.
|
||
|
||
Makes request to EMAS SSO callback endpoint and captures session cookies.
|
||
Also parses the account info from the HTML response config.
|
||
"""
|
||
try:
|
||
# Build SSO callback URL
|
||
callback_url = f"{EMAS_REDIRECT_URI}?code={code}&state={state}"
|
||
|
||
# Create session to capture cookies
|
||
session = requests.Session()
|
||
|
||
response = session.get(
|
||
callback_url,
|
||
allow_redirects=True,
|
||
timeout=30
|
||
)
|
||
|
||
# Extract cookies
|
||
cookies = {}
|
||
for cookie in session.cookies:
|
||
cookies[cookie.name] = cookie.value
|
||
|
||
# Look for required cookies
|
||
session_cookie = cookies.get("emek_ve_mesgulluq_alt_sistemi_session")
|
||
xsrf_token_raw = cookies.get("XSRF-TOKEN")
|
||
|
||
# Also check for _s cookie (Laravel session with data)
|
||
s_cookie = cookies.get("emek_ve_mesgulluq_alt_sistemi_s")
|
||
|
||
if not session_cookie:
|
||
return {
|
||
"success": False,
|
||
"message": "Failed to get ƏMAS session cookie"
|
||
}
|
||
|
||
# IMPORTANT: Laravel's XSRF-TOKEN cookie is URL-encoded, must decode it!
|
||
# The cookie value contains %3D instead of = etc.
|
||
xsrf_token = unquote(xsrf_token_raw) if xsrf_token_raw else None
|
||
|
||
# Try to extract CSRF token from HTML meta tag (preferred - already decoded)
|
||
import re
|
||
meta_csrf_token = None
|
||
match = re.search(r'name="csrf-token" content="([^"]+)"', response.text)
|
||
if match:
|
||
meta_csrf_token = match.group(1)
|
||
|
||
# Prefer meta tag token over cookie (meta tag is already decoded)
|
||
csrf_token = meta_csrf_token or xsrf_token
|
||
|
||
# Parse token and account info from HTML config
|
||
html_config = parse_config_from_html(response.text)
|
||
token = html_config.get("token")
|
||
|
||
# Extract account info from the config
|
||
account_info = None
|
||
if html_config.get("accountOid"):
|
||
account_info = {
|
||
"accountOid": html_config.get("accountOid"),
|
||
"accountName": html_config.get("accountName", ""),
|
||
"accountNumber": html_config.get("accountNumber", ""),
|
||
"userId": html_config.get("userId", ""),
|
||
"userLevel": html_config.get("userLevel", "owner"),
|
||
"branchId": html_config.get("userBranchId", "0000"),
|
||
"positionId": html_config.get("userPosId", "0000"),
|
||
}
|
||
|
||
session_data = {
|
||
"session_cookie": session_cookie,
|
||
"xsrf_token": csrf_token,
|
||
"s_cookie": s_cookie,
|
||
"token": token,
|
||
"all_cookies": cookies,
|
||
"account_info": account_info,
|
||
"html_config": html_config
|
||
}
|
||
|
||
return {
|
||
"success": True,
|
||
"session_data": session_data
|
||
}
|
||
|
||
except requests.exceptions.RequestException as e:
|
||
return {"success": False, "message": f"Network error: {str(e)}"}
|
||
except Exception as e:
|
||
return {"success": False, "message": f"Error: {str(e)}"}
|
||
|
||
|
||
def get_emas_session_data(asan_login_name):
|
||
"""Get EMAS session data from document"""
|
||
doc = frappe.get_doc("Asan Login", asan_login_name)
|
||
|
||
if not doc.emas_session:
|
||
return None
|
||
|
||
try:
|
||
return json.loads(doc.emas_session)
|
||
except (json.JSONDecodeError, TypeError):
|
||
return None
|
||
|
||
|
||
def refresh_csrf_token_internal(asan_login_name):
|
||
"""
|
||
Refresh CSRF token by making a GET request to EMAS dashboard.
|
||
EMAS requires fresh CSRF token for EVERY operation.
|
||
Returns updated session_data and csrf_token.
|
||
"""
|
||
import re
|
||
|
||
session_data = get_emas_session_data(asan_login_name)
|
||
if not session_data:
|
||
return None, None
|
||
|
||
doc = frappe.get_doc("Asan Login", asan_login_name)
|
||
cookies = session_data.get("all_cookies", {})
|
||
|
||
try:
|
||
# Make GET request to dashboard to get fresh CSRF token
|
||
response = requests.get(
|
||
f"{EMAS_BASE_URL}/core.dashboard",
|
||
cookies=cookies,
|
||
timeout=30
|
||
)
|
||
|
||
if response.status_code != 200:
|
||
return session_data, doc.emas_csrf_token
|
||
|
||
# Update cookies from response
|
||
for cookie in response.cookies:
|
||
cookies[cookie.name] = cookie.value
|
||
session_data["all_cookies"] = cookies
|
||
|
||
# Extract CSRF token from HTML meta tag (preferred)
|
||
csrf_token = None
|
||
match = re.search(r'name="csrf-token" content="([^"]+)"', response.text)
|
||
if match:
|
||
csrf_token = match.group(1)
|
||
|
||
# Also try from cookie
|
||
if not csrf_token and "XSRF-TOKEN" in cookies:
|
||
csrf_token = unquote(cookies["XSRF-TOKEN"])
|
||
|
||
if csrf_token:
|
||
doc.emas_csrf_token = csrf_token
|
||
session_data["xsrf_token"] = csrf_token
|
||
|
||
# Update token from page config if available
|
||
html_config = parse_config_from_html(response.text)
|
||
if html_config.get("token"):
|
||
session_data["token"] = html_config.get("token")
|
||
|
||
# Save updated session
|
||
doc.emas_session = json.dumps(session_data)
|
||
doc.emas_last_activity = frappe.utils.now_datetime()
|
||
doc.save(ignore_permissions=True)
|
||
frappe.db.commit()
|
||
|
||
return session_data, csrf_token
|
||
|
||
except Exception as e:
|
||
frappe.log_error(f"CSRF refresh error: {str(e)}", "ƏMAS API Error")
|
||
return session_data, doc.emas_csrf_token
|
||
|
||
|
||
def make_emas_request(asan_login_name, endpoint, data):
|
||
"""
|
||
Make an authenticated request to EMAS API.
|
||
|
||
Handles session cookies, CSRF token, and request headers.
|
||
IMPORTANT: Refreshes CSRF token before EVERY request (EMAS requirement).
|
||
"""
|
||
# IMPORTANT: Refresh CSRF token before EVERY operation
|
||
# EMAS changes CSRF token after each request
|
||
session_data, csrf_token = refresh_csrf_token_internal(asan_login_name)
|
||
|
||
if not session_data:
|
||
return {
|
||
"success": False,
|
||
"message": "ƏMAS session not found. Please connect to ƏMAS first."
|
||
}
|
||
|
||
doc = frappe.get_doc("Asan Login", asan_login_name)
|
||
|
||
# Build request headers with fresh CSRF token
|
||
headers = EMAS_HEADERS.copy()
|
||
csrf_token = csrf_token or doc.emas_csrf_token or session_data.get("xsrf_token", "")
|
||
headers["X-CSRF-TOKEN"] = csrf_token
|
||
headers["MAGUS-REQUEST-NUMBER"] = generate_request_number()
|
||
|
||
# Build cookies (updated by refresh)
|
||
cookies = session_data.get("all_cookies", {})
|
||
|
||
# Add required data fields with fresh token
|
||
data["requestNumber"] = headers["MAGUS-REQUEST-NUMBER"]
|
||
data["_token"] = csrf_token
|
||
|
||
# Add currentTabToken if available
|
||
# NOTE: The token from config is already in format "{uuid}.{accountOid}"
|
||
# Do NOT append accountOid again!
|
||
token = session_data.get("token")
|
||
if token:
|
||
data["currentTabToken"] = token
|
||
|
||
try:
|
||
url = f"{EMAS_BASE_URL}{endpoint}"
|
||
|
||
response = requests.post(
|
||
url,
|
||
data=data,
|
||
headers=headers,
|
||
cookies=cookies,
|
||
timeout=60
|
||
)
|
||
|
||
# Update cookies from response
|
||
new_cookies = parse_cookies_from_response(response)
|
||
if new_cookies:
|
||
cookies.update(new_cookies)
|
||
session_data["all_cookies"] = cookies
|
||
|
||
# Update CSRF token if changed - IMPORTANT: decode URL-encoded token!
|
||
if "XSRF-TOKEN" in new_cookies:
|
||
decoded_token = unquote(new_cookies["XSRF-TOKEN"])
|
||
doc.emas_csrf_token = decoded_token
|
||
session_data["xsrf_token"] = decoded_token
|
||
|
||
doc.emas_session = json.dumps(session_data)
|
||
doc.emas_last_activity = frappe.utils.now_datetime()
|
||
doc.save(ignore_permissions=True)
|
||
frappe.db.commit()
|
||
|
||
if response.status_code == 200:
|
||
try:
|
||
result = response.json()
|
||
|
||
# Check for token/CSRF errors in response
|
||
response_info = result.get("response", {})
|
||
error_code = response_info.get("code", "")
|
||
error_msg = str(response_info.get("message", ""))
|
||
|
||
# Handle various token errors
|
||
if error_code in ("CSRF", "TOKENS_ARE_NOT_SAME", "CSRF_TOKEN_MISMATCH"):
|
||
frappe.log_error(
|
||
f"Token error: {error_code}. CSRF: {csrf_token[:20]}..., TabToken: {data.get('currentTabToken', 'N/A')[:30]}...",
|
||
"ƏMAS Token Error"
|
||
)
|
||
return {
|
||
"success": False,
|
||
"csrf_error": True,
|
||
"message": f"Token error ({error_code}). Please reconnect to ƏMAS."
|
||
}
|
||
|
||
if "CSRF" in error_msg or "TOKEN" in error_msg.upper():
|
||
frappe.log_error(
|
||
f"Token error in message: {error_msg}",
|
||
"ƏMAS Token Error"
|
||
)
|
||
return {
|
||
"success": False,
|
||
"csrf_error": True,
|
||
"message": f"Token error. Please reconnect to ƏMAS."
|
||
}
|
||
|
||
# Update token if present in response
|
||
if result.get("token"):
|
||
session_data["token"] = result.get("token")
|
||
doc.emas_session = json.dumps(session_data)
|
||
doc.save(ignore_permissions=True)
|
||
frappe.db.commit()
|
||
|
||
return {"success": True, "data": result}
|
||
except json.JSONDecodeError:
|
||
return {
|
||
"success": False,
|
||
"message": "Invalid response from ƏMAS"
|
||
}
|
||
elif response.status_code == 419:
|
||
# Laravel CSRF token mismatch status code
|
||
return {
|
||
"success": False,
|
||
"csrf_error": True,
|
||
"message": "CSRF token expired. Please reconnect to ƏMAS."
|
||
}
|
||
elif response.status_code == 401:
|
||
doc.emas_auth_status = "Error"
|
||
doc.save(ignore_permissions=True)
|
||
frappe.db.commit()
|
||
return {
|
||
"success": False,
|
||
"session_expired": True,
|
||
"message": "ƏMAS session expired. Please reconnect."
|
||
}
|
||
else:
|
||
# Log unexpected errors for debugging
|
||
frappe.log_error(
|
||
f"ƏMAS API error: status {response.status_code}\nResponse: {response.text[:500]}",
|
||
"ƏMAS API Error"
|
||
)
|
||
return {
|
||
"success": False,
|
||
"message": f"ƏMAS API error: status {response.status_code}"
|
||
}
|
||
|
||
except requests.exceptions.RequestException as e:
|
||
frappe.log_error(f"ƏMAS API network error: {str(e)}", "ƏMAS API Error")
|
||
return {"success": False, "message": f"Network error: {str(e)}"}
|
||
|
||
|
||
@frappe.whitelist()
|
||
def get_emas_accounts(asan_login_name):
|
||
"""
|
||
Get list of organizations/accounts user has access to in EMAS.
|
||
Fetches from /request/accounts endpoint.
|
||
"""
|
||
try:
|
||
# IMPORTANT: Refresh CSRF token before EVERY operation
|
||
session_data, csrf_token = refresh_csrf_token_internal(asan_login_name)
|
||
|
||
if not session_data:
|
||
return {"success": False, "accounts": [], "message": "No ƏMAS session"}
|
||
|
||
doc = frappe.get_doc("Asan Login", asan_login_name)
|
||
|
||
# Build request headers with fresh CSRF token
|
||
headers = EMAS_HEADERS.copy()
|
||
csrf_token = csrf_token or doc.emas_csrf_token or session_data.get("xsrf_token", "")
|
||
headers["X-CSRF-TOKEN"] = csrf_token
|
||
headers["MAGUS-REQUEST-NUMBER"] = generate_request_number()
|
||
|
||
# Build cookies (updated by refresh)
|
||
cookies = session_data.get("all_cookies", {})
|
||
|
||
# Build request data with fresh token
|
||
data = {
|
||
"requestNumber": headers["MAGUS-REQUEST-NUMBER"],
|
||
"_token": csrf_token,
|
||
}
|
||
|
||
# Add currentTabToken
|
||
token = session_data.get("token")
|
||
if token:
|
||
data["currentTabToken"] = token
|
||
|
||
# Make request to /request/accounts
|
||
response = requests.post(
|
||
f"{EMAS_BASE_URL}{EMAS_GET_ACCOUNTS}",
|
||
data=data,
|
||
headers=headers,
|
||
cookies=cookies,
|
||
timeout=30
|
||
)
|
||
|
||
# Update cookies from response
|
||
new_cookies = parse_cookies_from_response(response)
|
||
if new_cookies:
|
||
cookies.update(new_cookies)
|
||
session_data["all_cookies"] = cookies
|
||
|
||
# Update CSRF token if changed
|
||
if "XSRF-TOKEN" in new_cookies:
|
||
decoded_token = unquote(new_cookies["XSRF-TOKEN"])
|
||
doc.emas_csrf_token = decoded_token
|
||
session_data["xsrf_token"] = decoded_token
|
||
|
||
doc.emas_session = json.dumps(session_data)
|
||
doc.save(ignore_permissions=True)
|
||
frappe.db.commit()
|
||
|
||
if response.status_code == 200:
|
||
try:
|
||
accounts = response.json()
|
||
# The response is a direct array of accounts
|
||
if isinstance(accounts, list):
|
||
return {"success": True, "accounts": accounts}
|
||
else:
|
||
return {"success": False, "accounts": [], "message": "Unexpected response format"}
|
||
except json.JSONDecodeError:
|
||
return {"success": False, "accounts": [], "message": "Invalid JSON response"}
|
||
else:
|
||
return {"success": False, "accounts": [], "message": f"HTTP {response.status_code}"}
|
||
|
||
except Exception as e:
|
||
frappe.log_error(
|
||
f"EMAS get accounts error: {str(e)}\n{frappe.get_traceback()}",
|
||
"ƏMAS API Error"
|
||
)
|
||
return {"success": False, "message": f"Error: {str(e)}", "accounts": []}
|
||
|
||
|
||
@frappe.whitelist()
|
||
def change_emas_account(asan_login_name, account_oid, account_name, account_number, role_name="chairman"):
|
||
"""
|
||
Select/switch to a specific organization account in EMAS.
|
||
"""
|
||
try:
|
||
data = {
|
||
"accountOid": account_oid,
|
||
"accountName": account_name,
|
||
"accountNumber": account_number,
|
||
"roleName": role_name,
|
||
"branchId": "0000",
|
||
"positionId": "0000",
|
||
"domain": "emas"
|
||
}
|
||
|
||
result = make_emas_request(
|
||
asan_login_name,
|
||
EMAS_CHANGE_ACCOUNT,
|
||
data
|
||
)
|
||
|
||
if not result.get("success"):
|
||
return result
|
||
|
||
response_data = result.get("data", {})
|
||
|
||
# Check response code
|
||
response_info = response_data.get("response", {})
|
||
if response_info.get("code") != "0":
|
||
return {
|
||
"success": False,
|
||
"message": response_info.get("message", "Failed to change account")
|
||
}
|
||
|
||
# Store selected account info
|
||
doc = frappe.get_doc("Asan Login", asan_login_name)
|
||
doc.emas_account_oid = account_oid
|
||
doc.emas_account_name = account_name
|
||
doc.emas_account_number = account_number
|
||
doc.emas_auth_status = "Connected"
|
||
doc.emas_last_activity = frappe.utils.now_datetime()
|
||
|
||
# Update session with token from response
|
||
token = response_data.get("token")
|
||
if token:
|
||
session_data = get_emas_session_data(asan_login_name)
|
||
if session_data:
|
||
session_data["token"] = token
|
||
doc.emas_session = json.dumps(session_data)
|
||
|
||
doc.save(ignore_permissions=True)
|
||
frappe.db.commit()
|
||
|
||
return {
|
||
"success": True,
|
||
"message": f"Switched to account: {account_name}"
|
||
}
|
||
|
||
except Exception as e:
|
||
frappe.log_error(
|
||
f"EMAS change account error: {str(e)}\n{frappe.get_traceback()}",
|
||
"ƏMAS API Error"
|
||
)
|
||
return {"success": False, "message": f"Error: {str(e)}"}
|
||
|
||
|
||
@frappe.whitelist()
|
||
def get_employees_report(asan_login_name, offset=0, limit=100):
|
||
"""
|
||
Get list of employees from EMAS.
|
||
Uses the emasValidEmploymentContractOnline report.
|
||
"""
|
||
try:
|
||
doc = frappe.get_doc("Asan Login", asan_login_name)
|
||
|
||
if not doc.emas_account_oid:
|
||
return {
|
||
"success": False,
|
||
"message": "No ƏMAS account selected. Please select an organization first."
|
||
}
|
||
|
||
data = {
|
||
"reportId": "emasValidEmploymentContractOnline",
|
||
"pagination[offset]": str(offset),
|
||
"pagination[limit]": str(limit),
|
||
"attributes[newExcel]": "true"
|
||
}
|
||
|
||
result = make_emas_request(asan_login_name, EMAS_EXECUTE_REPORT, data)
|
||
|
||
if not result.get("success"):
|
||
return result
|
||
|
||
response_data = result.get("data", {})
|
||
|
||
# Check response code
|
||
response_info = response_data.get("response", {})
|
||
if response_info.get("code") != "0":
|
||
return {
|
||
"success": False,
|
||
"message": response_info.get("message", "Failed to get employees report")
|
||
}
|
||
|
||
# Extract employees from result
|
||
employees = response_data.get("result", [])
|
||
has_more = response_data.get("hasMore", False)
|
||
|
||
return {
|
||
"success": True,
|
||
"employees": employees,
|
||
"has_more": has_more,
|
||
"offset": offset,
|
||
"limit": limit
|
||
}
|
||
|
||
except Exception as e:
|
||
frappe.log_error(
|
||
f"EMAS employees report error: {str(e)}\n{frappe.get_traceback()}",
|
||
"ƏMAS API Error"
|
||
)
|
||
return {"success": False, "message": f"Error: {str(e)}"}
|
||
|
||
|
||
@frappe.whitelist()
|
||
def get_contract_stats(asan_login_name):
|
||
"""
|
||
Get employment contract statistics from EMAS.
|
||
"""
|
||
try:
|
||
doc = frappe.get_doc("Asan Login", asan_login_name)
|
||
|
||
if not doc.emas_account_oid:
|
||
return {
|
||
"success": False,
|
||
"message": "No ƏMAS account selected. Please select an organization first."
|
||
}
|
||
|
||
data = {
|
||
"reportId": "graphContractStatsEmployer",
|
||
"pagination[offset]": "0",
|
||
"pagination[limit]": "100"
|
||
}
|
||
|
||
result = make_emas_request(asan_login_name, EMAS_EXECUTE_REPORT, data)
|
||
|
||
if not result.get("success"):
|
||
return result
|
||
|
||
response_data = result.get("data", {})
|
||
|
||
response_info = response_data.get("response", {})
|
||
if response_info.get("code") != "0":
|
||
return {
|
||
"success": False,
|
||
"message": response_info.get("message", "Failed to get contract stats")
|
||
}
|
||
|
||
stats = response_data.get("result", [])
|
||
|
||
return {
|
||
"success": True,
|
||
"stats": stats[0] if stats else {}
|
||
}
|
||
|
||
except Exception as e:
|
||
frappe.log_error(
|
||
f"EMAS contract stats error: {str(e)}\n{frappe.get_traceback()}",
|
||
"ƏMAS API Error"
|
||
)
|
||
return {"success": False, "message": f"Error: {str(e)}"}
|
||
|
||
|
||
@frappe.whitelist()
|
||
def check_emas_connection(asan_login_name):
|
||
"""
|
||
Check if EMAS connection is active.
|
||
"""
|
||
try:
|
||
doc = frappe.get_doc("Asan Login", asan_login_name)
|
||
|
||
return {
|
||
"success": True,
|
||
"emas_auth_status": doc.emas_auth_status or "Not Connected",
|
||
"emas_account_name": doc.emas_account_name,
|
||
"emas_account_number": doc.emas_account_number,
|
||
"emas_last_activity": str(doc.emas_last_activity) if doc.emas_last_activity else None,
|
||
"has_session": bool(doc.emas_session),
|
||
"has_mygovid_token": bool(doc.mygovid_token)
|
||
}
|
||
|
||
except Exception as e:
|
||
frappe.log_error(
|
||
f"EMAS check connection error: {str(e)}\n{frappe.get_traceback()}",
|
||
"ƏMAS API Error"
|
||
)
|
||
return {"success": False, "message": f"Error: {str(e)}"}
|
||
|
||
|
||
@frappe.whitelist()
|
||
def refresh_emas_session(asan_login_name):
|
||
"""
|
||
Refresh EMAS session by making a GET request to get fresh CSRF token.
|
||
Use this when you get CSRF_TOKEN_MISMATCH errors.
|
||
"""
|
||
try:
|
||
doc = frappe.get_doc("Asan Login", asan_login_name)
|
||
session_data = get_emas_session_data(asan_login_name)
|
||
|
||
if not session_data:
|
||
return {
|
||
"success": False,
|
||
"message": "No ƏMAS session found. Please connect first."
|
||
}
|
||
|
||
cookies = session_data.get("all_cookies", {})
|
||
|
||
# Make GET request to EMAS dashboard to get fresh CSRF token
|
||
response = requests.get(
|
||
f"{EMAS_BASE_URL}/core.dashboard",
|
||
cookies=cookies,
|
||
timeout=30
|
||
)
|
||
|
||
if response.status_code != 200:
|
||
return {
|
||
"success": False,
|
||
"message": f"Failed to refresh session: status {response.status_code}"
|
||
}
|
||
|
||
# Update cookies
|
||
for cookie in response.cookies:
|
||
cookies[cookie.name] = cookie.value
|
||
|
||
session_data["all_cookies"] = cookies
|
||
|
||
# Extract new CSRF token from HTML
|
||
import re
|
||
match = re.search(r'name="csrf-token" content="([^"]+)"', response.text)
|
||
if match:
|
||
new_csrf_token = match.group(1)
|
||
doc.emas_csrf_token = new_csrf_token
|
||
session_data["xsrf_token"] = new_csrf_token
|
||
|
||
# Also update from cookie if available
|
||
if "XSRF-TOKEN" in cookies:
|
||
decoded_token = unquote(cookies["XSRF-TOKEN"])
|
||
# Prefer meta tag token, but update session data
|
||
if not match:
|
||
doc.emas_csrf_token = decoded_token
|
||
session_data["xsrf_token"] = decoded_token
|
||
|
||
# Parse config for updated token
|
||
html_config = parse_config_from_html(response.text)
|
||
if html_config.get("token"):
|
||
session_data["token"] = html_config.get("token")
|
||
|
||
doc.emas_session = json.dumps(session_data)
|
||
doc.emas_last_activity = frappe.utils.now_datetime()
|
||
doc.save(ignore_permissions=True)
|
||
frappe.db.commit()
|
||
|
||
return {
|
||
"success": True,
|
||
"message": "ƏMAS session refreshed successfully"
|
||
}
|
||
|
||
except Exception as e:
|
||
frappe.log_error(
|
||
f"EMAS refresh session error: {str(e)}\n{frappe.get_traceback()}",
|
||
"ƏMAS API Error"
|
||
)
|
||
return {"success": False, "message": f"Error: {str(e)}"}
|
||
|
||
|
||
@frappe.whitelist()
|
||
def disconnect_emas(asan_login_name):
|
||
"""
|
||
Disconnect from EMAS by clearing session data.
|
||
"""
|
||
try:
|
||
doc = frappe.get_doc("Asan Login", asan_login_name)
|
||
|
||
doc.emas_session = None
|
||
doc.emas_csrf_token = None
|
||
doc.emas_account_oid = None
|
||
doc.emas_account_name = None
|
||
doc.emas_account_number = None
|
||
doc.emas_auth_status = "Not Connected"
|
||
doc.emas_last_activity = None
|
||
|
||
doc.save(ignore_permissions=True)
|
||
frappe.db.commit()
|
||
|
||
return {
|
||
"success": True,
|
||
"message": "Disconnected from ƏMAS"
|
||
}
|
||
|
||
except Exception as e:
|
||
frappe.log_error(
|
||
f"EMAS disconnect error: {str(e)}\n{frappe.get_traceback()}",
|
||
"ƏMAS API Error"
|
||
)
|
||
return {"success": False, "message": f"Error: {str(e)}"}
|
||
|
||
|
||
@frappe.whitelist()
|
||
def get_connected_asan_logins():
|
||
"""
|
||
Get list of Asan Logins with active ƏMAS connection.
|
||
Used for employee import dialog.
|
||
"""
|
||
try:
|
||
asan_logins = frappe.get_all(
|
||
"Asan Login",
|
||
filters={
|
||
"emas_auth_status": "Connected",
|
||
"emas_account_oid": ["is", "set"]
|
||
},
|
||
fields=["name", "emas_account_name", "emas_account_number", "emas_account_oid"]
|
||
)
|
||
|
||
result = []
|
||
for al in asan_logins:
|
||
result.append({
|
||
"name": al.name,
|
||
"organization_name": al.emas_account_name or "Unknown",
|
||
"organization_voen": al.emas_account_number or ""
|
||
})
|
||
|
||
return {
|
||
"success": True,
|
||
"asan_logins": result
|
||
}
|
||
|
||
except Exception as e:
|
||
frappe.log_error(
|
||
f"Get connected Asan Logins error: {str(e)}\n{frappe.get_traceback()}",
|
||
"ƏMAS API Error"
|
||
)
|
||
return {"success": False, "message": f"Error: {str(e)}", "asan_logins": []}
|
||
|
||
|
||
# Exact list of API fields that map 1:1 to Əmas Employees DocType fields
|
||
EMAS_EMPLOYEE_FIELDS = [
|
||
"full_name",
|
||
"identification_number",
|
||
"ssn",
|
||
"birthday",
|
||
"work_position_text",
|
||
"monthly_salary",
|
||
"doc_no",
|
||
"doc_date",
|
||
"doc_type",
|
||
"contract_type",
|
||
"contract_status",
|
||
"begin_date",
|
||
"end_date",
|
||
"main_contract_begin_date",
|
||
"emp_job_start_date",
|
||
"sign_type",
|
||
"is_valid",
|
||
"dur_state",
|
||
"operation",
|
||
"after_exec_label",
|
||
"workplace_type",
|
||
"form_size",
|
||
"flow",
|
||
"flow_alias",
|
||
"tree_path_name",
|
||
"view_type",
|
||
"doc_oid",
|
||
]
|
||
|
||
|
||
@frappe.whitelist()
|
||
def import_employees(asan_login_name, employees):
|
||
"""
|
||
Import employees from ƏMAS to Əmas Employees doctype.
|
||
NOW FETCHES COMPLETE DETAIL DATA for each employee.
|
||
|
||
Args:
|
||
asan_login_name: Name of the Asan Login document
|
||
employees: List of employee data from ƏMAS API (list report)
|
||
"""
|
||
try:
|
||
if isinstance(employees, str):
|
||
employees = json.loads(employees)
|
||
|
||
doc = frappe.get_doc("Asan Login", asan_login_name)
|
||
organization_name = doc.emas_account_name
|
||
organization_voen = doc.emas_account_number
|
||
|
||
imported_count = 0
|
||
updated_count = 0
|
||
error_count = 0
|
||
errors = []
|
||
|
||
for emp_data in employees:
|
||
try:
|
||
doc_oid = emp_data.get('doc_oid')
|
||
doc_no = emp_data.get('doc_no')
|
||
doc_type = emp_data.get('doc_type', 'docType_47')
|
||
|
||
if not doc_oid:
|
||
# Can't fetch details without doc_oid, skip
|
||
name_val = emp_data.get('full_name', '?')
|
||
errors.append(f"Employee {name_val}: No doc_oid")
|
||
error_count += 1
|
||
continue
|
||
|
||
# FETCH COMPLETE DETAIL DATA
|
||
detail_result = get_employee_detail(asan_login_name, doc_oid, doc_no, doc_type)
|
||
|
||
if not detail_result.get('success'):
|
||
# Detail fetch failed, use list data as fallback
|
||
name_val = emp_data.get('full_name', '?')
|
||
frappe.log_error(
|
||
f"Failed to fetch detail for {doc_oid}: {detail_result.get('message')}",
|
||
"EMAS Employee Detail Fetch Error"
|
||
)
|
||
errors.append(f"{name_val}: Detail fetch failed, using list data")
|
||
|
||
# Fallback to list data
|
||
fin = emp_data.get("identification_number")
|
||
if not fin:
|
||
error_count += 1
|
||
continue
|
||
|
||
fin = str(fin).strip()
|
||
existing = frappe.db.exists(
|
||
"Emas Employees",
|
||
{"identification_number": fin, "organization_voen": organization_voen}
|
||
)
|
||
|
||
if existing:
|
||
employee_doc = frappe.get_doc("Emas Employees", existing)
|
||
update_employee_from_data(employee_doc, emp_data)
|
||
employee_doc.save(ignore_permissions=True)
|
||
updated_count += 1
|
||
else:
|
||
employee_doc = frappe.new_doc("Emas Employees")
|
||
employee_doc.asan_login = asan_login_name
|
||
employee_doc.organization_name = organization_name
|
||
employee_doc.organization_voen = organization_voen
|
||
update_employee_from_data(employee_doc, emp_data)
|
||
employee_doc.insert(ignore_permissions=True)
|
||
imported_count += 1
|
||
continue
|
||
|
||
detail_data = detail_result.get('data')
|
||
|
||
# Get FIN from detail data (more reliable than list)
|
||
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')
|
||
|
||
if not fin:
|
||
name_val = emp_data.get('full_name', '?')
|
||
errors.append(f"{name_val}: No FIN in detail data")
|
||
error_count += 1
|
||
continue
|
||
|
||
fin = str(fin).strip()
|
||
|
||
# Check if exists
|
||
existing = frappe.db.exists(
|
||
"Emas Employees",
|
||
{
|
||
"identification_number": fin,
|
||
"organization_voen": organization_voen
|
||
}
|
||
)
|
||
|
||
if existing:
|
||
employee_doc = frappe.get_doc("Emas Employees", existing)
|
||
# First populate basic fields from list data
|
||
update_employee_from_data(employee_doc, emp_data)
|
||
# Then overlay with detailed data
|
||
update_employee_from_detail_data(employee_doc, detail_data)
|
||
# Ensure FIN is set (fallback to verified FIN)
|
||
if not employee_doc.identification_number:
|
||
employee_doc.identification_number = fin
|
||
employee_doc.save(ignore_permissions=True)
|
||
updated_count += 1
|
||
else:
|
||
employee_doc = frappe.new_doc("Emas Employees")
|
||
employee_doc.asan_login = asan_login_name
|
||
employee_doc.organization_name = organization_name
|
||
employee_doc.organization_voen = organization_voen
|
||
# Set FIN first (required field)
|
||
employee_doc.identification_number = fin
|
||
# First populate basic fields from list data
|
||
update_employee_from_data(employee_doc, emp_data)
|
||
# Then overlay with detailed data
|
||
update_employee_from_detail_data(employee_doc, detail_data)
|
||
# Ensure FIN is still set after mapping
|
||
if not employee_doc.identification_number:
|
||
employee_doc.identification_number = fin
|
||
employee_doc.insert(ignore_permissions=True)
|
||
imported_count += 1
|
||
|
||
except Exception as e:
|
||
name_val = emp_data.get('full_name', '?')
|
||
error_msg = f"{name_val}: {str(e)}"
|
||
frappe.log_error(
|
||
f"{error_msg}\n{frappe.get_traceback()}",
|
||
"EMAS Employee Import Error"
|
||
)
|
||
errors.append(error_msg)
|
||
error_count += 1
|
||
|
||
frappe.db.commit()
|
||
|
||
return {
|
||
"success": True,
|
||
"imported_count": imported_count,
|
||
"updated_count": updated_count,
|
||
"error_count": error_count,
|
||
"errors": errors[:10],
|
||
"total": len(employees)
|
||
}
|
||
|
||
except Exception as e:
|
||
frappe.log_error(
|
||
f"Import employees error: {str(e)}\n{frappe.get_traceback()}",
|
||
"EMAS API Error"
|
||
)
|
||
return {"success": False, "message": f"Error: {str(e)}"}
|
||
|
||
|
||
def update_employee_from_data(employee_doc, emp_data):
|
||
"""
|
||
Update employee document fields from ƏMAS API data.
|
||
Hard 1:1 mapping - API field names match DocType field names exactly.
|
||
"""
|
||
# Map all API fields directly to DocType fields
|
||
for field in EMAS_EMPLOYEE_FIELDS:
|
||
value = emp_data.get(field)
|
||
if value is not None:
|
||
setattr(employee_doc, field, str(value))
|
||
else:
|
||
setattr(employee_doc, field, None)
|
||
|
||
# Store raw data for reference
|
||
employee_doc.raw_data = json.dumps(emp_data, ensure_ascii=False, indent=2)
|
||
|
||
# Import timestamp
|
||
employee_doc.import_date = frappe.utils.now_datetime()
|
||
|
||
|
||
def parse_emas_date(date_str):
|
||
"""
|
||
Parse date string from ƏMAS into YYYY-MM-DD format for Frappe Date fields.
|
||
ƏMAS dates can be in various formats.
|
||
"""
|
||
if not date_str or not str(date_str).strip():
|
||
return None
|
||
|
||
date_str = str(date_str).strip()
|
||
|
||
from datetime import datetime
|
||
|
||
for fmt in ("%d.%m.%Y", "%Y-%m-%d", "%d/%m/%Y", "%Y-%m-%dT%H:%M:%S"):
|
||
try:
|
||
return datetime.strptime(date_str, fmt).strftime("%Y-%m-%d")
|
||
except (ValueError, TypeError):
|
||
continue
|
||
|
||
return None
|
||
|
||
|
||
def resolve_designation(position_text, create_if_missing):
|
||
"""
|
||
Find or create a Designation from ƏMAS position text.
|
||
Returns designation name or None.
|
||
"""
|
||
if not position_text or not str(position_text).strip():
|
||
return None
|
||
|
||
position_text = str(position_text).strip()
|
||
|
||
# Search for existing designation by name
|
||
existing = frappe.db.exists("Designation", position_text)
|
||
if existing:
|
||
return existing
|
||
|
||
# Try case-insensitive search by designation_name field
|
||
result = frappe.db.get_value(
|
||
"Designation",
|
||
filters={"designation_name": ["like", position_text]},
|
||
fieldname="name"
|
||
)
|
||
if result:
|
||
return result
|
||
|
||
# Not found — create if allowed
|
||
if create_if_missing:
|
||
designation = frappe.new_doc("Designation")
|
||
designation.designation_name = position_text
|
||
designation.insert(ignore_permissions=True)
|
||
return designation.name
|
||
|
||
return None
|
||
|
||
|
||
@frappe.whitelist()
|
||
def create_employees_from_emas(asan_login_name, employees, company, create_designation=0):
|
||
"""
|
||
Create Employee records directly from ƏMAS API data.
|
||
|
||
Receives raw employee data from the ƏMAS API (same format as get_employees_report returns).
|
||
Also saves to Emas Employees doctype for record keeping.
|
||
|
||
Field mapping (ƏMAS → Employee):
|
||
full_name → first_name / last_name (split by space)
|
||
identification_number → passport_number
|
||
birthday → date_of_birth
|
||
begin_date → date_of_joining
|
||
end_date → contract_end_date
|
||
monthly_salary → ctc
|
||
contract_status → status
|
||
work_position_text → designation (Link, searched/created)
|
||
"""
|
||
try:
|
||
if isinstance(employees, str):
|
||
employees = json.loads(employees)
|
||
|
||
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.emas_account_name
|
||
organization_voen = doc.emas_account_number
|
||
|
||
created_count = 0
|
||
updated_count = 0
|
||
error_count = 0
|
||
errors = []
|
||
|
||
# Status mapping: ƏMAS contract_status → Employee status
|
||
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",
|
||
}
|
||
|
||
for emp_data in employees:
|
||
try:
|
||
doc_oid = emp_data.get("doc_oid")
|
||
doc_no = emp_data.get("doc_no")
|
||
doc_type = emp_data.get("doc_type", "docType_47")
|
||
|
||
# Try to fetch detail data
|
||
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:
|
||
errors.append(f"{full_name}: No identification_number")
|
||
error_count += 1
|
||
continue
|
||
|
||
fin = str(fin).strip()
|
||
|
||
# 1. Save/update Emas Employees record
|
||
emas_existing = None
|
||
if doc_oid:
|
||
emas_existing = frappe.db.exists("Emas Employees", {"doc_oid": str(doc_oid)})
|
||
if not emas_existing:
|
||
emas_existing = frappe.db.exists(
|
||
"Emas Employees",
|
||
{"identification_number": fin, "organization_voen": organization_voen}
|
||
)
|
||
|
||
if emas_existing:
|
||
emas_doc = frappe.get_doc("Emas Employees", emas_existing)
|
||
# Always populate basic fields from list data first
|
||
update_employee_from_data(emas_doc, emp_data)
|
||
# Then overlay with detail data if available
|
||
if detail_data:
|
||
update_employee_from_detail_data(emas_doc, detail_data)
|
||
# Ensure FIN is set (required field)
|
||
if not emas_doc.identification_number:
|
||
emas_doc.identification_number = fin
|
||
emas_doc.save(ignore_permissions=True)
|
||
else:
|
||
emas_doc = frappe.new_doc("Emas Employees")
|
||
emas_doc.asan_login = asan_login_name
|
||
emas_doc.organization_name = organization_name
|
||
emas_doc.organization_voen = organization_voen
|
||
# Set FIN first (required field)
|
||
emas_doc.identification_number = fin
|
||
# Always populate basic fields from list data first
|
||
update_employee_from_data(emas_doc, emp_data)
|
||
# Then overlay with detail data if available
|
||
if detail_data:
|
||
update_employee_from_detail_data(emas_doc, detail_data)
|
||
# Ensure FIN is still set after mapping
|
||
if not emas_doc.identification_number:
|
||
emas_doc.identification_number = fin
|
||
emas_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:
|
||
# Get from detail data
|
||
if 'iamasBean' in detail_data and detail_data.get('iamasBean'):
|
||
dob = parse_emas_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_emas_date(details.get('beginDate'))
|
||
contract_end = parse_emas_date(details.get('endDate'))
|
||
else:
|
||
# Fallback to list data
|
||
dob = parse_emas_date(emp_data.get("birthday"))
|
||
doj = parse_emas_date(emp_data.get("begin_date"))
|
||
contract_end = parse_emas_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)
|
||
|
||
if existing_employee:
|
||
emp = frappe.get_doc("Employee", existing_employee)
|
||
emp.first_name = first_name
|
||
emp.last_name = last_name
|
||
emp.employee_name = full_name or first_name
|
||
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
|
||
emp.save(ignore_permissions=True)
|
||
updated_count += 1
|
||
else:
|
||
emp = frappe.new_doc("Employee")
|
||
emp.first_name = first_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 = "Male" # default, user can change later
|
||
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
|
||
emp.insert(ignore_permissions=True)
|
||
created_count += 1
|
||
|
||
# Link Emas Employees → Employee
|
||
emas_doc.employee = emp.name
|
||
emas_doc.save(ignore_permissions=True)
|
||
|
||
except Exception as e:
|
||
fin_val = emp_data.get("identification_number", "?")
|
||
name_val = emp_data.get("full_name", "?")
|
||
error_msg = f"{name_val} ({fin_val}): {str(e)}"
|
||
frappe.log_error(error_msg, "EMAS Employee Create Error")
|
||
errors.append(error_msg)
|
||
error_count += 1
|
||
|
||
frappe.db.commit()
|
||
|
||
return {
|
||
"success": True,
|
||
"created_count": created_count,
|
||
"updated_count": updated_count,
|
||
"error_count": error_count,
|
||
"errors": errors[:10]
|
||
}
|
||
|
||
except Exception as e:
|
||
frappe.log_error(
|
||
f"Create employees from EMAS error: {str(e)}\n{frappe.get_traceback()}",
|
||
"EMAS API Error"
|
||
)
|
||
return {"success": False, "message": f"Error: {str(e)}"}
|
||
|
||
|
||
# ==================== EMPLOYEE DETAIL API FUNCTIONS ====================
|
||
# These functions fetch complete employee details from ƏMAS
|
||
# (called when viewing employee details in ƏMAS, not from list report)
|
||
|
||
|
||
def get_edit_form_data(asan_login_name, doc_oid, doc_no, doc_type):
|
||
"""
|
||
Call eroom.getEditForm to get contract form data.
|
||
Returns: EmasContractsEnt, EmasContractDetailsEnt, EmasContractTextDetailsEnt
|
||
"""
|
||
data = {
|
||
"serviceName": "AppEmploymentContractOnline",
|
||
"docOid": doc_oid,
|
||
"docNo": doc_no,
|
||
"docType": doc_type
|
||
}
|
||
return make_emas_request(asan_login_name, "/service/eroom.getEditForm", data)
|
||
|
||
|
||
def get_staff_data(asan_login_name, staff_oid):
|
||
"""Call embas.getStaffData for position info"""
|
||
data = {"staffOid": staff_oid}
|
||
return make_emas_request(asan_login_name, "/service/embas.getStaffData", data)
|
||
|
||
|
||
def get_person_data(asan_login_name, entity_oid):
|
||
"""Call refdata.getPersonDataByEntOid for personal data"""
|
||
data = {"entityOid": entity_oid} # Fixed: was "entOid", should be "entityOid"
|
||
return make_emas_request(asan_login_name, "/service/refdata.getPersonDataByEntOid", data)
|
||
|
||
|
||
def get_address_data(asan_login_name, address_oid):
|
||
"""Call refdata.getAddressData for work address"""
|
||
data = {"addressOid": address_oid}
|
||
return make_emas_request(asan_login_name, "/service/refdata.getAddressData", data)
|
||
|
||
|
||
def get_doc_main_data(asan_login_name, doc_oid, doc_no, doc_type):
|
||
"""Call eroom.getDocMainData for document metadata"""
|
||
data = {
|
||
"docOid": doc_oid,
|
||
"docNo": doc_no,
|
||
"docType": doc_type
|
||
}
|
||
return make_emas_request(asan_login_name, "/service/eroom.getDocMainData", data)
|
||
|
||
|
||
def get_contract_attachments(asan_login_name, doc_oid, attach_type="attachType_5"):
|
||
"""
|
||
Call eroom.getAttachListByType to get list of contract attachments.
|
||
Returns list of attachments with fileId for downloading.
|
||
"""
|
||
data = {
|
||
"docOid[]": doc_oid,
|
||
"attachType": attach_type
|
||
}
|
||
return make_emas_request(asan_login_name, "/service/eroom.getAttachListByType", data)
|
||
|
||
|
||
def download_contract_file(asan_login_name, file_id):
|
||
"""
|
||
Call request/readFromStorage to download contract HTML file.
|
||
Returns HTML content of the employment contract.
|
||
"""
|
||
data = {"fileId": file_id}
|
||
return make_emas_request(asan_login_name, "/request/readFromStorage", data)
|
||
|
||
|
||
@frappe.whitelist()
|
||
def get_employee_detail(asan_login_name, doc_oid, doc_no, doc_type="docType_47"):
|
||
"""
|
||
Fetch complete employee detail from ƏMAS.
|
||
Makes 7 API calls to collect all data.
|
||
|
||
Returns combined dict with:
|
||
- EmasContractsEnt, EmasContractDetailsEnt, EmasContractTextDetailsEnt
|
||
- staffData, iamasBean, addressBean, workAddress
|
||
- docMainData, contractHtml
|
||
"""
|
||
combined_data = {}
|
||
|
||
try:
|
||
# 1. Get contract form data
|
||
form_result = get_edit_form_data(asan_login_name, doc_oid, doc_no, doc_type)
|
||
if form_result and form_result.get('success'):
|
||
data = form_result.get('data')
|
||
if data:
|
||
result_data = data.get('resultData', {})
|
||
if result_data:
|
||
combined_data.update(result_data)
|
||
|
||
# 2. Get staff data (need staff_oid from contract)
|
||
if 'EmasContractsEnt' in combined_data and isinstance(combined_data['EmasContractsEnt'], list) and len(combined_data['EmasContractsEnt']) > 0:
|
||
staff_oid = combined_data['EmasContractsEnt'][0].get('staff')
|
||
if staff_oid:
|
||
staff_result = get_staff_data(asan_login_name, staff_oid)
|
||
if staff_result and staff_result.get('success'):
|
||
data = staff_result.get('data')
|
||
if data:
|
||
# staffData is on top level, not in resultData
|
||
combined_data['staffData'] = data.get('staffData')
|
||
|
||
# 3. Get person data (need entity_oid from contract)
|
||
if 'EmasContractsEnt' in combined_data and isinstance(combined_data['EmasContractsEnt'], list) and len(combined_data['EmasContractsEnt']) > 0:
|
||
entity_oid = combined_data['EmasContractsEnt'][0].get('entity')
|
||
if entity_oid:
|
||
person_result = get_person_data(asan_login_name, entity_oid)
|
||
if person_result and person_result.get('success'):
|
||
data = person_result.get('data')
|
||
if data:
|
||
# iamasBean and addressBean are on top level, not in resultData
|
||
combined_data['iamasBean'] = data.get('iamasBean')
|
||
combined_data['addressBean'] = data.get('addressBean')
|
||
|
||
# 4. Get work address (need address_oid from contract details)
|
||
if 'EmasContractDetailsEnt' in combined_data and isinstance(combined_data['EmasContractDetailsEnt'], list) and len(combined_data['EmasContractDetailsEnt']) > 0:
|
||
address_oid = combined_data['EmasContractDetailsEnt'][0].get('address')
|
||
if address_oid:
|
||
addr_result = get_address_data(asan_login_name, address_oid)
|
||
if addr_result and addr_result.get('success'):
|
||
data = addr_result.get('data')
|
||
if data:
|
||
# addressData is on top level, not in resultData
|
||
combined_data['workAddress'] = data.get('addressData')
|
||
|
||
# 5. Get document main data
|
||
doc_result = get_doc_main_data(asan_login_name, doc_oid, doc_no, doc_type)
|
||
if doc_result and doc_result.get('success'):
|
||
data = doc_result.get('data')
|
||
if data:
|
||
# Document fields are on top level, not in resultData
|
||
# Build docMainData from top-level fields
|
||
combined_data['docMainData'] = {
|
||
'docOid': data.get('docOid'),
|
||
'docNo': data.get('docNo'),
|
||
'docDate': data.get('docDate'),
|
||
'docType': data.get('docType'),
|
||
'docState': data.get('docState'),
|
||
'listDocInfo': data.get('listDocInfo', [])
|
||
}
|
||
|
||
# 6. Get contract document (HTML)
|
||
attachments_result = get_contract_attachments(asan_login_name, doc_oid)
|
||
if attachments_result and attachments_result.get('success'):
|
||
data = attachments_result.get('data')
|
||
if data and 'attachList' in data:
|
||
attach_list = data.get('attachList', [])
|
||
if attach_list and len(attach_list) > 0:
|
||
# Get the first attachment (main contract document)
|
||
file_id = attach_list[0].get('fileId')
|
||
if file_id:
|
||
file_result = download_contract_file(asan_login_name, file_id)
|
||
if file_result and file_result.get('success'):
|
||
file_data = file_result.get('data')
|
||
if file_data:
|
||
combined_data['contractHtml'] = file_data.get('fileContent')
|
||
|
||
return {
|
||
"success": True,
|
||
"data": combined_data
|
||
}
|
||
|
||
except Exception as e:
|
||
frappe.log_error(
|
||
f"Get employee detail error: {str(e)}\n{frappe.get_traceback()}",
|
||
"ƏMAS API Error"
|
||
)
|
||
return {"success": False, "message": f"Error: {str(e)}"}
|
||
|
||
|
||
def update_employee_from_detail_data(employee_doc, detail_data):
|
||
"""
|
||
Map all 115 fields from detail API responses to Emas Employees document.
|
||
Handles nested objects from 6 different API calls.
|
||
"""
|
||
if not detail_data:
|
||
return
|
||
|
||
# Map EmasContractsEnt fields (contract info)
|
||
if 'EmasContractsEnt' in detail_data and isinstance(detail_data.get('EmasContractsEnt'), list) and len(detail_data['EmasContractsEnt']) > 0:
|
||
contract = detail_data['EmasContractsEnt'][0]
|
||
employee_doc.contract_number = contract.get('contractNumber')
|
||
employee_doc.ssn = contract.get('ssn')
|
||
employee_doc.contract_type = contract.get('contractType')
|
||
employee_doc.contract_status = contract.get('contractStatus')
|
||
# Only set identification_number if it exists in detail data
|
||
pin = contract.get('pin')
|
||
if pin:
|
||
employee_doc.identification_number = pin
|
||
employee_doc.employer_tin = contract.get('employerTin')
|
||
employee_doc.employer_pin = contract.get('employerPin')
|
||
employee_doc.prev_contract = contract.get('prevContract')
|
||
employee_doc.is_signed = 1 if contract.get('isSigned') else 0
|
||
employee_doc.contract_validity = contract.get('validity')
|
||
employee_doc.workplace_type = contract.get('workplaceType')
|
||
employee_doc.staff_oid = contract.get('staff')
|
||
employee_doc.structure_unit_oid = contract.get('structureUnitOid')
|
||
employee_doc.entity_oid = contract.get('entity')
|
||
employee_doc.entity_details_oid = contract.get('entityDetails')
|
||
employee_doc.from_migration = 1 if contract.get('fromMigration') else 0
|
||
employee_doc.doc_oid = contract.get('fkDocOid')
|
||
|
||
# Map EmasContractDetailsEnt fields (salary, schedule, vacation)
|
||
if 'EmasContractDetailsEnt' in detail_data and isinstance(detail_data.get('EmasContractDetailsEnt'), list) and len(detail_data['EmasContractDetailsEnt']) > 0:
|
||
details = detail_data['EmasContractDetailsEnt'][0]
|
||
employee_doc.base_salary = details.get('salary')
|
||
employee_doc.monthly_salary = str(details.get('monthlySalary') or '')
|
||
employee_doc.salary_add = details.get('salaryAdd')
|
||
employee_doc.salary_add_hw = details.get('salaryAddHw')
|
||
employee_doc.award_amount = details.get('awardAmount')
|
||
employee_doc.award_type = details.get('awardType')
|
||
employee_doc.overtime_amount = details.get('overTimeAmount')
|
||
employee_doc.bad_condition_amount = details.get('badCondAmount')
|
||
employee_doc.bad_condition_id = details.get('badCondId')
|
||
employee_doc.currency_type = details.get('currencyType')
|
||
employee_doc.payment_type = details.get('paymentType')
|
||
employee_doc.payment_first_day = details.get('paymentFirstDay')
|
||
employee_doc.bank_oid = details.get('bank')
|
||
|
||
# Work schedule
|
||
employee_doc.work_mode_type = details.get('workModeType')
|
||
employee_doc.is_full_time = details.get('isFullTime')
|
||
employee_doc.first_shift_start = details.get('firstShiftStart')
|
||
employee_doc.first_shift_end = details.get('firstShiftEnd')
|
||
employee_doc.second_shift_start = details.get('secondShiftStart')
|
||
employee_doc.second_shift_end = details.get('secondShiftEnd')
|
||
employee_doc.lunch_start = details.get('lunchStart')
|
||
employee_doc.lunch_end = details.get('lunchEnd')
|
||
employee_doc.work_time = details.get('workTime')
|
||
employee_doc.working_time_cumulative = 1 if details.get('workingTimeCumulative') else 0
|
||
|
||
# Vacation
|
||
employee_doc.vacation_days_count = details.get('vacationDaysCount')
|
||
employee_doc.vacation_general_duration = details.get('vacGenDur')
|
||
employee_doc.vacation_main_duration = details.get('vacMainDur')
|
||
employee_doc.vacation_add_dur_one = details.get('vacAddDurOne')
|
||
employee_doc.vacation_add_dur_two = details.get('vacAddDurTwo')
|
||
employee_doc.vacation_add_dur_four = details.get('vacAddDurFour')
|
||
employee_doc.vacation_amount = details.get('vacAmount')
|
||
employee_doc.profession_employee_for_vacation = details.get('professionEmployeeForVacation')
|
||
|
||
# Education
|
||
employee_doc.education = details.get('education')
|
||
employee_doc.scientific_degree = details.get('scientificDegree')
|
||
employee_doc.speciality_degree = details.get('specialityDegree')
|
||
employee_doc.state_speciality_degree = details.get('stateSpecialityDegree')
|
||
employee_doc.qualification_degree_salary_add = details.get('qualificationDegreeSalaryAdd')
|
||
|
||
# Dates
|
||
employee_doc.main_contract_begin_date = str(details.get('mainContractBeginDate') or '')
|
||
employee_doc.contract_begin_date = parse_emas_date(details.get('beginDate'))
|
||
employee_doc.contract_end_date = parse_emas_date(details.get('endDate'))
|
||
employee_doc.emp_job_start_date = str(details.get('empJobStartDate') or '')
|
||
employee_doc.begin_date = str(details.get('beginDate') or '')
|
||
employee_doc.end_date = str(details.get('endDate') or '')
|
||
|
||
# Other fields
|
||
employee_doc.position_id = details.get('positionId')
|
||
employee_doc.activity_code = details.get('activityCode')
|
||
employee_doc.work_address_oid = details.get('address')
|
||
employee_doc.chairman_pin = details.get('chairmanPin')
|
||
employee_doc.contract_reason = details.get('contractReason')
|
||
employee_doc.workplace_sector = details.get('workplaceSector')
|
||
|
||
# Map EmasContractTextDetailsEnt fields
|
||
if 'EmasContractTextDetailsEnt' in detail_data and isinstance(detail_data.get('EmasContractTextDetailsEnt'), list) and len(detail_data['EmasContractTextDetailsEnt']) > 0:
|
||
text_details = detail_data['EmasContractTextDetailsEnt'][0]
|
||
if text_details:
|
||
employee_doc.work_position = text_details.get('workPosition')
|
||
employee_doc.activity_name_detail = text_details.get('activityName')
|
||
employee_doc.labour_function = text_details.get('labourFunction')
|
||
employee_doc.protection_tools = text_details.get('protectionTools')
|
||
employee_doc.payment_other_cond = text_details.get('paymentOtherCond')
|
||
employee_doc.rule_text = text_details.get('ruleText')
|
||
|
||
# Map staffData fields
|
||
if 'staffData' in detail_data and detail_data.get('staffData'):
|
||
staff = detail_data['staffData']
|
||
employee_doc.position_text = staff.get('positionText')
|
||
employee_doc.tree_path_name = staff.get('treePathName')
|
||
employee_doc.staff_unit = staff.get('staffUnit')
|
||
employee_doc.structure_path = staff.get('treePathName')
|
||
employee_doc.work_position_text = staff.get('positionText')
|
||
|
||
# Map iamasBean (personal data)
|
||
if 'iamasBean' in detail_data and detail_data.get('iamasBean'):
|
||
person = detail_data['iamasBean']
|
||
# Construct full name
|
||
name = person.get('name', '')
|
||
surname = person.get('surname', '')
|
||
employee_doc.full_name = f"{name} {surname}".strip()
|
||
|
||
employee_doc.father_name = person.get('fatherName')
|
||
employee_doc.gender = person.get('gender')
|
||
employee_doc.birthday = str(person.get('birthDate') or '')
|
||
employee_doc.nationality = person.get('nationality')
|
||
employee_doc.blood_type = person.get('bloodType')
|
||
employee_doc.marital_status = person.get('maritalStatus')
|
||
employee_doc.military_status = person.get('militaryStatus')
|
||
employee_doc.document_number = person.get('documentNumber')
|
||
employee_doc.document_issue_date = parse_emas_date(person.get('issueDate'))
|
||
employee_doc.document_expiry_date = parse_emas_date(person.get('expireDate'))
|
||
employee_doc.document_issued_by = person.get('issuedBy')
|
||
employee_doc.personal_address = person.get('address')
|
||
employee_doc.personal_city = person.get('city')
|
||
employee_doc.personal_district = person.get('district')
|
||
|
||
# Map addressBean (contact info)
|
||
if 'addressBean' in detail_data and detail_data.get('addressBean'):
|
||
address = detail_data['addressBean']
|
||
employee_doc.contact_phone = address.get('mobilePhoneNumber')
|
||
employee_doc.contact_email = address.get('email')
|
||
|
||
# Map workAddress
|
||
if 'workAddress' in detail_data and detail_data.get('workAddress'):
|
||
work_addr = detail_data['workAddress']
|
||
employee_doc.work_address_street = work_addr.get('street')
|
||
employee_doc.work_address_district = work_addr.get('district')
|
||
|
||
# Map docMainData
|
||
if 'docMainData' in detail_data and detail_data.get('docMainData'):
|
||
doc_main = detail_data['docMainData']
|
||
employee_doc.doc_no = doc_main.get('docNo')
|
||
employee_doc.doc_date = str(doc_main.get('docDate') or '')
|
||
employee_doc.doc_type = doc_main.get('docType')
|
||
employee_doc.doc_state = doc_main.get('docState')
|
||
employee_doc.flow = doc_main.get('flow')
|
||
employee_doc.flow_alias = doc_main.get('flowAlias')
|
||
if 'listDocInfo' in doc_main and isinstance(doc_main['listDocInfo'], list) and len(doc_main['listDocInfo']) > 0:
|
||
employee_doc.after_exec_label = doc_main['listDocInfo'][0].get('afterExecLabel')
|
||
created_date_str = doc_main['listDocInfo'][0].get('createDate')
|
||
if created_date_str:
|
||
employee_doc.created_date = created_date_str
|
||
|
||
# Map contract HTML document
|
||
if 'contractHtml' in detail_data and detail_data.get('contractHtml'):
|
||
employee_doc.contract_html = detail_data.get('contractHtml')
|
||
|
||
# Store complete raw data as JSON (only if detail_data has meaningful content)
|
||
# Check if detail_data has any of the expected keys
|
||
has_detail_content = any(key in detail_data for key in [
|
||
'EmasContractsEnt', 'EmasContractDetailsEnt', 'EmasContractTextDetailsEnt',
|
||
'staffData', 'iamasBean', 'addressBean', 'workAddress', 'docMainData', 'contractHtml'
|
||
])
|
||
|
||
if has_detail_content:
|
||
employee_doc.raw_data = json.dumps(detail_data, ensure_ascii=False, indent=2)
|
||
|
||
employee_doc.import_date = frappe.utils.now_datetime()
|
||
|
||
|
||
def on_delete_employee(doc, method):
|
||
"""
|
||
When an Employee is deleted, also delete linked Emas Employees records.
|
||
"""
|
||
emas_records = frappe.get_all(
|
||
"Emas Employees",
|
||
filters={"employee": doc.name},
|
||
fields=["name"]
|
||
)
|
||
for rec in emas_records:
|
||
frappe.delete_doc("Emas Employees", rec.name, ignore_permissions=True)
|