added expense/income to mappings
This commit is contained in:
parent
b3acbfa6e9
commit
0ef93e4b36
16
CLAUDE.md
16
CLAUDE.md
|
|
@ -90,10 +90,10 @@ bench --site [site-name] console
|
|||
- 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 (Sub uçot hesabı → Sub uçot hesabı, Sub uçot hesabı → Büdcə, etc.)
|
||||
- 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 + classification_code
|
||||
- Priority 2: Default mapping with matching operation_type (empty classification_code)
|
||||
- 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
|
||||
|
|
@ -148,7 +148,7 @@ bench --site [site-name] console
|
|||
- **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 optional classification code filtering
|
||||
- **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
|
||||
|
|
@ -170,7 +170,7 @@ bench --site [site-name] console
|
|||
- **Two-step Signing**: Create draft invoice, then sign with ASAN Imza
|
||||
- **Retry Signing**: Retry signing for failed/pending invoices
|
||||
- **VAT Import**: Import VAT account operations as Journal Entries with configurable account mappings
|
||||
- **VAT Account Mappings**: Configure debit/credit accounts by operation type (Sub uçot hesabı → Sub uçot hesabı, Sub uçot hesabı → Büdcə, etc.)
|
||||
- **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
|
||||
|
||||
### API Endpoints (E-Taxes)
|
||||
|
|
@ -273,12 +273,16 @@ bench --site [site-name] console
|
|||
|
||||
**VAT Account Mapping Configuration:**
|
||||
- Configure in E-Taxes Settings > VAT Account Mappings tab
|
||||
- Each mapping specifies: operation type, classification code (optional), debit account, credit account
|
||||
- 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)
|
||||
|
|
|
|||
|
|
@ -659,7 +659,15 @@ VATETaxes.errors = {
|
|||
}
|
||||
|
||||
if (error.operation_type) {
|
||||
errorsHtml += '<div><strong>' + __('Operation Type:') + '</strong> ' + error.operation_type + '</div>';
|
||||
let opTypeDisplay = error.operation_type;
|
||||
if (error.expense_income_type) {
|
||||
opTypeDisplay += ' (' + error.expense_income_type.toLowerCase() + ')';
|
||||
}
|
||||
errorsHtml += '<div><strong>' + __('Operation Type:') + '</strong> ' + opTypeDisplay + '</div>';
|
||||
}
|
||||
|
||||
if (error.classification_code) {
|
||||
errorsHtml += '<div><strong>' + __('Classification Code:') + '</strong> ' + error.classification_code + '</div>';
|
||||
}
|
||||
|
||||
if (error.missing_tin) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"field_order": [
|
||||
"operation_type",
|
||||
"classification_code",
|
||||
"expense_income",
|
||||
"column_break_1",
|
||||
"debit_account",
|
||||
"credit_account"
|
||||
|
|
@ -28,6 +29,14 @@
|
|||
"label": "Classification Code",
|
||||
"options": "Classification code"
|
||||
},
|
||||
{
|
||||
"fieldname": "expense_income",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Expense/Income",
|
||||
"options": "Expense\nIncome",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_1",
|
||||
"fieldtype": "Column Break"
|
||||
|
|
@ -53,7 +62,7 @@
|
|||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-14 20:00:00",
|
||||
"modified": "2026-01-20 12:00:00",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Invoice Az",
|
||||
"name": "E-Taxes VAT Account Mapping",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from frappe.model.document import Document
|
|||
class ETaxesVATAccountMapping(Document):
|
||||
def validate(self):
|
||||
"""
|
||||
Проверка уникальности комбинации operation_type + classification_code
|
||||
Проверка уникальности комбинации operation_type + classification_code + expense_income
|
||||
в рамках одного родительского документа (E-Taxes Settings)
|
||||
"""
|
||||
if not self.parent or not self.parenttype:
|
||||
|
|
@ -22,10 +22,11 @@ class ETaxesVATAccountMapping(Document):
|
|||
'parent': self.parent,
|
||||
'parenttype': self.parenttype,
|
||||
'operation_type': self.operation_type,
|
||||
'expense_income': self.expense_income,
|
||||
'name': ['!=', self.name] # Исключаем текущую строку
|
||||
}
|
||||
|
||||
# Получаем все строки с таким же operation_type
|
||||
# Получаем все строки с таким же operation_type и expense_income
|
||||
existing_mappings = frappe.get_all(
|
||||
'E-Taxes VAT Account Mapping',
|
||||
filters=filters,
|
||||
|
|
@ -40,13 +41,15 @@ class ETaxesVATAccountMapping(Document):
|
|||
if current_code == existing_code:
|
||||
if current_code:
|
||||
frappe.throw(
|
||||
f'Duplicate mapping found: Operation Type "{self.operation_type}" '
|
||||
f'with Classification Code "{current_code}" already exists. '
|
||||
f'Duplicate mapping found: Operation Type "{self.operation_type}", '
|
||||
f'Expense/Income "{self.expense_income}", '
|
||||
f'Classification Code "{current_code}" already exists. '
|
||||
f'Each combination must be unique.'
|
||||
)
|
||||
else:
|
||||
frappe.throw(
|
||||
f'Duplicate mapping found: Operation Type "{self.operation_type}" '
|
||||
f'Duplicate mapping found: Operation Type "{self.operation_type}", '
|
||||
f'Expense/Income "{self.expense_income}" '
|
||||
f'with empty Classification Code already exists. '
|
||||
f'Each combination must be unique.'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -267,19 +267,20 @@ def find_account_by_number(account_number, company):
|
|||
return None
|
||||
|
||||
|
||||
def get_accounts_for_operation_type(operation_type, company, tax_code=None):
|
||||
def get_accounts_for_operation_type(operation_type, company, expense_income_type, tax_code=None):
|
||||
"""
|
||||
Получить счета дебета и кредита для типа операции из E-Taxes Settings.
|
||||
Если маппинг не найден, вернуть (None, None).
|
||||
|
||||
Логика строгого соответствия (БЕЗ fallback):
|
||||
- Если передан tax_code - ищем ТОЛЬКО маппинг с operation_type + этот tax_code
|
||||
- Если НЕ передан tax_code - ищем ТОЛЬКО маппинг с operation_type + пустой classification_code
|
||||
- Если передан tax_code - ищем ТОЛЬКО маппинг с operation_type + expense_income_type + этот tax_code
|
||||
- Если НЕ передан tax_code - ищем ТОЛЬКО маппинг с operation_type + expense_income_type + пустой classification_code
|
||||
- Не нашли точное совпадение - возвращаем (None, None)
|
||||
|
||||
Args:
|
||||
operation_type (str): Тип операции (например, Sub uçot hesabı → Sub uçot hesabı, Sub uçot hesabı → Büdcə)
|
||||
company (str): Название компании
|
||||
expense_income_type (str): Тип операции - 'Expense' или 'Income'
|
||||
tax_code (str, optional): Код классификации из taxCodeInfo.code
|
||||
|
||||
Returns:
|
||||
|
|
@ -305,16 +306,17 @@ def get_accounts_for_operation_type(operation_type, company, tax_code=None):
|
|||
{'parent': settings_name,
|
||||
'parenttype': 'E-Taxes Settings',
|
||||
'operation_type': operation_type,
|
||||
'expense_income': expense_income_type,
|
||||
'classification_code': tax_code},
|
||||
['debit_account', 'credit_account'],
|
||||
as_dict=True)
|
||||
|
||||
if mapping:
|
||||
frappe.logger().info(f"Found mapping with classification_code: {tax_code}")
|
||||
frappe.logger().info(f"Found mapping with classification_code: {tax_code}, expense/income: {expense_income_type}")
|
||||
return (mapping.get('debit_account'), mapping.get('credit_account'))
|
||||
else:
|
||||
# Не нашли - возвращаем None (БЕЗ fallback на пустой classification_code)
|
||||
frappe.logger().info(f"No mapping found for operation_type: {operation_type} with classification_code: {tax_code}")
|
||||
frappe.logger().info(f"No mapping found for operation_type: {operation_type}, expense/income: {expense_income_type} with classification_code: {tax_code}")
|
||||
return (None, None)
|
||||
else:
|
||||
# Случай Б: Нет tax_code - ищем ТОЛЬКО с пустым classification_code
|
||||
|
|
@ -322,18 +324,19 @@ def get_accounts_for_operation_type(operation_type, company, tax_code=None):
|
|||
filters={
|
||||
'parent': settings_name,
|
||||
'parenttype': 'E-Taxes Settings',
|
||||
'operation_type': operation_type
|
||||
'operation_type': operation_type,
|
||||
'expense_income': expense_income_type
|
||||
},
|
||||
fields=['debit_account', 'credit_account', 'classification_code'])
|
||||
|
||||
# Ищем маппинг с пустым classification_code
|
||||
for mapping in mappings:
|
||||
if not mapping.get('classification_code'):
|
||||
frappe.logger().info(f"Found default mapping (no classification_code)")
|
||||
frappe.logger().info(f"Found default mapping (no classification_code) for expense/income: {expense_income_type}")
|
||||
return (mapping.get('debit_account'), mapping.get('credit_account'))
|
||||
|
||||
# Не нашли маппинг с пустым classification_code
|
||||
frappe.logger().info(f"No default mapping found for operation_type: {operation_type}")
|
||||
frappe.logger().info(f"No default mapping found for operation_type: {operation_type}, expense/income: {expense_income_type}")
|
||||
return (None, None)
|
||||
|
||||
# Маппинг не найден - возвращаем None, None
|
||||
|
|
@ -372,10 +375,14 @@ def create_journal_entry_from_vat_operation(operation_data, company):
|
|||
income = operation_data.get('income', 0)
|
||||
expense = operation_data.get('expense', 0)
|
||||
|
||||
# Определяем сумму операции - используем то что заполнено
|
||||
amount = income if income and income > 0 else expense
|
||||
|
||||
if not amount or amount <= 0:
|
||||
# Определяем тип операции (Expense/Income) и сумму
|
||||
if income and income > 0:
|
||||
expense_income_type = 'Income'
|
||||
amount = income
|
||||
elif expense and expense > 0:
|
||||
expense_income_type = 'Expense'
|
||||
amount = expense
|
||||
else:
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'Invalid amount: both income and expense are empty or zero'
|
||||
|
|
@ -392,17 +399,32 @@ def create_journal_entry_from_vat_operation(operation_data, company):
|
|||
tax_code = tax_code_info.get('code')
|
||||
|
||||
# Ищем счета по маппингу с учетом tax_code (если есть)
|
||||
debit_account, credit_account = get_accounts_for_operation_type(operation_type, company, tax_code)
|
||||
debit_account, credit_account = get_accounts_for_operation_type(
|
||||
operation_type, company, expense_income_type, tax_code
|
||||
)
|
||||
|
||||
# Проверяем что маппинг найден
|
||||
if not debit_account or not credit_account:
|
||||
return {
|
||||
# Формируем понятное сообщение об ошибке
|
||||
expense_income_label = expense_income_type.lower() # "Income" → "income", "Expense" → "expense"
|
||||
error_message = f"VAT account mapping not found: operation type '{operation_type}', {expense_income_label}"
|
||||
|
||||
if tax_code:
|
||||
error_message += f", classification code '{tax_code}'"
|
||||
|
||||
error_dict = {
|
||||
'success': False,
|
||||
'message': f'VAT account mapping not found for operation type: {operation_type}',
|
||||
'message': error_message,
|
||||
'error_type': 'mapping_not_found',
|
||||
'operation_type': operation_type
|
||||
'operation_type': operation_type,
|
||||
'expense_income_type': expense_income_type
|
||||
}
|
||||
|
||||
if tax_code:
|
||||
error_dict['classification_code'] = tax_code
|
||||
|
||||
return error_dict
|
||||
|
||||
# Проверяем типы счетов - нужен ли party
|
||||
debit_account_type = frappe.db.get_value('Account', debit_account, 'account_type')
|
||||
credit_account_type = frappe.db.get_value('Account', credit_account, 'account_type')
|
||||
|
|
|
|||
Loading…
Reference in New Issue