added relogin to emas and other

This commit is contained in:
Ali 2026-02-13 00:30:31 +04:00
parent 72cf279a64
commit 0fd5399b7f
9 changed files with 1813 additions and 534 deletions

179
CLAUDE.md
View File

@ -4,13 +4,21 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Overview
Invoice Az is a Frappe application that integrates with Azerbaijan's e-taxes.gov.az system for:
Invoice Az is a Frappe application that integrates with Azerbaijan government systems:
**E-Taxes Integration (e-taxes.gov.az):**
- Downloading and importing purchase invoices from e-taxes
- Downloading and importing sales invoices from e-taxes
- Sending sales invoices to e-taxes with ASAN Imza signing
- Sending purchase acts to e-taxes for Individual suppliers
- Importing VAT Account operations and creating Journal Entries
- Managing authentication via ASAN Login with automatic token renewal
**AMAS Integration (e-social.gov.az):**
- Importing employees from AMAS (Azerbaijan Employment Management System)
- MyGovID/ASAN authentication for accessing AMAS
- Automatic employee synchronization with ERPNext
## ⚠️ CRITICAL RULES FOR E-TAXES INTEGRATION ⚠️
**READ THIS BEFORE IMPLEMENTING ANY E-TAXES API CALLS!**
@ -434,22 +442,54 @@ bench --site [site-name] console
6. **VAT Operations Hooks** (`invoice_az/vat_operations.py`)
- Delete related E-Taxes VAT Operations when Journal Entry is deleted/cancelled
7. **Purchase Send API** (`invoice_az/send_purchase_api.py`)
- Send Purchase Invoices (acts) to e-taxes for Individual suppliers
- Two-step workflow: create draft act, then sign with ASAN Imza
- Support for 6 act types: Agricultural Products, Metal Scrap Reception, Tire Disposal, Plastic Disposal, Rawhide Supply, Other Product Receipt
- Generate serial numbers from e-taxes for each act type
- Build act payload with product group codes and tax types
- E-Taxes Purchase Outbox tracking
- Cancel/remove draft acts functionality
- Retry signing for failed/pending acts
- Tax type validation (some acts allow only "Taxable" items)
8. **Supplier API** (`invoice_az/supplier_api.py`)
- Fetch Individual supplier data from E-Taxes by FIN and Passport Serial Number
- Auto-populate supplier fields (full name, phone, date of birth, first name, last name, patronymic)
- Validate supplier type (Individual only)
- Used for completing supplier information before creating purchase acts
9. **AMAS Integration API** (`invoice_az/amas_api.py`)
- Connect to AMAS (e-social.gov.az) using MyGovID JWT token
- OAuth flow: JWT token → authorization code → AMAS session
- Fetch employee lists from AMAS with pagination
- Execute AMAS reports (Magusbi reporting system)
- Multi-account support (switch between different company accounts)
- Session management with cookie handling
- Import employees into ERPNext Employee doctype
- Key functions: `connect_amas()`, `get_employees_report()`, `import_employees_from_amas()`, `get_connected_asan_logins()`
### Document Integration (hooks.py)
**Extended Doctypes:**
- Purchase Order - import from e-taxes, delete hooks
- Purchase Invoice - import from e-taxes
- Purchase Invoice - import from e-taxes, send acts to e-taxes, delete hooks
- Sales Order - import from e-taxes, delete hooks
- Sales Invoice - send to e-taxes, delete hooks
- Journal Entry - import VAT operations, delete hooks
- Journal Entry - import VAT operations, delete hooks, before_save hook for title
- Supplier - fetch data from e-taxes by FIN/passport
- Employee - import from AMAS, delete hooks
- E-Taxes Settings - on_update hook to sync mapped statuses
**Client-side Scripts (`invoice_az/client/`):**
- `purchase_order.js` - E-taxes import buttons and dialogs
- `purchase_invoice.js` - E-taxes import functionality
- `purchase_invoice.js` - E-taxes import functionality, send acts to e-taxes, sign acts
- `sales_order.js` - E-taxes import buttons and dialogs
- `sales_invoice.js` - Send to e-taxes, sign document, retry signing, cancel buttons
- `journal_entry.js` - VAT operations import functionality with operation type display
- `supplier.js` - Fetch supplier data from e-taxes by FIN/passport
- `employee.js` - Import employees from AMAS
- `amas_employees.js` - AMAS Employees list functionality
- `e_taxes_items_list.js` - Sync items from e-taxes
- `e_taxes_suppliers_list.js` - Sync suppliers from e-taxes
- `e_taxes_customers_list.js` - Sync customers from e-taxes
@ -459,7 +499,7 @@ bench --site [site-name] console
### Database Schema
**Authentication:**
- **Asan Login** - Stores ASAN credentials, tokens, certificates, auth status
- **Asan Login** - Stores ASAN credentials, tokens, certificates, auth status, MyGovID token, AMAS session
**Settings:**
- **E-Taxes Settings** - Global settings, status mappings, item/party/customer/supplier/VAT account mappings
@ -473,6 +513,9 @@ bench --site [site-name] console
- **E-Taxes Item Group** - Product group codes (fixtures)
- **Classification code** - Tax classification codes from e-taxes (code and description)
**AMAS Entities (cached from AMAS):**
- **AMAS Employees** - Employee records from AMAS (e-social.gov.az) with FIN, name, position, status
**Mapping Tables:**
- **E-Taxes Item Mapping** - Links ERPNext Items to e-taxes items
- **E-Taxes Party Mapping** - Links ERPNext parties to e-taxes parties (legacy)
@ -485,10 +528,12 @@ bench --site [site-name] console
- **E-Taxes Purchase** - Tracks imported purchase documents
- **E-Taxes Sales** - Tracks imported sales documents (from e-taxes)
- **E-Taxes Sales Outbox** - Tracks sent sales invoices (to e-taxes)
- **E-Taxes Purchase Outbox** - Tracks sent purchase acts (to e-taxes)
- **E-Taxes VAT Operations** - Tracks imported VAT operations
### Key Features
**E-Taxes Integration:**
- **Token Renewal**: Automatic token renewal via scheduler (every 4 minutes) with activity check
- **Token Validity Check**: Verify token validity before API calls, auto-refresh on 401 errors
- **Activity Tracking**: Records user activity to optimize token renewals
@ -498,34 +543,64 @@ bench --site [site-name] console
- **Batch Processing**: Load reference data from invoices in batches
- **Error Handling**: Comprehensive error logging and user-friendly error messages
- **Azerbaijani Support**: Character normalization for Ə, Ü, Ö, Ğ, İ, Ç, Ş characters
- **Two-step Signing**: Create draft invoice, then sign with ASAN Imza
- **Retry Signing**: Retry signing for failed/pending invoices
- **Two-step Signing**: Create draft invoice/act, then sign with ASAN Imza
- **Retry Signing**: Retry signing for failed/pending invoices/acts
- **VAT Import**: Import VAT account operations as Journal Entries with configurable account mappings
- **VAT Account Mappings**: Configure debit/credit accounts by operation type and expense/income type (Sub uçot hesabı → Sub uçot hesabı, Sub uçot hesabı → Büdcə, etc.)
- **Smart Party Assignment**: Automatically adds party (Customer/Supplier) only for Receivable/Payable accounts
- **Purchase Acts**: Send 6 types of purchase acts to e-taxes for Individual suppliers
- **Supplier Data Fetching**: Auto-populate Individual supplier data from e-taxes by FIN/passport
### API Endpoints (E-Taxes)
**AMAS Integration:**
- **MyGovID OAuth**: Automatic OAuth flow using existing MyGovID token
- **Employee Import**: Bulk import employees from AMAS to ERPNext
- **Multi-Account Support**: Switch between different company accounts in AMAS
- **Session Management**: Cookie-based session handling with automatic renewal
- **Duplicate Detection**: Prevents duplicate employee imports by FIN
- **Status Tracking**: Import employment status (Active/Inactive/Terminated)
**Base URL:** `https://new.e-taxes.gov.az`
### API Endpoints
**E-Taxes (https://new.e-taxes.gov.az):**
- `/api/po/auth/public/v1/*` - Authentication endpoints
- `/api/po/invoice/public/v2/invoice/find.inbox` - Purchase invoices
- `/api/po/invoice/public/v2/invoice/find.outbox` - Sales invoices (sent)
- `/api/po/invoice/public/v2/invoice` - Create invoice
- `/api/po/invoice/public/v1/invoice/sign/withAsanImza` - Sign invoice
- `/api/po/invoice/public/v1/generateSerialNumber/defaultInvoice` - Generate serial
- `/api/po/invoice/public/v1/act` - Create/manage acts
- `/api/po/invoice/public/v1/act/generateSerialNumber/*` - Generate act serials
- `/api/po/invoice/public/v1/common/removeDrafts` - Remove draft invoices
- `/api/po/vatacc/public/v1/operation/find.outbox` - VAT operations
- `/api/po/profile/public/v1/taxpayer/findByFinAndPassport` - Find taxpayer by FIN
- `/api/po/profile/public/v1/taxpayer/{tin}/object/find` - Customer objects
- `/api/po/dictionary/public/v1/productGroups/find` - Product groups dictionary
**AMAS (https://eroom.e-social.gov.az):**
- `/ssoAsan/v2` - ASAN SSO callback
- `/service/aas.changeAccount` - Switch company accounts
- `/service/magusbi.executeReport` - Execute employee reports
- `/service/aas.domains` - Get available domains
- `/request/accounts` - Get available accounts
**MyGovID (https://api.mygovid.gov.az):**
- `/ssoauth/oauth2/auth/codes` - Get authorization codes for OAuth
### Fixtures
- **E-Taxes Item Group** - Pre-populated product group codes
- **E-Taxes Item Group** - Pre-populated product group codes (for purchase acts)
- **Classification code** - Pre-populated tax classification codes from e-taxes (for VAT operations)
### Install Hooks
- `after_install` and `after_migrate` both call `setup_token_renewal()` to ensure scheduler is configured
- `after_install` - Calls `setup_token_renewal()` and `install_custom_fields()` to configure scheduler and add custom fields
- `after_migrate` - Calls `setup_token_renewal()`, `create_journal_entry_custom_fields()`, and `create_supplier_custom_fields()` to ensure scheduler and custom fields are up to date
**Custom Fields Added:**
- **Purchase Invoice**: `purchase_type`, `act_kind`, `product_group_code`, `tax_type`, `etaxes_status`, `etaxes_document_id`, `serial_number`, `seller_tin`, `buyer_tin`
- **Sales Invoice**: `customer_object_name`, `etaxes_status`, `etaxes_document_id`, `serial_number`, `seller_tin`, `buyer_tin`
- **Journal Entry**: `etaxes_vat_operation`, `customer_name_from_vat` (for title display)
- **Supplier**: `fin`, `passport_serial_number`, `date_of_birth`, `first_name`, `last_name`, `patronymic` (for Individual suppliers and acts)
### Security Considerations
@ -537,16 +612,32 @@ bench --site [site-name] console
### Important Files
**Backend (Python):**
- `invoice_az/hooks.py` - App configuration, event hooks, scheduler setup, and fixtures (~310 lines)
- `invoice_az/auth.py` - Authentication and token management (~970 lines)
- `invoice_az/api.py` - Purchase invoice API and reference data management (~4460 lines)
- `invoice_az/sales_api.py` - Sales invoice import API (~890 lines)
- `invoice_az/send_sales_api.py` - Sales invoice send API (~1060 lines)
- `invoice_az/send_purchase_api.py` - Purchase acts send API (~800 lines)
- `invoice_az/supplier_api.py` - Supplier data fetching API (~150 lines)
- `invoice_az/vat_api.py` - VAT operations import API (~620 lines)
- `invoice_az/vat_operations.py` - VAT operations hooks (~40 lines)
- `invoice_az/amas_api.py` - AMAS integration API (~500 lines)
- `invoice_az/install.py` - Custom field installation
**Frontend (JavaScript):**
- `invoice_az/client/*.js` - Client-side functionality for UI enhancements
- `invoice_az/client/sales_invoice.js` - Send sales invoices to e-taxes
- `invoice_az/client/purchase_invoice.js` - Send purchase acts to e-taxes
- `invoice_az/client/supplier.js` - Fetch supplier data from e-taxes
- `invoice_az/client/employee.js` - Import employees from AMAS
**Doctypes:**
- `invoice_az/invoice_az/doctype/*/` - Custom doctype definitions
**Fixtures:**
- `invoice_az/fixtures/e_taxes_item_group.json` - Item group fixtures
- `invoice_az/fixtures/classification_code.json` - Classification code fixtures
### Workflow: Importing Purchases
@ -675,3 +766,69 @@ Supported act types for Purchase Invoices with Individual suppliers:
- Naməlum → Büdcə (UNKNOWN_TO_BUDGET in API)
**Note:** AUTO operation types from API are automatically mapped to "Sub uçot hesabı → Büdcə" (SUB_TO_BUDGET).
### Workflow: Importing Employees from AMAS
AMAS (e-social.gov.az) is Azerbaijan's Employment Management System maintained by the Ministry of Labour and Social Protection.
**Prerequisites:**
1. User must have MyGovID authentication (same as ASAN Login)
2. AMAS connection must be established in Asan Login
**Workflow:**
1. User opens Asan Login
2. Clicks "Connect to AMAS" button (uses existing MyGovID token)
3. System performs OAuth flow:
- Gets authorization code from MyGovID API
- Exchanges code for AMAS session
- Stores AMAS cookies in Asan Login
4. User opens Employee list
5. Clicks "Load from AMAS" button
6. System fetches employee list from AMAS (up to 1000 records)
7. System displays employee selection dialog with:
- FIN, Full Name, Position, Employment Status
8. User selects employees to import
9. System creates/updates ERPNext Employee records
10. Creates AMAS Employees tracking records
**Key Features:**
- Automatic MyGovID authentication reuse
- Multi-account support (can switch between company accounts in AMAS)
- Pagination support for large employee lists
- Duplicate detection (checks existing employees by FIN)
- Status tracking (Active/Inactive/Terminated)
**AMAS API Endpoints:**
- Base URL: `https://eroom.e-social.gov.az`
- SSO Callback: `/ssoAsan/v2`
- Execute Report: `/service/magusbi.executeReport`
- Change Account: `/service/aas.changeAccount`
- Get Accounts: `/request/accounts`
**MyGovID OAuth:**
- API URL: `https://api.mygovid.gov.az`
- Auth Codes: `/ssoauth/oauth2/auth/codes`
- Client ID: `64942e33ec8d49059333a1e0ebad7fe2`
### Workflow: Fetching Supplier Data from E-Taxes
For Individual suppliers, the system can auto-populate supplier information from E-Taxes.
**Prerequisites:**
1. Supplier must be type "Individual"
2. FIN field must be filled
3. Passport Serial Number field must be filled
**Workflow:**
1. User creates new Supplier (type: Individual)
2. Fills FIN and Passport Serial Number
3. Clicks "Fetch Data from E-Taxes" button
4. System calls `/api/po/profile/public/v1/taxpayer/findByFinAndPassport`
5. System auto-populates fields:
- Full Name
- Phone
- Date of Birth
- First Name
- Last Name
- Patronymic
6. User can now use this supplier for Purchase Invoices with acts

View File

@ -959,18 +959,36 @@ def disconnect_amas(asan_login_name):
def get_connected_asan_logins():
"""
Get list of Asan Logins with active ƏMAS connection.
Used for employee import dialog.
Prioritizes is_default = 1, similar to e-taxes.
"""
try:
asan_logins = frappe.get_all(
# First try to get default Asan Login with ƏMAS connection
default_login = frappe.get_all(
"Asan Login",
filters={
"is_default": 1,
"amas_auth_status": "Connected",
"amas_account_oid": ["is", "set"]
},
fields=["name", "amas_account_name", "amas_account_number", "amas_account_oid"]
fields=["name", "amas_account_name", "amas_account_number", "amas_account_oid"],
limit=1
)
if default_login:
# Use default login
asan_logins = default_login
else:
# Fall back to any connected login
asan_logins = frappe.get_all(
"Asan Login",
filters={
"amas_auth_status": "Connected",
"amas_account_oid": ["is", "set"]
},
fields=["name", "amas_account_name", "amas_account_number", "amas_account_oid"],
limit=1
)
result = []
for al in asan_logins:
result.append({
@ -1221,6 +1239,103 @@ def parse_amas_date(date_str):
return None
def map_gender(amas_gender):
"""
Transform AMAS gender to ERPNext gender.
AMAS values: "Kişi" (Male), "Qadın" (Female), "Male", "Female"
ERPNext values: "Male", "Female", "Other"
"""
if not amas_gender:
return "Male" # Default fallback
gender_str = str(amas_gender).strip().lower()
# Map Azerbaijani values
if gender_str in ("kişi", "kisi", "male", "m", "erkək", "erkek"):
return "Male"
elif gender_str in ("qadın", "qadin", "female", "f", "qız", "qiz"):
return "Female"
return "Male" # Default fallback
def map_marital_status(amas_status):
"""
Transform AMAS marital status to ERPNext marital status.
AMAS values: "Evli", "Subay", "Married", "Single", etc.
ERPNext values: "Single", "Married", "Divorced", "Widowed"
"""
if not amas_status:
return None
status_str = str(amas_status).strip().lower()
# Map Azerbaijani values
if status_str in ("evli", "married", "nikahli", "nikаhlı"):
return "Married"
elif status_str in ("subay", "subаy", "single", "bekar", "bekаr"):
return "Single"
elif status_str in ("boşanmış", "bosanmis", "divorced"):
return "Divorced"
elif status_str in ("dul", "widowed", "widow"):
return "Widowed"
return None
def map_blood_group(amas_blood):
"""
Transform AMAS blood type to ERPNext blood group.
AMAS format: "A(II) Rh+", "O(I) Rh-", etc.
ERPNext values: "A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"
"""
if not amas_blood:
return None
blood_str = str(amas_blood).strip().upper()
# Extract blood type and Rh factor
# Examples: "A(II) Rh+", "AB(IV) Rh-", "O(I) Rh+"
# Map blood group letter
if "AB" in blood_str or "IV" in blood_str:
blood_type = "AB"
elif "A" in blood_str or "II" in blood_str:
blood_type = "A"
elif "B" in blood_str or "III" in blood_str:
blood_type = "B"
elif "O" in blood_str or "I" in blood_str or "0" in blood_str:
blood_type = "O"
else:
return None
# Map Rh factor
if "+" in blood_str or "POSITIVE" in blood_str or "POZİTİV" in blood_str:
rh_factor = "+"
elif "-" in blood_str or "NEGATIVE" in blood_str or "NEQATİV" in blood_str:
rh_factor = "-"
else:
rh_factor = "+" # Default to positive
return f"{blood_type}{rh_factor}"
def build_full_address(address, city, district):
"""
Combine AMAS address fields into single address string.
"""
parts = []
if address:
parts.append(str(address).strip())
if district:
parts.append(str(district).strip())
if city:
parts.append(str(city).strip())
return ", ".join(parts) if parts else None
def resolve_designation(position_text, create_if_missing):
"""
Find or create a Designation from ƏMAS position text.
@ -1255,6 +1370,286 @@ def resolve_designation(position_text, create_if_missing):
return None
@frappe.whitelist()
def create_single_employee_from_amas(asan_login_name, employee_data, company, create_designation=0):
"""
Create/update single Employee record from ƏMAS data.
Used for sequential processing with frontend progress updates.
"""
try:
if isinstance(employee_data, str):
employee_data = json.loads(employee_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 = employee_data.get("doc_oid")
doc_no = employee_data.get("doc_no")
doc_type = employee_data.get("doc_type", "docType_47")
# Fetch detail data
detail_data = None
full_name = str(employee_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
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 '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 = employee_data.get("identification_number")
if not fin:
return {"success": False, "message": f"{full_name}: No identification_number"}
fin = str(fin).strip()
# 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, employee_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, employee_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)
# Create/update Employee record
existing_employee = frappe.db.exists("Employee", {"passport_number": fin})
# Split full_name
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
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(employee_data.get("birthday"))
doj = parse_amas_date(employee_data.get("begin_date"))
contract_end = parse_amas_date(employee_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(employee_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 = employee_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 = employee_data.get("work_position_text")
designation = resolve_designation(position_text, create_designation)
# Extract additional fields from amas_doc (already populated with detail data)
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 = map_marital_status(amas_doc.marital_status if hasattr(amas_doc, 'marital_status') else None)
blood_group = 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:
emp.marital_status = marital_status
if blood_group:
emp.blood_group = blood_group
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
# Link to AMAS Employees record
emp.custom_amas_employee = amas_doc.name
emp.save(ignore_permissions=True)
# Link Amas Employees → Employee
amas_doc.employee = emp.name
amas_doc.save(ignore_permissions=True)
frappe.db.commit()
return {
"success": True,
"updated": True,
"created": False,
"employee_name": emp.name
}
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:
emp.marital_status = marital_status
if blood_group:
emp.blood_group = blood_group
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
# Link to AMAS Employees record
emp.custom_amas_employee = amas_doc.name
emp.insert(ignore_permissions=True)
# Link Amas Employees → Employee
amas_doc.employee = emp.name
amas_doc.save(ignore_permissions=True)
frappe.db.commit()
return {
"success": True,
"created": True,
"updated": False,
"employee_name": emp.name
}
except Exception as e:
frappe.log_error(
f"Single employee create error: {str(e)}\n{frappe.get_traceback()}",
"ƏMAS Employee Create Error"
)
return {
"success": False,
"message": str(e)
}
@frappe.whitelist()
def create_employees_from_amas(asan_login_name, employees, company, create_designation=0):
"""
@ -1303,13 +1698,22 @@ def create_employees_from_amas(asan_login_name, employees, company, create_desig
"suspended": "Suspended",
}
for emp_data in employees:
total_employees = len(employees)
for idx, emp_data in enumerate(employees, start=1):
try:
# Publish progress
frappe.publish_progress(
percent=(idx / total_employees) * 100,
title=f"Loading employee {idx} of {total_employees}",
description=f"Processing {emp_data.get('full_name', 'employee')}..."
)
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
# Fetch detail data for complete employee information
detail_data = None
full_name = str(emp_data.get("full_name") or "").strip()
@ -1434,11 +1838,30 @@ def create_employees_from_amas(asan_login_name, employees, company, create_desig
designation = resolve_designation(position_text, create_designation)
# Extract additional fields from amas_doc (already populated with detail data)
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:
@ -1449,17 +1872,35 @@ def create_employees_from_amas(asan_login_name, employees, company, create_desig
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)
updated_count += 1
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 = "Male" # default, user can change later
emp.gender = gender
emp.status = status
if contract_end:
emp.contract_end_date = contract_end
@ -1467,10 +1908,26 @@ def create_employees_from_amas(asan_login_name, employees, company, create_desig
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)
created_count += 1
# Link Emas Employees → Employee
# Link Amas Employees → Employee
amas_doc.employee = emp.name
amas_doc.save(ignore_permissions=True)

View File

@ -1,7 +1,277 @@
// Load employees from ƏMAS (e-social.gov.az) directly into ERPNext Employee
// Pattern: same as e-taxes import — button on list → API call → selection → create
frappe.ui.form.on('Employee', {});
frappe.ui.form.on('Employee', {
refresh: function(frm) {
// Add "View AMAS Contract" button if employee was imported from AMAS
if (frm.doc.custom_amas_employee) {
frm.add_custom_button(__('View AMAS Contract'), function() {
frappe.set_route('Form', 'Amas Employees', frm.doc.custom_amas_employee);
}, __('AMAS'));
// Load and display AMAS data dynamically
load_amas_data(frm);
}
}
});
// Load AMAS data from linked record and populate HTML fields
function load_amas_data(frm) {
if (!frm.doc.custom_amas_employee) return;
frappe.call({
method: 'frappe.client.get',
args: {
doctype: 'Amas Employees',
name: frm.doc.custom_amas_employee
},
callback: function(r) {
if (r.message) {
const amas_data = r.message;
populate_amas_contract_tab(amas_data);
populate_amas_position_tab(amas_data);
populate_amas_salary_tab(amas_data);
populate_amas_schedule_tab(amas_data);
populate_amas_personal_tab(amas_data);
populate_amas_documents_tab(amas_data);
populate_amas_integration_tab(amas_data);
}
}
});
}
// Helper function to create a field row
function field_row(label, value) {
if (!value) return '';
return `<tr><td style="font-weight: 600; width: 40%; padding: 8px;">${label}</td><td style="padding: 8px;">${frappe.utils.escape_html(value)}</td></tr>`;
}
// Populate Contract tab
function populate_amas_contract_tab(data) {
let html = '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Contract Information</th></tr></thead><tbody>';
html += field_row('Document No', data.doc_no);
html += field_row('Document Date', data.doc_date);
html += field_row('Document Type', data.doc_type);
html += field_row('Contract Type', data.contract_type);
html += field_row('Contract Status', data.contract_status);
html += field_row('Contract Number', data.contract_number);
html += field_row('Is Signed', data.is_signed ? 'Yes' : 'No');
html += field_row('Contract Validity', data.contract_validity);
html += '</tbody></table>';
html += '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Contract Dates</th></tr></thead><tbody>';
html += field_row('Begin Date', data.begin_date);
html += field_row('End Date', data.end_date);
html += field_row('Main Contract Begin Date', data.main_contract_begin_date);
html += field_row('Contract Begin Date', data.contract_begin_date);
html += field_row('Contract End Date', data.contract_end_date);
html += '</tbody></table>';
html += '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Contract Details</th></tr></thead><tbody>';
html += field_row('Previous Contract', data.prev_contract);
html += field_row('Contract Reason', data.contract_reason);
html += field_row('From Migration', data.from_migration ? 'Yes' : 'No');
html += field_row('Staff OID', data.staff_oid);
html += field_row('Structure Unit OID', data.structure_unit_oid);
html += field_row('Entity OID', data.entity_oid);
html += field_row('Entity Details OID', data.entity_details_oid);
html += '</tbody></table>';
$('#amas_contract_data').html(html);
}
// Populate Position tab
function populate_amas_position_tab(data) {
let html = '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Position</th></tr></thead><tbody>';
html += field_row('Work Position', data.work_position);
html += field_row('Position ID', data.position_id);
html += field_row('Position Text', data.position_text);
html += field_row('Activity Code', data.activity_code);
html += field_row('Activity Name', data.activity_name);
html += field_row('Staff Unit', data.staff_unit);
html += field_row('Structure Path', data.structure_path);
html += field_row('Workplace Sector', data.workplace_sector);
html += '</tbody></table>';
html += '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Work Mode</th></tr></thead><tbody>';
html += field_row('Workplace Type', data.workplace_type);
html += field_row('Form Size', data.form_size);
html += field_row('Flow', data.flow);
html += field_row('Flow Alias', data.flow_alias);
html += field_row('View Type', data.view_type);
html += '</tbody></table>';
$('#amas_position_data').html(html);
}
// Populate Salary tab
function populate_amas_salary_tab(data) {
let html = '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Base Salary</th></tr></thead><tbody>';
html += field_row('Base Salary', data.base_salary);
html += field_row('Monthly Salary', data.monthly_salary);
html += field_row('Salary Add', data.salary_add);
html += field_row('Salary Add HW', data.salary_add_hw);
html += field_row('Currency Type', data.currency_type);
html += field_row('Payment Type', data.payment_type);
html += field_row('Payment First Day', data.payment_first_day);
html += field_row('Bank OID', data.bank_oid);
html += '</tbody></table>';
html += '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Additional Payments</th></tr></thead><tbody>';
html += field_row('Award Amount', data.award_amount);
html += field_row('Award Type', data.award_type);
html += field_row('Overtime Amount', data.overtime_amount);
html += field_row('Bad Condition Amount', data.bad_condition_amount);
html += field_row('Bad Condition ID', data.bad_condition_id);
html += field_row('Qualification Degree Salary Add', data.qualification_degree_salary_add);
html += '</tbody></table>';
$('#amas_salary_data').html(html);
}
// Populate Schedule tab
function populate_amas_schedule_tab(data) {
let html = '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Work Schedule</th></tr></thead><tbody>';
html += field_row('Work Mode Type', data.work_mode_type);
html += field_row('Is Full Time', data.is_full_time);
html += field_row('Work Time', data.work_time);
html += field_row('Working Time Cumulative', data.working_time_cumulative ? 'Yes' : 'No');
html += field_row('First Shift Start', data.first_shift_start);
html += field_row('First Shift End', data.first_shift_end);
html += field_row('Second Shift Start', data.second_shift_start);
html += field_row('Second Shift End', data.second_shift_end);
html += field_row('Lunch Start', data.lunch_start);
html += field_row('Lunch End', data.lunch_end);
html += '</tbody></table>';
html += '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Vacation</th></tr></thead><tbody>';
html += field_row('Vacation Days Count', data.vacation_days_count);
html += field_row('Vacation General Duration', data.vacation_general_duration);
html += field_row('Vacation Main Duration', data.vacation_main_duration);
html += field_row('Vacation Add Duration 1', data.vacation_add_dur_one);
html += field_row('Vacation Add Duration 2', data.vacation_add_dur_two);
html += field_row('Vacation Add Duration 4', data.vacation_add_dur_four);
html += field_row('Vacation Amount', data.vacation_amount);
html += field_row('Profession Employee For Vacation', data.profession_employee_for_vacation);
html += '</tbody></table>';
$('#amas_schedule_data').html(html);
}
// Populate Personal tab
function populate_amas_personal_tab(data) {
let html = '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Personal Information</th></tr></thead><tbody>';
html += field_row('FIN', data.identification_number);
html += field_row('SSN', data.ssn);
html += field_row('Father Name', data.father_name);
html += field_row('Birthday', data.birthday);
html += field_row('Gender', data.gender);
html += field_row('Nationality', data.nationality);
html += field_row('Blood Type', data.blood_type);
html += field_row('Marital Status', data.marital_status);
html += field_row('Military Status', data.military_status);
html += '</tbody></table>';
html += '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Address</th></tr></thead><tbody>';
html += field_row('Personal Address', data.personal_address);
html += field_row('Personal City', data.personal_city);
html += field_row('Personal District', data.personal_district);
html += field_row('Work Address OID', data.work_address_oid);
html += field_row('Work Address Street', data.work_address_street);
html += field_row('Work Address District', data.work_address_district);
html += '</tbody></table>';
html += '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Contact</th></tr></thead><tbody>';
html += field_row('Contact Phone', data.contact_phone);
html += field_row('Contact Email', data.contact_email);
html += '</tbody></table>';
html += '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Education</th></tr></thead><tbody>';
html += field_row('Education', data.education);
html += field_row('Scientific Degree', data.scientific_degree);
html += field_row('Speciality Degree', data.speciality_degree);
html += field_row('State Speciality Degree', data.state_speciality_degree);
html += '</tbody></table>';
$('#amas_personal_data').html(html);
}
// Populate Documents tab
function populate_amas_documents_tab(data) {
let html = '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Document Information</th></tr></thead><tbody>';
html += field_row('Document Number', data.document_number);
html += field_row('Document Issue Date', data.document_issue_date);
html += field_row('Document Expiry Date', data.document_expiry_date);
html += field_row('Document Issued By', data.document_issued_by);
html += '</tbody></table>';
html += '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Employer Information</th></tr></thead><tbody>';
html += field_row('Employer PIN', data.employer_pin);
html += field_row('Employer TIN', data.employer_tin);
html += field_row('Chairman PIN', data.chairman_pin);
html += '</tbody></table>';
html += '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Additional Metadata</th></tr></thead><tbody>';
html += field_row('Document State', data.doc_state);
html += field_row('Created Date', data.created_date);
html += field_row('Labour Function', data.labour_function);
html += field_row('Activity Name Detail', data.activity_name_detail);
html += field_row('Protection Tools', data.protection_tools);
html += field_row('Payment Other Conditions', data.payment_other_cond);
html += field_row('Rule Text', data.rule_text);
html += '</tbody></table>';
html += '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">System Fields</th></tr></thead><tbody>';
html += field_row('Job Start Date', data.emp_job_start_date);
html += field_row('Sign Type', data.sign_type);
html += field_row('Is Valid', data.is_valid);
html += field_row('Duration State', data.dur_state);
html += field_row('Operation', data.operation);
html += field_row('After Execution Label', data.after_exec_label);
html += field_row('Tree Path Name', data.tree_path_name);
html += field_row('Document OID', data.doc_oid);
html += '</tbody></table>';
$('#amas_documents_data').html(html);
}
// Populate Integration tab
function populate_amas_integration_tab(data) {
let html = '<table class="table table-bordered" style="margin-top: 15px;">';
html += '<thead><tr><th colspan="2" style="background-color: #f5f5f5;">Integration Information</th></tr></thead><tbody>';
html += field_row('AMAS Employee Record', data.name);
html += field_row('Import Date', data.import_date);
html += field_row('Organization Name', data.organization_name);
html += field_row('Organization VOEN', data.organization_voen);
html += field_row('Asan Login', data.asan_login);
html += field_row('Employee', data.employee);
html += '</tbody></table>';
html += '<div style="margin-top: 15px; padding: 15px; background-color: #f8f9fa; border-radius: 4px;">';
html += '<p style="margin-bottom: 10px;"><strong>Actions:</strong></p>';
html += '<button class="btn btn-primary btn-sm" onclick="frappe.set_route(\'Form\', \'Amas Employees\', \'' + data.name + '\');">';
html += '<i class="fa fa-external-link"></i> View Full AMAS Record</button>';
html += '</div>';
$('#amas_integration_data').html(html);
}
// List view — add "Load from ƏMAS" button
if (frappe.listview_settings['Employee']) {
@ -43,11 +313,8 @@ function start_amas_employee_import(listview) {
const asan_logins = r.message.asan_logins || [];
if (asan_logins.length === 0) {
frappe.msgprint({
title: __('No ƏMAS Connection'),
indicator: 'orange',
message: __('No Asan Login with active ƏMAS connection found. Please connect to ƏMAS first in Asan Login.')
});
// No connected ƏMAS - start connection process automatically
start_amas_connection_for_import(listview);
return;
}
@ -57,6 +324,61 @@ function start_amas_employee_import(listview) {
});
}
// Start ƏMAS connection process automatically for import
function start_amas_connection_for_import(listview) {
// Get first available Asan Login
frappe.call({
method: 'frappe.client.get_list',
args: {
doctype: 'Asan Login',
fields: ['name', 'phone', 'user_id'],
limit_page_length: 1
},
callback: function(r) {
if (!r.message || r.message.length === 0) {
frappe.msgprint({
title: __('No Asan Login Found'),
indicator: 'red',
message: __('Please create an Asan Login first.')
});
return;
}
const asan_login = r.message[0];
// Show confirmation dialog (same design as e-taxes)
const d = new frappe.ui.Dialog({
title: __('ƏMAS Connection Required'),
fields: [
{
fieldname: 'message',
fieldtype: 'HTML',
options: `
<div class="text-center">
<p>ƏMAS connection is required to import employees.</p>
<p>Would you like to connect now?</p>
</div>
`
}
],
primary_action_label: __('Connect'),
primary_action: function() {
d.hide();
// Start full ƏMAS connection process (auto-select first organization)
start_mygovid_then_amas_for_employees(asan_login.name, function() {
// After successful connection, restart import
start_amas_employee_import(listview);
}, false);
},
secondary_action_label: __('Cancel')
});
d.show();
}
});
}
// Step 2: Fetch employees from ƏMAS API
function fetch_employees_from_amas(listview, asan_login_name, organization_name) {
frappe.show_alert({
@ -75,6 +397,15 @@ function fetch_employees_from_amas(listview, asan_login_name, organization_name)
freeze_message: __('Loading employees from') + ' ' + organization_name + '...',
callback: function(r) {
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
fetch_employees_from_amas(listview, asan_login_name, organization_name);
});
return;
}
frappe.msgprint({
title: __('Error'),
indicator: 'red',
@ -286,6 +617,15 @@ function create_employees(listview, asan_login_name, selected_employees, company
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;
}
frappe.msgprint({
title: __('Error'),
indicator: 'red',
@ -342,3 +682,414 @@ function create_employees(listview, asan_login_name, selected_employees, company
}
});
}
// Show reconnection dialog for ƏMAS when token expires (CSRF error)
function show_amas_reconnect_dialog(asan_login_name, retry_callback) {
const d = new frappe.ui.Dialog({
title: __('ƏMAS Authentication Required'),
fields: [
{
fieldname: 'message',
fieldtype: 'HTML',
options: `
<div class="text-center" style="padding: 20px;">
<p style="font-size: 16px; margin-bottom: 15px;">
<i class="fa fa-exclamation-triangle" style="color: #f39c12; font-size: 48px;"></i>
</p>
<p style="font-size: 14px;">Your ƏMAS session has expired or authentication is required.</p>
<p style="font-size: 14px; margin-bottom: 0;">Would you like to reconnect to ƏMAS?</p>
</div>
`
}
],
primary_action_label: __('Reconnect'),
primary_action: function() {
d.hide();
// Try to reconnect to ƏMAS
frappe.call({
method: 'invoice_az.amas_api.connect_amas',
args: {
'asan_login_name': asan_login_name
},
freeze: true,
freeze_message: __('Reconnecting to ƏMAS...'),
callback: function(r) {
if (r.message && r.message.success) {
// Check if we need to select organization
const accounts = r.message.accounts || [];
if (accounts.length > 0) {
// Show organization selection dialog
show_amas_accounts_dialog(asan_login_name, accounts, retry_callback);
} else {
// No accounts to select, just proceed
frappe.show_alert({
message: __('ƏMAS reconnected successfully!'),
indicator: 'green'
}, 3);
// Retry the original operation
if (typeof retry_callback === 'function') {
setTimeout(retry_callback, 500);
}
}
} else if (r.message && r.message.need_mygovid_login) {
// MyGovID token expired - start full authentication flow with organization selection
start_mygovid_then_amas_for_employees(asan_login_name, retry_callback, true);
} else {
frappe.msgprint({
title: __('Reconnection Failed'),
indicator: 'red',
message: r.message ? r.message.message : __('Failed to reconnect to ƏMAS.')
});
}
}
});
},
secondary_action_label: __('Cancel')
});
d.show();
}
// Start MyGovID authentication followed by ƏMAS connection (for employees)
// show_org_selection: true = show organization selection dialog, false = auto-select first org
function start_mygovid_then_amas_for_employees(asan_login_name, retry_callback, show_org_selection) {
// Default to false (auto-select) for backward compatibility
if (show_org_selection === undefined) {
show_org_selection = false;
}
frappe.show_alert({
message: __('Starting MyGovID authentication...'),
indicator: 'blue'
}, 3);
// Create status dialog
const status_dialog = new frappe.ui.Dialog({
title: __('MyGovID Authentication for ƏMAS'),
fields: [
{
fieldname: 'status_html',
fieldtype: 'HTML',
options: `
<div class="text-center">
<div class="mygovid-spinner" style="display: inline-block; width: 64px; height: 64px;">
<div style="width: 64px; height: 64px; border: 6px solid #4e73df; border-radius: 50%; border-color: #4e73df transparent #4e73df transparent; animation: mygovid-spin 1.2s linear infinite;"></div>
</div>
<style>
@keyframes mygovid-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
<div class="mt-3" id="mygovid_status">
<h4>Waiting for confirmation on your phone</h4>
<p>Please check your phone and confirm the authentication request in ASAN Sign app.</p>
</div>
<div id="mygovid_code" style="display:none; margin-top: 15px;">
<div style="background: #f8f9fa; padding: 10px; display: inline-block; border-radius: 5px; font-size: 24px; font-weight: bold; letter-spacing: 2px; color: #333; border: 1px solid #ddd;">
<span id="mygovid_code_text">----</span>
</div>
<p style="margin-top: 5px; font-size: 12px; color: #666;">Verification Code</p>
</div>
</div>
`
}
],
primary_action_label: __('Cancel'),
primary_action: function() {
window.mygovidStop = true;
status_dialog.hide();
}
});
status_dialog.show();
window.mygovidStop = false;
// Start MyGovID login
frappe.call({
method: 'invoice_az.invoice_az.doctype.asan_login.asan_login.start_mygovid_login',
args: {
'asan_login_name': asan_login_name
},
timeout: 130000,
callback: function(r) {
if (window.mygovidStop) return;
if (r.message && r.message.success) {
// MyGovID login successful, now connect to ƏMAS
$('#mygovid_status').html(`
<h4 style="color: blue;">Connecting to ƏMAS...</h4>
<p>MyGovID authentication successful. Now connecting to ƏMAS...</p>
`);
$('#mygovid_code').hide();
// Connect to ƏMAS
frappe.call({
method: 'invoice_az.amas_api.connect_amas',
args: {
'asan_login_name': asan_login_name
},
callback: function(r2) {
if (r2.message && r2.message.success) {
const accounts = r2.message.accounts || [];
if (accounts.length > 0) {
// Check if we should show selection dialog or auto-select
if (show_org_selection) {
// Show organization selection dialog (for re-authentication)
status_dialog.hide();
show_amas_accounts_dialog(asan_login_name, accounts, retry_callback);
} else {
// Auto-select first organization (for initial connection)
const first_account = accounts[0];
const oid = first_account.accountOid || first_account.oid || '';
const name = first_account.accountName || first_account.name || 'Unknown';
const number = first_account.accountNumber || first_account.voen || first_account.number || '';
const role = first_account.roleName || first_account.role || 'chairman';
$('#mygovid_status').html(`
<h4 style="color: blue;">Selecting organization...</h4>
<p>ƏMAS connected. Selecting ${frappe.utils.escape_html(name)}...</p>
`);
// Auto-select first account
frappe.call({
method: 'invoice_az.amas_api.change_amas_account',
args: {
'asan_login_name': asan_login_name,
'account_oid': oid,
'account_name': name,
'account_number': number,
'role_name': role
},
callback: function(r3) {
if (r3.message && r3.message.success) {
$('#mygovid_status').html(`
<h4 style="color: green;">ƏMAS Connected!</h4>
<p>Successfully connected to ${frappe.utils.escape_html(name)}.</p>
`);
$('.mygovid-spinner').html(`
<div style="width: 64px; height: 64px; display: flex; align-items: center; justify-content: center;">
<i class="fa fa-check" style="font-size: 48px; color: #5cb85c;"></i>
</div>
`);
setTimeout(function() {
status_dialog.hide();
if (typeof retry_callback === 'function') {
retry_callback();
}
}, 2000);
} else {
$('#mygovid_status').html(`
<h4 style="color: red;">Organization Selection Failed</h4>
<p>${r3.message ? r3.message.message : 'Failed to select organization'}</p>
`);
$('.mygovid-spinner').css('display', 'none');
status_dialog.set_primary_action(__('Close'), function() {
status_dialog.hide();
});
}
}
});
}
} else {
// No accounts - just complete
$('#mygovid_status').html(`
<h4 style="color: green;">ƏMAS Connected!</h4>
<p>Successfully connected to ƏMAS.</p>
`);
$('.mygovid-spinner').html(`
<div style="width: 64px; height: 64px; display: flex; align-items: center; justify-content: center;">
<i class="fa fa-check" style="font-size: 48px; color: #5cb85c;"></i>
</div>
`);
status_dialog.set_primary_action(__('Continue'), function() {
status_dialog.hide();
if (typeof retry_callback === 'function') {
setTimeout(retry_callback, 500);
}
});
setTimeout(function() {
status_dialog.hide();
if (typeof retry_callback === 'function') {
retry_callback();
}
}, 2000);
}
} else {
$('#mygovid_status').html(`
<h4 style="color: red;">ƏMAS Connection Failed</h4>
<p>${r2.message ? r2.message.message : 'Unknown error'}</p>
`);
$('.mygovid-spinner').css('display', 'none');
status_dialog.set_primary_action(__('Close'), function() {
status_dialog.hide();
});
}
}
});
} else {
// MyGovID login failed
$('#mygovid_status').html(`
<h4 style="color: red;">Authentication Failed</h4>
<p>${r.message ? r.message.message : 'Unknown error'}</p>
`);
$('.mygovid-spinner').css('display', 'none');
$('#mygovid_code').hide();
status_dialog.set_primary_action(__('Close'), function() {
status_dialog.hide();
});
}
},
error: function() {
if (window.mygovidStop) return;
$('#mygovid_status').html(`
<h4 style="color: red;">Authentication Timeout</h4>
<p>The request timed out. Please try again.</p>
`);
$('.mygovid-spinner').css('display', 'none');
$('#mygovid_code').hide();
status_dialog.set_primary_action(__('Close'), function() {
status_dialog.hide();
});
}
});
// Get verification code
setTimeout(function() {
frappe.call({
method: 'invoice_az.invoice_az.doctype.asan_login.asan_login.get_mygovid_verification_code',
args: { 'asan_login_name': asan_login_name },
callback: function(r) {
if (r.message && r.message.success) {
$('#mygovid_code_text').text(r.message.code);
$('#mygovid_code').show();
}
}
});
}, 500);
}
// Show ƏMAS accounts selection dialog
function show_amas_accounts_dialog(asan_login_name, accounts, retry_callback) {
if (!accounts || accounts.length === 0) {
frappe.msgprint(__('No organizations found in ƏMAS'));
return;
}
// Build accounts table
let table_html = '<div style="max-height: 400px; overflow-y: auto;">';
table_html += '<table class="table table-bordered table-hover">';
table_html += '<thead><tr><th style="width: 50%;">Organization Name</th><th style="width: 25%;">VOEN</th><th style="width: 15%;">Role</th><th style="width: 10%;">Action</th></tr></thead>';
table_html += '<tbody>';
accounts.forEach(function(account, index) {
const name = account.accountName || account.name || 'Unknown';
const voen = account.accountNumber || account.voen || account.number || '';
const role = account.roleName || account.role || '';
const oid = account.accountOid || account.oid || '';
table_html += `<tr>
<td>${frappe.utils.escape_html(name)}</td>
<td>${frappe.utils.escape_html(voen)}</td>
<td>${frappe.utils.escape_html(role)}</td>
<td>
<button class="btn btn-xs btn-primary select-amas-account"
data-oid="${frappe.utils.escape_html(oid)}"
data-name="${frappe.utils.escape_html(name)}"
data-number="${frappe.utils.escape_html(voen)}"
data-role="${frappe.utils.escape_html(role)}">
Select
</button>
</td>
</tr>`;
});
table_html += '</tbody></table></div>';
const d = new frappe.ui.Dialog({
title: __('Select ƏMAS Organization'),
fields: [
{
fieldname: 'info',
fieldtype: 'HTML',
options: '<div class="alert alert-info" style="margin-bottom: 15px;"><strong>Note:</strong> Please select the organization you want to work with.</div>'
},
{
fieldname: 'accounts_html',
fieldtype: 'HTML',
options: table_html
}
],
primary_action_label: __('Cancel'),
primary_action: function() {
d.hide();
}
});
// Make dialog wider
d.$wrapper.find('.modal-dialog').css({
'max-width': '70%',
'width': '70%'
});
d.show();
// Add click handler for account selection
d.$wrapper.find('.select-amas-account').on('click', function() {
const oid = $(this).data('oid');
const name = $(this).data('name');
const number = $(this).data('number');
const role = $(this).data('role');
// Disable all buttons
d.$wrapper.find('.select-amas-account').prop('disabled', true);
$(this).html('<i class="fa fa-spinner fa-spin"></i> Selecting...');
// Call backend to select account
frappe.call({
method: 'invoice_az.amas_api.change_amas_account',
args: {
'asan_login_name': asan_login_name,
'account_oid': oid,
'account_name': name,
'account_number': number,
'role_name': role || 'chairman'
},
callback: function(r) {
if (r.message && r.message.success) {
d.hide();
frappe.show_alert({
message: __('Organization selected: ') + name,
indicator: 'green'
}, 3);
// Retry the original operation after short delay
if (typeof retry_callback === 'function') {
setTimeout(retry_callback, 500);
}
} else {
// Re-enable buttons on error
d.$wrapper.find('.select-amas-account').prop('disabled', false);
$(this).html('Select');
frappe.msgprint({
title: __('Error'),
indicator: 'red',
message: r.message ? r.message.message : __('Failed to select organization')
});
}
}
});
});
}

View File

@ -12,7 +12,8 @@ doctype_js = {
"Sales Invoice": "client/sales_invoice.js",
"Journal Entry": "client/journal_entry.js",
"Supplier": "client/supplier.js",
"Amas Employees": "client/amas_employees.js"
"Amas Employees": "client/amas_employees.js",
"Employee": "client/employee.js"
}
doctype_list_js = {
@ -79,11 +80,12 @@ def after_install():
def after_migrate():
"""Run migration tasks"""
from invoice_az.auth import setup_token_renewal
from invoice_az.install import create_journal_entry_custom_fields, create_supplier_custom_fields
from invoice_az.install import create_journal_entry_custom_fields, create_supplier_custom_fields, create_employee_amas_fields
setup_token_renewal()
create_journal_entry_custom_fields()
create_supplier_custom_fields()
create_employee_amas_fields()
# Fixtures for master data
# ------------------------

View File

@ -8,6 +8,7 @@ def after_install():
"""Run after app installation"""
create_journal_entry_custom_fields()
create_supplier_custom_fields()
create_employee_amas_fields()
def create_journal_entry_custom_fields():
@ -38,3 +39,139 @@ def create_supplier_custom_fields():
# Clear cache for Supplier
frappe.clear_cache(doctype='Supplier')
frappe.logger().info("Custom fields created/updated successfully for Supplier")
def create_employee_amas_fields():
"""Create custom fields for Employee doctype to display AMAS data
This creates only the essential link field and HTML containers.
The AMAS data is displayed dynamically via JavaScript from the linked Amas Employees record.
This approach avoids MySQL's 65KB row size limit.
"""
custom_fields = {
'Employee': [
# AMAS Employee Link (essential field)
{
'fieldname': 'custom_amas_employee',
'fieldtype': 'Link',
'label': 'AMAS Employee Record',
'options': 'Amas Employees',
'read_only': 1,
'insert_after': 'profile_tab'
},
# Main AMAS Data Tab
{
'fieldname': 'amas_data_tab',
'fieldtype': 'Tab Break',
'label': 'AMAS Data',
'depends_on': 'eval:doc.custom_amas_employee'
},
# ===== SUB TAB 1: Contract =====
{
'fieldname': 'amas_contract_tab',
'fieldtype': 'Tab Break',
'label': 'Contract',
'js_parent_subtab': 'AMAS Data'
},
{
'fieldname': 'amas_contract_html',
'fieldtype': 'HTML',
'label': 'Contract Information',
'options': '<div id="amas_contract_data">Loading...</div>'
},
# ===== SUB TAB 2: Position & Activity =====
{
'fieldname': 'amas_position_tab',
'fieldtype': 'Tab Break',
'label': 'Position & Activity',
'js_parent_subtab': 'AMAS Data'
},
{
'fieldname': 'amas_position_html',
'fieldtype': 'HTML',
'label': 'Position Information',
'options': '<div id="amas_position_data">Loading...</div>'
},
# ===== SUB TAB 3: Salary & Compensation =====
{
'fieldname': 'amas_salary_tab',
'fieldtype': 'Tab Break',
'label': 'Salary & Compensation',
'js_parent_subtab': 'AMAS Data'
},
{
'fieldname': 'amas_salary_html',
'fieldtype': 'HTML',
'label': 'Salary Information',
'options': '<div id="amas_salary_data">Loading...</div>'
},
# ===== SUB TAB 4: Work Schedule & Vacation =====
{
'fieldname': 'amas_schedule_tab',
'fieldtype': 'Tab Break',
'label': 'Work Schedule & Vacation',
'js_parent_subtab': 'AMAS Data'
},
{
'fieldname': 'amas_schedule_html',
'fieldtype': 'HTML',
'label': 'Schedule Information',
'options': '<div id="amas_schedule_data">Loading...</div>'
},
# ===== SUB TAB 5: Personal & Education =====
{
'fieldname': 'amas_personal_tab',
'fieldtype': 'Tab Break',
'label': 'Personal & Education',
'js_parent_subtab': 'AMAS Data'
},
{
'fieldname': 'amas_personal_html',
'fieldtype': 'HTML',
'label': 'Personal Information',
'options': '<div id="amas_personal_data">Loading...</div>'
},
# ===== SUB TAB 6: Documents & Metadata =====
{
'fieldname': 'amas_documents_tab',
'fieldtype': 'Tab Break',
'label': 'Documents & Metadata',
'js_parent_subtab': 'AMAS Data'
},
{
'fieldname': 'amas_documents_html',
'fieldtype': 'HTML',
'label': 'Document Information',
'options': '<div id="amas_documents_data">Loading...</div>'
},
# ===== SUB TAB 7: AMAS Integration =====
{
'fieldname': 'amas_integration_tab',
'fieldtype': 'Tab Break',
'label': 'AMAS Integration',
'js_parent_subtab': 'AMAS Data'
},
{
'fieldname': 'amas_integration_html',
'fieldtype': 'HTML',
'label': 'Integration Information',
'options': '<div id="amas_integration_data">Loading...</div>'
}
]
}
frappe.logger().info("Creating/updating custom AMAS fields for Employee")
create_custom_fields(custom_fields, update=True)
# Clear cache for Employee
frappe.clear_cache(doctype='Employee')
frappe.logger().info("Custom AMAS fields created/updated successfully for Employee")

View File

@ -1,6 +1,6 @@
{
"actions": [],
"allow_rename": 1,
"allow_rename": 0,
"autoname": "format:AMAS-EMP-{####}",
"creation": "2026-02-06 10:00:00",
"doctype": "DocType",
@ -168,7 +168,8 @@
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Full Name"
"label": "Full Name",
"read_only": 1
},
{
"fieldname": "identification_number",
@ -176,12 +177,14 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "FIN",
"reqd": 1
"reqd": 1,
"read_only": 1
},
{
"fieldname": "ssn",
"fieldtype": "Data",
"label": "SSN"
"label": "SSN",
"read_only": 1
},
{
"fieldname": "column_break_emp1",
@ -190,19 +193,22 @@
{
"fieldname": "birthday",
"fieldtype": "Data",
"label": "Birthday"
"label": "Birthday",
"read_only": 1
},
{
"fieldname": "work_position_text",
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Position"
"label": "Position",
"read_only": 1
},
{
"fieldname": "monthly_salary",
"fieldtype": "Data",
"label": "Monthly Salary"
"label": "Monthly Salary",
"read_only": 1
},
{
"fieldname": "personal_info_section",
@ -213,22 +219,26 @@
{
"fieldname": "father_name",
"fieldtype": "Data",
"label": "Father Name"
"label": "Father Name",
"read_only": 1
},
{
"fieldname": "gender",
"fieldtype": "Data",
"label": "Gender"
"label": "Gender",
"read_only": 1
},
{
"fieldname": "nationality",
"fieldtype": "Data",
"label": "Nationality"
"label": "Nationality",
"read_only": 1
},
{
"fieldname": "blood_type",
"fieldtype": "Data",
"label": "Blood Type"
"label": "Blood Type",
"read_only": 1
},
{
"fieldname": "column_break_personal1",
@ -237,27 +247,32 @@
{
"fieldname": "marital_status",
"fieldtype": "Data",
"label": "Marital Status"
"label": "Marital Status",
"read_only": 1
},
{
"fieldname": "military_status",
"fieldtype": "Data",
"label": "Military Status"
"label": "Military Status",
"read_only": 1
},
{
"fieldname": "personal_address",
"fieldtype": "Small Text",
"label": "Personal Address"
"label": "Personal Address",
"read_only": 1
},
{
"fieldname": "personal_city",
"fieldtype": "Data",
"label": "Personal City"
"label": "Personal City",
"read_only": 1
},
{
"fieldname": "personal_district",
"fieldtype": "Data",
"label": "Personal District"
"label": "Personal District",
"read_only": 1
},
{
"fieldname": "document_section",
@ -268,17 +283,20 @@
{
"fieldname": "document_number",
"fieldtype": "Data",
"label": "Document Number"
"label": "Document Number",
"read_only": 1
},
{
"fieldname": "document_issue_date",
"fieldtype": "Date",
"label": "Document Issue Date"
"label": "Document Issue Date",
"read_only": 1
},
{
"fieldname": "document_expiry_date",
"fieldtype": "Date",
"label": "Document Expiry Date"
"label": "Document Expiry Date",
"read_only": 1
},
{
"fieldname": "column_break_document1",
@ -287,7 +305,8 @@
{
"fieldname": "document_issued_by",
"fieldtype": "Data",
"label": "Document Issued By"
"label": "Document Issued By",
"read_only": 1
},
{
"fieldname": "contract_section",
@ -298,22 +317,26 @@
"fieldname": "doc_no",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Document No"
"label": "Document No",
"read_only": 1
},
{
"fieldname": "doc_date",
"fieldtype": "Data",
"label": "Document Date"
"label": "Document Date",
"read_only": 1
},
{
"fieldname": "doc_type",
"fieldtype": "Data",
"label": "Document Type"
"label": "Document Type",
"read_only": 1
},
{
"fieldname": "contract_type",
"fieldtype": "Data",
"label": "Contract Type"
"label": "Contract Type",
"read_only": 1
},
{
"fieldname": "column_break_contract1",
@ -324,22 +347,26 @@
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Contract Status"
"label": "Contract Status",
"read_only": 1
},
{
"fieldname": "begin_date",
"fieldtype": "Data",
"label": "Begin Date"
"label": "Begin Date",
"read_only": 1
},
{
"fieldname": "end_date",
"fieldtype": "Data",
"label": "End Date"
"label": "End Date",
"read_only": 1
},
{
"fieldname": "main_contract_begin_date",
"fieldtype": "Data",
"label": "Main Contract Begin Date"
"label": "Main Contract Begin Date",
"read_only": 1
},
{
"fieldname": "contract_details_section",
@ -350,22 +377,26 @@
{
"fieldname": "contract_number",
"fieldtype": "Data",
"label": "Contract Number"
"label": "Contract Number",
"read_only": 1
},
{
"fieldname": "prev_contract",
"fieldtype": "Data",
"label": "Previous Contract"
"label": "Previous Contract",
"read_only": 1
},
{
"fieldname": "is_signed",
"fieldtype": "Check",
"label": "Is Signed"
"label": "Is Signed",
"read_only": 1
},
{
"fieldname": "contract_validity",
"fieldtype": "Data",
"label": "Contract Validity"
"label": "Contract Validity",
"read_only": 1
},
{
"fieldname": "column_break_contract_details1",
@ -374,22 +405,26 @@
{
"fieldname": "staff_oid",
"fieldtype": "Data",
"label": "Staff OID"
"label": "Staff OID",
"read_only": 1
},
{
"fieldname": "structure_unit_oid",
"fieldtype": "Data",
"label": "Structure Unit OID"
"label": "Structure Unit OID",
"read_only": 1
},
{
"fieldname": "entity_oid",
"fieldtype": "Data",
"label": "Entity OID"
"label": "Entity OID",
"read_only": 1
},
{
"fieldname": "entity_details_oid",
"fieldtype": "Data",
"label": "Entity Details OID"
"label": "Entity Details OID",
"read_only": 1
},
{
"fieldname": "contract_details_section2",
@ -399,22 +434,26 @@
{
"fieldname": "contract_reason",
"fieldtype": "Data",
"label": "Contract Reason"
"label": "Contract Reason",
"read_only": 1
},
{
"fieldname": "from_migration",
"fieldtype": "Check",
"label": "From Migration"
"label": "From Migration",
"read_only": 1
},
{
"fieldname": "contract_begin_date",
"fieldtype": "Date",
"label": "Contract Begin Date"
"label": "Contract Begin Date",
"read_only": 1
},
{
"fieldname": "contract_end_date",
"fieldtype": "Date",
"label": "Contract End Date"
"label": "Contract End Date",
"read_only": 1
},
{
"fieldname": "position_activity_section",
@ -425,22 +464,26 @@
{
"fieldname": "work_position",
"fieldtype": "Data",
"label": "Work Position"
"label": "Work Position",
"read_only": 1
},
{
"fieldname": "position_id",
"fieldtype": "Data",
"label": "Position ID"
"label": "Position ID",
"read_only": 1
},
{
"fieldname": "position_text",
"fieldtype": "Data",
"label": "Position Text"
"label": "Position Text",
"read_only": 1
},
{
"fieldname": "activity_code",
"fieldtype": "Data",
"label": "Activity Code"
"label": "Activity Code",
"read_only": 1
},
{
"fieldname": "column_break_position1",
@ -449,22 +492,26 @@
{
"fieldname": "activity_name",
"fieldtype": "Data",
"label": "Activity Name"
"label": "Activity Name",
"read_only": 1
},
{
"fieldname": "staff_unit",
"fieldtype": "Data",
"label": "Staff Unit"
"label": "Staff Unit",
"read_only": 1
},
{
"fieldname": "structure_path",
"fieldtype": "Small Text",
"label": "Structure Path"
"label": "Structure Path",
"read_only": 1
},
{
"fieldname": "workplace_sector",
"fieldtype": "Data",
"label": "Workplace Sector"
"label": "Workplace Sector",
"read_only": 1
},
{
"fieldname": "salary_compensation_section",
@ -475,27 +522,32 @@
{
"fieldname": "base_salary",
"fieldtype": "Float",
"label": "Base Salary"
"label": "Base Salary",
"read_only": 1
},
{
"fieldname": "salary_add",
"fieldtype": "Float",
"label": "Salary Add"
"label": "Salary Add",
"read_only": 1
},
{
"fieldname": "salary_add_hw",
"fieldtype": "Float",
"label": "Salary Add HW"
"label": "Salary Add HW",
"read_only": 1
},
{
"fieldname": "award_amount",
"fieldtype": "Float",
"label": "Award Amount"
"label": "Award Amount",
"read_only": 1
},
{
"fieldname": "award_type",
"fieldtype": "Data",
"label": "Award Type"
"label": "Award Type",
"read_only": 1
},
{
"fieldname": "column_break_salary1",
@ -504,37 +556,44 @@
{
"fieldname": "overtime_amount",
"fieldtype": "Float",
"label": "Overtime Amount"
"label": "Overtime Amount",
"read_only": 1
},
{
"fieldname": "bad_condition_amount",
"fieldtype": "Float",
"label": "Bad Condition Amount"
"label": "Bad Condition Amount",
"read_only": 1
},
{
"fieldname": "bad_condition_id",
"fieldtype": "Data",
"label": "Bad Condition ID"
"label": "Bad Condition ID",
"read_only": 1
},
{
"fieldname": "currency_type",
"fieldtype": "Data",
"label": "Currency Type"
"label": "Currency Type",
"read_only": 1
},
{
"fieldname": "payment_type",
"fieldtype": "Data",
"label": "Payment Type"
"label": "Payment Type",
"read_only": 1
},
{
"fieldname": "payment_first_day",
"fieldtype": "Int",
"label": "Payment First Day"
"label": "Payment First Day",
"read_only": 1
},
{
"fieldname": "bank_oid",
"fieldtype": "Data",
"label": "Bank OID"
"label": "Bank OID",
"read_only": 1
},
{
"fieldname": "work_schedule_section",
@ -545,22 +604,26 @@
{
"fieldname": "work_mode_type",
"fieldtype": "Data",
"label": "Work Mode Type"
"label": "Work Mode Type",
"read_only": 1
},
{
"fieldname": "is_full_time",
"fieldtype": "Data",
"label": "Is Full Time"
"label": "Is Full Time",
"read_only": 1
},
{
"fieldname": "work_time",
"fieldtype": "Float",
"label": "Work Time"
"label": "Work Time",
"read_only": 1
},
{
"fieldname": "working_time_cumulative",
"fieldtype": "Check",
"label": "Working Time Cumulative"
"label": "Working Time Cumulative",
"read_only": 1
},
{
"fieldname": "column_break_schedule1",
@ -569,32 +632,38 @@
{
"fieldname": "first_shift_start",
"fieldtype": "Time",
"label": "First Shift Start"
"label": "First Shift Start",
"read_only": 1
},
{
"fieldname": "first_shift_end",
"fieldtype": "Time",
"label": "First Shift End"
"label": "First Shift End",
"read_only": 1
},
{
"fieldname": "second_shift_start",
"fieldtype": "Time",
"label": "Second Shift Start"
"label": "Second Shift Start",
"read_only": 1
},
{
"fieldname": "second_shift_end",
"fieldtype": "Time",
"label": "Second Shift End"
"label": "Second Shift End",
"read_only": 1
},
{
"fieldname": "lunch_start",
"fieldtype": "Time",
"label": "Lunch Start"
"label": "Lunch Start",
"read_only": 1
},
{
"fieldname": "lunch_end",
"fieldtype": "Time",
"label": "Lunch End"
"label": "Lunch End",
"read_only": 1
},
{
"fieldname": "vacation_section",
@ -605,22 +674,26 @@
{
"fieldname": "vacation_days_count",
"fieldtype": "Int",
"label": "Vacation Days Count"
"label": "Vacation Days Count",
"read_only": 1
},
{
"fieldname": "vacation_general_duration",
"fieldtype": "Int",
"label": "Vacation General Duration"
"label": "Vacation General Duration",
"read_only": 1
},
{
"fieldname": "vacation_main_duration",
"fieldtype": "Int",
"label": "Vacation Main Duration"
"label": "Vacation Main Duration",
"read_only": 1
},
{
"fieldname": "vacation_add_dur_one",
"fieldtype": "Int",
"label": "Vacation Add Duration 1"
"label": "Vacation Add Duration 1",
"read_only": 1
},
{
"fieldname": "column_break_vacation1",
@ -629,22 +702,26 @@
{
"fieldname": "vacation_add_dur_two",
"fieldtype": "Int",
"label": "Vacation Add Duration 2"
"label": "Vacation Add Duration 2",
"read_only": 1
},
{
"fieldname": "vacation_add_dur_four",
"fieldtype": "Int",
"label": "Vacation Add Duration 4"
"label": "Vacation Add Duration 4",
"read_only": 1
},
{
"fieldname": "vacation_amount",
"fieldtype": "Float",
"label": "Vacation Amount"
"label": "Vacation Amount",
"read_only": 1
},
{
"fieldname": "profession_employee_for_vacation",
"fieldtype": "Data",
"label": "Profession Employee For Vacation"
"label": "Profession Employee For Vacation",
"read_only": 1
},
{
"fieldname": "education_section",
@ -655,17 +732,20 @@
{
"fieldname": "education",
"fieldtype": "Data",
"label": "Education"
"label": "Education",
"read_only": 1
},
{
"fieldname": "scientific_degree",
"fieldtype": "Data",
"label": "Scientific Degree"
"label": "Scientific Degree",
"read_only": 1
},
{
"fieldname": "speciality_degree",
"fieldtype": "Data",
"label": "Speciality Degree"
"label": "Speciality Degree",
"read_only": 1
},
{
"fieldname": "column_break_education1",
@ -674,12 +754,14 @@
{
"fieldname": "state_speciality_degree",
"fieldtype": "Data",
"label": "State Speciality Degree"
"label": "State Speciality Degree",
"read_only": 1
},
{
"fieldname": "qualification_degree_salary_add",
"fieldtype": "Float",
"label": "Qualification Degree Salary Add"
"label": "Qualification Degree Salary Add",
"read_only": 1
},
{
"fieldname": "contact_info_section",
@ -690,12 +772,14 @@
{
"fieldname": "contact_phone",
"fieldtype": "Data",
"label": "Contact Phone"
"label": "Contact Phone",
"read_only": 1
},
{
"fieldname": "contact_email",
"fieldtype": "Data",
"label": "Contact Email"
"label": "Contact Email",
"read_only": 1
},
{
"fieldname": "column_break_contact1",
@ -704,17 +788,20 @@
{
"fieldname": "work_address_oid",
"fieldtype": "Data",
"label": "Work Address OID"
"label": "Work Address OID",
"read_only": 1
},
{
"fieldname": "work_address_street",
"fieldtype": "Data",
"label": "Work Address Street"
"label": "Work Address Street",
"read_only": 1
},
{
"fieldname": "work_address_district",
"fieldtype": "Data",
"label": "Work Address District"
"label": "Work Address District",
"read_only": 1
},
{
"fieldname": "employer_info_section",
@ -725,17 +812,20 @@
{
"fieldname": "employer_pin",
"fieldtype": "Data",
"label": "Employer PIN"
"label": "Employer PIN",
"read_only": 1
},
{
"fieldname": "employer_tin",
"fieldtype": "Data",
"label": "Employer TIN"
"label": "Employer TIN",
"read_only": 1
},
{
"fieldname": "chairman_pin",
"fieldtype": "Data",
"label": "Chairman PIN"
"label": "Chairman PIN",
"read_only": 1
},
{
"fieldname": "additional_metadata_section",
@ -746,17 +836,20 @@
{
"fieldname": "doc_state",
"fieldtype": "Data",
"label": "Document State"
"label": "Document State",
"read_only": 1
},
{
"fieldname": "created_date",
"fieldtype": "Datetime",
"label": "Created Date"
"label": "Created Date",
"read_only": 1
},
{
"fieldname": "labour_function",
"fieldtype": "Small Text",
"label": "Labour Function"
"label": "Labour Function",
"read_only": 1
},
{
"fieldname": "column_break_metadata1",
@ -765,22 +858,26 @@
{
"fieldname": "protection_tools",
"fieldtype": "Small Text",
"label": "Protection Tools"
"label": "Protection Tools",
"read_only": 1
},
{
"fieldname": "payment_other_cond",
"fieldtype": "Small Text",
"label": "Payment Other Conditions"
"label": "Payment Other Conditions",
"read_only": 1
},
{
"fieldname": "activity_name_detail",
"fieldtype": "Small Text",
"label": "Activity Name Detail"
"label": "Activity Name Detail",
"read_only": 1
},
{
"fieldname": "rule_text",
"fieldtype": "Small Text",
"label": "Rule Text"
"label": "Rule Text",
"read_only": 1
},
{
"fieldname": "dates_section",
@ -790,17 +887,20 @@
{
"fieldname": "emp_job_start_date",
"fieldtype": "Data",
"label": "Job Start Date"
"label": "Job Start Date",
"read_only": 1
},
{
"fieldname": "sign_type",
"fieldtype": "Data",
"label": "Sign Type"
"label": "Sign Type",
"read_only": 1
},
{
"fieldname": "is_valid",
"fieldtype": "Data",
"label": "Is Valid"
"label": "Is Valid",
"read_only": 1
},
{
"fieldname": "column_break_dates1",
@ -809,17 +909,20 @@
{
"fieldname": "dur_state",
"fieldtype": "Data",
"label": "Duration State"
"label": "Duration State",
"read_only": 1
},
{
"fieldname": "operation",
"fieldtype": "Data",
"label": "Operation"
"label": "Operation",
"read_only": 1
},
{
"fieldname": "after_exec_label",
"fieldtype": "Data",
"label": "After Execution Label"
"label": "After Execution Label",
"read_only": 1
},
{
"fieldname": "details_section",
@ -830,17 +933,20 @@
{
"fieldname": "workplace_type",
"fieldtype": "Data",
"label": "Workplace Type"
"label": "Workplace Type",
"read_only": 1
},
{
"fieldname": "form_size",
"fieldtype": "Data",
"label": "Form Size"
"label": "Form Size",
"read_only": 1
},
{
"fieldname": "flow",
"fieldtype": "Data",
"label": "Flow"
"label": "Flow",
"read_only": 1
},
{
"fieldname": "column_break_details1",
@ -849,23 +955,27 @@
{
"fieldname": "flow_alias",
"fieldtype": "Data",
"label": "Flow Alias"
"label": "Flow Alias",
"read_only": 1
},
{
"fieldname": "tree_path_name",
"fieldtype": "Small Text",
"label": "Tree Path Name"
"label": "Tree Path Name",
"read_only": 1
},
{
"fieldname": "view_type",
"fieldtype": "Data",
"label": "View Type"
"label": "View Type",
"read_only": 1
},
{
"fieldname": "doc_oid",
"fieldtype": "Data",
"label": "Document OID",
"unique": 1
"unique": 1,
"read_only": 1
},
{
"fieldname": "organization_section",
@ -876,7 +986,8 @@
"fieldname": "asan_login",
"fieldtype": "Link",
"label": "Asan Login",
"options": "Asan Login"
"options": "Asan Login",
"read_only": 1
},
{
"fieldname": "organization_name",
@ -919,14 +1030,16 @@
"fieldtype": "Code",
"label": "Employment Contract (HTML)",
"options": "HTML",
"hidden": 1
"hidden": 1,
"read_only": 1
},
{
"fieldname": "raw_data",
"fieldtype": "Code",
"hidden": 1,
"label": "Raw Data (JSON)",
"options": "JSON"
"options": "JSON",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
@ -939,7 +1052,7 @@
"owner": "Administrator",
"permissions": [
{
"create": 1,
"create": 0,
"email": 1,
"export": 1,
"print": 1,
@ -947,7 +1060,8 @@
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
"write": 0,
"delete": 0
}
],
"sort_field": "modified",

View File

@ -1,285 +0,0 @@
// Copyright (c) 2026, Your Company and contributors
// For license information, please see license.txt
frappe.listview_settings['Amas Employees'] = {
onload: function(listview) {
listview.page.add_inner_button(__('Import from ƏMAS'), function() {
show_import_employees_dialog();
});
}
};
function show_import_employees_dialog() {
frappe.call({
method: 'invoice_az.amas_api.get_connected_asan_logins',
callback: function(r) {
if (r.message && r.message.success) {
const asan_logins = r.message.asan_logins || [];
if (asan_logins.length === 0) {
frappe.msgprint({
title: __('No ƏMAS Connection'),
indicator: 'orange',
message: __('No Asan Login with active ƏMAS connection found. Please connect to ƏMAS first in Asan Login.')
});
return;
}
if (asan_logins.length === 1) {
load_employees_for_import(asan_logins[0].name, asan_logins[0].organization_name);
return;
}
// Multiple logins - show selection
let options = asan_logins.map(al => `${al.organization_name} (${al.organization_voen})`).join('\n');
const d = new frappe.ui.Dialog({
title: __('Select Organization'),
fields: [{
fieldname: 'asan_login',
fieldtype: 'Select',
label: __('Organization'),
reqd: 1,
options: options
}],
primary_action_label: __('Load Employees'),
primary_action: function(values) {
const selected_index = options.split('\n').indexOf(values.asan_login);
if (selected_index >= 0) {
const selected = asan_logins[selected_index];
d.hide();
load_employees_for_import(selected.name, selected.organization_name);
}
}
});
d.show();
} else {
frappe.msgprint({
title: __('Error'),
indicator: 'red',
message: r.message ? r.message.message : __('Failed to get Asan Logins')
});
}
}
});
}
function load_employees_for_import(asan_login_name, organization_name) {
const loading_dialog = new frappe.ui.Dialog({
title: __('Loading Employees from ƏMAS'),
fields: [{
fieldname: 'loading_html',
fieldtype: 'HTML',
options: `
<div class="text-center">
<i class="fa fa-spinner fa-spin fa-3x"></i>
<p class="mt-3">${__('Loading employees from')} ${frappe.utils.escape_html(organization_name)}...</p>
</div>
`
}]
});
loading_dialog.show();
frappe.call({
method: 'invoice_az.amas_api.get_employees_report',
args: {
'asan_login_name': asan_login_name,
'offset': 0,
'limit': 1000
},
callback: function(r) {
loading_dialog.hide();
if (r.message && r.message.success) {
const employees = r.message.employees || [];
if (employees.length === 0) {
frappe.msgprint({
title: __('No Employees'),
indicator: 'blue',
message: __('No employees found in ƏMAS for this organization.')
});
return;
}
show_employees_selection_dialog(asan_login_name, organization_name, employees);
} else {
frappe.msgprint({
title: __('Error'),
indicator: 'red',
message: r.message ? r.message.message : __('Failed to load employees from ƏMAS')
});
}
}
});
}
function show_employees_selection_dialog(asan_login_name, organization_name, employees) {
// Dynamically detect columns from the first employee
const columns = Object.keys(employees[0]);
// Known column label mappings
const column_labels = {
'adi': 'Ad',
'soyadi': 'Soyad',
'ataAdi': 'Ata adı',
'fin': 'FIN',
'vezife': 'Vəzifə',
'muqavileNo': 'Müqavilə No',
'muqavileTarixi': 'Müqavilə tarixi',
'status': 'Status',
'emekHaqqi': 'Əmək haqqı',
'isBaslamaTarixi': 'İş başlama',
'isBitmeTarixi': 'İş bitmə',
'dogumTarixi': 'Doğum tarixi',
'cinsi': 'Cinsi',
'isSaati': 'İş saatı',
'isGunu': 'İş günü'
};
let table_html = `
<div class="mb-3">
<strong>${__('Organization')}:</strong> ${frappe.utils.escape_html(organization_name)}
<span class="ml-3"><strong>${__('Total')}:</strong> ${employees.length}</span>
</div>
<div class="mb-2">
<button class="btn btn-xs btn-default select-all-employees">${__('Select All')}</button>
<button class="btn btn-xs btn-default deselect-all-employees ml-2">${__('Deselect All')}</button>
</div>
<div style="max-height: 400px; overflow-y: auto;">
<table class="table table-bordered table-striped table-hover" style="font-size: 12px;">
<thead>
<tr>
<th style="width: 30px;"><input type="checkbox" class="select-all-checkbox"></th>
`;
columns.forEach(function(col) {
const label = column_labels[col] || col;
table_html += `<th>${frappe.utils.escape_html(label)}</th>`;
});
table_html += '</tr></thead><tbody>';
employees.forEach(function(emp, index) {
table_html += `<tr><td><input type="checkbox" class="employee-checkbox" data-index="${index}"></td>`;
columns.forEach(function(col) {
const value = emp[col] !== null && emp[col] !== undefined ? String(emp[col]) : '';
table_html += `<td>${frappe.utils.escape_html(value)}</td>`;
});
table_html += '</tr>';
});
table_html += '</tbody></table></div>';
const d = new frappe.ui.Dialog({
title: __('Select Employees to Import'),
size: 'extra-large',
fields: [{
fieldname: 'employees_html',
fieldtype: 'HTML',
options: table_html
}],
primary_action_label: __('Import Selected'),
primary_action: function() {
const selected_indices = [];
d.$wrapper.find('.employee-checkbox:checked').each(function() {
selected_indices.push($(this).data('index'));
});
if (selected_indices.length === 0) {
frappe.msgprint(__('Please select at least one employee to import.'));
return;
}
d.hide();
import_selected_employees(asan_login_name, employees, selected_indices);
},
secondary_action_label: __('Cancel'),
secondary_action: function() {
d.hide();
}
});
d.show();
d.$wrapper.find('.select-all-checkbox').on('change', function() {
d.$wrapper.find('.employee-checkbox').prop('checked', $(this).prop('checked'));
});
d.$wrapper.find('.select-all-employees').on('click', function() {
d.$wrapper.find('.employee-checkbox').prop('checked', true);
d.$wrapper.find('.select-all-checkbox').prop('checked', true);
});
d.$wrapper.find('.deselect-all-employees').on('click', function() {
d.$wrapper.find('.employee-checkbox').prop('checked', false);
d.$wrapper.find('.select-all-checkbox').prop('checked', false);
});
}
function import_selected_employees(asan_login_name, employees, selected_indices) {
const selected_employees = selected_indices.map(i => employees[i]);
const progress_dialog = new frappe.ui.Dialog({
title: __('Importing Employees'),
fields: [{
fieldname: 'progress_html',
fieldtype: 'HTML',
options: `
<div class="text-center">
<i class="fa fa-spinner fa-spin fa-3x"></i>
<p class="mt-3">${__('Importing')} ${selected_employees.length} ${__('employees')}...</p>
</div>
`
}]
});
progress_dialog.show();
frappe.call({
method: 'invoice_az.amas_api.import_employees',
args: {
'asan_login_name': asan_login_name,
'employees': selected_employees
},
callback: function(r) {
progress_dialog.hide();
if (r.message && r.message.success) {
let summary_msg = `<p><strong>${__('Import Summary')}:</strong></p>`;
summary_msg += `<p>${__('Imported')}: ${r.message.imported_count}</p>`;
if (r.message.updated_count > 0) {
summary_msg += `<p>${__('Updated')}: ${r.message.updated_count}</p>`;
}
if (r.message.error_count > 0) {
summary_msg += `<p style="color: red;">${__('Errors')}: ${r.message.error_count}</p>`;
if (r.message.errors && r.message.errors.length > 0) {
summary_msg += '<ul style="color: red; font-size: 12px;">';
r.message.errors.forEach(function(err) {
summary_msg += `<li>${frappe.utils.escape_html(err)}</li>`;
});
summary_msg += '</ul>';
}
}
frappe.msgprint({
title: __('Import Complete'),
indicator: r.message.error_count > 0 ? 'orange' : 'green',
message: summary_msg
});
cur_list.refresh();
} else {
frappe.msgprint({
title: __('Import Failed'),
indicator: 'red',
message: r.message ? r.message.message : __('Failed to import employees')
});
}
}
});
}

View File

@ -6,41 +6,20 @@ frappe.ui.form.on('Asan Login', {
window.authStatusInterval = null;
}
// Add EMAS buttons
add_amas_buttons(frm);
// Кнопка для MyGovID Login
frm.add_custom_button(__('MyGovID Login'), function() {
// Кнопка для E-Taxes Login (переименованная "Login with Asan Imza")
frm.add_custom_button(__('E-Taxes Login'), function() {
// Проверяем, что поля phone и user_id заполнены
if (!frm.doc.phone || !frm.doc.user_id) {
frappe.msgprint(__('Please fill in Phone and User ID fields'));
return;
}
// Показываем сообщение о том, что запрос отправлен
frappe.show_alert({
message: __('MyGovID authentication request sent. Please confirm on your phone.'),
indicator: 'blue'
}, 5);
// Start MyGovID login with status dialog
startMyGovIDLogin(frm);
}, __('Login'));
// Кнопка для получения токена
frm.add_custom_button(__('Login with Asan Imza'), function() {
// Проверяем, что поля phone и user_id заполнены
if (!frm.doc.phone || !frm.doc.user_id) {
frappe.msgprint(__('Please fill in Phone and User ID fields'));
return;
}
// Показываем сообщение о том, что запрос отправлен
frappe.show_alert({
message: __('Authentication request sent. Please confirm on your phone.'),
indicator: 'blue'
}, 5);
// Отправляем запрос для начала процесса аутентификации
frappe.call({
method: 'invoice_az.auth.handle_authentication',
@ -53,20 +32,20 @@ frappe.ui.form.on('Asan Login', {
if (r.message.bearer_token) {
frm.set_value('bearer_token', r.message.bearer_token);
}
// Запускаем опрос статуса
startPollingWithTimeout(frm);
// Задержка, чтобы дать диалогу полностью создаться
setTimeout(function() {
// Если есть код верификации, отображаем его
if (r.message.verification_code) {
// Явно указываем текст кода
$('#verification_code').text(r.message.verification_code);
// Отображаем контейнер кода верификации
$('#verification_code_container').show();
// Принудительно устанавливаем стиль отображения
$('#verification_code_container').attr('style', 'display: block !important; margin-top: 15px;');
}
@ -82,6 +61,18 @@ frappe.ui.form.on('Asan Login', {
}
});
}, __('Login'));
// Кнопка для ƏMAS Login (все в 1 клик)
frm.add_custom_button(__('ƏMAS Login'), function() {
// Проверяем, что поля phone и user_id заполнены
if (!frm.doc.phone || !frm.doc.user_id) {
frappe.msgprint(__('Please fill in Phone and User ID fields'));
return;
}
// Запускаем весь процесс ƏMAS в 1 клик
start_amas_one_click_connection(frm);
}, __('Login'));
// Кнопка для получения сертификатов (доступна только если авторизация успешна)
if (frm.doc.auth_status === 'Authenticated' || frm.doc.auth_status === 'Fully Authenticated') {
@ -967,61 +958,22 @@ function startMyGovIDLogin(frm) {
// ƏMAS (e-social.gov.az) Integration Functions
// ============================================
function add_amas_buttons(frm) {
// Only show ƏMAS buttons if user is authenticated with e-taxes or MyGovID
const is_authenticated = frm.doc.auth_status === 'Authenticated' ||
frm.doc.auth_status === 'Fully Authenticated' ||
frm.doc.mygovid_token;
function start_amas_one_click_connection(frm) {
// One-click ƏMAS connection - handles everything automatically
if (!is_authenticated) {
// Check if MyGovID token exists
if (!frm.doc.mygovid_token) {
// No MyGovID token - start full flow with MyGovID login first
frappe.show_alert({
message: __('Starting authentication for ƏMAS...'),
indicator: 'blue'
}, 3);
start_mygovid_then_amas(frm);
return;
}
// Check current ƏMAS status
const amas_status = frm.doc.amas_auth_status || 'Not Connected';
if (amas_status === 'Connected' && frm.doc.amas_account_oid) {
// ƏMAS is connected - show employee buttons
frm.add_custom_button(__('Get Employees'), function() {
show_employees_dialog(frm);
}, __('ƏMAS'));
frm.add_custom_button(__('Contract Stats'), function() {
show_contract_stats(frm);
}, __('ƏMAS'));
frm.add_custom_button(__('Refresh Session'), function() {
refresh_amas_session(frm);
}, __('ƏMAS'));
frm.add_custom_button(__('Disconnect'), function() {
disconnect_amas(frm);
}, __('ƏMAS'));
} else if (amas_status === 'Connected' && !frm.doc.amas_account_oid) {
// Connected but no account selected
frm.add_custom_button(__('Select Organization'), function() {
show_accounts_dialog(frm);
}, __('ƏMAS'));
frm.add_custom_button(__('Refresh Session'), function() {
refresh_amas_session(frm);
}, __('ƏMAS'));
frm.add_custom_button(__('Disconnect'), function() {
disconnect_amas(frm);
}, __('ƏMAS'));
} else {
// Not connected - show connect button
frm.add_custom_button(__('Connect ƏMAS'), function() {
start_amas_connection(frm);
}, __('ƏMAS'));
}
}
function start_amas_connection(frm) {
// Show loading indicator
// MyGovID token exists - try to connect directly
frappe.show_alert({
message: __('Connecting to ƏMAS...'),
indicator: 'blue'
@ -1036,33 +988,29 @@ function start_amas_connection(frm) {
freeze_message: __('Connecting to ƏMAS...'),
callback: function(r) {
if (r.message && r.message.success) {
// If accounts returned, show selection dialog
// Connected successfully - show organization selection
const accounts = r.message.accounts || [];
if (accounts.length > 0) {
frappe.show_alert({
message: __('ƏMAS connected! Please select an organization.'),
indicator: 'blue'
}, 5);
frm.reload_doc().then(function() {
show_accounts_dialog_with_data(frm, accounts);
});
} else {
frappe.show_alert({
message: __('ƏMAS connected but no organizations found.'),
indicator: 'orange'
}, 5);
frappe.msgprint({
title: __('Warning'),
indicator: 'orange',
message: __('ƏMAS connected but no organizations found.')
});
frm.reload_doc();
}
} else if (r.message && r.message.need_mygovid_login) {
// Need to login with MyGovID first
frappe.confirm(
__('MyGovID authentication required. Do you want to login with MyGovID now?'),
function() {
// Start MyGovID login, then connect to ƏMAS
start_mygovid_then_amas(frm);
}
);
// Token expired or not found - restart with MyGovID login
frappe.show_alert({
message: __('MyGovID token expired. Restarting authentication...'),
indicator: 'orange'
}, 3);
start_mygovid_then_amas(frm);
} else {
frappe.msgprint({
title: __('Error'),

View File

@ -2,7 +2,7 @@
"actions": [],
"allow_rename": 1,
"autoname": "hash",
"creation": "2025-04-28 10:00:00",
"creation": "2026-02-12 13:46:55.473077",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
@ -157,27 +157,27 @@
{
"fieldname": "amas_section",
"fieldtype": "Section Break",
"label": "ƏMAS (e-social.gov.az)"
"label": "\u018fMAS (e-social.gov.az)"
},
{
"default": "Not Connected",
"fieldname": "amas_auth_status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "ƏMAS Status",
"label": "\u018fMAS Status",
"options": "Not Connected\nWaiting for SSO\nConnected\nError",
"read_only": 1
},
{
"fieldname": "amas_account_name",
"fieldtype": "Data",
"label": "ƏMAS Account Name",
"label": "\u018fMAS Account Name",
"read_only": 1
},
{
"fieldname": "amas_account_number",
"fieldtype": "Data",
"label": "ƏMAS Account VOEN",
"label": "\u018fMAS Account VOEN",
"read_only": 1
},
{
@ -187,31 +187,29 @@
{
"fieldname": "amas_account_oid",
"fieldtype": "Small Text",
"hidden": 1,
"label": "AMAS Account OID"
},
{
"fieldname": "amas_last_activity",
"fieldtype": "Datetime",
"label": "ƏMAS Last Activity",
"label": "\u018fMAS Last Activity",
"read_only": 1
},
{
"fieldname": "amas_session",
"fieldtype": "Small Text",
"hidden": 1,
"label": "ƏMAS Session"
"label": "\u018fMAS Session"
},
{
"fieldname": "amas_csrf_token",
"fieldtype": "Small Text",
"hidden": 1,
"label": "ƏMAS CSRF Token"
"label": "\u018fMAS CSRF Token"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-02-03 19:43:35.729888",
"modified": "2026-02-12 21:52:52.885253",
"modified_by": "Administrator",
"module": "Invoice Az",
"name": "Asan Login",