added relogin to emas and other
This commit is contained in:
parent
72cf279a64
commit
0fd5399b7f
179
CLAUDE.md
179
CLAUDE.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
# ------------------------
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue