# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

Invoice Az is a Frappe application that integrates with Azərbaycan 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 (Azərbaycan 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!**

### 1. Authentication Pattern (MANDATORY)

**❌ WRONG - Direct API call:**
```javascript
function my_function(frm) {
    frappe.call({
        method: 'invoice_az.my_api.some_method',
        // ... this will fail with "Authentication expired"
    });
}
```

**✅ CORRECT - Always wrap with authentication check:**
```javascript
function my_function(frm) {
    ETaxes.auth.checkAndProcess(function() {
        // Your code here - only runs after auth is verified
        frappe.call({
            method: 'invoice_az.my_api.some_method',
            // ... works correctly
        });
    });
}
```

**Why?** `ETaxes.auth.checkAndProcess()` does:
1. Checks if token is still valid
2. If expired → prompts user to login via ASAN Imza
3. Only after successful auth → executes your callback
4. Prevents "Authentication expired" errors

### 2. ETaxes Module Inclusion (MANDATORY)

**⚠️ ETaxes is NOT a global module!** It must be defined in EACH JavaScript file that uses it.

**❌ WRONG - Assuming ETaxes exists:**
```javascript
// my_custom_form.js
frappe.ui.form.on('My DocType', {
    refresh: function(frm) {
        ETaxes.auth.checkAndProcess(function() {
            // ERROR: ETaxes is not defined
        });
    }
});
```

**✅ CORRECT - Include ETaxes module at the top of your JS file:**

Copy the entire ETaxes module from `sales_invoice.js` (lines 1-564) to the **beginning** of your JS file:
```javascript
// ======= COPY THIS ENTIRE BLOCK =======
// From sales_invoice.js lines 1-564:
const ETaxes = {
    PROGRESS_UPDATE_DELAY: 50,
    AUTH_POLL_INTERVAL: 6000,
    AUTH_MAX_ATTEMPTS: 20,
    // ... (full module definition)
};
ETaxes.utils = { /* ... */ };
ETaxes.dialogs = { /* ... */ };
ETaxes.auth = { /* ... */ };
// ======= END COPY =======

// Now your code can use ETaxes:
frappe.ui.form.on('My DocType', {
    refresh: function(frm) {
        ETaxes.auth.checkAndProcess(function() {
            // ✅ Works correctly
        });
    }
});
```

**Files that need ETaxes module:**
- ✅ `sales_invoice.js` - has it (564 lines)
- ✅ `purchase_order.js` - has it
- ✅ `supplier.js` - has it (after fix)
- ❌ Any new JS file - **MUST include it!**

### 3. Backend API Pattern (MANDATORY)

**All backend functions that call E-Taxes API MUST follow this pattern:**

```python
import frappe
import requests
from invoice_az.auth import record_etaxes_activity, get_default_asan_login, DEFAULT_HEADERS

@frappe.whitelist()
def my_etaxes_function(doc_name):
    """My E-Taxes API function"""
    try:
        # Step 1: ALWAYS record activity (for token renewal)
        record_etaxes_activity()

        # Step 2: Get document
        doc = frappe.get_doc("My DocType", doc_name)

        # Step 3: Validate (business logic checks)
        validation = validate_my_document(doc)
        if not validation.get("valid"):
            return {
                "success": False,
                "message": validation.get("message")
            }

        # Step 4: Get authentication token
        asan_login = get_default_asan_login()
        if not asan_login.get("found") or not asan_login.get("main_token"):
            return {
                "error": "unauthorized",
                "message": "E-Taxes authentication not found. Please login via ASAN."
            }

        token = asan_login.get("main_token")

        # Step 5: Build headers with token
        headers = DEFAULT_HEADERS.copy()
        headers["x-authorization"] = f"Bearer {token}"

        # Step 6: Make API request
        response = requests.post(
            "https://new.e-taxes.gov.az/api/...",
            json=payload,
            headers=headers,
            timeout=60
        )

        # Step 7: 401 — RETRY ONCE with a fresh token from DB, then fail
        if response.status_code == 401:
            fresh_login = get_default_asan_login()
            if fresh_login.get("found") and fresh_login.get("main_token"):
                headers["x-authorization"] = f"Bearer {fresh_login['main_token']}"
                response = requests.post(url, json=payload, headers=headers, timeout=60)
            if response.status_code == 401:
                return {
                    "error": "unauthorized",
                    "message": "Authentication required. Please login again."
                }

        # Step 8: Other error codes
        if response.status_code == 500:
            return {
                "error": "server_error",
                "message": "Service temporarily unavailable. Please try again in a few minutes.",
                "status_code": 500
            }

        if response.status_code == 404:
            return {
                "success": False,
                "message": "Resource not found."
            }

        response.raise_for_status()

        # Step 9: Process response
        result = response.json()

        # Step 10: Persist any state changes explicitly
        frappe.db.commit()

        # Step 11: Return success
        return {
            "success": True,
            "data": result
        }

    except requests.exceptions.RequestException as e:
        frappe.log_error(f"Network error: {e}", "My Feature Error")
        return {"success": False, "message": f"Network error: {e}"}
    except Exception as e:
        frappe.log_error(
            f"Unexpected error: {e}\n{frappe.get_traceback()}",
            "My Feature Error"
        )
        return {"success": False, "message": f"Error: {e}"}
```

**Key points:**
1. `@frappe.whitelist()` — always bare, no `methods=[...]`, no `allow_guest`
2. `record_etaxes_activity()` — ALWAYS first (resets inactivity timer, prevents token expiry mid-call)
3. `get_default_asan_login()` — read token from DB (scheduler may have refreshed it)
4. `headers["x-authorization"] = f"Bearer {token}"` — required for every call
5. **401 MUST retry once** with a fresh token before giving up — see §10
6. Return codes by use case — see §11
7. `frappe.db.commit()` after any `frappe.db.set_value` / `.insert()` / `.save()` / `.delete_doc()` — see §12
8. Use `ignore_permissions=True` when inserting E-Taxes internal/tracking records
9. `frappe.log_error(msg, "Title Case Error")` — see §13 for title convention
10. Always try-except around the whole body

### 4. Constants and URLs

**Base URL:**
```python
BASE_URL = "https://new.e-taxes.gov.az"
```

**Common endpoints:**
- Auth: `/api/po/auth/public/v1/*`
- Invoices: `/api/po/invoice/public/v2/invoice`
- Sign: `/api/po/invoice/public/v1/invoice/sign/withAsanImza`
- Serial: `/api/po/invoice/public/v1/generateSerialNumber/defaultInvoice`
- Agricultural Serial: `/api/po/invoice/public/v1/act/generateSerialNumber/agriculturalProductsAct`
- Metal Scrap Serial: `/api/po/invoice/public/v1/act/generateSerialNumber/metalProductsAct`
- Tire Disposal Serial: `/api/po/invoice/public/v1/act/generateSerialNumber/tireProductsForDisposalAct`
- Plastic Disposal Serial: `/api/po/invoice/public/v1/act/generateSerialNumber/plasticProductsForDisposalAct`
- Rawhide Supply Serial: `/api/po/invoice/public/v1/act/generateSerialNumber/rawhideSupply`
- Other Product Receipt Serial: `/api/po/invoice/public/v1/act/generateSerialNumber/otherProductReceiptAct`
- All Acts: `/api/po/invoice/public/v1/act`
- Remove Drafts: `/api/po/invoice/public/v1/common/removeDrafts`
- VAT Operations: `/api/po/vatacc/public/v1/operation/find.outbox`
- Taxpayer by FIN: `/api/po/profile/public/v1/taxpayer/findByFinAndPassport`

**Standard headers:**
```python
DEFAULT_HEADERS = {
    "Accept": "application/json, text/plain, */*",
    "Accept-Language": "en-US,en;q=0.9",
    "Content-Type": "application/json",
    "Cache-Control": "no-cache",
    "Pragma": "no-cache"
}
```

### 5. Error Handling

**HTTP Status Codes:**
- `200` - Success
- `401` - Token expired → Show "Please login again"
- `404` - Resource not found
- `500` - E-Taxes server error → Show "Try again later"

**Always check in this order:**
```python
if response.status_code == 401:
    return {"success": False, "message": "Authentication expired. Please login again."}

if response.status_code == 500:
    return {"success": False, "message": "E-Taxes service error. Please try again later."}

if response.status_code == 404:
    return {"success": False, "message": "Not found"}

response.raise_for_status()  # For other errors
```

### 6. Frontend Button Pattern

**Always show buttons conditionally:**
```javascript
frappe.ui.form.on('My DocType', {
    refresh: function(frm) {
        // Only for submitted documents
        if (frm.doc.docstatus === 1) {
            add_etaxes_buttons(frm);
        }
    }
});

function add_etaxes_buttons(frm) {
    // Check status and show appropriate buttons
    if (frm.doc.etaxes_status === 'Sent and Signed') {
        frm.add_custom_button(__('View Details'), function() {
            // Show details
        }, __('E-Taxes'));
    } else if (frm.doc.etaxes_status === 'Created, not signed') {
        frm.add_custom_button(__('Sign with ASAN Imza'), function() {
            // Sign document
        }, __('E-Taxes'));
    } else {
        frm.add_custom_button(__('Send to E-Taxes'), function() {
            // Send to E-Taxes
        }, __('E-Taxes'));
    }
}
```

### 7. Build Process

**After modifying JavaScript files:**
```bash
# Build assets
cd /home/frappe/frappe-bench
bench build --app invoice_az

# Clear cache
bench --site site1 clear-cache
```

**After modifying Python files:**
```bash
# Just clear cache (no build needed)
bench --site site1 clear-cache
```

### 8. Common Mistakes to Avoid

❌ **Don't do this:**
1. Call E-Taxes API without `ETaxes.auth.checkAndProcess()`
2. Forget to include ETaxes module in new JS files
3. Skip `record_etaxes_activity()` in backend functions
4. Forget to handle 401/500/404 errors
5. Use `app_include_js` in hooks.py (doesn't work for doctype-specific code)
6. Make API calls without Bearer token in headers

✅ **Always do this:**
1. Wrap ALL E-Taxes operations in `ETaxes.auth.checkAndProcess()`
2. Copy ETaxes module (564 lines) to every new JS file
3. Call `record_etaxes_activity()` first in every API function
4. Handle 401, 500, 404 explicitly
5. Return consistent `{success, message}` format
6. Add `x-authorization: Bearer {token}` header
7. Build assets after JS changes

### 9. Example Files to Reference

**Backend (Python):**
- ✅ `send_sales_api.py` - Perfect example of all patterns
- ✅ `send_purchase_api.py` - Agricultural act example
- ✅ `supplier_api.py` - Simple API call example
- ✅ `auth.py` - Authentication implementation

**Frontend (JavaScript):**
- ✅ `sales_invoice.js` - Complete ETaxes module (lines 1-564)
- ✅ `purchase_invoice.js` - Button patterns
- ✅ `supplier.js` - Minimal working example

**When in doubt:** Look at `send_sales_api.py` + `sales_invoice.js` - they contain all correct patterns!

### 10. 401 Auto-Refresh Pattern (MANDATORY)

**A bare 401 must never be shown to the user as "Authentication expired".** The scheduler renews the token every 4 minutes in the background, so an in-flight request that hits 401 is frequently fixable by re-reading the token from the DB and retrying once. Only the *second* 401 in a row means genuine re-auth is required.

**Reference implementation:** `invoice_az/sales_api.py:98-118`, `invoice_az/vat_api.py:~120-150`.

```python
response = requests.get(url, headers=headers, timeout=60)

# 401 → refresh token from DB and retry ONCE
if response.status_code == 401:
    fresh_login = get_default_asan_login()
    if fresh_login.get("found") and fresh_login.get("main_token"):
        headers["x-authorization"] = f"Bearer {fresh_login['main_token']}"
        response = requests.get(url, headers=headers, timeout=60)
    if response.status_code == 401:
        return {
            "error": "unauthorized",
            "message": "Authentication required. Please login again."
        }
```

**Rules:**
- Retry **exactly once** — never loop, never escalate to `renew_token()` from inside a request handler.
- On the second 401, return the `error: "unauthorized"` shape so the frontend can trigger re-auth (see §11 and §14).
- Never return `{"success": False, "message": "Authentication expired..."}` — the frontend has no way to recognize that as an auth failure vs a business error.

### 11. Response Shapes (TWO canonical formats)

The app uses **two** response shapes — pick one per function based on use case. Never mix `success` and `error` in the same response.

**Format A — `{success, message}`** (user-triggered operations: send invoice, sign act, fetch supplier data):
```python
return {"success": True,  "message": "Act signed", "document_id": "..."}
return {"success": False, "message": "Item 3 has no product_group_code"}
```

**Format B — `{error, message, status_code?}`** (data-fetch / lower-level calls where the frontend may want to branch on the error code):
```python
return {"error": "unauthorized",    "message": "Authentication required. Please login again."}
return {"error": "server_error",    "message": "Service temporarily unavailable.", "status_code": 500}
return {"error": "http_error",      "message": f"HTTP error {code}: {msg}"}
return {"error": "connection_error", "message": "..."}
```

**When to use which:**
- Format A: the frontend only needs "did it work, what to say to the user".
- Format B: the frontend needs to **branch on the failure mode** (e.g. trigger re-auth on `unauthorized`, show a retry button on `server_error`). Data-loading functions (load objects, load invoices, fetch reference data) should use Format B.

**You MAY combine on "already authenticated check":** if `get_default_asan_login()` returns no token at all, return `{"error": "unauthorized", ...}` directly — the frontend reacts identically to a fresh 401.

### 12. `frappe.db.commit()` Standard

Frappe auto-commits at request end, but the E-Taxes code always commits **explicitly** after any state change so that:
- tokens persist immediately (another worker may read them before the request ends),
- partial progress in long-running loops isn't lost if the request later errors.

**Commit after:**
- `frappe.db.set_value("Asan Login", ..., "last_activity_time", ...)` — see `auth.py:46, 57, 157, 173`
- `doc.insert(ignore_permissions=True)` for E-Taxes tracking records
- Any bulk loop that processes >1 record (commit after the loop, not after each iteration)
- `frappe.delete_doc(..., force=True)` cleanups

**Do NOT commit after:** `frappe.get_doc()` reads, pure validations, dry-runs.

### 13. Logging Title Convention

`frappe.log_error(message, title)` — the **title** is `Title Case <Feature> Error` (or `Info`, `Success`, `Debug`):

| Good | Bad |
|------|-----|
| `"Token Renewal Error"` | `"token_renewal_error"` |
| `"Sales Invoice Validation Error"` | `"sales_invoice_validation"` |
| `"E-Taxes Object Load"` | `"object_load"` |
| `"Sales Order Tax Info"` | `"info"` |
| `"Signing Error"` | `"error signing"` |

Group related entries under the same title so the Error Log is grouped usefully. Put the full traceback in the **message**, not the title.

### 14. Frontend Auth Handling (MANDATORY pair with §10)

When a backend call returns Format B `{error: "unauthorized"}` the frontend must detect it and offer re-auth. Pure `{success: false}` branches MUST NOT be used for auth — the frontend has no way to distinguish.

**Canonical frontend handler** — see `client/e_taxes_customers_list.js:752-765`:

```javascript
frappe.call({
    method: 'invoice_az.company_api.load_company_objects',
    args: { company: frm.doc.name },
    callback: function(r) {
        const msg = r.message || {};

        if (msg.error === 'unauthorized') {
            frappe.confirm(
                __('Your E-Taxes session has expired. Would you like to authenticate now?'),
                function() {
                    // Retry the same action after re-auth
                    if (typeof ETaxes !== 'undefined' && ETaxes.auth) {
                        ETaxes.auth.checkAndProcess(function() { load_objects(frm); });
                    } else {
                        frappe.msgprint(__('Please authenticate via Asan Login, then try again.'));
                    }
                }
            );
            return;
        }

        if (msg.error) {
            frappe.msgprint({ title: __('Error'), message: msg.message, indicator: 'red' });
            return;
        }

        if (msg.success) {
            frappe.show_alert({ message: msg.message, indicator: 'green' }, 7);
        }
    }
});
```

### 15. Field Naming & DocType Structure

**E-Taxes reference doctypes (cached from API):** primary field uses the `etaxes_<entity>_name` pattern:
- `E-Taxes Item` → `etaxes_item_name` (unique, primary, autoname: `field:etaxes_item_name`)
- `E-Taxes Unit` → `etaxes_unit_name`
- `E-Taxes Suppliers` → `etaxes_party_name`
- `E-Taxes Customers` → `etaxes_party_name`

**Fixture-style doctypes (static/reference lookup data, not tied to invoices):** plain names — NO `etaxes_` prefix:
- `E-Taxes Item Group` → `code`, `name_az`
- `Classification code` → `classification_name`
- `E-Taxes Object` → `object_code`, `object_name` (taxpayer-owned, fixture-shaped)

Rule of thumb: if the entity comes from an **invoice** and needs mapping back to an ERP doctype, use the `etaxes_` prefix. If it's a standalone reference/dictionary, use plain names.

**Standard field order:**
1. Primary identifier (`unique=1`, `reqd=1`, `read_only=1`, `in_list_view=1`)
2. Secondary identifier (code / tax_id)
3. Status (`Select`, `in_list_view=1`, `in_standard_filter=1`)
4. Source tracking (optional `source_invoice`)
5. `Section Break` — grouped attribute sections (Activity, Classification, Address...)
6. `Section Break` — mapping/link section at the end (link to ERP entity or Company)

**Standard permissions:**
- System Manager: full CRUD + share/export/print/report/email
- Accounts User: read-only + export/print/report/email/share (no create/write/delete)

### 16. Background Jobs with Socket.IO (for bulk imports)

Since the latest commit, bulk import operations use `frappe.enqueue` + `frappe.publish_realtime` instead of blocking requests. **Use this for any loop that processes more than ~50 records or calls E-Taxes more than ~10 times.** For small synchronous fetches (object list, supplier profile, serial number) stay synchronous.

**Reference implementation:** `invoice_az/sales_api.py:883-979`.

```python
# Entry point (whitelisted)
@frappe.whitelist()
def import_bulk_foo(ids):
    frappe.enqueue(
        _process_bulk_foo,
        ids=ids,
        user=frappe.session.user,
        queue="default",
        timeout=1200,
    )
    return {"success": True, "enqueued": True, "total": len(ids)}

# Worker (not whitelisted, underscore-prefixed)
def _process_bulk_foo(ids, user):
    frappe.set_user(user)
    record_etaxes_activity()

    total = len(ids)
    for idx, id_ in enumerate(ids):
        try:
            process_one(id_)
        except Exception as e:
            frappe.log_error(f"Item {id_}: {e}", "Bulk Foo Error")

        frappe.publish_realtime(
            "bulk_foo_progress",
            {"current": idx + 1, "total": total},
            user=user,
        )

    frappe.publish_realtime(
        "bulk_foo_complete",
        {"total": total},
        user=user,
    )
```

**Event naming convention:** `{feature}_progress` (periodic), `{feature}_complete` (final). Frontend subscribes via `frappe.realtime.on(event, handler)`.

## Development Commands

### Linting and Code Quality
```bash
# Run ruff for Python linting
ruff check invoice_az/

# Run ruff format
ruff format invoice_az/

# Run pre-commit hooks (includes ruff, eslint, prettier, pyupgrade)
pre-commit run --all-files
```

### Testing
```bash
# Run unit tests for a specific doctype
bench run-tests --app invoice_az --doctype "E-Taxes Item"

# Run all tests for the app
bench run-tests --app invoice_az
```

### Development
```bash
# Clear cache after making changes
bench clear-cache

# Restart workers after API changes
bench restart

# Watch logs
bench --site [site-name] console
```

## Architecture

### Core Modules

1. **Authentication Module** (`invoice_az/auth.py`)
   - ASAN Login integration for Azərbaycan government authentication
   - Token management with automatic renewal every 4 minutes via cron job
   - Activity tracking to prevent unnecessary token renewals
   - Retry logic with exponential backoff for failed requests
   - Token validity checking and automatic refresh on 401 errors
   - Full authentication flow: start auth → poll status → get certificates → select taxpayer
   - Key functions: `renew_token()`, `get_auth_token()`, `check_auth_status()`, `get_certificates()`, `choose_taxpayer()`, `check_token_validity()`, `handle_unauthorized_request()`, `handle_authentication()`, `poll_auth_status()`, `select_certificate()`, `select_taxpayer()`

2. **Purchase API** (`invoice_az/api.py`)
   - Download and import purchase invoices from e-taxes
   - Party, item, and unit mapping system
   - Separate Customer and Supplier mapping systems
   - Create Purchase Orders and Purchase Invoices
   - E-Taxes Purchase tracking records
   - Reference data management (sync items, units, parties from e-taxes)
   - Batch invoice processing for loading reference data
   - Auto-matching for items, units, customers, suppliers by name similarity

3. **Sales Import API** (`invoice_az/sales_api.py`)
   - Download and import sales invoices from e-taxes outbox
   - Customer mapping system (separate from suppliers)
   - Create Sales Orders and Sales Invoices
   - E-Taxes Sales tracking records
   - Azərbaycan character normalization for matching

4. **Sales Send API** (`invoice_az/send_sales_api.py`)
   - Send Sales Invoices to e-taxes system
   - Two-step workflow: create draft, then sign with ASAN Imza
   - Generate serial numbers from e-taxes
   - Build invoice payload with product codes and VAT fields
   - E-Taxes Sales Outbox tracking
   - Cancel/remove draft invoices functionality
   - Retry signing for failed/pending invoices
   - Get customer objects by TIN for delivery address selection

5. **VAT Operations API** (`invoice_az/vat_api.py`)
   - Import VAT Account operations from e-taxes
   - Import all operations (both income and expense types)
   - Create Journal Entries with configurable account mappings
   - VAT Account Mappings by operation type and expense/income type (Sub uçot hesabı → Sub uçot hesabı, Sub uçot hesabı → Büdcə, etc.)
   - Classification Code support - allows specific account mappings per tax code with priority:
     - Priority 1: Mapping with matching operation_type + expense_income + classification_code
     - Priority 2: Default mapping with matching operation_type + expense_income (empty classification_code)
   - Required mapping configuration - operation fails with error if mapping not found
   - Smart party assignment: adds Customer/Supplier only for Receivable/Payable accounts
   - Customer lookup by TIN
   - E-Taxes VAT Operations tracking
   - Key functions: `get_accounts_for_operation_type()`, `find_account_by_number()`, `create_journal_entry_from_vat_operation()`, `map_operation_type()`

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, 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, 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, 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
- `e_taxes_unit_list.js` - Sync units from e-taxes
- `etaxes_common.js` - Shared utilities

### Database Schema

**Authentication:**
- **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

**E-Taxes Entities (cached from e-taxes):**
- **E-Taxes Item** - Products/services from e-taxes with EQM codes
- **E-Taxes Parties** - All parties (legacy, for purchases)
- **E-Taxes Suppliers** - Supplier companies for purchases
- **E-Taxes Customers** - Customer companies for sales
- **E-Taxes Unit** - Units of measurement
- **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)
- **E-Taxes Supplier Mappings** - Links ERPNext Suppliers to e-taxes suppliers
- **E-Taxes Customer Mappings** - Links ERPNext Customers to e-taxes customers
- **E-Taxes Unit Mapping** - Links ERPNext UOM to e-taxes units
- **E-Taxes VAT Account Mapping** - Maps VAT operation types to Chart of Accounts (debit/credit), with expense/income type and optional classification code filtering

**Tracking Records:**
- **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
- **Entity Mapping**: Maps ERPNext items, parties, suppliers, customers, and units to e-taxes equivalents
- **Auto-Matching**: Automatic matching of items, units, customers, suppliers by name similarity
- **Bulk Operations**: Support for syncing multiple entities at once
- **Batch Processing**: Load reference data from invoices in batches
- **Error Handling**: Comprehensive error logging and user-friendly error messages
- **Azərbaycan Support**: Character normalization for Ə, Ü, Ö, Ğ, İ, Ç, Ş characters
- **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

**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)

### 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 (for purchase acts)
- **Classification code** - Pre-populated tax classification codes from e-taxes (for VAT operations)

### Install Hooks

- `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

- All API endpoints are whitelisted with `@frappe.whitelist()`
- Token storage uses Frappe's password field type
- Activity tracking prevents unnecessary API calls
- Retry logic includes exponential backoff to prevent API flooding
- Automatic token refresh on 401 errors

### 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

1. User opens Purchase Order/Invoice list
2. Clicks "Import from E-Taxes" button
3. System fetches invoices from e-taxes inbox
4. User selects invoice to import
5. System validates mappings (items, suppliers, units)
6. Creates Purchase Order with items
7. Creates E-Taxes Purchase tracking record

### Workflow: Importing Sales (from E-Taxes)

1. User opens Sales Order list
2. Clicks "Import from E-Taxes" button
3. System fetches invoices from e-taxes outbox
4. User selects invoice to import
5. System validates mappings (items, customers, units)
6. Creates Sales Order and Sales Invoice
7. Creates E-Taxes Sales tracking record

### Workflow: Sending Sales Invoice to E-Taxes

1. User creates and submits Sales Invoice
2. Fills customer_object_name field (required for delivery address)
3. Clicks "Send to E-Taxes" button
4. System generates serial number
5. Creates draft invoice on e-taxes
6. User clicks "Sign Document" button
7. System signs with ASAN Imza
8. E-Taxes Sales Outbox record updated

**Retry/Cancel:**
- If signing fails, user can click "Retry Signing" button
- User can cancel draft invoice using "Cancel on E-Taxes" button

### Workflow: Sending Purchase Acts to E-Taxes

Supported act types for Purchase Invoices with Individual suppliers:

1. **Agricultural Products Act** - For agricultural goods (kind: "agriculturalProductsAct")
   - Tax types: "Tax Free" (0%) or "Taxable" (5%)
   - Endpoint: `/api/po/invoice/public/v1/act/generateSerialNumber/agriculturalProductsAct`

2. **Metal Scrap Reception Act** - For ferrous/non-ferrous metal scrap (kind: "metalProductsAct")
   - Tax types: Only "Taxable" (5%) - tax-free items not allowed
   - Validation: All items must have tax_type = "Taxable"
   - Endpoint: `/api/po/invoice/public/v1/act/generateSerialNumber/metalProductsAct`

3. **Tire Products For Disposal Act** - For tire disposal operations (kind: "tireProductsForDisposalAct")
   - Tax types: Only "Taxable" (5%) - tax-free items not allowed
   - Validation: All items must have tax_type = "Taxable"
   - Endpoint: `/api/po/invoice/public/v1/act/generateSerialNumber/tireProductsForDisposalAct`

4. **Plastic Products For Disposal Act** - For plastic disposal operations (kind: "plasticProductsForDisposalAct")
   - Tax types: Only "Taxable" (5%) - tax-free items not allowed
   - Validation: All items must have tax_type = "Taxable"
   - Endpoint: `/api/po/invoice/public/v1/act/generateSerialNumber/plasticProductsForDisposalAct`

5. **Rawhide Supply** - For rawhide supply operations (kind: "rawhideSupply")
   - Tax types: Only "Taxable" (5%) - tax-free items not allowed
   - Validation: All items must have tax_type = "Taxable"
   - Endpoint: `/api/po/invoice/public/v1/act/generateSerialNumber/rawhideSupply`

6. **Other Product Receipt Act** - For other product receipts (kind: "otherProductReceiptAct")
   - Tax types: "Tax Free" (0%) or "Taxable" (5%) - both allowed
   - Validation: No tax type restrictions (same as Agricultural)
   - Endpoint: `/api/po/invoice/public/v1/act/generateSerialNumber/otherProductReceiptAct`

**Workflow:**
1. User creates and submits Purchase Invoice
2. Selects Individual supplier (with FIN, passport, DOB)
3. Checks `purchase_type` checkbox to enable act fields
4. Selects `act_kind` (one of 6 available types)
5. Adds items with product_group_code and tax_type
6. Clicks "Send Act to E-Taxes" button (button text changes based on act_kind)
7. System generates serial number from appropriate endpoint
8. Creates draft act on e-taxes
9. User clicks "Sign Act with ASAN Imza" button
10. System signs with ASAN Imza
11. E-Taxes Purchase Outbox record updated

**Retry/Cancel:**
- If signing fails, user can click "Retry Signing" button
- User can cancel draft act using "Cancel Act on E-Taxes" button

### Workflow: Importing VAT Operations

1. User opens Journal Entry list
2. Clicks "Import VAT from E-Taxes" button
3. Selects date range
4. System fetches all VAT operations (both income and expense types)
5. System shows operation selection dialog with columns:
   - Date, TIN, Name, Operation Type, Classification Code, Amount
6. Creates Journal Entries using account mappings:
   - API returns technical operation types (SUB_TO_SUB, etc.) which are mapped to Azərbaycan names
   - API returns taxCodeInfo with code (e.g., "114117") and description
   - Looks up mapping in E-Taxes Settings with priority:
     - First: operation_type + classification_code (if taxCodeInfo.code exists)
     - Second: operation_type + empty classification_code (default mapping)
   - If mapping not found, operation fails with error (shown in error log)
   - Automatically adds party (Customer) only if account type is Receivable/Payable
   - Supports different debit/credit accounts for different operation types and classification codes
7. Creates E-Taxes VAT Operations tracking records

**VAT Account Mapping Configuration:**
- Configure in E-Taxes Settings > VAT Account Mappings tab
- Each mapping specifies: operation type, expense/income type (required), classification code (optional), debit account, credit account
- Expense/Income field: Select "Expense" or "Income" to distinguish between expense and income operations
- Classification Code field links to Classification code doctype
- Uniqueness: Each combination of operation_type + expense_income + classification_code must be unique
- Priority logic:
  - System determines expense/income type from API data (income > 0 → Income, expense > 0 → Expense)
  - If classification_code is set: mapping applies only to operations with matching taxCodeInfo.code
  - If classification_code is empty: mapping is default for this operation type
  - System searches with priority: specific classification_code first, then default (empty)
  - All searches include expense_income filter
- Supported operation types:
  - Sub uçot hesabı → Sub uçot hesabı (SUB_TO_SUB in API)
  - Naməlum → digər Sub hesab (UNKNOWN_TO_OTHER_SUB in API)
  - Cari → Sub uçot hesabı (CURRENT_TO_SUB in API)
  - Cari → Naməlum (CURRENT_TO_UNKNOWN in API)
  - Sub uçot hesabı → İdxal (SUB_TO_IMPORT in API)
  - Naməlum → İdxal (UNKNOWN_TO_IMPORT in API)
  - Sub uçot hesabı → Büdcə (SUB_TO_BUDGET and AUTO in API)
  - 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 Azərbaycan'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
