1031 lines
43 KiB
Markdown
1031 lines
43 KiB
Markdown
# 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 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!**
|
||
|
||
### 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 Azerbaijan 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
|
||
- Azerbaijani 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
|
||
- **Azerbaijani 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 Azerbaijani 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 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
|